From ce7d0ab8cff169d27105da907ac01243cc15b788 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:58:54 +0300 Subject: [PATCH] android: fix keyboard not closing between views, photo not saved on camera rotation, repeat authentication on opening chat via notification, other minor issues (#1314) * android: User's problems fix * Hiding keyboard, state preserving while rotating camera orientation * Allows to select image from camera multiple times without crashing * Images sending after orientation change * Do not ask again about auth when switching between dialogs --- .../java/chat/simplex/app/MainActivity.kt | 10 +++ .../chat/simplex/app/views/chat/ChatView.kt | 13 +++- .../simplex/app/views/chat/ComposeView.kt | 63 +++++-------------- .../app/views/chat/group/GroupProfileView.kt | 6 +- .../app/views/chat/item/CIImageView.kt | 2 + .../views/chat/item/ImageFullScreenView.kt | 1 + .../chat/simplex/app/views/helpers/Enums.kt | 2 +- .../simplex/app/views/helpers/GetImageView.kt | 60 +++++++++++------- .../app/views/helpers/SearchTextField.kt | 6 ++ .../chat/simplex/app/views/helpers/Util.kt | 11 ++++ .../simplex/app/views/newchat/AddGroupView.kt | 6 +- .../app/views/usersettings/UserProfileView.kt | 8 ++- 12 files changed, 107 insertions(+), 81 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt index 68893ebef6..78d964b0d8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt @@ -103,6 +103,16 @@ class MainActivity: FragmentActivity() { } } + override fun onPause() { + super.onPause() + /** + * When new activity is created after a click on notification, the old one receives onPause before + * recreation but receives onStop after recreation. So using both (onPause and onStop) to prevent + * unwanted multiple auth dialogs from [runAuthenticate] + * */ + enteredBackground.value = elapsedRealtime() + } + override fun onStop() { super.onStop() enteredBackground.value = elapsedRealtime() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 88aad59f6f..74db098fe3 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -1,8 +1,10 @@ package chat.simplex.app.views.chat +import android.content.Context import android.content.res.Configuration import android.graphics.Bitmap import android.net.Uri +import android.view.inputmethod.InputMethodManager import androidx.activity.compose.BackHandler import androidx.compose.foundation.* import androidx.compose.foundation.gestures.* @@ -90,7 +92,7 @@ fun ChatView(chatModel: ChatModel) { } } } - + val view = LocalView.current if (activeChat.value == null || user == null) { chatModel.chatId.value = null } else { @@ -124,8 +126,12 @@ fun ChatView(chatModel: ChatModel) { searchText, useLinkPreviews = useLinkPreviews, chatModelIncognito = chatModel.incognito.value, - back = { chatModel.chatId.value = null }, + back = { + hideKeyboard(view) + chatModel.chatId.value = null + }, info = { + hideKeyboard(view) withApi { val cInfo = chat.chatInfo if (cInfo is ChatInfo.Direct) { @@ -142,6 +148,7 @@ fun ChatView(chatModel: ChatModel) { } }, showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> + hideKeyboard(view) withApi { val stats = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId) ModalManager.shared.showModalCloseable(true) { close -> @@ -185,6 +192,7 @@ fun ChatView(chatModel: ChatModel) { } }, acceptCall = { contact -> + hideKeyboard(view) val invitation = chatModel.callInvitations.remove(contact.id) if (invitation == null) { AlertManager.shared.showAlertMsg("Call already ended!") @@ -193,6 +201,7 @@ fun ChatView(chatModel: ChatModel) { } }, addMembers = { groupInfo -> + hideKeyboard(view) withApi { setGroupMembers(groupInfo, chatModel) ModalManager.shared.showModalCloseable(true) { close -> diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index 6e0045fedd..62a91d6973 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -2,7 +2,6 @@ package chat.simplex.app.views.chat import ComposeFileView import android.Manifest -import android.app.Activity import android.content.* import android.content.pm.PackageManager import android.graphics.Bitmap @@ -10,11 +9,9 @@ import android.graphics.ImageDecoder import android.graphics.drawable.AnimatedImageDrawable import android.net.Uri import android.provider.MediaStore -import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContract -import androidx.annotation.CallSuper import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -26,6 +23,7 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.outlined.Reply import androidx.compose.runtime.* import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -34,7 +32,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider import chat.simplex.app.* import chat.simplex.app.R import chat.simplex.app.model.* @@ -44,7 +41,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString -import java.io.File @Serializable sealed class ComposePreview { @@ -137,53 +133,22 @@ fun ComposeView( showChooseAttachment: () -> Unit ) { val context = LocalContext.current - val linkUrl = remember { mutableStateOf(null) } - val prevLinkUrl = remember { mutableStateOf(null) } - val pendingLinkUrl = remember { mutableStateOf(null) } - val cancelledLinks = remember { mutableSetOf() } + val linkUrl = rememberSaveable { mutableStateOf(null) } + val prevLinkUrl = rememberSaveable { mutableStateOf(null) } + val pendingLinkUrl = rememberSaveable { mutableStateOf(null) } + val cancelledLinks = rememberSaveable { mutableSetOf() } val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() val smallFont = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground) val textStyle = remember { mutableStateOf(smallFont) } // attachments - val chosenContent = remember { mutableStateOf>(emptyList()) } - val chosenFile = remember { mutableStateOf(null) } - val photoUri = remember { mutableStateOf(null) } - val photoTmpFile = remember { mutableStateOf(null) } - - class ComposeTakePicturePreview: ActivityResultContract() { - @CallSuper - override fun createIntent(context: Context, input: Void?): Intent { - photoTmpFile.value = File.createTempFile("image", ".bmp", SimplexApp.context.filesDir) - photoUri.value = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", photoTmpFile.value!!) - return Intent(MediaStore.ACTION_IMAGE_CAPTURE) - .putExtra(MediaStore.EXTRA_OUTPUT, photoUri.value) - } - - override fun getSynchronousResult( - context: Context, - input: Void? - ): SynchronousResult? = null - - override fun parseResult(resultCode: Int, intent: Intent?): Bitmap? { - val photoUriVal = photoUri.value - val photoTmpFileVal = photoTmpFile.value - return if (resultCode == Activity.RESULT_OK && photoUriVal != null && photoTmpFileVal != null) { - val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, photoUriVal) - val bitmap = ImageDecoder.decodeBitmap(source) - photoTmpFileVal.delete() - bitmap - } else { - Log.e(TAG, "Getting image from camera cancelled or failed.") - photoTmpFile.value?.delete() - null - } - } - } - - val cameraLauncher = rememberLauncherForActivityResult(contract = ComposeTakePicturePreview()) { bitmap: Bitmap? -> - if (bitmap != null) { + val chosenContent = rememberSaveable { mutableStateOf>(emptyList()) } + val chosenFile = rememberSaveable { mutableStateOf(null) } + val cameraLauncher = rememberCameraLauncher { uri: Uri? -> + if (uri != null) { + val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri) + val bitmap = ImageDecoder.decodeBitmap(source) val imagePreview = resizeImageToStrSize(bitmap, maxDataSize = 14000) - chosenContent.value = listOf(UploadContent.SimpleImage(bitmap)) + chosenContent.value = listOf(UploadContent.SimpleImage(uri)) composeState.value = composeState.value.copy(preview = ComposePreview.ImagePreview(listOf(imagePreview))) } } @@ -214,7 +179,7 @@ fun ComposeView( ) } } else { - if (bitmap != null) content.add(UploadContent.SimpleImage(bitmap)) + content.add(UploadContent.SimpleImage(uri)) } if (bitmap != null) { imagesPreview.add(resizeImageToStrSize(bitmap, maxDataSize = 14000)) @@ -391,7 +356,7 @@ fun ComposeView( is ComposePreview.ImagePreview -> { chosenContent.value.forEachIndexed { index, it -> val file = when (it) { - is UploadContent.SimpleImage -> saveImage(context, it.bitmap) + is UploadContent.SimpleImage -> saveImage(context, it.uri) is UploadContent.AnimatedImage -> saveAnimImage(context, it.uri) } if (file != null) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt index 2d96714a3f..cf50249df0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt @@ -2,11 +2,13 @@ package chat.simplex.app.views.chat.group import android.content.res.Configuration import android.graphics.Bitmap +import android.net.Uri import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -53,8 +55,8 @@ fun GroupProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = remember { mutableStateOf(groupProfile.displayName) } val fullName = remember { mutableStateOf(groupProfile.fullName) } - val chosenImage = remember { mutableStateOf(null) } - val profileImage = remember { mutableStateOf(groupProfile.image) } + val chosenImage = rememberSaveable { mutableStateOf(null) } + val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() val focusRequester = remember { FocusRequester() } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt index 161e7f5945..644bacfe6a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIImageView.kt @@ -160,7 +160,9 @@ fun CIImageView( placeholder = BitmapPainter(imageBitmap.asImageBitmap()), // show original image while it's still loading by coil imageLoader = imageLoader ) + val view = LocalView.current imageView(imagePainter, onClick = { + hideKeyboard(view) if (getLoadedFilePath(context, file) != null) { ModalManager.shared.showCustomModal(animated = false) { close -> ImageFullScreenView(imageProvider, close) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt index 63b70e740e..3da7dfda5e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ImageFullScreenView.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.input.pointer.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import chat.simplex.app.R import chat.simplex.app.views.helpers.* diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt index ae741e3725..a7ed87d761 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt @@ -33,6 +33,6 @@ enum class NewChatSheetState { } sealed class UploadContent { - data class SimpleImage(val bitmap: Bitmap): UploadContent() + data class SimpleImage(val uri: Uri): UploadContent() data class AnimatedImage(val uri: Uri): UploadContent() } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt index 57fe3ec454..82e7a27de8 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GetImageView.kt @@ -19,8 +19,9 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Collections import androidx.compose.material.icons.outlined.PhotoCamera -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.platform.LocalContext @@ -30,8 +31,12 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import chat.simplex.app.* import chat.simplex.app.R +import chat.simplex.app.model.json +import chat.simplex.app.views.chat.ComposeState import chat.simplex.app.views.chat.PickFromGallery import chat.simplex.app.views.newchat.ActionButton +import kotlinx.serialization.builtins.* +import kotlinx.serialization.decodeFromString import java.io.ByteArrayOutputStream import java.io.File import kotlin.math.min @@ -106,15 +111,12 @@ fun base64ToBitmap(base64ImageString: String): Bitmap { } } -class CustomTakePicturePreview: ActivityResultContract() { - private var uri: Uri? = null - private var tmpFile: File? = null - lateinit var externalContext: Context - +class CustomTakePicturePreview(var uri: Uri?, var tmpFile: File?): ActivityResultContract() { @CallSuper override fun createIntent(context: Context, input: Void?): Intent { - externalContext = context tmpFile = File.createTempFile("image", ".bmp", context.filesDir) + // Since the class should return Uri, the file should be deleted somewhere else. And in order to be sure, delegate this to system + tmpFile?.deleteOnExit() uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", tmpFile!!) return Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, uri) @@ -123,20 +125,28 @@ class CustomTakePicturePreview: ActivityResultContract() { override fun getSynchronousResult( context: Context, input: Void? - ): SynchronousResult? = null + ): SynchronousResult? = null - override fun parseResult(resultCode: Int, intent: Intent?): Bitmap? { + override fun parseResult(resultCode: Int, intent: Intent?): Uri? { return if (resultCode == Activity.RESULT_OK && uri != null) { - val source = ImageDecoder.createSource(externalContext.contentResolver, uri!!) - val bitmap = ImageDecoder.decodeBitmap(source) - tmpFile?.delete() - bitmap + uri } else { Log.e(TAG, "Getting image from camera cancelled or failed.") - tmpFile?.delete() null } } + + companion object { + fun saver(): Saver = Saver( + save = { json.encodeToString(ListSerializer(String.serializer().nullable), listOf(it.uri?.toString(), it.tmpFile?.toString())) }, + restore = { + val data = json.decodeFromString(ListSerializer(String.serializer().nullable), it) + val uri = if (data[0] != null) Uri.parse(data[0]) else null + val tmpFile = if (data[1] != null) File(data[1]) else null + CustomTakePicturePreview(uri, tmpFile) + } + ) + } } //class GetGalleryContent: ActivityResultContracts.GetContent() { // override fun createIntent(context: Context, input: String): Intent { @@ -148,8 +158,12 @@ class CustomTakePicturePreview: ActivityResultContract() { //fun rememberGalleryLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher = // rememberLauncherForActivityResult(contract = GetGalleryContent(), cb) @Composable -fun rememberCameraLauncher(cb: (Bitmap?) -> Unit): ManagedActivityResultLauncher = - rememberLauncherForActivityResult(contract = CustomTakePicturePreview(), cb) +fun rememberCameraLauncher(cb: (Uri?) -> Unit): ManagedActivityResultLauncher { + val contract = rememberSaveable(stateSaver = CustomTakePicturePreview.saver()) { + mutableStateOf(CustomTakePicturePreview(null, null)) + } + return rememberLauncherForActivityResult(contract = contract.value, cb) +} @Composable fun rememberPermissionLauncher(cb: (Boolean) -> Unit): ManagedActivityResultLauncher = @@ -165,7 +179,7 @@ fun rememberGetMultipleContentsLauncher(cb: (List) -> Unit): ManagedActivit @Composable fun GetImageBottomSheet( - imageBitmap: MutableState, + imageBitmap: MutableState, onImageChange: (Bitmap) -> Unit, hideBottomSheet: () -> Unit ) { @@ -174,15 +188,17 @@ fun GetImageBottomSheet( if (uri != null) { val source = ImageDecoder.createSource(context.contentResolver, uri) val bitmap = ImageDecoder.decodeBitmap(source) - imageBitmap.value = bitmap + imageBitmap.value = uri onImageChange(bitmap) } } val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery()) { processPickedImage(it) } val galleryLauncherFallback = rememberGetContentLauncher { processPickedImage(it) } - val cameraLauncher = rememberCameraLauncher { bitmap: Bitmap? -> - if (bitmap != null) { - imageBitmap.value = bitmap + val cameraLauncher = rememberCameraLauncher { uri: Uri? -> + if (uri != null) { + imageBitmap.value = uri + val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri) + val bitmap = ImageDecoder.decodeBitmap(source) onImageChange(bitmap) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt index 09967ca0b6..261d4f8acc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt @@ -40,6 +40,12 @@ fun SearchTextField(modifier: Modifier, placeholder: String, onValueChange: (Str keyboard?.show() } + DisposableEffect(Unit) { + onDispose { + if (searchText.text.isNotEmpty()) onValueChange("") + } + } + val enabled = true val colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Unspecified, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index cb6344f28f..6272dc4f1e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -14,7 +14,9 @@ import android.text.SpannedString import android.text.style.* import android.util.Base64 import android.util.Log +import android.view.View import android.view.ViewTreeObserver +import android.view.inputmethod.InputMethodManager import androidx.annotation.StringRes import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color @@ -69,6 +71,9 @@ fun getKeyboardState(): State { return keyboardState } +fun hideKeyboard(view: View) = + (SimplexApp.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0) + // Resource to annotated string from // https://stackoverflow.com/questions/68549248/android-jetpack-compose-how-to-show-styled-text-from-string-resources fun generalGetString(id: Int): String { @@ -306,6 +311,12 @@ fun getFileSize(context: Context, uri: Uri): Long? { } } +fun saveImage(context: Context, uri: Uri): String? { + val source = ImageDecoder.createSource(SimplexApp.context.contentResolver, uri) + val bitmap = ImageDecoder.decodeBitmap(source) + return saveImage(context, bitmap) +} + fun saveImage(context: Context, image: Bitmap): String? { return try { val ext = if (image.hasAlpha()) "png" else "jpg" diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt index 73f52cc3d6..86f864b86c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt @@ -1,6 +1,7 @@ package chat.simplex.app.views.newchat import android.graphics.Bitmap +import android.net.Uri import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -8,6 +9,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowForwardIos import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -60,8 +62,8 @@ fun AddGroupLayout(chatModelIncognito: Boolean, createGroup: (GroupProfile) -> U val scope = rememberCoroutineScope() val displayName = remember { mutableStateOf("") } val fullName = remember { mutableStateOf("") } - val profileImage = remember { mutableStateOf(null) } - val chosenImage = remember { mutableStateOf(null) } + val chosenImage = rememberSaveable { mutableStateOf(null) } + val profileImage = rememberSaveable { mutableStateOf(null) } val focusRequester = remember { FocusRequester() } ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index 96353c3ece..67f03033c7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -2,6 +2,7 @@ package chat.simplex.app.views.usersettings import android.content.res.Configuration import android.graphics.Bitmap +import android.net.Uri import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -12,6 +13,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -34,7 +36,7 @@ import kotlinx.coroutines.launch fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { val user = chatModel.currentUser.value if (user != null) { - val editProfile = remember { mutableStateOf(false) } + val editProfile = rememberSaveable { mutableStateOf(false) } var profile by remember { mutableStateOf(user.profile.toProfile()) } UserProfileLayout( editProfile = editProfile, @@ -67,8 +69,8 @@ fun UserProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = remember { mutableStateOf(profile.displayName) } val fullName = remember { mutableStateOf(profile.fullName) } - val chosenImage = remember { mutableStateOf(null) } - val profileImage = remember { mutableStateOf(profile.image) } + val chosenImage = rememberSaveable { mutableStateOf(null) } + val profileImage = rememberSaveable { mutableStateOf(profile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() val keyboardState by getKeyboardState()