瀏覽代碼

Move image picker logic to new class 'PickImage'

This is a preparation to solve issue #2555 and centralize the image picker
functionality.

See: #2555

Signed-off-by: Tim Krüger <t@timkrueger.me>
Tim Krüger 2 年之前
父節點
當前提交
f44c528b4f

+ 41 - 47
app/src/main/java/com/nextcloud/talk/profile/ProfileActivity.kt

@@ -3,6 +3,8 @@
  *
  * @author Tobias Kaminsky
  * @author Andy Scherzinger
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
  * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
  * Copyright (C) 2021 Tobias Kaminsky <tobias.kaminsky@nextcloud.com>
  *
@@ -28,7 +30,6 @@ import android.graphics.Bitmap
 import android.graphics.BitmapFactory
 import android.graphics.drawable.ColorDrawable
 import android.net.Uri
-import android.os.Bundle
 import android.text.Editable
 import android.text.TextUtils
 import android.text.TextWatcher
@@ -46,8 +47,6 @@ import androidx.recyclerview.widget.RecyclerView
 import autodagger.AutoInjector
 import com.github.dhaval2404.imagepicker.ImagePicker
 import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError
-import com.github.dhaval2404.imagepicker.ImagePicker.Companion.with
-import com.github.dhaval2404.imagepicker.constant.ImageProvider
 import com.nextcloud.talk.R
 import com.nextcloud.talk.activities.BaseActivity
 import com.nextcloud.talk.activities.TakePhotoActivity
@@ -61,19 +60,16 @@ import com.nextcloud.talk.models.json.userprofile.Scope
 import com.nextcloud.talk.models.json.userprofile.UserProfileData
 import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
-import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
 import com.nextcloud.talk.ui.dialog.ScopeDialog
 import com.nextcloud.talk.ui.theme.ViewThemeUtils
 import com.nextcloud.talk.users.UserManager
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.DisplayUtils
-import com.nextcloud.talk.utils.FileUtils
 import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG
-import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX
 import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
-import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MIME_TYPE_FILTER
+import com.nextcloud.talk.utils.PickImage
+import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA
 import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
-import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
 import io.reactivex.Observer
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.Disposable
@@ -81,13 +77,7 @@ import io.reactivex.schedulers.Schedulers
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.MultipartBody
 import okhttp3.RequestBody.Companion.asRequestBody
-import okhttp3.ResponseBody
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Response
 import java.io.File
-import java.io.FileOutputStream
-import java.io.IOException
 import java.util.LinkedList
 import javax.inject.Inject
 
@@ -102,15 +92,14 @@ class ProfileActivity : BaseActivity() {
     @Inject
     lateinit var userManager: UserManager
 
-    @Inject
-    lateinit var permissionUtil: PlatformPermissionUtil
-
     private var currentUser: User? = null
     private var edit = false
     private var adapter: UserInfoAdapter? = null
     private var userInfo: UserProfileData? = null
     private var editableFields = ArrayList<String>()
 
+    private lateinit var pickImage: PickImage
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -128,9 +117,11 @@ class ProfileActivity : BaseActivity() {
         binding.userinfoList.setItemViewCacheSize(DEFAULT_CACHE_SIZE)
         currentUser = userManager.currentUser.blockingGet()
         val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
-        binding.avatarUpload.setOnClickListener { sendSelectLocalFileIntent() }
-        binding.avatarChoose.setOnClickListener { showBrowserScreen() }
-        binding.avatarCamera.setOnClickListener { checkPermissionAndTakePicture() }
+
+        pickImage = PickImage(this, currentUser)
+        binding.avatarUpload.setOnClickListener { pickImage.selectLocal() }
+        binding.avatarChoose.setOnClickListener { pickImage.selectRemote() }
+        binding.avatarCamera.setOnClickListener { pickImage.takePicture() }
         binding.avatarDelete.setOnClickListener {
             ncApi.deleteAvatar(
                 credentials,
@@ -541,7 +532,7 @@ class ProfileActivity : BaseActivity() {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults)
         if (requestCode == REQUEST_PERMISSION_CAMERA) {
             if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                takePictureForAvatar()
+                pickImage.takePicture()
             } else {
                 Toast
                     .makeText(context, context.getString(R.string.take_photo_permission), Toast.LENGTH_LONG)
@@ -597,27 +588,37 @@ class ProfileActivity : BaseActivity() {
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
         super.onActivityResult(requestCode, resultCode, data)
-        if (resultCode == Activity.RESULT_OK) {
-            if (requestCode == REQUEST_CODE_IMAGE_PICKER) {
-                val uri: Uri = data?.data!!
-                uploadAvatar(uri.toFile())
-            } else if (requestCode == REQUEST_CODE_SELECT_REMOTE_FILES) {
-                val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
-                if (pathList?.size!! >= 1) {
-                    handleAvatar(pathList[0])
-                }
-            } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
-                data?.data?.path?.let {
-                    openImageWithPicker(File(it))
-                }
-            } else {
-                Log.w(TAG, "Unknown intent request code")
+        when (resultCode) {
+            Activity.RESULT_OK -> {
+                pickImage.handleActivityResult(
+                    requestCode,
+                    resultCode,
+                    data
+                ) { uploadAvatar(it.toFile()) }
+            }
+            ImagePicker.RESULT_ERROR -> {
+                Toast.makeText(this, getError(data), Toast.LENGTH_SHORT).show()
+            }
+            else -> {
+                Log.i(TAG, "Task Cancelled")
             }
-        } else if (resultCode == ImagePicker.RESULT_ERROR) {
-            Toast.makeText(this, getError(data), Toast.LENGTH_SHORT).show()
-        } else {
-            Log.i(TAG, "Task Cancelled")
         }
+
+        // if (requestCode == REQUEST_CODE_IMAGE_PICKER) {
+        //     val uri: Uri = data?.data!!
+        //     uploadAvatar(uri.toFile())
+        // } else if (requestCode == REQUEST_CODE_SELECT_REMOTE_FILES) {
+        //     val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
+        //     if (pathList?.size!! >= 1) {
+        //         handleAvatar(pathList[0])
+        //     }
+        // } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
+        //     data?.data?.path?.let {
+        //         openImageWithPicker(File(it))
+        //     }
+        // } else {
+        //     Log.w(TAG, "Unknown intent request code")
+        // }
     }
 
     private fun uploadAvatar(file: File?) {
@@ -865,15 +866,8 @@ class ProfileActivity : BaseActivity() {
 
     companion object {
         private const val TAG: String = "ProfileController"
-        private const val AVATAR_PATH = "photos/avatar.png"
-        private const val REQUEST_CODE_SELECT_REMOTE_FILES = 22
         private const val DEFAULT_CACHE_SIZE: Int = 20
         private const val DEFAULT_RETRIES: Long = 3
-        private const val MAX_SIZE: Int = 1024
-        private const val REQUEST_CODE_IMAGE_PICKER: Int = 1
-        private const val REQUEST_CODE_TAKE_PICTURE: Int = 2
-        private const val REQUEST_PERMISSION_CAMERA: Int = 1
-        private const val FULL_QUALITY: Int = 100
         private const val HIGH_EMPHASIS_ALPHA: Float = 0.87f
         private const val MEDIUM_EMPHASIS_ALPHA: Float = 0.6f
     }

+ 217 - 0
app/src/main/java/com/nextcloud/talk/utils/PickImage.kt

@@ -0,0 +1,217 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Tim Krüger
+ * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.nextcloud.talk.utils
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import autodagger.AutoInjector
+import com.github.dhaval2404.imagepicker.ImagePicker
+import com.github.dhaval2404.imagepicker.constant.ImageProvider
+import com.nextcloud.talk.activities.TakePhotoActivity
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.controllers.base.BaseController
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
+import okhttp3.ResponseBody
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class PickImage(
+    private val controller: BaseController,
+    private var currentUser: User?
+) {
+
+    @Inject
+    lateinit var ncApi: NcApi
+
+    @Inject
+    lateinit var permissionUtil: PlatformPermissionUtil
+
+    init {
+        NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
+    }
+
+    fun selectLocal() {
+        val activity = controller.activity
+        if (activity != null) {
+            ImagePicker.Companion.with(activity)
+                .provider(ImageProvider.GALLERY)
+                .crop()
+                .cropSquare()
+                .compress(MAX_SIZE)
+                .maxResultSize(MAX_SIZE, MAX_SIZE)
+                .createIntent { intent -> controller.startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER) }
+        }
+    }
+
+    private fun selectLocal(file: File) {
+        val activity = controller.activity
+        if (activity != null) {
+            ImagePicker.Companion.with(activity)
+                .provider(ImageProvider.URI)
+                .crop()
+                .cropSquare()
+                .compress(MAX_SIZE)
+                .maxResultSize(MAX_SIZE, MAX_SIZE)
+                .setUri(Uri.fromFile(file))
+                .createIntent { intent -> controller.startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER) }
+        }
+    }
+
+    fun selectRemote() {
+        val activity = controller.activity
+        if (activity != null) {
+            val bundle = Bundle()
+            bundle.putString(BundleKeys.KEY_MIME_TYPE_FILTER, Mimetype.IMAGE_PREFIX)
+
+            val avatarIntent = Intent(activity, RemoteFileBrowserActivity::class.java)
+            avatarIntent.putExtras(bundle)
+
+            controller.startActivityForResult(avatarIntent, REQUEST_CODE_SELECT_REMOTE_FILES)
+        }
+    }
+
+    fun takePicture() {
+
+        if (permissionUtil.isCameraPermissionGranted()) {
+            controller.startActivityForResult(
+                TakePhotoActivity.createIntent(controller.context),
+                REQUEST_CODE_TAKE_PICTURE
+            )
+        } else {
+            controller.requestPermissions(
+                arrayOf(android.Manifest.permission.CAMERA),
+                REQUEST_PERMISSION_CAMERA
+            )
+        }
+    }
+
+    private fun handleAvatar(remotePath: String?) {
+        val uri = currentUser!!.baseUrl + "/index.php/apps/files/api/v1/thumbnail/512/512/" +
+            Uri.encode(remotePath, "/")
+        val downloadCall = ncApi.downloadResizedImage(
+            ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token),
+            uri
+        )
+        downloadCall.enqueue(object : Callback<ResponseBody> {
+            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
+                saveBitmapAndPassToImagePicker(BitmapFactory.decodeStream(response.body()!!.byteStream()))
+            }
+
+            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
+                // unused atm
+            }
+        })
+    }
+
+    // only possible with API26
+    private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) {
+        val file: File = saveBitmapToTempFile(bitmap) ?: return
+        selectLocal(file)
+    }
+
+    private fun saveBitmapToTempFile(bitmap: Bitmap): File? {
+        try {
+            val file = createTempFileForAvatar()
+            try {
+                FileOutputStream(file).use { out ->
+                    bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out)
+                }
+                return file
+            } catch (e: IOException) {
+                Log.e(TAG, "Error compressing bitmap", e)
+            }
+        } catch (e: IOException) {
+            Log.e(TAG, "Error creating temporary avatar image", e)
+        }
+        return null
+    }
+
+    private fun createTempFileForAvatar(): File {
+        FileUtils.removeTempCacheFile(
+            controller.context,
+            AVATAR_PATH
+        )
+        return FileUtils.getTempCacheFile(
+            controller.context,
+            AVATAR_PATH
+        )
+    }
+
+    fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?, handleImage: (uri: Uri) -> Unit) {
+
+        if (resultCode != Activity.RESULT_OK) {
+            Log.w(
+                TAG,
+                "Check result code before calling 'PickImage#handleActivtyResult'. It should be ${
+                Activity
+                    .RESULT_OK
+                }, but it is $resultCode!"
+            )
+            return
+        }
+
+        when (requestCode) {
+            REQUEST_CODE_IMAGE_PICKER -> {
+                val uri: Uri = data?.data!!
+                handleImage(uri)
+            }
+            REQUEST_CODE_SELECT_REMOTE_FILES -> {
+                val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
+                if (pathList?.size!! >= 1) {
+                    handleAvatar(pathList[0])
+                }
+            }
+            REQUEST_CODE_TAKE_PICTURE -> {
+                data?.data?.path?.let {
+                    selectLocal(File(it))
+                }
+            }
+            else -> {
+                Log.w(TAG, "Unknown intent request code")
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG: String = "PickImage"
+        private const val MAX_SIZE: Int = 1024
+        private const val AVATAR_PATH = "photos/avatar.png"
+        private const val FULL_QUALITY: Int = 100
+        const val REQUEST_CODE_IMAGE_PICKER: Int = 1
+        const val REQUEST_CODE_TAKE_PICTURE: Int = 2
+        const val REQUEST_PERMISSION_CAMERA: Int = 1
+        const val REQUEST_CODE_SELECT_REMOTE_FILES = 22
+    }
+}