diff --git a/app/build.gradle b/app/build.gradle index 95e37a849c..da0d2f3fa5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -268,9 +268,9 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" - implementation 'androidx.activity:activity-ktx:1.5.1' - implementation 'androidx.activity:activity-compose:1.5.1' - implementation 'androidx.fragment:fragment-ktx:1.5.3' + implementation 'androidx.activity:activity-ktx:1.9.2' + implementation 'androidx.activity:activity-compose:1.9.2' + implementation 'androidx.fragment:fragment-ktx:1.8.4' implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.work:work-runtime-ktx:2.7.1" diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index ad2db82cfb..0132a876cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1536,14 +1536,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe sendEmojiRemoval(emoji, message) } + /** + * Called when the user is attempting to clear all instance of a specific emoji. + */ override fun onClearAll(emoji: String, messageId: MessageId) { - reactionDb.deleteEmojiReactions(emoji, messageId) - viewModel.openGroup?.let { openGroup -> - lokiMessageDb.getServerID(messageId.id, !messageId.mms)?.let { serverId -> - OpenGroupApi.deleteAllReactions(openGroup.room, openGroup.server, serverId, emoji) - } - } - threadDb.notifyThreadUpdated(viewModel.threadId) + viewModel.onEmojiClear(emoji, messageId) } override fun onMicrophoneButtonMove(event: MotionEvent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt index 26775f01c2..b9756a13be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt @@ -14,12 +14,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.HideDeleteAllDevicesDialog -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.HideDeleteEveryoneDialog -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.MarkAsDeletedForEveryone -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.MarkAsDeletedLocally -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ShowOpenUrlDialog +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY +import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.* import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.DialogButtonModel import org.thoughtcrime.securesms.ui.GetString @@ -202,6 +201,34 @@ fun ConversationV2Dialogs( ) } + + // Clear emoji + if(dialogsState.clearAllEmoji != null){ + AlertDialog( + onDismissRequest = { + // hide dialog + sendCommand(HideClearEmoji) + }, + text = stringResource(R.string.emojiReactsClearAll).let { txt -> + Phrase.from(txt).put(EMOJI_KEY, dialogsState.clearAllEmoji.emoji).format().toString() + }, + buttons = listOf( + DialogButtonModel( + text = GetString(stringResource(id = R.string.clear)), + color = LocalColors.current.danger, + onClick = { + // delete emoji + sendCommand( + ClearEmoji(dialogsState.clearAllEmoji.emoji, dialogsState.clearAllEmoji.messageId) + ) + } + ), + DialogButtonModel( + GetString(stringResource(R.string.cancel)) + ) + ) + ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 316aeec86c..29a9b9d79d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -37,7 +37,10 @@ import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.database.LokiMessageDatabase +import org.thoughtcrime.securesms.database.ReactionDatabase import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.groups.OpenGroupManager @@ -52,6 +55,8 @@ class ConversationViewModel( private val repository: ConversationRepository, private val storage: Storage, private val messageDataProvider: MessageDataProvider, + private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, private val lokiMessageDb: LokiMessageDatabase, private val textSecurePreferences: TextSecurePreferences ) : ViewModel() { @@ -720,6 +725,12 @@ class ConversationViewModel( } } + is Commands.HideClearEmoji -> { + _dialogsState.update { + it.copy(clearAllEmoji = null) + } + } + is Commands.HideDeleteAllDevicesDialog -> { _dialogsState.update { it.copy(deleteAllDevices = null) @@ -737,6 +748,35 @@ class ConversationViewModel( is Commands.MarkAsDeletedForEveryone -> { markAsDeletedForEveryone(command.data) } + + + is Commands.ClearEmoji -> { + clearEmoji(command.emoji, command.messageId) + } + } + } + + private fun clearEmoji(emoji: String, messageId: MessageId){ + viewModelScope.launch(Dispatchers.Default) { + reactionDb.deleteEmojiReactions(emoji, messageId) + openGroup?.let { openGroup -> + lokiMessageDb.getServerID(messageId.id, !messageId.mms)?.let { serverId -> + OpenGroupApi.deleteAllReactions( + openGroup.room, + openGroup.server, + serverId, + emoji + ) + } + } + threadDb.notifyThreadUpdated(threadId) + } + } + + fun onEmojiClear(emoji: String, messageId: MessageId) { + // show a confirmation dialog + _dialogsState.update { + it.copy(clearAllEmoji = ClearAllEmoji(emoji, messageId)) } } @@ -753,6 +793,8 @@ class ConversationViewModel( private val repository: ConversationRepository, private val storage: Storage, private val messageDataProvider: MessageDataProvider, + private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, private val lokiMessageDb: LokiMessageDatabase, private val textSecurePreferences: TextSecurePreferences ) : ViewModelProvider.Factory { @@ -765,6 +807,8 @@ class ConversationViewModel( repository = repository, storage = storage, messageDataProvider = messageDataProvider, + threadDb = threadDb, + reactionDb = reactionDb, lokiMessageDb = lokiMessageDb, textSecurePreferences = textSecurePreferences ) as T @@ -773,6 +817,7 @@ class ConversationViewModel( data class DialogsState( val openLinkDialogUrl: String? = null, + val clearAllEmoji: ClearAllEmoji? = null, val deleteEveryone: DeleteForEveryoneDialogData? = null, val deleteAllDevices: DeleteForEveryoneDialogData? = null, ) @@ -785,10 +830,19 @@ class ConversationViewModel( val warning: String? = null ) + data class ClearAllEmoji( + val emoji: String, + val messageId: MessageId + ) + sealed class Commands { data class ShowOpenUrlDialog(val url: String?) : Commands() + + data class ClearEmoji(val emoji:String, val messageId: MessageId) : Commands() + data object HideDeleteEveryoneDialog : Commands() data object HideDeleteAllDevicesDialog : Commands() + data object HideClearEmoji : Commands() data class MarkAsDeletedLocally(val messages: Set): Commands() data class MarkAsDeletedForEveryone(val data: DeleteForEveryoneDialogData): Commands() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index bbf075ca7c..eaebcb57f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -134,6 +134,16 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { cropImage(inputFile, outputFile) } + private val hideRecoveryLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult + + if(result.data?.getBooleanExtra(RecoveryPasswordActivity.RESULT_RECOVERY_HIDDEN, false) == true){ + viewModel.permanentlyHidePassword() + } + } + private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage) private var showAvatarDialog: Boolean by mutableStateOf(false) @@ -183,7 +193,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } binding.composeView.setThemedContent { - Buttons() + val recoveryHidden by viewModel.recoveryHidden.collectAsState() + Buttons(recoveryHidden = recoveryHidden) } lifecycleScope.launch { @@ -390,7 +401,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun Buttons() { + fun Buttons( + recoveryHidden: Boolean + ) { Column( modifier = Modifier .padding(horizontal = LocalDimensions.current.spacing) @@ -452,12 +465,15 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { Divider() // Only show the recovery password option if the user has not chosen to permanently hide it - if (!prefs.getHidePassword()) { + if (!recoveryHidden) { LargeItemButton( R.string.sessionRecoveryPassword, R.drawable.ic_shield_outline, Modifier.contentDescription(R.string.AccessibilityId_sessionRecoveryPasswordMenuItem) - ) { push() } + ) { + hideRecoveryLauncher.launch(Intent(baseContext, RecoveryPasswordActivity::class.java)) + overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + } Divider() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index bedc913109..5b6fa78d44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -65,6 +66,10 @@ class SettingsViewModel @Inject constructor( val showLoader: StateFlow get() = _showLoader + private val _recoveryHidden: MutableStateFlow = MutableStateFlow(prefs.getHidePassword()) + val recoveryHidden: StateFlow + get() = _recoveryHidden + /** * Refreshes the avatar on the main settings page */ @@ -230,6 +235,12 @@ class SettingsViewModel @Inject constructor( } } + fun permanentlyHidePassword() { + //todo we can simplify this once we expose all our sharedPrefs as flows + prefs.setHidePassword(true) + _recoveryHidden.update { true } + } + sealed class AvatarDialogState() { object NoAvatar : AvatarDialogState() data class UserAvatar(val address: Address) : AvatarDialogState() diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt index a6d38c13a0..cc9630ef57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt @@ -1,16 +1,21 @@ package org.thoughtcrime.securesms.recoverypassword +import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import network.loki.messenger.R import org.thoughtcrime.securesms.BaseActionBarActivity -import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.setComposeContent + class RecoveryPasswordActivity : BaseActionBarActivity() { + companion object { + const val RESULT_RECOVERY_HIDDEN = "recovery_hidden" + } + private val viewModel: RecoveryPasswordViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -25,7 +30,9 @@ class RecoveryPasswordActivity : BaseActionBarActivity() { mnemonic = mnemonic, seed = seed, confirmHideRecovery = { - viewModel.permanentlyHidePassword() + val returnIntent = Intent() + returnIntent.putExtra(RESULT_RECOVERY_HIDDEN, true) + setResult(RESULT_OK, returnIntent) finish() }, copyMnemonic = viewModel::copyMnemonic diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt index 0ad207cd23..b159accf23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt @@ -34,10 +34,6 @@ class RecoveryPasswordViewModel @Inject constructor( .map { MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }.encode(it, MnemonicCodec.Language.Configuration.english) } .stateIn(viewModelScope, SharingStarted.Eagerly, "") - fun permanentlyHidePassword() { - prefs.setHidePassword(true) - } - fun copyMnemonic() { prefs.setHasViewedSeed(true) ClipData.newPlainText("Seed", mnemonic.value) diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 77702dc4eb..dd62a33007 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -304,7 +304,7 @@ android:paddingHorizontal="@dimen/massive_spacing" android:paddingVertical="@dimen/small_spacing" android:textSize="@dimen/text_size" - android:text="@string/block"/> + android:text="@string/deleteAfterGroupPR1BlockUser"/> + android:text="@string/delete" /> diff --git a/app/src/main/res/layout/view_global_search_input.xml b/app/src/main/res/layout/view_global_search_input.xml index 2d5c03ec5f..cd164772bd 100644 --- a/app/src/main/res/layout/view_global_search_input.xml +++ b/app/src/main/res/layout/view_global_search_input.xml @@ -27,7 +27,7 @@ app:tint="?searchIconColor" android:contentDescription="@string/search" /> @dimen/very_large_font_size + + diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index 3a59cff9ab..a12af5f5c1 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -36,7 +36,8 @@ class ConversationViewModelTest: BaseViewModelTest() { private lateinit var messageRecord: MessageRecord private val viewModel: ConversationViewModel by lazy { - ConversationViewModel(threadId, edKeyPair, application, repository, storage, mock(), mock(), mock()) + ConversationViewModel(threadId, edKeyPair, application, repository, storage, + mock(), mock(), mock(), mock(), mock()) } @Before diff --git a/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt b/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt index 4733e67563..adb082f8ee 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt @@ -46,7 +46,8 @@ object LocalisedTimeUtil { "${this.inWholeHours}h ${minutesRemaining}m" } else if (this.inWholeMinutes > 0) { val secondsRemaining = this.minus(1.minutes.times(this.inWholeMinutes.toInt())).inWholeSeconds - "${this.inWholeMinutes}m ${secondsRemaining}s" + if(secondsRemaining > 0) "${this.inWholeMinutes}m ${secondsRemaining}s" + else "${this.inWholeMinutes}m" } else { "0m ${this.inWholeSeconds}s" }