mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
Feature/strings nice to haves (#1686)
* Initial commit with high level structure for new message deletion logic * Adding admin logic * New dialog styles * Matching existing dialog closer to new designs * Using the theme attribute danger instead of a hardcoded colour * Using classes for the dialogs Also cleaned up older references to align with newer look * Adding cancel handling Cleaning unused code * Handling local deletion with batch message deletion * Reusing the 'delete locally' * Delete on device should "marl the message as deleted", not remove it from the db directly * Displaying "marked as deleted" messages Split the `BASE_DELETED_TYPE` into two types: BASE_DELETED_OUTGOING_TYPE and BASE_DELETED_INCOMING_TYPE so we can differentiate them visually. * Proper handling of merged code * Removed temp bg color * Making sure the deleted message view is visible * Renaming functions for clarity * Adding the ability to customise the text for the deleted control messages * Removing code that was added back from merging dev back in * Using the updated strings * Toast confirmation on 'delete locally' * Recreating xml dialogs in Compose and moved logic in VM * Removing hardcoded strings * Updated message deletion logic Still need to finalise "note to self" and "legacy groups" * Deletion logic rework Moving away from promises * More deletion logic Hndling unsend request retrieval as per figma docs * Making sure multi-select works as expectec * Multi message handling Sharing admin logic * Deleting reactions when deleting a message * Deleting reactions when deleting a message * Grabbing server hash from notification data * Fixed unit tests * Handling deletion od "marked as deleted" messages * Handling Control Messages longpress and deletion * Back up handling of no map data for huawei notifications Also rethemed the send buttona dn home plus button to have better ax contrast by standardising the colour displayed on the accent color to be the same as the one on the sent messages * Removed test line * Reworking the deletion dialogs We removed the 'delete locally' dialog, instead we show the 'delete for everyone' with the second option disabled * Outgoing messages can all be marked as 'delete for everyone' Cleaned up invisible copy button on black bgs * Adding a confirmation dialog when clearing emoji * Message request text update * Restyling menu items to not show in uppercase * Proper hint for seach * Do not show seconds when they're 0 * Making the change to "hidden recovery" reactive so it can be dynamically updated in the settings page. This can be simplified once we make SharedPreferences widely accessible as Flows --------- Co-authored-by: ThomasArtProcessors <71994342+ThomasArtProcessors@users.noreply.github.com>
This commit is contained in:
parent
68750e6146
commit
f6d50ac858
@ -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"
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<MessageRecord>): Commands()
|
||||
data class MarkAsDeletedForEveryone(val data: DeleteForEveryoneDialogData): Commands()
|
||||
|
@ -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<RecoveryPasswordActivity>() }
|
||||
) {
|
||||
hideRecoveryLauncher.launch(Intent(baseContext, RecoveryPasswordActivity::class.java))
|
||||
overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
|
||||
|
@ -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<Boolean>
|
||||
get() = _showLoader
|
||||
|
||||
private val _recoveryHidden: MutableStateFlow<Boolean> = MutableStateFlow(prefs.getHidePassword())
|
||||
val recoveryHidden: StateFlow<Boolean>
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sendAcceptsTextView"
|
||||
@ -340,7 +340,7 @@
|
||||
android:layout_height="@dimen/medium_button_height"
|
||||
android:layout_marginStart="@dimen/medium_spacing"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/decline" />
|
||||
android:text="@string/delete" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
app:tint="?searchIconColor"
|
||||
android:contentDescription="@string/search" />
|
||||
<EditText
|
||||
android:hint="@string/messages"
|
||||
android:hint="@string/search"
|
||||
android:imeOptions="actionSearch"
|
||||
android:id="@+id/search_input"
|
||||
android:paddingHorizontal="@dimen/small_spacing"
|
||||
|
@ -23,6 +23,12 @@
|
||||
<item name="android:textSize">@dimen/very_large_font_size</item>
|
||||
</style>
|
||||
|
||||
<style name="MenuTextAppearance" parent="TextAppearance.AppCompat.Widget.ActionBar.Menu">
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textSize">@dimen/small2_font_size</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Session.Dialog.Title" parent="TextAppearance.AppCompat.Title">
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">@dimen/medium2_font_size</item>
|
||||
|
@ -60,6 +60,8 @@
|
||||
|
||||
<item name="conversation_icon_attach_audio">@drawable/ic_audio_dark</item>
|
||||
<item name="conversation_icon_attach_video">@drawable/ic_video_dark</item>
|
||||
|
||||
<item name="android:actionMenuTextAppearance">@style/MenuTextAppearance</item>
|
||||
</style>
|
||||
|
||||
<!-- This should be the default theme for the application. -->
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user