From cb2ee730fa397c7c5a60ff5915692b6047222866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey?= Date: Tue, 28 Jun 2022 16:50:22 +0200 Subject: [PATCH] WIP: upload profile picture from camera MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey --- .../application/NextcloudTalkApplication.kt | 4 +- .../talk/controllers/ChatController.kt | 17 +--- .../talk/controllers/ProfileController.kt | 99 +++++++++++++++---- .../talk/dagger/modules/UtilsModule.kt | 38 +++++++ .../permissions/PlatformPermissionUtil.kt | 26 +++++ .../permissions/PlatformPermissionUtilImpl.kt | 41 ++++++++ .../main/res/layout/controller_profile.xml | 11 +++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 204 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt create mode 100644 app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt create mode 100644 app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index ea3d97237..e3f05ccc1 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -57,6 +57,7 @@ import com.nextcloud.talk.dagger.modules.ContextModule import com.nextcloud.talk.dagger.modules.DatabaseModule import com.nextcloud.talk.dagger.modules.RepositoryModule import com.nextcloud.talk.dagger.modules.RestModule +import com.nextcloud.talk.dagger.modules.UtilsModule import com.nextcloud.talk.dagger.modules.ViewModelModule import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.CapabilitiesWorker @@ -99,7 +100,8 @@ import javax.inject.Singleton UserModule::class, ArbitraryStorageModule::class, ViewModelModule::class, - RepositoryModule::class + RepositoryModule::class, + UtilsModule::class ] ) @Singleton diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 9c719f32e..14edb69a5 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -169,6 +169,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY import com.nextcloud.talk.utils.database.user.UserUtils +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder import com.nextcloud.talk.utils.text.Spans import com.nextcloud.talk.webrtc.MagicWebSocketInstance @@ -231,6 +232,9 @@ class ChatController(args: Bundle) : @JvmField var eventBus: EventBus? = null + @Inject + lateinit var permissionUtil: PlatformPermissionUtil + val disposableList = ArrayList() var roomToken: String? = null @@ -1111,17 +1115,6 @@ class ChatController(args: Bundle) : } } - private fun isCameraPermissionGranted(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return PermissionChecker.checkSelfPermission( - context!!, - Manifest.permission.CAMERA - ) == PermissionChecker.PERMISSION_GRANTED - } else { - true - } - } - private fun startAudioRecording(file: String) { binding.messageInputView.audioRecordDuration.base = SystemClock.elapsedRealtime() binding.messageInputView.audioRecordDuration.start() @@ -3128,7 +3121,7 @@ class ChatController(args: Bundle) : } fun sendPictureFromCamIntent() { - if (!isCameraPermissionGranted()) { + if (!permissionUtil.isCameraPermissionGranted()) { requestCameraPermissions() } else { startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_PICK_CAMERA) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt index ad901cf9c..0d8bbf35c 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ProfileController.kt @@ -22,7 +22,9 @@ package com.nextcloud.talk.controllers import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Intent +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -52,6 +54,7 @@ import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getError import com.github.dhaval2404.imagepicker.ImagePicker.Companion.getFile import com.github.dhaval2404.imagepicker.ImagePicker.Companion.with import com.nextcloud.talk.R +import com.nextcloud.talk.activities.TakePhotoActivity import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication @@ -76,6 +79,7 @@ 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.database.user.UserUtils +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -95,6 +99,7 @@ import java.util.Locale import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) +@Suppress("Detekt.TooManyFunctions") class ProfileController : NewBaseController(R.layout.controller_profile) { private val binding: ControllerProfileBinding by viewBinding(ControllerProfileBinding::bind) @@ -104,6 +109,9 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { @Inject lateinit var userUtils: UserUtils + @Inject + lateinit var permissionUtil: PlatformPermissionUtil + private var currentUser: UserEntity? = null private var edit = false private var adapter: UserInfoAdapter? = null @@ -197,6 +205,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) binding.avatarUpload.setOnClickListener { sendSelectLocalFileIntent() } binding.avatarChoose.setOnClickListener { showBrowserScreen() } + binding.avatarCamera.setOnClickListener { checkPermissionAndTakePicture() } binding.avatarDelete.setOnClickListener { ncApi.deleteAvatar( credentials, @@ -493,7 +502,23 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { startActivityForResult(avatarIntent, REQUEST_CODE_SELECT_REMOTE_FILES) } - fun handleAvatar(remotePath: String?) { + private fun checkPermissionAndTakePicture() { + if (permissionUtil.isCameraPermissionGranted()) { + takePictureForAvatar() + } else { + requestPermissions(arrayOf(android.Manifest.permission.CAMERA), REQUEST_PERMISSION_CAMERA) + } + } + + private fun takePictureForAvatar() { + try { + startActivityForResult(TakePhotoActivity.createIntent(context!!), REQUEST_CODE_TAKE_PICTURE) + } catch (e: ActivityNotFoundException) { + // TODO + } + } + + 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( @@ -511,30 +536,54 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { }) } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_PERMISSION_CAMERA) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + takePictureForAvatar() + } else { + Toast + .makeText(context, context?.getString(R.string.take_photo_permission), Toast.LENGTH_LONG) + .show() + } + } + } + // only possible with API26 private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) { - var file: File? = null + val file: File = saveBitmapToTempFile(bitmap) ?: return + openImageWithPicker(file) + } + + private fun saveBitmapToTempFile(bitmap: Bitmap): File? { try { - FileUtils.removeTempCacheFile( - this.context!!, - AVATAR_PATH - ) - file = FileUtils.getTempCacheFile( - this.context!!, - AVATAR_PATH - ) + val file = createTempFileForAvatar() try { - FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, FULL_QUALITY, out) } + 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) } - if (file == null) { - // TODO exception - return - } + return null + } + + private fun createTempFileForAvatar(): File? { + FileUtils.removeTempCacheFile( + this.context!!, + AVATAR_PATH + ) + return FileUtils.getTempCacheFile( + context!!, + AVATAR_PATH + ) + } + + private fun openImageWithPicker(file: File) { val intent = with(activity!!) .fileOnly() .crop() @@ -546,20 +595,24 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER) } - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE_IMAGE_PICKER) { - uploadAvatar(getFile(intent)) + uploadAvatar(getFile(data)) } else if (requestCode == REQUEST_CODE_SELECT_REMOTE_FILES) { - val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS) + 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") } } else if (resultCode == ImagePicker.RESULT_ERROR) { - Toast.makeText(activity, getError(intent), Toast.LENGTH_SHORT).show() + Toast.makeText(activity, getError(data), Toast.LENGTH_SHORT).show() } else { Log.i(TAG, "Task Cancelled") } @@ -595,7 +648,11 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { } override fun onError(e: Throwable) { - Toast.makeText(applicationContext, "Error", Toast.LENGTH_LONG).show() + Toast.makeText( + applicationContext, context!!.getString(R.string.default_error_msg), + Toast + .LENGTH_LONG + ).show() Log.e(TAG, "Error uploading avatar", e) } @@ -823,6 +880,8 @@ class ProfileController : NewBaseController(R.layout.controller_profile) { 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 diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt new file mode 100644 index 000000000..cd765c5d2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * 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 . + */ + +package com.nextcloud.talk.dagger.modules + +import android.content.Context +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtilImpl +import dagger.Module +import dagger.Provides +import dagger.Reusable + +@Module(includes = [ContextModule::class]) +class UtilsModule { + @Provides + @Reusable + fun providePermissionUtil(context: Context): PlatformPermissionUtil { + return PlatformPermissionUtilImpl(context) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt new file mode 100644 index 000000000..01ebe4fde --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * 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 . + */ + +package com.nextcloud.talk.utils.permissions + +interface PlatformPermissionUtil { + fun isCameraPermissionGranted(): Boolean +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt new file mode 100644 index 000000000..bff017d97 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt @@ -0,0 +1,41 @@ +/* + * Nextcloud Talk application + * + * @author Álvaro Brey + * Copyright (C) 2022 Álvaro Brey + * Copyright (C) 2022 Nextcloud GmbH + * + * 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 . + */ + +package com.nextcloud.talk.utils.permissions + +import android.Manifest +import android.content.Context +import android.os.Build +import androidx.core.content.PermissionChecker + +class PlatformPermissionUtilImpl(private val context: Context) : PlatformPermissionUtil { + + override fun isCameraPermissionGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return PermissionChecker.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PermissionChecker.PERMISSION_GRANTED + } else { + true + } + } +} diff --git a/app/src/main/res/layout/controller_profile.xml b/app/src/main/res/layout/controller_profile.xml index 9cf360582..21447143d 100644 --- a/app/src/main/res/layout/controller_profile.xml +++ b/app/src/main/res/layout/controller_profile.xml @@ -98,6 +98,17 @@ android:src="@drawable/ic_mimetype_folder" app:tint="@color/colorPrimary" /> + + All Send without notification Call without notification + Set avatar from camera