Merge pull request #3752 from nextcloud/new-result-apis

Use new Result APIs instead of startActivityForResult() and onActivityResult().
This commit is contained in:
Sowjanya Kota 2024-04-03 08:51:19 +02:00 committed by GitHub
commit 4c7ddae084
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 339 additions and 253 deletions

View File

@ -1,6 +1,7 @@
/* /*
* Nextcloud Talk - Android Client * Nextcloud Talk - Android Client
* *
* SPDX-FileCopyrightText: 2024 Parneet Singh <gurayaparneet@gmail.com>
* SPDX-FileCopyrightText: 2024 Giacomo Pacini <giacomo@paciosoft.com> * SPDX-FileCopyrightText: 2024 Giacomo Pacini <giacomo@paciosoft.com>
* SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com> * SPDX-FileCopyrightText: 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
* SPDX-FileCopyrightText: 2021-2022 Marcel Hibbe <dev@mhibbe.de> * SPDX-FileCopyrightText: 2021-2022 Marcel Hibbe <dev@mhibbe.de>
@ -9,11 +10,13 @@
* SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com> * SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package com.nextcloud.talk.chat package com.nextcloud.talk.chat
import android.Manifest import android.Manifest
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
@ -71,6 +74,8 @@ import android.widget.RelativeLayout.LayoutParams
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.TextView import android.widget.TextView
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@ -278,6 +283,46 @@ class ChatActivity :
private lateinit var editMessage: ChatMessage private lateinit var editMessage: ChatMessage
private val startSelectContactForResult = registerForActivityResult(
ActivityResultContracts
.StartActivityForResult()
) {
executeIfResultOk(it) { intent ->
onSelectContactResult(intent)
}
}
private val startChooseFileIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
executeIfResultOk(it) { intent ->
onChooseFileResult(intent)
}
}
private val startRemoteFileBrowsingForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
executeIfResultOk(it) { intent ->
onRemoteFileBrowsingResult(intent)
}
}
private val startMessageSearchForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
executeIfResultOk(it) { intent ->
onMessageSearchResult(intent)
}
}
private val startPickCameraIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
executeIfResultOk(it) { intent ->
onPickCameraResult(intent)
}
}
override val view: View override val view: View
get() = binding.root get() = binding.root
@ -2967,170 +3012,167 @@ class ChatActivity :
} }
} }
@Throws(IllegalStateException::class) private fun onRemoteFileBrowsingResult(intent: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
super.onActivityResult(requestCode, resultCode, intent) if (pathList?.size!! >= 1) {
if (resultCode != RESULT_OK && (requestCode != REQUEST_CODE_MESSAGE_SEARCH)) { pathList
Log.e(TAG, "resultCode for received intent was != ok") .chunked(CHUNK_SIZE)
return .forEach { paths ->
val data = Data.Builder()
.putLong(KEY_INTERNAL_USER_ID, conversationUser!!.id!!)
.putString(KEY_ROOM_TOKEN, roomToken)
.putStringArray(KEY_FILE_PATHS, paths.toTypedArray())
.build()
val worker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java)
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(worker)
}
} }
}
when (requestCode) { @Throws(IllegalStateException::class)
REQUEST_CODE_SELECT_REMOTE_FILES -> { private fun onChooseFileResult(intent: Intent?) {
val pathList = intent?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS) try {
if (pathList?.size!! >= 1) { checkNotNull(intent)
pathList filesToUpload.clear()
.chunked(CHUNK_SIZE) intent.clipData?.let {
.forEach { paths -> for (index in 0 until it.itemCount) {
val data = Data.Builder() filesToUpload.add(it.getItemAt(index).uri.toString())
.putLong(KEY_INTERNAL_USER_ID, conversationUser!!.id!!) }
.putString(KEY_ROOM_TOKEN, roomToken) } ?: run {
.putStringArray(KEY_FILE_PATHS, paths.toTypedArray()) checkNotNull(intent.data)
.build() intent.data.let {
val worker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java) filesToUpload.add(intent.data.toString())
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(worker)
}
} }
} }
require(filesToUpload.isNotEmpty())
REQUEST_CODE_CHOOSE_FILE -> { val filenamesWithLineBreaks = StringBuilder("\n")
try {
checkNotNull(intent)
filesToUpload.clear()
intent.clipData?.let {
for (index in 0 until it.itemCount) {
filesToUpload.add(it.getItemAt(index).uri.toString())
}
} ?: run {
checkNotNull(intent.data)
intent.data.let {
filesToUpload.add(intent.data.toString())
}
}
require(filesToUpload.isNotEmpty())
val filenamesWithLineBreaks = StringBuilder("\n") for (file in filesToUpload) {
val filename = FileUtils.getFileName(Uri.parse(file), context)
for (file in filesToUpload) { filenamesWithLineBreaks.append(filename).append("\n")
val filename = FileUtils.getFileName(Uri.parse(file), context)
filenamesWithLineBreaks.append(filename).append("\n")
}
val newFragment = FileAttachmentPreviewFragment.newInstance(
filenamesWithLineBreaks.toString(),
filesToUpload
)
newFragment.setListener { files, caption ->
uploadFiles(files, caption)
}
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
} catch (e: IllegalStateException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
} }
REQUEST_CODE_SELECT_CONTACT -> { val newFragment = FileAttachmentPreviewFragment.newInstance(
val contactUri = intent?.data ?: return filenamesWithLineBreaks.toString(),
val cursor: Cursor? = contentResolver!!.query(contactUri, null, null, null, null) filesToUpload
)
if (cursor != null && cursor.moveToFirst()) { newFragment.setListener { files, caption ->
val id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID)) uploadFiles(files, caption)
val fileName = ContactUtils.getDisplayNameFromDeviceContact(context, id) + ".vcf"
val file = File(context.cacheDir, fileName)
writeContactToVcfFile(cursor, file)
val shareUri = FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID,
File(file.absolutePath)
)
uploadFile(shareUri.toString(), false)
}
cursor?.close()
} }
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
} catch (e: IllegalStateException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
).show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
}
REQUEST_CODE_PICK_CAMERA -> { private fun onSelectContactResult(intent: Intent?) {
if (resultCode == RESULT_OK) { val contactUri = intent?.data ?: return
try { val cursor: Cursor? = contentResolver!!.query(contactUri, null, null, null, null)
filesToUpload.clear()
if (intent != null && intent.data != null) { if (cursor != null && cursor.moveToFirst()) {
run { val id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
intent.data.let { val fileName = ContactUtils.getDisplayNameFromDeviceContact(context, id) + ".vcf"
filesToUpload.add(intent.data.toString()) val file = File(context.cacheDir, fileName)
} writeContactToVcfFile(cursor, file)
}
require(filesToUpload.isNotEmpty())
} else if (videoURI != null) {
filesToUpload.add(videoURI.toString())
videoURI = null
} else {
error("Failed to get data from intent and uri")
}
if (permissionUtil.isFilesPermissionGranted()) { val shareUri = FileProvider.getUriForFile(
val filenamesWithLineBreaks = StringBuilder("\n") this,
BuildConfig.APPLICATION_ID,
File(file.absolutePath)
)
uploadFile(shareUri.toString(), false)
}
cursor?.close()
}
for (file in filesToUpload) { @Throws(IllegalStateException::class)
val filename = FileUtils.getFileName(Uri.parse(file), context) private fun onPickCameraResult(intent: Intent?) {
filenamesWithLineBreaks.append(filename).append("\n") try {
} filesToUpload.clear()
val newFragment = FileAttachmentPreviewFragment.newInstance( if (intent != null && intent.data != null) {
filenamesWithLineBreaks.toString(), run {
filesToUpload intent.data.let {
) filesToUpload.add(intent.data.toString())
newFragment.setListener { files, caption -> uploadFiles(files, caption) }
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this)
}
} catch (e: IllegalStateException) {
Snackbar.make(
binding.root,
R.string.nc_upload_failed,
Snackbar.LENGTH_LONG
)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
)
.show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} }
} }
require(filesToUpload.isNotEmpty())
} else if (videoURI != null) {
filesToUpload.add(videoURI.toString())
videoURI = null
} else {
error("Failed to get data from intent and uri")
} }
REQUEST_CODE_MESSAGE_SEARCH -> { if (permissionUtil.isFilesPermissionGranted()) {
val messageId = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID) val filenamesWithLineBreaks = StringBuilder("\n")
messageId?.let { id ->
scrollToMessageWithId(id) for (file in filesToUpload) {
val filename = FileUtils.getFileName(Uri.parse(file), context)
filenamesWithLineBreaks.append(filename).append("\n")
} }
val newFragment = FileAttachmentPreviewFragment.newInstance(
filenamesWithLineBreaks.toString(),
filesToUpload
)
newFragment.setListener { files, caption -> uploadFiles(files, caption) }
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
} else {
UploadAndShareFilesWorker.requestStoragePermission(this)
} }
} catch (e: IllegalStateException) {
Snackbar.make(
binding.root,
R.string.nc_upload_failed,
Snackbar.LENGTH_LONG
)
.show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
context.resources?.getString(R.string.nc_upload_failed)?.let {
Snackbar.make(
binding.root,
it,
Snackbar.LENGTH_LONG
)
.show()
}
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
}
private fun onMessageSearchResult(intent: Intent?) {
val messageId = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
messageId?.let { id ->
scrollToMessageWithId(id)
}
}
private fun executeIfResultOk(result: ActivityResult, onResult: (intent: Intent?) -> Unit) {
if (result.resultCode == Activity.RESULT_OK) {
onResult(result.data)
} else {
Log.e(TAG, "resultCode for received intent was != ok")
} }
} }
@ -3202,7 +3244,7 @@ class ChatActivity :
} else if (requestCode == REQUEST_READ_CONTACT_PERMISSION) { } else if (requestCode == REQUEST_READ_CONTACT_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) val intent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
startActivityForResult(intent, REQUEST_CODE_SELECT_CONTACT) startSelectContactForResult.launch(intent)
} else { } else {
Snackbar.make( Snackbar.make(
binding.root, binding.root,
@ -3275,14 +3317,13 @@ class ChatActivity :
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
} }
startActivityForResult( startChooseFileIntentForResult.launch(
Intent.createChooser( Intent.createChooser(
action, action,
context.resources?.getString( context.resources?.getString(
R.string.nc_upload_choose_local_files R.string.nc_upload_choose_local_files
) )
), )
REQUEST_CODE_CHOOSE_FILE
) )
} }
@ -3328,7 +3369,7 @@ class ChatActivity :
fun showBrowserScreen() { fun showBrowserScreen() {
val sharingFileBrowserIntent = Intent(this, RemoteFileBrowserActivity::class.java) val sharingFileBrowserIntent = Intent(this, RemoteFileBrowserActivity::class.java)
startActivityForResult(sharingFileBrowserIntent, REQUEST_CODE_SELECT_REMOTE_FILES) startRemoteFileBrowsingForResult.launch(sharingFileBrowserIntent)
} }
fun showShareLocationScreen() { fun showShareLocationScreen() {
@ -4095,7 +4136,7 @@ class ChatActivity :
val intent = Intent(this, MessageSearchActivity::class.java) val intent = Intent(this, MessageSearchActivity::class.java)
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName) intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
intent.putExtra(KEY_ROOM_TOKEN, roomToken) intent.putExtra(KEY_ROOM_TOKEN, roomToken)
startActivityForResult(intent, REQUEST_CODE_MESSAGE_SEARCH) startMessageSearchForResult.launch(intent)
} }
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> { private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
@ -4797,7 +4838,7 @@ class ChatActivity :
if (!permissionUtil.isCameraPermissionGranted()) { if (!permissionUtil.isCameraPermissionGranted()) {
requestCameraPermissions() requestCameraPermissions()
} else { } else {
startActivityForResult(TakePhotoActivity.createIntent(context), REQUEST_CODE_PICK_CAMERA) startPickCameraIntentForResult.launch(TakePhotoActivity.createIntent(context))
} }
} }
@ -4825,7 +4866,7 @@ class ChatActivity :
videoFile?.also { videoFile?.also {
videoURI = FileProvider.getUriForFile(context, context.packageName, it) videoURI = FileProvider.getUriForFile(context, context.packageName, it)
takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoURI) takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoURI)
startActivityForResult(takeVideoIntent, REQUEST_CODE_PICK_CAMERA) startPickCameraIntentForResult.launch(takeVideoIntent)
} }
} }
} }
@ -4897,15 +4938,10 @@ class ChatActivity :
private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000 private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000
private const val HTTP_CODE_OK: Int = 200 private const val HTTP_CODE_OK: Int = 200
private const val AGE_THRESHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000) private const val AGE_THRESHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000)
private const val REQUEST_CODE_CHOOSE_FILE: Int = 555
private const val REQUEST_CODE_SELECT_CONTACT: Int = 666
private const val REQUEST_CODE_MESSAGE_SEARCH: Int = 777
private const val REQUEST_SHARE_FILE_PERMISSION: Int = 221 private const val REQUEST_SHARE_FILE_PERMISSION: Int = 221
private const val REQUEST_RECORD_AUDIO_PERMISSION = 222 private const val REQUEST_RECORD_AUDIO_PERMISSION = 222
private const val REQUEST_READ_CONTACT_PERMISSION = 234 private const val REQUEST_READ_CONTACT_PERMISSION = 234
private const val REQUEST_CAMERA_PERMISSION = 223 private const val REQUEST_CAMERA_PERMISSION = 223
private const val REQUEST_CODE_PICK_CAMERA: Int = 333
private const val REQUEST_CODE_SELECT_REMOTE_FILES = 888
private const val OBJECT_MESSAGE: String = "{object}" private const val OBJECT_MESSAGE: String = "{object}"
private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000 private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
private const val MINIMUM_VOICE_RECORD_TO_STOP: Int = 200 private const val MINIMUM_VOICE_RECORD_TO_STOP: Int = 200

View File

@ -8,7 +8,6 @@
package com.nextcloud.talk.conversationinfoedit package com.nextcloud.talk.conversationinfoedit
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
@ -16,6 +15,8 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -49,8 +50,7 @@ import java.io.File
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class ConversationInfoEditActivity : class ConversationInfoEditActivity : BaseActivity() {
BaseActivity() {
private lateinit var binding: ActivityConversationInfoEditBinding private lateinit var binding: ActivityConversationInfoEditBinding
@ -75,6 +75,31 @@ class ConversationInfoEditActivity :
private lateinit var spreedCapabilities: SpreedCapability private lateinit var spreedCapabilities: SpreedCapability
private val startImagePickerForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
handleResult(it) { result ->
pickImage.onImagePickerResult(result.data) { uri ->
uploadAvatar(uri.toFile())
}
}
}
private val startSelectRemoteFilesIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
handleResult(it) { result ->
pickImage.onSelectRemoteFilesResult(startImagePickerForResult, result.data)
}
}
private val startTakePictureIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
handleResult(it) { result ->
pickImage.onTakePictureResult(startImagePickerForResult, result.data)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -158,9 +183,18 @@ class ConversationInfoEditActivity :
} }
private fun setupAvatarOptions() { private fun setupAvatarOptions() {
binding.avatarUpload.setOnClickListener { pickImage.selectLocal() } binding.avatarUpload.setOnClickListener {
binding.avatarChoose.setOnClickListener { pickImage.selectRemote() } pickImage.selectLocal(startImagePickerForResult = startImagePickerForResult)
binding.avatarCamera.setOnClickListener { pickImage.takePicture() } }
binding.avatarChoose.setOnClickListener {
pickImage.selectRemote(startSelectRemoteFilesIntentForResult = startSelectRemoteFilesIntentForResult)
}
binding.avatarCamera.setOnClickListener {
pickImage.takePicture(startTakePictureIntentForResult = startTakePictureIntentForResult)
}
if (conversation?.hasCustomAvatar == true) { if (conversation?.hasCustomAvatar == true) {
binding.avatarDelete.visibility = View.VISIBLE binding.avatarDelete.visibility = View.VISIBLE
binding.avatarDelete.setOnClickListener { deleteAvatar() } binding.avatarDelete.setOnClickListener { deleteAvatar() }
@ -293,19 +327,12 @@ class ConversationInfoEditActivity :
}) })
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private fun handleResult(result: ActivityResult, onResult: (result: ActivityResult) -> Unit) {
super.onActivityResult(requestCode, resultCode, data) when (result.resultCode) {
when (resultCode) { Activity.RESULT_OK -> onResult(result)
Activity.RESULT_OK -> {
pickImage.handleActivityResult(
requestCode,
resultCode,
data
) { uploadAvatar(it.toFile()) }
}
ImagePicker.RESULT_ERROR -> { ImagePicker.RESULT_ERROR -> {
Snackbar.make(binding.root, ImagePicker.getError(data), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, ImagePicker.getError(result.data), Snackbar.LENGTH_SHORT).show()
} }
else -> { else -> {

View File

@ -11,9 +11,10 @@ package com.nextcloud.talk.lock
import android.app.Activity import android.app.Activity
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import autodagger.AutoInjector import autodagger.AutoInjector
@ -35,6 +36,13 @@ class LockedActivity : AppCompatActivity() {
@Inject @Inject
lateinit var appPreferences: AppPreferences lateinit var appPreferences: AppPreferences
private val startForCredentialsResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult
()
) {
onConfirmDeviceCredentials(it)
}
private lateinit var binding: ActivityLockedBinding private lateinit var binding: ActivityLockedBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -111,27 +119,23 @@ class LockedActivity : AppCompatActivity() {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager? val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
val intent = keyguardManager?.createConfirmDeviceCredentialIntent(null, null) val intent = keyguardManager?.createConfirmDeviceCredentialIntent(null, null)
if (intent != null) { if (intent != null) {
startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) startForCredentialsResult.launch(intent)
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private fun onConfirmDeviceCredentials(result: ActivityResult) {
super.onActivityResult(requestCode, resultCode, data) if (result.resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { if (
if (resultCode == Activity.RESULT_OK) { SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)
if ( ) {
SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout) finish()
) {
finish()
}
} else {
Log.d(TAG, "Authorization failed")
} }
} else {
Log.d(TAG, "Authorization failed")
} }
} }
companion object { companion object {
private val TAG = LockedActivity::class.java.simpleName private val TAG = LockedActivity::class.java.simpleName
private const val REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112
} }
} }

View File

@ -10,7 +10,6 @@
package com.nextcloud.talk.profile package com.nextcloud.talk.profile
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
@ -24,6 +23,8 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toFile import androidx.core.net.toFile
@ -50,13 +51,13 @@ import com.nextcloud.talk.ui.dialog.ScopeDialog
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SpreedFeatures import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG import com.nextcloud.talk.utils.Mimetype.IMAGE_JPG
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
import com.nextcloud.talk.utils.PickImage import com.nextcloud.talk.utils.PickImage
import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA import com.nextcloud.talk.utils.PickImage.Companion.REQUEST_PERMISSION_CAMERA
import com.nextcloud.talk.utils.CapabilitiesUtil import com.nextcloud.talk.utils.SpreedFeatures
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -87,6 +88,31 @@ class ProfileActivity : BaseActivity() {
private lateinit var pickImage: PickImage private lateinit var pickImage: PickImage
private val startImagePickerForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
handleResult(it) { result ->
pickImage.onImagePickerResult(result.data) { uri ->
uploadAvatar(uri.toFile())
}
}
}
private val startSelectRemoteFilesIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
handleResult(it) { result ->
pickImage.onSelectRemoteFilesResult(startImagePickerForResult, result.data)
}
}
private val startTakePictureIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
handleResult(it) { result ->
pickImage.onTakePictureResult(startImagePickerForResult, result.data)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@ -106,9 +132,15 @@ class ProfileActivity : BaseActivity() {
val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) val credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
pickImage = PickImage(this, currentUser) pickImage = PickImage(this, currentUser)
binding.avatarUpload.setOnClickListener { pickImage.selectLocal() } binding.avatarUpload.setOnClickListener {
binding.avatarChoose.setOnClickListener { pickImage.selectRemote() } pickImage.selectLocal(startImagePickerForResult = startImagePickerForResult)
binding.avatarCamera.setOnClickListener { pickImage.takePicture() } }
binding.avatarChoose.setOnClickListener {
pickImage.selectRemote(startSelectRemoteFilesIntentForResult = startSelectRemoteFilesIntentForResult)
}
binding.avatarCamera.setOnClickListener {
pickImage.takePicture(startTakePictureIntentForResult = startTakePictureIntentForResult)
}
binding.avatarDelete.setOnClickListener { binding.avatarDelete.setOnClickListener {
ncApi.deleteAvatar( ncApi.deleteAvatar(
credentials, credentials,
@ -479,7 +511,7 @@ class ProfileActivity : BaseActivity() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_PERMISSION_CAMERA) { if (requestCode == REQUEST_PERMISSION_CAMERA) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
pickImage.takePicture() pickImage.takePicture(startTakePictureIntentForResult = startTakePictureIntentForResult)
} else { } else {
Snackbar Snackbar
.make(binding.root, context.getString(R.string.take_photo_permission), Snackbar.LENGTH_LONG) .make(binding.root, context.getString(R.string.take_photo_permission), Snackbar.LENGTH_LONG)
@ -488,19 +520,14 @@ class ProfileActivity : BaseActivity() {
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { private fun handleResult(result: ActivityResult, onResult: (result: ActivityResult) -> Unit) {
super.onActivityResult(requestCode, resultCode, data) when (result.resultCode) {
when (resultCode) { Activity.RESULT_OK -> onResult(result)
Activity.RESULT_OK -> {
pickImage.handleActivityResult(
requestCode,
resultCode,
data
) { uploadAvatar(it.toFile()) }
}
ImagePicker.RESULT_ERROR -> { ImagePicker.RESULT_ERROR -> {
Snackbar.make(binding.root, getError(data), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.root, getError(result.data), Snackbar.LENGTH_SHORT).show()
} }
else -> { else -> {
Log.i(TAG, "Task Cancelled") Log.i(TAG, "Task Cancelled")
} }

View File

@ -1,9 +1,11 @@
/* /*
* Nextcloud Talk - Android Client * Nextcloud Talk - Android Client
* *
* SPDX-FileCopyrightText: 2024 Parneet Singh <gurayaparneet@gmail.com>
* SPDX-FileCopyrightText: 2022 Tim Krüger <t@timkrueger.me> * SPDX-FileCopyrightText: 2022 Tim Krüger <t@timkrueger.me>
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package com.nextcloud.talk.utils package com.nextcloud.talk.utils
import android.app.Activity import android.app.Activity
@ -13,6 +15,7 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import autodagger.AutoInjector import autodagger.AutoInjector
import com.github.dhaval2404.imagepicker.ImagePicker import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider import com.github.dhaval2404.imagepicker.constant.ImageProvider
@ -48,17 +51,19 @@ class PickImage(
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
} }
fun selectLocal() { fun selectLocal(startImagePickerForResult: ActivityResultLauncher<Intent>) {
ImagePicker.Companion.with(activity) ImagePicker.Companion.with(activity)
.provider(ImageProvider.GALLERY) .provider(ImageProvider.GALLERY)
.crop() .crop()
.cropSquare() .cropSquare()
.compress(MAX_SIZE) .compress(MAX_SIZE)
.maxResultSize(MAX_SIZE, MAX_SIZE) .maxResultSize(MAX_SIZE, MAX_SIZE)
.createIntent { intent -> this.activity.startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER) } .createIntent { intent ->
startImagePickerForResult.launch(intent)
}
} }
private fun selectLocal(file: File) { private fun selectLocal(startImagePickerForResult: ActivityResultLauncher<Intent>, file: File) {
ImagePicker.Companion.with(activity) ImagePicker.Companion.with(activity)
.provider(ImageProvider.URI) .provider(ImageProvider.URI)
.crop() .crop()
@ -66,25 +71,23 @@ class PickImage(
.compress(MAX_SIZE) .compress(MAX_SIZE)
.maxResultSize(MAX_SIZE, MAX_SIZE) .maxResultSize(MAX_SIZE, MAX_SIZE)
.setUri(Uri.fromFile(file)) .setUri(Uri.fromFile(file))
.createIntent { intent -> this.activity.startActivityForResult(intent, REQUEST_CODE_IMAGE_PICKER) } .createIntent { intent ->
startImagePickerForResult.launch(intent)
}
} }
fun selectRemote() { fun selectRemote(startSelectRemoteFilesIntentForResult: ActivityResultLauncher<Intent>) {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(BundleKeys.KEY_MIME_TYPE_FILTER, Mimetype.IMAGE_PREFIX) bundle.putString(BundleKeys.KEY_MIME_TYPE_FILTER, Mimetype.IMAGE_PREFIX)
val avatarIntent = Intent(activity, RemoteFileBrowserActivity::class.java) val avatarIntent = Intent(activity, RemoteFileBrowserActivity::class.java)
avatarIntent.putExtras(bundle) avatarIntent.putExtras(bundle)
startSelectRemoteFilesIntentForResult.launch(avatarIntent)
this.activity.startActivityForResult(avatarIntent, REQUEST_CODE_SELECT_REMOTE_FILES)
} }
fun takePicture() { fun takePicture(startTakePictureIntentForResult: ActivityResultLauncher<Intent>) {
if (permissionUtil.isCameraPermissionGranted()) { if (permissionUtil.isCameraPermissionGranted()) {
activity.startActivityForResult( startTakePictureIntentForResult.launch(TakePhotoActivity.createIntent(activity))
TakePhotoActivity.createIntent(activity),
REQUEST_CODE_TAKE_PICTURE
)
} else { } else {
activity.requestPermissions( activity.requestPermissions(
arrayOf(android.Manifest.permission.CAMERA), arrayOf(android.Manifest.permission.CAMERA),
@ -93,7 +96,7 @@ class PickImage(
} }
} }
private fun handleAvatar(remotePath: String?) { private fun handleAvatar(startImagePickerForResult: ActivityResultLauncher<Intent>, remotePath: String?) {
val uri = currentUser!!.baseUrl + "/index.php/apps/files/api/v1/thumbnail/512/512/" + val uri = currentUser!!.baseUrl + "/index.php/apps/files/api/v1/thumbnail/512/512/" +
Uri.encode(remotePath, "/") Uri.encode(remotePath, "/")
val downloadCall = ncApi.downloadResizedImage( val downloadCall = ncApi.downloadResizedImage(
@ -102,7 +105,10 @@ class PickImage(
) )
downloadCall.enqueue(object : Callback<ResponseBody> { downloadCall.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
saveBitmapAndPassToImagePicker(BitmapFactory.decodeStream(response.body()!!.byteStream())) saveBitmapAndPassToImagePicker(
startImagePickerForResult,
BitmapFactory.decodeStream(response.body()!!.byteStream())
)
} }
override fun onFailure(call: Call<ResponseBody>, t: Throwable) { override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
@ -112,9 +118,12 @@ class PickImage(
} }
// only possible with API26 // only possible with API26
private fun saveBitmapAndPassToImagePicker(bitmap: Bitmap) { private fun saveBitmapAndPassToImagePicker(
startImagePickerForResult: ActivityResultLauncher<Intent>,
bitmap: Bitmap
) {
val file: File = saveBitmapToTempFile(bitmap) ?: return val file: File = saveBitmapToTempFile(bitmap) ?: return
selectLocal(file) selectLocal(startImagePickerForResult, file)
} }
private fun saveBitmapToTempFile(bitmap: Bitmap): File? { private fun saveBitmapToTempFile(bitmap: Bitmap): File? {
@ -145,35 +154,21 @@ class PickImage(
) )
} }
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?, handleImage: (uri: Uri) -> Unit) { fun onImagePickerResult(data: Intent?, handleImage: (uri: Uri) -> Unit) {
if (resultCode != Activity.RESULT_OK) { val uri: Uri = data?.data!!
Log.w( handleImage(uri)
TAG, }
"Check result code before calling " +
"'PickImage#handleActivtyResult'. It should be ${Activity.RESULT_OK}, but it is $resultCode!"
)
return
}
when (requestCode) { fun onSelectRemoteFilesResult(startImagePickerForResult: ActivityResultLauncher<Intent>, data: Intent?) {
REQUEST_CODE_IMAGE_PICKER -> { val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
val uri: Uri = data?.data!! if (pathList?.size!! >= 1) {
handleImage(uri) handleAvatar(startImagePickerForResult, pathList[0])
} }
REQUEST_CODE_SELECT_REMOTE_FILES -> { }
val pathList = data?.getStringArrayListExtra(RemoteFileBrowserActivity.EXTRA_SELECTED_PATHS)
if (pathList?.size!! >= 1) { fun onTakePictureResult(startImagePickerForResult: ActivityResultLauncher<Intent>, data: Intent?) {
handleAvatar(pathList[0]) data?.data?.path?.let {
} selectLocal(startImagePickerForResult, File(it))
}
REQUEST_CODE_TAKE_PICTURE -> {
data?.data?.path?.let {
selectLocal(File(it))
}
}
else -> {
Log.w(TAG, "Unknown intent request code")
}
} }
} }
@ -182,9 +177,6 @@ class PickImage(
private const val MAX_SIZE: Int = 1024 private const val MAX_SIZE: Int = 1024
private const val AVATAR_PATH = "photos/avatar.png" private const val AVATAR_PATH = "photos/avatar.png"
private const val FULL_QUALITY: Int = 100 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_PERMISSION_CAMERA: Int = 1
const val REQUEST_CODE_SELECT_REMOTE_FILES = 22
} }
} }