diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index dc1950011..060a5f523 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -63,8 +63,11 @@ fun ChatInfoView( setContactAlias(chat.chatInfo.apiId, it, chatModel) }, openPreferences = { - ModalManager.shared.showModal(true) { - ContactPreferencesView(chatModel, chatModel.currentUser.value ?: return@showModal, contact.contactId) + ModalManager.shared.showCustomModal { close -> + val user = chatModel.currentUser.value + if (user != null) { + ContactPreferencesView(chatModel, user, contact.contactId, close) + } } }, deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) }, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt index 8fb3b41fe..4b28d1831 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt @@ -25,33 +25,45 @@ fun ContactPreferencesView( m: ChatModel, user: User, contactId: Long, + close: () -> Unit, ) { val contact = remember { derivedStateOf { (m.getContactChat(contactId)?.chatInfo as? ChatInfo.Direct)?.contact } } val ct = contact.value ?: return var featuresAllowed by remember(ct) { mutableStateOf(contactUserPrefsToFeaturesAllowed(ct.mergedPreferences)) } var currentFeaturesAllowed by remember(ct) { mutableStateOf(featuresAllowed) } - ContactPreferencesLayout( - featuresAllowed, - currentFeaturesAllowed, - user, - ct, - applyPrefs = { prefs -> - featuresAllowed = prefs - }, - reset = { - featuresAllowed = currentFeaturesAllowed - }, - savePrefs = { - withApi { - val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) - val toContact = m.controller.apiSetContactPrefs(ct.contactId, prefs) - if (toContact != null) { - m.updateContact(toContact) - currentFeaturesAllowed = featuresAllowed - } + + fun savePrefs(afterSave: () -> Unit = {}) { + withApi { + val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) + val toContact = m.controller.apiSetContactPrefs(ct.contactId, prefs) + if (toContact != null) { + m.updateContact(toContact) + currentFeaturesAllowed = featuresAllowed } + afterSave() + } + } + ModalView( + close = { + if (featuresAllowed == currentFeaturesAllowed) close() + else showUnsavedChangesAlert({ savePrefs(close) }, close) }, - ) + background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight + ) { + ContactPreferencesLayout( + featuresAllowed, + currentFeaturesAllowed, + user, + ct, + applyPrefs = { prefs -> + featuresAllowed = prefs + }, + reset = { + featuresAllowed = currentFeaturesAllowed + }, + savePrefs = ::savePrefs, + ) + } } @Composable @@ -139,3 +151,13 @@ private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Bool } } } + +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(R.string.save_preferences_question), + confirmText = generalGetString(R.string.save_and_notify_contact), + dismissText = generalGetString(R.string.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt index 2d216dd72..1ff21e665 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt @@ -44,8 +44,8 @@ fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, ch selectedRole = selectedRole, allowModifyMembers = allowModifyMembers, openPreferences = { - ModalManager.shared.showModal(true) { - GroupPreferencesView(chatModel, groupInfo.id) + ModalManager.shared.showCustomModal { close -> + GroupPreferencesView(chatModel, groupInfo.id, close) } }, inviteMembers = { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt index 144dafc27..d37941443 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt @@ -65,10 +65,11 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) { ModalManager.shared.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) } }, openPreferences = { - ModalManager.shared.showModal(true) { + ModalManager.shared.showCustomModal { close -> GroupPreferencesView( chatModel, - chat.id + chat.id, + close ) } }, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt index e015de1ba..98b5f2581 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupPreferences.kt @@ -13,41 +13,50 @@ import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import chat.simplex.app.R import chat.simplex.app.model.* -import chat.simplex.app.ui.theme.HighOrLowlight -import chat.simplex.app.ui.theme.SimplexGreen +import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* @Composable -fun GroupPreferencesView(m: ChatModel, chatId: String) { +fun GroupPreferencesView(m: ChatModel, chatId: String, close: () -> Unit,) { val groupInfo = remember { derivedStateOf { (m.getChat(chatId)?.chatInfo as? ChatInfo.Group)?.groupInfo } } val gInfo = groupInfo.value ?: return var preferences by remember(gInfo) { mutableStateOf(gInfo.fullGroupPreferences) } var currentPreferences by remember(gInfo) { mutableStateOf(preferences) } - GroupPreferencesLayout( - preferences, - currentPreferences, - gInfo, - applyPrefs = { prefs -> - preferences = prefs - }, - reset = { - preferences = currentPreferences - }, - savePrefs = { - withApi { - val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences()) - val gInfo = m.controller.apiUpdateGroup(gInfo.groupId, gp) - if (gInfo != null) { - m.updateGroup(gInfo) - currentPreferences = preferences - } + + fun savePrefs(afterSave: () -> Unit = {}) { + withApi { + val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences()) + val gInfo = m.controller.apiUpdateGroup(gInfo.groupId, gp) + if (gInfo != null) { + m.updateGroup(gInfo) + currentPreferences = preferences } + afterSave() + } + } + ModalView( + close = { + if (preferences == currentPreferences) close() + else showUnsavedChangesAlert({ savePrefs(close) }, close) }, - ) + background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight + ) { + GroupPreferencesLayout( + preferences, + currentPreferences, + gInfo, + applyPrefs = { prefs -> + preferences = prefs + }, + reset = { + preferences = currentPreferences + }, + savePrefs = ::savePrefs, + ) + } } @Composable @@ -130,3 +139,13 @@ private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Bool } } } + +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(R.string.save_preferences_question), + confirmText = generalGetString(R.string.save_and_notify_group_members), + dismissText = generalGetString(R.string.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt index cc1f06274..4909d3eb6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/AlertManager.kt @@ -5,9 +5,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import androidx.compose.ui.window.Dialog import chat.simplex.app.R import chat.simplex.app.TAG @@ -93,6 +94,41 @@ class AlertManager { } } + fun showAlertDialogStacked( + title: String, + text: String? = null, + confirmText: String = generalGetString(R.string.ok), + onConfirm: (() -> Unit)? = null, + dismissText: String = generalGetString(R.string.cancel_verb), + onDismiss: (() -> Unit)? = null, + onDismissRequest: (() -> Unit)? = null, + destructive: Boolean = false + ) { + val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) } + showAlert { + AlertDialog( + onDismissRequest = { onDismissRequest?.invoke(); hideAlert() }, + title = { Text(title) }, + text = alertText, + buttons = { + Column( + Modifier.fillMaxWidth().padding(horizontal = 8.dp).padding(top = 16.dp, bottom = 2.dp), + horizontalAlignment = Alignment.End + ) { + TextButton(onClick = { + onDismiss?.invoke() + hideAlert() + }) { Text(dismissText) } + TextButton(onClick = { + onConfirm?.invoke() + hideAlert() + }) { Text(confirmText, color = if (destructive) MaterialTheme.colors.error else Color.Unspecified) } + } + }, + ) + } + } + fun showAlertMsg( title: String, text: String? = null, confirmText: String = generalGetString(R.string.ok), onConfirm: (() -> Unit)? = null @@ -128,4 +164,4 @@ class AlertManager { companion object { val shared = AlertManager() } -} \ No newline at end of file +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt index 33494d916..ac94c9aa9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt @@ -19,33 +19,40 @@ import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.* @Composable -fun PreferencesView(m: ChatModel, user: User) { +fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { var preferences by remember { mutableStateOf(user.fullPreferences) } var currentPreferences by remember { mutableStateOf(preferences) } - PreferencesLayout( - preferences, - currentPreferences, - applyPrefs = { prefs -> - preferences = prefs - }, - reset = { - preferences = currentPreferences - }, - savePrefs = { - withApi { - val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences()) - val updatedProfile = m.controller.apiUpdateProfile(newProfile) - if (updatedProfile != null) { - val updatedUser = user.copy( - profile = updatedProfile.toLocalProfile(user.profile.profileId), - fullPreferences = preferences - ) - currentPreferences = preferences - m.currentUser.value = updatedUser - } + + fun savePrefs(afterSave: () -> Unit = {}) { + withApi { + val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences()) + val updatedProfile = m.controller.apiUpdateProfile(newProfile) + if (updatedProfile != null) { + val updatedUser = user.copy( + profile = updatedProfile.toLocalProfile(user.profile.profileId), + fullPreferences = preferences + ) + currentPreferences = preferences + m.currentUser.value = updatedUser } + afterSave() + } + } + ModalView( + close = { + if (preferences == currentPreferences) close() + else showUnsavedChangesAlert({ savePrefs(close) }, close) }, - ) + background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight + ) { + PreferencesLayout( + preferences, + currentPreferences, + applyPrefs = { preferences = it }, + reset = { preferences = currentPreferences }, + savePrefs = ::savePrefs, + ) + } } @Composable @@ -107,3 +114,13 @@ private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Bool } } } + +private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) { + AlertManager.shared.showAlertDialogStacked( + title = generalGetString(R.string.save_preferences_question), + confirmText = generalGetString(R.string.save_and_notify_contacts), + dismissText = generalGetString(R.string.exit_without_saving), + onConfirm = save, + onDismiss = revert, + ) +} diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 048ce6845..5a1397657 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -116,7 +116,7 @@ fun SettingsLayout( SectionDivider() SettingsActionItem(Icons.Outlined.QrCode, stringResource(R.string.your_simplex_contact_address), showModal { CreateLinkView(it, CreateLinkTab.LONG_TERM) }, disabled = stopped) SectionDivider() - ChatPreferencesItem(showSettingsModal) + ChatPreferencesItem(showCustomModal) } SectionSpacer() @@ -239,14 +239,14 @@ fun MaintainIncognitoState(chatModel: ChatModel) { } } -@Composable fun ChatPreferencesItem(showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { +@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit))) { SettingsActionItem( Icons.Outlined.ToggleOn, stringResource(R.string.chat_preferences), click = { withApi { - showSettingsModal { - PreferencesView(it, it.currentUser.value ?: return@showSettingsModal) + showCustomModal { m, close -> + PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) }() } } diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index a42701dc6..49f091060 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -475,9 +475,11 @@ Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\n\nSimpleX-Server können Ihr Profil nicht sehen. Bild bearbeiten Bild löschen - Speichern (und Kontakt benachrichtigen) - Speichern (und Kontakte benachrichtigen) - Speichern (und Gruppenmitglieder benachrichtigen) + Präferenzen speichern? + Speichern und Kontakt benachrichtigen + Speichern und Kontakte benachrichtigen + Speichern und Gruppenmitglieder benachrichtigen + Beenden ohne Speichern Sie haben volle Kontrolle über Ihren Chat! diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 4602ee501..772d3a912 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -472,9 +472,11 @@ Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.\n\nSimpleX серверы не могут получить доступ к вашему профилю. Поменять аватар Удалить аватар - Сохранить (и уведомить контакт) - Сохранить (и послать обновление контактам) - Сохранить (и уведомить членов группы) + Сохранить предпочтения? + Сохранить и уведомить контакт + Сохранить и уведомить контакты + Сохранить и уведомить членов группы + Выйти без сохранения Вы котролируете ваш чат! diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index b5b97611a..4863d5e27 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -475,9 +475,11 @@ Your profile is stored on your device and shared only with your contacts.\n\nSimpleX servers cannot see your profile. Edit image Delete image - Save (and notify contact) - Save (and notify contacts) - Save (and notify group members) + Save preferences? + Save and notify contact + Save and notify contacts + Save and notify group members + Exit without saving You control your chat!