diff --git a/app/build.gradle b/app/build.gradle index 88609da7af..7ecb6e45f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ configurations.forEach { it.exclude module: "commons-logging" } -def canonicalVersionCode = 382 -def canonicalVersionName = "1.20.0" +def canonicalVersionCode = 383 +def canonicalVersionName = "1.20.1" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, @@ -56,7 +56,7 @@ android { splits { abi { - enable true + enable !project.hasProperty('huawei') // huawei builds do not need the split variants reset() include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' universalApk true @@ -68,7 +68,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion '1.5.14' + kotlinCompilerExtensionVersion '1.5.15' } defaultConfig { @@ -118,6 +118,25 @@ android { } } + signingConfigs { + play { + if (project.hasProperty('SESSION_STORE_FILE')) { + storeFile file(SESSION_STORE_FILE) + storePassword SESSION_STORE_PASSWORD + keyAlias SESSION_KEY_ALIAS + keyPassword SESSION_KEY_PASSWORD + } + } + huawei { + if (project.hasProperty('SESSION_HUAWEI_STORE_FILE')) { + storeFile file(SESSION_HUAWEI_STORE_FILE) + storePassword SESSION_HUAWEI_STORE_PASSWORD + keyAlias SESSION_HUAWEI_KEY_ALIAS + keyPassword SESSION_HUAWEI_KEY_PASSWORD + } + } + } + flavorDimensions "distribution" productFlavors { play { @@ -129,6 +148,7 @@ android { buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" buildConfigField 'String', 'PUSH_KEY_SUFFIX', '\"\"' + signingConfig signingConfigs.play } huawei { @@ -138,6 +158,7 @@ android { buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.HUAWEI" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" buildConfigField 'String', 'PUSH_KEY_SUFFIX', '\"_HUAWEI\"' + signingConfig signingConfigs.huawei } website { @@ -150,13 +171,15 @@ android { } } - applicationVariants.forEach { variant -> + applicationVariants.configureEach { variant -> variant.outputs.each { output -> def abiName = output.getFilter("ABI") ?: 'universal' def postFix = abiPostFix.get(abiName, 0) + def flavour = (variant.flavorName == 'huawei') ? "-huawei" : "" + if (postFix >= postFixSize) throw new AssertionError("postFix is too large") - output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}.apk" + output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}${flavour}.apk" output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix } } @@ -353,15 +376,21 @@ dependencies { testImplementation 'org.conscrypt:conscrypt-openjdk-uber:2.5.2' // For Robolectric testImplementation 'app.cash.turbine:turbine:1.1.0' - implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.5' - implementation "androidx.compose.ui:ui:$composeVersion" - implementation "androidx.compose.animation:animation:$composeVersion" - implementation "androidx.compose.ui:ui-tooling:$composeVersion" - implementation "androidx.compose.runtime:runtime-livedata:$composeVersion" - implementation "androidx.compose.foundation:foundation-layout:$composeVersion" - implementation "androidx.compose.material3:material3:1.2.1" - androidTestImplementation "androidx.compose.ui:ui-test-junit4-android:$composeVersion" - debugImplementation "androidx.compose.ui:ui-test-manifest:$composeVersion" + // compose + Dependency composeBom = platform('androidx.compose:compose-bom:2024.09.01') + implementation composeBom + testImplementation composeBom + androidTestImplementation composeBom + + implementation "androidx.compose.ui:ui" + implementation "androidx.compose.animation:animation" + implementation "androidx.compose.ui:ui-tooling" + implementation "androidx.compose.runtime:runtime-livedata" + implementation "androidx.compose.foundation:foundation-layout" + implementation "androidx.compose.material3:material3" + + androidTestImplementation "androidx.compose.ui:ui-test-junit4-android" + debugImplementation "androidx.compose.ui:ui-test-manifest" implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.33.1-alpha" implementation "com.google.accompanist:accompanist-permissions:0.36.0" diff --git a/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt index 69d58411f3..3e9fb7bcfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt @@ -62,7 +62,10 @@ class SessionDialogBuilder(val context: Context) { // Main title entry point fun title(text: String?) { - text(text, R.style.TextAppearance_Session_Dialog_Title) { setPadding(dp20, 0, dp20, 0) } + text( + text = text, + qaTag = context.getString(R.string.AccessibilityId_modalTitle), + style = R.style.TextAppearance_Session_Dialog_Title) { setPadding(dp20, 0, dp20, 0) } } // Convenience assessor for title that takes a string resource @@ -74,18 +77,24 @@ class SessionDialogBuilder(val context: Context) { fun text(@StringRes id: Int, style: Int? = null) = text(context.getString(id), style) fun text(text: CharSequence?, @StyleRes style: Int? = null) { - text(text, style) { + text(text = text, style = style) { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) .apply { updateMargins(dp40, 0, dp40, 0) } } } - private fun text(text: CharSequence?, @StyleRes style: Int? = null, modify: TextView.() -> Unit) { + private fun text( + text: CharSequence?, + qaTag: String = context.getString(R.string.AccessibilityId_modalMessage), + @StyleRes style: Int? = null, + modify: TextView.() -> Unit + ) { text ?: return TextView(context, null, 0, style ?: R.style.TextAppearance_Session_Dialog_Message) .apply { setText(text) textAlignment = View.TEXT_ALIGNMENT_CENTER + contentDescription = qaTag modify() }.let(topView::addView) @@ -166,7 +175,7 @@ class SessionDialogBuilder(val context: Context) { textColor?.let{ setTextColor(it) } - contentDescription = resources.getString(contentDescriptionRes) + contentDescription = resources.getString(text) // QA now wants the qa tag to mtch the button's text layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, dp60, 1f) setOnClickListener { listener.invoke() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt index f00fbf44a9..f65dce4974 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/home/StartConversation.kt @@ -66,21 +66,21 @@ internal fun StartConversationScreen( icon = R.drawable.ic_message, modifier = Modifier.contentDescription(R.string.AccessibilityId_messageNew), onClick = delegate::onNewMessageSelected) - Divider(startIndent = LocalDimensions.current.dividerIndent) + Divider(startIndent = LocalDimensions.current.minItemButtonHeight) ItemButton( textId = R.string.groupCreate, icon = R.drawable.ic_group, modifier = Modifier.contentDescription(R.string.AccessibilityId_groupCreate), onClick = delegate::onCreateGroupSelected ) - Divider(startIndent = LocalDimensions.current.dividerIndent) + Divider(startIndent = LocalDimensions.current.minItemButtonHeight) ItemButton( textId = R.string.communityJoin, icon = R.drawable.ic_globe, modifier = Modifier.contentDescription(R.string.AccessibilityId_communityJoin), onClick = delegate::onJoinCommunitySelected ) - Divider(startIndent = LocalDimensions.current.dividerIndent) + Divider(startIndent = LocalDimensions.current.minItemButtonHeight) ItemButton( textId = R.string.sessionInviteAFriend, icon = R.drawable.ic_invite_friend, 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 a1eca65eb4..9bbbc787ef 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 @@ -38,7 +38,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat -import androidx.core.view.drawToBitmap import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -59,6 +58,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @@ -181,6 +181,7 @@ import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.NetworkUtils import org.thoughtcrime.securesms.util.SaveAttachmentTask +import org.thoughtcrime.securesms.util.drawToBitmap import org.thoughtcrime.securesms.util.isScrolledToBottom import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom import org.thoughtcrime.securesms.util.push @@ -734,9 +735,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun restoreDraftIfNeeded() { val mediaURI = intent.data val mediaType = AttachmentManager.MediaType.from(intent.type) + val mimeType = MediaUtil.getMimeType(this, mediaURI) if (mediaURI != null && mediaType != null) { - if (AttachmentManager.MediaType.IMAGE == mediaType || AttachmentManager.MediaType.GIF == mediaType || AttachmentManager.MediaType.VIDEO == mediaType) { - val media = Media(mediaURI, MediaUtil.getMimeType(this, mediaURI)!!, 0, 0, 0, 0, Optional.absent(), Optional.absent()) + if (mimeType != null && (AttachmentManager.MediaType.IMAGE == mediaType || AttachmentManager.MediaType.GIF == mediaType || AttachmentManager.MediaType.VIDEO == mediaType)) { + val media = Media(mediaURI, mimeType, 0, 0, 0, 0, Optional.absent(), Optional.absent()) startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient!!, ""), PICK_FROM_LIBRARY) return } else { @@ -813,7 +815,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe if (shouldShowLegacy) { val txt = Phrase.from(applicationContext, R.string.disappearingMessagesLegacy) - .put(NAME_KEY, legacyRecipient!!.name) + .put(NAME_KEY, legacyRecipient!!.toShortString()) .format() binding?.outdatedBannerTextView?.text = txt } @@ -1191,14 +1193,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe title(R.string.block) text( Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.toShortString()) .format() ) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { viewModel.block() // Block confirmation toast added as per SS-64 - val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, recipient.name).format().toString() + val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, recipient.toShortString()).format().toString() Toast.makeText(context, txt, Toast.LENGTH_LONG).show() if (deleteThread) { @@ -1249,7 +1251,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe title(R.string.blockUnblock) text( Phrase.from(context, R.string.blockUnblockName) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.toShortString()) .format() ) dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { viewModel.unblock() } @@ -1737,10 +1739,19 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.inputBar.text = "" binding.inputBar.cancelQuoteDraft() binding.inputBar.cancelLinkPreviewDraft() - // Put the message in the database - message.id = smsDb.insertMessageOutbox(viewModel.threadId, outgoingTextMessage, false, message.sentTimestamp!!, null, true) - // Send it - MessageSender.send(message, recipient.address) + lifecycleScope.launch(Dispatchers.Default) { + // Put the message in the database + message.id = smsDb.insertMessageOutbox( + viewModel.threadId, + outgoingTextMessage, + false, + message.sentTimestamp!!, + null, + true + ) + // Send it + MessageSender.send(message, recipient.address) + } // Send a typing stopped message ApplicationContext.getInstance(this).typingStatusSender.onTypingStopped(viewModel.threadId) return Pair(recipient.address, sentTimestamp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt index d3e1de9912..f9e45547d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/DownloadDialog.kt @@ -35,7 +35,7 @@ class DownloadDialog(private val recipient: Recipient) : DialogFragment() { title(getString(R.string.attachmentsAutoDownloadModalTitle)) val explanation = Phrase.from(context, R.string.attachmentsAutoDownloadModalDescription) - .put(CONVERSATION_NAME_KEY, recipient.name) + .put(CONVERSATION_NAME_KEY, recipient.toShortString()) .format() val spannable = SpannableStringBuilder(explanation) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 8fdbe2accc..d7ad48b497 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1583,6 +1583,7 @@ open class Storage( if (recipient.isLocalNumber || !recipient.isContactRecipient) return configFactory.contacts?.upsertContact(recipient.address.serialize()) { this.approved = approved + this.priority = PRIORITY_VISIBLE } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index d91f4c428c..c022598d2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -144,14 +144,17 @@ public class ThreadRecord extends DisplayRecord { .format().toString(); } else if (MmsSmsColumns.Types.isMessageRequestResponse(type)) { - if (lastMessage.getRecipient().getAddress().serialize().equals( - TextSecurePreferences.getLocalNumber(context))) { - return UtilKt.getSubbedCharSequence( - context, - R.string.messageRequestYouHaveAccepted, - new Pair<>(NAME_KEY, getName()) - ); + try { + if (lastMessage.getRecipient().getAddress().serialize().equals( + TextSecurePreferences.getLocalNumber(context))) { + return UtilKt.getSubbedCharSequence( + context, + R.string.messageRequestYouHaveAccepted, + new Pair<>(NAME_KEY, getName()) + ); + } } + catch (Exception e){} // the above can throw a null exception return context.getString(R.string.messageRequestsAccepted); } else if (getCount() == 0) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt index f277d1f40b..3ae656fa60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/debugmenu/DebugMenu.kt @@ -74,12 +74,10 @@ fun DebugMenu( buttons = listOf( DialogButtonModel( text = GetString(R.string.cancel), - contentDescription = GetString(R.string.cancel), onClick = { sendCommand(HideEnvironmentWarningDialog) } ), DialogButtonModel( text = GetString(R.string.ok), - contentDescription = GetString(R.string.ok), onClick = { sendCommand(ChangeEnvironment) } ) ) @@ -149,9 +147,11 @@ fun ColumnScope.DebugCell( ) { Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing)) - Cell { + Cell( + modifier = modifier + ) { Column( - modifier = modifier.padding(LocalDimensions.current.spacing) + modifier = Modifier.padding(LocalDimensions.current.spacing) ) { Text( text = title, diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index aeaa7338f2..2bb3d63cc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -71,6 +71,7 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import com.bumptech.glide.Glide import com.bumptech.glide.RequestManager +import org.session.libsession.utilities.truncateIdForDisplay import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.preferences.SettingsActivity @@ -507,7 +508,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), showSessionDialog { title(R.string.block) text(Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, thread.recipient.name) + .put(NAME_KEY, thread.recipient.toShortString()) .format()) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { lifecycleScope.launch(Dispatchers.IO) { @@ -518,7 +519,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } } // Block confirmation toast added as per SS-64 - val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, thread.recipient.name).format().toString() + val txt = Phrase.from(context, R.string.blockBlockedUser).put(NAME_KEY, thread.recipient.toShortString()).format().toString() Toast.makeText(context, txt, Toast.LENGTH_LONG).show() } cancelButton() @@ -528,7 +529,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), private fun unblockConversation(thread: ThreadRecord) { showSessionDialog { title(R.string.blockUnblock) - text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format()) + text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.toShortString()).format()) dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { lifecycleScope.launch(Dispatchers.IO) { storage.setBlocked(listOf(thread.recipient), false) @@ -616,7 +617,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), if (recipient.name != null) { title = getString(R.string.conversationsDelete) message = Phrase.from(this.applicationContext, R.string.conversationsDeleteDescription) - .put(NAME_KEY, recipient.name) + .put(NAME_KEY, recipient.toShortString()) .format() } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt index 4a25556a42..3e99134506 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchViewModel.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.home.search import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.BufferOverflow @@ -13,11 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import kotlinx.coroutines.plus +import kotlinx.coroutines.withContext import org.thoughtcrime.securesms.search.SearchRepository import org.thoughtcrime.securesms.search.model.SearchResult import javax.inject.Inject @@ -38,9 +41,14 @@ class GlobalSearchViewModel @Inject constructor( .buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) .mapLatest { query -> if (query.trim().isEmpty()) { - // searching for 05 as contactDb#getAllContacts was not returning contacts - // without a nickname/name who haven't approved us. - GlobalSearchResult(query.toString(), searchRepository.queryContacts("05").first.toList()) + withContext(Dispatchers.Default) { + // searching for 05 as contactDb#getAllContacts was not returning contacts + // without a nickname/name who haven't approved us. + GlobalSearchResult( + query.toString(), + searchRepository.queryContacts("05").first.toList() + ) + } } else { // User input delay in case we get a new query within a few hundred ms this // coroutine will be cancelled and the expensive query will not be run. diff --git a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt index 34ccc1c1c6..c6c6ca2e41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/media/MediaOverviewScreen.kt @@ -231,8 +231,8 @@ private fun SaveAttachmentWarningDialog( title = context.getString(R.string.warning), text = context.resources.getString(R.string.attachmentsWarning), buttons = listOf( - DialogButtonModel(GetString(R.string.save), GetString(R.string.AccessibilityId_saveAttachment), color = LocalColors.current.danger, onClick = onAccepted), - DialogButtonModel(GetString(android.R.string.cancel), GetString(R.string.AccessibilityId_cancel), dismissOnClick = true) + DialogButtonModel(GetString(R.string.save), color = LocalColors.current.danger, onClick = onAccepted), + DialogButtonModel(GetString(android.R.string.cancel), dismissOnClick = true) ) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt index 4d280a47ad..e27345afc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsActivity.kt @@ -87,7 +87,7 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat showSessionDialog { title(R.string.block) text(Phrase.from(context, R.string.blockDescription) - .put(NAME_KEY, thread.recipient.name) + .put(NAME_KEY, thread.recipient.toShortString()) .format()) dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) { doBlock() diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt index aab1421185..14eb2dbe06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/landing/Landing.kt @@ -85,12 +85,10 @@ internal fun LandingScreen( buttons = listOf( DialogButtonModel( text = GetString(R.string.onboardingTos), - contentDescription = GetString(R.string.AccessibilityId_onboardingTos), onClick = openTerms ), DialogButtonModel( text = GetString(R.string.onboardingPrivacy), - contentDescription = GetString(R.string.AccessibilityId_onboardingPrivacy), onClick = openPrivacyPolicy ) ) 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 83fca7fe7a..bbf075ca7c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -573,13 +573,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { buttons = listOf( DialogButtonModel( text = GetString(R.string.save), - contentDescription = GetString(R.string.AccessibilityId_save), enabled = state is TempAvatar, onClick = saveAvatar ), DialogButtonModel( text = GetString(R.string.remove), - contentDescription = GetString(R.string.AccessibilityId_remove), color = LocalColors.current.danger, enabled = state is UserAvatar || // can remove is the user has an avatar set (state is TempAvatar && state.hasAvatar), diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt index 4bc2724d6d..56ca84efee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt @@ -26,7 +26,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import network.loki.messenger.R +import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.Cell +import org.thoughtcrime.securesms.ui.DialogButtonModel +import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.SessionShieldIcon import org.thoughtcrime.securesms.ui.components.QrImage import org.thoughtcrime.securesms.ui.components.SlimOutlineButton @@ -45,8 +48,8 @@ import org.thoughtcrime.securesms.ui.theme.monospace internal fun RecoveryPasswordScreen( mnemonic: String, seed: String? = null, - copyMnemonic:() -> Unit = {}, - onHide:() -> Unit = {} + confirmHideRecovery: () -> Unit, + copyMnemonic:() -> Unit = {} ) { Column( verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing), @@ -57,7 +60,7 @@ internal fun RecoveryPasswordScreen( .padding(horizontal = LocalDimensions.current.spacing) ) { RecoveryPasswordCell(mnemonic, seed, copyMnemonic) - HideRecoveryPasswordCell(onHide) + HideRecoveryPasswordCell(confirmHideRecovery = confirmHideRecovery) } } @@ -151,7 +154,12 @@ private fun RecoveryPassword(mnemonic: String) { } @Composable -private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) { +private fun HideRecoveryPasswordCell( + confirmHideRecovery:() -> Unit +) { + var showHideRecoveryDialog by remember { mutableStateOf(false) } + var showHideRecoveryConfirmationDialog by remember { mutableStateOf(false) } + Cell { Row( modifier = Modifier.padding(LocalDimensions.current.smallSpacing) @@ -176,10 +184,44 @@ private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) { .align(Alignment.CenterVertically) .contentDescription(R.string.AccessibilityId_recoveryPasswordHideRecoveryPassword), color = LocalColors.current.danger, - onClick = onHide + onClick = { showHideRecoveryDialog = true } ) } } + + // recovery hide dialog + if(showHideRecoveryDialog) { + AlertDialog( + onDismissRequest = { showHideRecoveryDialog = false }, + title = stringResource(R.string.recoveryPasswordHidePermanently), + text = stringResource(R.string.recoveryPasswordHidePermanentlyDescription1), + buttons = listOf( + DialogButtonModel( + GetString(R.string.theContinue), + color = LocalColors.current.danger, + onClick = { showHideRecoveryConfirmationDialog = true } + ), + DialogButtonModel(GetString(android.R.string.cancel)) + ) + ) + } + + // recovery hide confirmation dialog + if(showHideRecoveryConfirmationDialog) { + AlertDialog( + onDismissRequest = { showHideRecoveryConfirmationDialog = false }, + title = stringResource(R.string.recoveryPasswordHidePermanently), + text = stringResource(R.string.recoveryPasswordHidePermanentlyDescription2), + buttons = listOf( + DialogButtonModel( + GetString(R.string.yes), + color = LocalColors.current.danger, + onClick = confirmHideRecovery + ), + DialogButtonModel(GetString(android.R.string.cancel)) + ) + ) + } } @Preview @@ -188,6 +230,9 @@ private fun PreviewRecoveryPasswordScreen( @PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors ) { PreviewTheme(colors) { - RecoveryPasswordScreen(mnemonic = "voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane") + RecoveryPasswordScreen( + mnemonic = "voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane", + confirmHideRecovery = {} + ) } } 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 543938757c..a6d38c13a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt @@ -24,33 +24,12 @@ class RecoveryPasswordActivity : BaseActionBarActivity() { RecoveryPasswordScreen( mnemonic = mnemonic, seed = seed, - copyMnemonic = viewModel::copyMnemonic, - onHide = ::onHide + confirmHideRecovery = { + viewModel.permanentlyHidePassword() + finish() + }, + copyMnemonic = viewModel::copyMnemonic ) } } - - private fun onHide() { - showSessionDialog { - title(R.string.recoveryPasswordHidePermanently) - text(R.string.recoveryPasswordHidePermanentlyDescription1) - dangerButton(R.string.theContinue, R.string.AccessibilityId_theContinue) { onHideConfirm() } - cancelButton() - } - } - - private fun onHideConfirm() { - showSessionDialog { - title(R.string.recoveryPasswordHidePermanently) - text(R.string.recoveryPasswordHidePermanentlyDescription2) - cancelButton() - dangerButton( - R.string.yes, - contentDescription = R.string.AccessibilityId_recoveryPasswordHidePermanentlyConfirm - ) { - viewModel.permanentlyHidePassword() - finish() - } - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt index c4770f2110..988220960e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt @@ -55,7 +55,6 @@ import org.thoughtcrime.securesms.ui.theme.bold class DialogButtonModel( val text: GetString, - val contentDescription: GetString = text, val color: Color = Color.Unspecified, val dismissOnClick: Boolean = true, val enabled: Boolean = true, @@ -130,6 +129,7 @@ fun AlertDialog( textAlign = TextAlign.Center, style = LocalType.current.h7, modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) + .qaTag(stringResource(R.string.AccessibilityId_modalTitle)) ) } text?.let { @@ -152,6 +152,7 @@ fun AlertDialog( textAlign = TextAlign.Center, style = textStyle, modifier = textModifier + .qaTag(stringResource(R.string.AccessibilityId_modalMessage)) ) } content() @@ -163,7 +164,7 @@ fun AlertDialog( text = it.text(), modifier = Modifier .fillMaxHeight() - .contentDescription(it.contentDescription()) + .qaTag(it.text.string()) .weight(1f), color = it.color, enabled = it.enabled @@ -201,13 +202,11 @@ fun OpenURLAlertDialog( buttons = listOf( DialogButtonModel( text = GetString(R.string.open), - contentDescription = GetString(R.string.AccessibilityId_urlOpenBrowser), color = LocalColors.current.danger, onClick = { context.openUrl(url) } ), DialogButtonModel( text = GetString(android.R.string.copyUrl), - contentDescription = GetString(R.string.AccessibilityId_copy), onClick = { context.copyURLToClipboard(url) Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() @@ -297,7 +296,8 @@ fun LoadingDialog( title?.let { Text( it, - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier.align(Alignment.CenterHorizontally) + .qaTag(stringResource(R.string.AccessibilityId_modalTitle)), style = LocalType.current.large ) } @@ -340,12 +340,10 @@ fun PreviewXCloseDialog() { buttons = listOf( DialogButtonModel( text = GetString(R.string.onboardingTos), - contentDescription = GetString(R.string.AccessibilityId_onboardingTos), onClick = {} ), DialogButtonModel( text = GetString(R.string.onboardingPrivacy), - contentDescription = GetString(R.string.AccessibilityId_onboardingPrivacy), onClick = {} ) ), diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index 355d74947d..1a8a917f40 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -275,23 +275,19 @@ fun ItemButton( onClick: () -> Unit ) { TextButton( - modifier = modifier.fillMaxWidth() - .height(IntrinsicSize.Min) - .heightIn(min = minHeight) - .padding(horizontal = LocalDimensions.current.xsSpacing), + modifier = modifier.fillMaxWidth(), colors = colors, onClick = onClick, + contentPadding = PaddingValues(), shape = RectangleShape, ) { Box( - modifier = Modifier.fillMaxHeight() - .aspectRatio(1f) - .align(Alignment.CenterVertically) - ) { - icon() - } - - Spacer(modifier = Modifier.width(LocalDimensions.current.smallSpacing)) + modifier = Modifier + .padding(horizontal = LocalDimensions.current.xxsSpacing) + .size(minHeight) + .align(Alignment.CenterVertically), + content = icon + ) Text( text, @@ -315,6 +311,18 @@ fun PreviewItemButton() { } } +@Preview +@Composable +fun PreviewLargeItemButton() { + PreviewTheme { + LargeItemButton( + textId = R.string.groupCreate, + icon = R.drawable.ic_group, + onClick = {} + ) + } +} + @Composable fun Cell( modifier: Modifier = Modifier, diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt similarity index 81% rename from app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt rename to app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt index 15f0292853..68587bc9e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/AnnotatedString.kt @@ -3,8 +3,16 @@ package org.thoughtcrime.securesms.ui.components import android.content.res.Resources import android.graphics.Typeface import android.text.Spanned -import android.text.SpannedString -import android.text.style.* +import android.text.style.AbsoluteSizeSpan +import android.text.style.BulletSpan +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan +import android.text.style.SubscriptSpan +import android.text.style.SuperscriptSpan +import android.text.style.TypefaceSpan +import android.text.style.UnderlineSpan import android.util.Log import androidx.annotation.StringRes import androidx.compose.runtime.Composable @@ -14,7 +22,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -26,13 +33,9 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em -import androidx.core.text.HtmlCompat -import com.squareup.phrase.Phrase -import network.loki.messenger.R -import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY -// TODO Remove this file once we update to composeVersion=1.7.0-alpha06 fixes https://issuetracker.google.com/issues/139320238?pli=1 -// which allows Stylized string in string resources +// Utilities for AnnotatedStrings, +// like converting the old view system's SpannableString to AnnotatedString @Composable @ReadOnlyComposable private fun resources(): Resources { @@ -40,31 +43,6 @@ private fun resources(): Resources { return LocalContext.current.resources } -fun Spanned.toHtmlWithoutParagraphs(): String { - return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) - .substringAfter("
").substringBeforeLast("
") -} - -fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence { - val escapedArgs = args.map { - if (it is Spanned) it.toHtmlWithoutParagraphs() else it - }.toTypedArray() - val resource = SpannedString(getText(id)) - val htmlResource = resource.toHtmlWithoutParagraphs() - val formattedHtml = String.format(htmlResource, *escapedArgs) - return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY) -} - -@Composable -fun annotatedStringResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString { - val resources = resources() - val density = LocalDensity.current - return remember(id, formatArgs) { - val text = resources.getText(id, *formatArgs) - spannableStringToAnnotatedString(text, density) - } -} - @Composable fun annotatedStringResource(@StringRes id: Int): AnnotatedString { val resources = resources() diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt index ac5ce8c4cf..d1608ea24e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/Dimensions.kt @@ -15,7 +15,6 @@ data class Dimensions( val mediumSpacing: Dp = 36.dp, val xlargeSpacing: Dp = 64.dp, - val dividerIndent: Dp = 60.dp, val appBarHeight: Dp = 64.dp, val minItemButtonHeight: Dp = 50.dp, val minLargeItemButtonHeight: Dp = 60.dp, diff --git a/content-descriptions/src/main/res/values/strings.xml b/content-descriptions/src/main/res/values/strings.xml index 5eaab10ae0..4474dc6c88 100644 --- a/content-descriptions/src/main/res/values/strings.xml +++ b/content-descriptions/src/main/res/values/strings.xml @@ -128,6 +128,9 @@