From ab8b2c42b9ceb0ddfbc0948686d1154b658d31cb Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 28 Jun 2023 10:34:48 +0930 Subject: [PATCH 01/51] Add jetpack compose --- app/build.gradle | 12 +++ .../securesms/calls/WebRtcCallActivity.kt | 13 +-- .../v2/menus/ConversationMenuHelper.kt | 2 +- .../securesms/dms/NewMessageFragment.kt | 2 +- .../securesms/groups/JoinCommunityFragment.kt | 2 +- .../home/search/GlobalSearchAdapterUtils.kt | 1 + .../keyboard/emoji/KeyboardPageSearchView.kt | 2 +- .../securesms/util/ViewUtilities.kt | 2 +- build.gradle | 3 +- gradle.properties | 4 +- .../messaging/jobs/BatchMessageReceiveJob.kt | 3 +- .../messaging/jobs/MessageReceiveJob.kt | 1 - .../messaging/mentions/MentionsManager.kt | 5 +- .../pollers/OpenGroupPoller.kt | 1 + .../utilities/UpdateMessageBuilder.kt | 95 ++++++++----------- 15 files changed, 72 insertions(+), 76 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8a4241973..6170763a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,7 @@ apply plugin: 'com.google.gms.google-services' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' + configurations.all { exclude module: "commons-logging" } @@ -158,6 +159,10 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.4' testImplementation 'org.robolectric:shadows-multidex:4.4' + + implementation 'androidx.compose.ui:ui:1.4.3' + implementation 'androidx.compose.material:material:1.4.3' + implementation 'androidx.compose.ui:ui-tooling:1.4.3' } def canonicalVersionCode = 338 @@ -203,6 +208,13 @@ android { } } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.4.7' + } + defaultConfig { versionCode canonicalVersionCode * postFixSize versionName canonicalVersionName diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index 7e732d1aa..b87eac12c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -249,17 +249,12 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { viewModel.callState.collect { state -> Log.d("Loki", "Consuming view model state $state") when (state) { - CALL_RINGING -> { - if (wantsToAnswer) { - answerCall() - wantsToAnswer = false - } - } - CALL_OUTGOING -> { - } - CALL_CONNECTED -> { + CALL_RINGING -> if (wantsToAnswer) { + answerCall() wantsToAnswer = false } + CALL_CONNECTED -> wantsToAnswer = false + else -> {} } updateControls(state) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index ce29efa3a..fee9b094b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -68,7 +68,7 @@ object ConversationMenuHelper { if (thread.expireMessages > 0) { inflater.inflate(R.menu.menu_conversation_expiration_on, menu) val item = menu.findItem(R.id.menu_expiring_messages) - val actionView = item.actionView + val actionView = item.actionView!! val iconView = actionView.findViewById(R.id.menu_badge_icon) val badgeView = actionView.findViewById(R.id.expiration_badge) @ColorInt val color = context.getColorFromAttr(android.R.attr.textColorPrimary) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt index 8b880d218..74e2cac4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dms/NewMessageFragment.kt @@ -98,7 +98,7 @@ class NewMessageFragment : Fragment() { private fun hideLoader() { binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) binding.loader.visibility = View.GONE } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt index d37b17ef9..068c01c32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt @@ -55,7 +55,7 @@ class JoinCommunityFragment : Fragment() { fun hideLoader() { binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) binding.loader.visibility = View.GONE } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 2c64ded86..47e9c7594 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -76,6 +76,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) { } binding.searchResultSubtitle.text = getHighlight(query, membersString) } + else -> {} } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt index 07da14b09..a85ea525a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/KeyboardPageSearchView.kt @@ -154,7 +154,7 @@ class KeyboardPageSearchView @JvmOverloads constructor( .setDuration(REVEAL_DURATION) .alpha(0f) .setListener(object : AnimationCompleteListener() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { visibility = INVISIBLE } }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt index 834547349..dfd4ffe41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt @@ -58,7 +58,7 @@ fun View.fadeIn(duration: Long = 150) { fun View.fadeOut(duration: Long = 150) { animate().setDuration(duration).alpha(0.0f).setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) visibility = View.GONE } diff --git a/build.gradle b/build.gradle index 7e7e14f00..39701076c 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "com.google.gms:google-services:$googleServicesVersion" classpath files('libs/gradle-witness.jar') + classpath "com.squareup:javapoet:1.13.0" } } @@ -52,6 +53,6 @@ allprojects { project.ext { androidMinimumSdkVersion = 23 androidTargetSdkVersion = 31 - androidCompileSdkVersion = 32 + androidCompileSdkVersion = 33 } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index fa51fdbca..7af942564 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,11 +4,11 @@ org.gradle.jvmargs=-Xmx8g gradlePluginVersion=7.3.1 googleServicesVersion=4.3.12 -kotlinVersion=1.6.21 +kotlinVersion=1.8.21 coroutinesVersion=1.6.4 kotlinxJsonVersion=1.3.3 lifecycleVersion=2.5.1 -daggerVersion=2.40.1 +daggerVersion=2.46.1 glideVersion=4.11.0 kovenantVersion=3.3.0 curve25519Version=0.6.0 diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index fa07a7d9c..62f27c19c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -235,10 +235,9 @@ class BatchMessageReceiveJob( val openGroupID = data.getStringOrDefault(OPEN_GROUP_ID_KEY, null) val parameters = (0 until numMessages).map { index -> - val data = contents[index] val serverHash = serverHashes[index].let { if (it.isEmpty()) null else it } val serverId = openGroupMessageServerIDs[index].let { if (it == -1L) null else it } - MessageReceiveParameters(data, serverHash, serverId) + MessageReceiveParameters(contents[index], serverHash, serverId) } return BatchMessageReceiveJob(parameters, openGroupID) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 2ba33b563..121e04dcf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -32,7 +32,6 @@ class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val fun executeAsync(dispatcherName: String): Promise { val deferred = deferred() try { - val isRetry: Boolean = failureCount != 0 val serverPublicKey = openGroupID?.let { MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(it.split(".").dropLast(1).joinToString(".")) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt b/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt index e9eae0ba5..fd16061e6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt @@ -2,6 +2,7 @@ package org.session.libsession.messaging.mentions import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact +import java.util.* object MentionsManager { var userPublicKeyCache = mutableMapOf>() // Thread ID to set of user hex encoded public keys @@ -32,9 +33,9 @@ object MentionsManager { candidates.sortedBy { it.displayName } if (query.length >= 2) { // Filter out any non-matching candidates - candidates = candidates.filter { it.displayName.toLowerCase().contains(query.toLowerCase()) } + candidates = candidates.filter { it.displayName.lowercase(Locale.getDefault()).contains(query.lowercase(Locale.getDefault())) } // Sort based on where in the candidate the query occurs - candidates.sortedBy { it.displayName.toLowerCase().indexOf(query.toLowerCase()) } + candidates.sortedBy { it.displayName.lowercase(Locale.getDefault()).indexOf(query.lowercase(Locale.getDefault())) } } // Return return candidates diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 387381c9c..a75a70433 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -169,6 +169,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S is Endpoint.Outbox, is Endpoint.OutboxSince -> { handleDirectMessages(server, true, response.body as List) } + else -> {} } if (secondToLastJob == null && !isCaughtUp) { isCaughtUp = true diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt index 35328b974..6cf18ba5c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt @@ -4,52 +4,51 @@ import android.content.Context import org.session.libsession.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType +import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED +import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING +import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED +import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.truncateIdForDisplay object UpdateMessageBuilder { + val storage = MessagingModuleConfiguration.shared.storage + + fun getSenderName(senderId: String) = storage.getContactWithSessionID(senderId) + ?.displayName(Contact.ContactContext.REGULAR) + ?: truncateIdForDisplay(senderId) fun buildGroupUpdateMessage(context: Context, updateMessageData: UpdateMessageData, senderId: String? = null, isOutgoing: Boolean = false): String { - var message = "" - val updateData = updateMessageData.kind ?: return message - if (!isOutgoing && senderId == null) return message - val storage = MessagingModuleConfiguration.shared.storage - val senderName: String = if (!isOutgoing) { - storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId) - } else { context.getString(R.string.MessageRecord_you) } + val updateData = updateMessageData.kind + if (updateData == null || !isOutgoing && senderId == null) return "" + val senderName: String = if (isOutgoing) context.getString(R.string.MessageRecord_you) + else getSenderName(senderId!!) - when (updateData) { - is UpdateMessageData.Kind.GroupCreation -> { - message = if (isOutgoing) { - context.getString(R.string.MessageRecord_you_created_a_new_group) - } else { - context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName) - } + return when (updateData) { + is UpdateMessageData.Kind.GroupCreation -> if (isOutgoing) { + context.getString(R.string.MessageRecord_you_created_a_new_group) + } else { + context.getString(R.string.MessageRecord_s_added_you_to_the_group, senderName) } - is UpdateMessageData.Kind.GroupNameChange -> { - message = if (isOutgoing) { - context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name) - } else { - context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name) - } + is UpdateMessageData.Kind.GroupNameChange -> if (isOutgoing) { + context.getString(R.string.MessageRecord_you_renamed_the_group_to_s, updateData.name) + } else { + context.getString(R.string.MessageRecord_s_renamed_the_group_to_s, senderName, updateData.name) } is UpdateMessageData.Kind.GroupMemberAdded -> { - val members = updateData.updatedMembers.joinToString(", ") { - storage.getContactWithSessionID(it)?.displayName(Contact.ContactContext.REGULAR) ?: it - } - message = if (isOutgoing) { + val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName) + if (isOutgoing) { context.getString(R.string.MessageRecord_you_added_s_to_the_group, members) } else { context.getString(R.string.MessageRecord_s_added_s_to_the_group, senderName, members) } } is UpdateMessageData.Kind.GroupMemberRemoved -> { - val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // 1st case: you are part of the removed members - message = if (userPublicKey in updateData.updatedMembers) { + return if (userPublicKey in updateData.updatedMembers) { if (isOutgoing) { context.getString(R.string.MessageRecord_left_group) } else { @@ -57,9 +56,7 @@ object UpdateMessageBuilder { } } else { // 2nd case: you are not part of the removed members - val members = updateData.updatedMembers.joinToString(", ") { - storage.getContactWithSessionID(it)?.displayName(Contact.ContactContext.REGULAR) ?: it - } + val members = updateData.updatedMembers.joinToString(", ", transform = ::getSenderName) if (isOutgoing) { context.getString(R.string.MessageRecord_you_removed_s_from_the_group, members) } else { @@ -67,22 +64,19 @@ object UpdateMessageBuilder { } } } - is UpdateMessageData.Kind.GroupMemberLeft -> { - message = if (isOutgoing) { - context.getString(R.string.MessageRecord_left_group) - } else { - context.getString(R.string.ConversationItem_group_action_left, senderName) - } + is UpdateMessageData.Kind.GroupMemberLeft -> if (isOutgoing) { + context.getString(R.string.MessageRecord_left_group) + } else { + context.getString(R.string.ConversationItem_group_action_left, senderName) } + else -> return "" } - return message } fun buildExpirationTimerMessage(context: Context, duration: Long, senderId: String? = null, isOutgoing: Boolean = false): String { if (!isOutgoing && senderId == null) return "" - val storage = MessagingModuleConfiguration.shared.storage - val senderName: String? = if (!isOutgoing) { - storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId) + val senderName: String = if (!isOutgoing) { + getSenderName(senderId!!) } else { context.getString(R.string.MessageRecord_you) } return if (duration <= 0) { if (isOutgoing) context.getString(R.string.MessageRecord_you_disabled_disappearing_messages) @@ -95,8 +89,7 @@ object UpdateMessageBuilder { } fun buildDataExtractionMessage(context: Context, kind: DataExtractionNotificationInfoMessage.Kind, senderId: String? = null): String { - val storage = MessagingModuleConfiguration.shared.storage - val senderName = storage.getContactWithSessionID(senderId!!)?.displayName(Contact.ContactContext.REGULAR) ?: truncateIdForDisplay(senderId) + val senderName = getSenderName(senderId!!) return when (kind) { DataExtractionNotificationInfoMessage.Kind.SCREENSHOT -> context.getString(R.string.MessageRecord_s_took_a_screenshot, senderName) @@ -105,18 +98,12 @@ object UpdateMessageBuilder { } } - fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String { - val storage = MessagingModuleConfiguration.shared.storage - val senderName = storage.getContactWithSessionID(sender)?.displayName(Contact.ContactContext.REGULAR) ?: sender - return when (type) { - CallMessageType.CALL_MISSED -> - context.getString(R.string.MessageRecord_missed_call_from, senderName) - CallMessageType.CALL_INCOMING -> - context.getString(R.string.MessageRecord_s_called_you, senderName) - CallMessageType.CALL_OUTGOING -> - context.getString(R.string.MessageRecord_called_s, senderName) - CallMessageType.CALL_FIRST_MISSED -> - context.getString(R.string.MessageRecord_missed_call_from, senderName) + fun buildCallMessage(context: Context, type: CallMessageType, sender: String): String = + when (type) { + CALL_INCOMING -> R.string.MessageRecord_s_called_you + CALL_OUTGOING -> R.string.MessageRecord_called_s + CALL_MISSED, CALL_FIRST_MISSED -> R.string.MessageRecord_missed_call_from + }.let { + context.getString(it, storage.getContactWithSessionID(sender)?.displayName(Contact.ContactContext.REGULAR) ?: sender) } - } } From fc108b34dbbd4cd31ceea8e44f3391d7a47c5e8e Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 29 Jun 2023 11:37:55 +0930 Subject: [PATCH 02/51] Create Message Details screen --- app/build.gradle | 6 +- .../conversation/v2/MessageDetailActivity.kt | 221 +++++++++++++----- 2 files changed, 166 insertions(+), 61 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6170763a6..8194ef4b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ configurations.all { } dependencies { + implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "com.google.android.material:material:$materialVersion" @@ -161,8 +162,11 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' implementation 'androidx.compose.ui:ui:1.4.3' - implementation 'androidx.compose.material:material:1.4.3' implementation 'androidx.compose.ui:ui-tooling:1.4.3' + implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.30.1" + + implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02' + implementation 'androidx.compose.material:material:1.5.0-alpha02' } def canonicalVersionCode = 338 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 0938b21dd..2c1698ae8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,32 +2,49 @@ package org.thoughtcrime.securesms.conversation.v2 import android.os.Bundle import android.view.View -import androidx.core.view.isVisible +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R -import network.loki.messenger.databinding.ActivityMessageDetailBinding -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.messaging.utilities.SessionId -import org.session.libsession.messaging.utilities.SodiumUtilities -import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.ExpirationUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.DateUtils -import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject @AndroidEntryPoint class MessageDetailActivity: PassphraseRequiredActionBarActivity() { - private lateinit var binding: ActivityMessageDetailBinding var messageRecord: MessageRecord? = null @Inject @@ -42,58 +59,142 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) - binding = ActivityMessageDetailBinding.inflate(layoutInflater) - setContentView(binding.root) - title = resources.getString(R.string.conversation_context__menu_message_details) + val timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) - // We only show this screen for messages fail to send, - // so the author of the messages must be the current user. - val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) - messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run { - finish() - return - } - val threadId = messageRecord!!.threadId - val openGroup = storage.getOpenGroup(threadId) - val blindedKey = openGroup?.let { group -> - val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null - val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase()) - if (blindingEnabled) { - SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes - ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString - } else null - } - updateContent() - binding.resendButton.setOnClickListener { - ResendMessageUtilities.resend(this, messageRecord!!, blindedKey) - finish() + + title = resources.getString(R.string.conversation_context__menu_message_details) + + setContentView(createComposeView()) + } + + private fun createComposeView(): ComposeView = ComposeView(this).apply { + id = View.generateViewId() + setContent { + MessageDetails() } } - fun updateContent() { - val dateLocale = Locale.getDefault() - val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale) - binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent)) + data class TitledText(val title: String, val value: String) - val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) - if (errorMessage != null) { - binding.errorMessage.text = errorMessage - binding.resendContainer.isVisible = true - binding.errorContainer.isVisible = true - } else { - binding.errorContainer.isVisible = false - binding.resendContainer.isVisible = false + @OptIn(ExperimentalLayoutApi::class) + @Preview + @Composable + fun MessageDetails() { + val fileDetails = listOf( + TitledText("File Id:", "1237896548514214124235985214"), + TitledText("File Type:", ".PNG"), + TitledText("File Size:", "6mb"), + TitledText("Resolution:", "550x550"), + TitledText("Duration:", "N/A"), + ) + + val sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022 ") + val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022 ") + val user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") + + AppCompatTheme { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp)) { + CardWithPadding { + FlowRow( + verticalArrangement = Arrangement.spacedBy(16.dp), + maxItemsInEachRow = 2 + ) { + fileDetails.forEach { + titledText(it, Modifier.weight(1f)) + } + } + } + CardWithPadding { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + titledText(sent) + titledText(received) + titledView("From:") { + Row { + Box(modifier = Modifier.width(60.dp).height(60.dp)) + Column { + titledText(user) + } + } + } + } + } + Card { + Column { + ItemButton("Reply", R.drawable.ic_reply) + Divider() + ItemButton("Resend", R.drawable.ic_reply) + Divider() + ItemButton("Delete", R.drawable.ic_delete_24, color = Color.Red) + } + } + } } + } - if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) { - binding.expiresContainer.visibility = View.GONE - } else { - binding.expiresContainer.visibility = View.VISIBLE - val elapsed = SnodeAPI.nowWithOffset - messageRecord!!.expireStarted - val remaining = messageRecord!!.expiresIn - elapsed + @Composable + fun Divider() { + Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = Color(0xff414141)) + } - val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1)) - binding.expiresIn.text = duration + @Composable + fun ItemButton(text: String, @DrawableRes icon: Int, color: Color = Color.White) { + TextButton( + modifier = Modifier + .fillMaxWidth() + .height(60.dp), + onClick = {}, + shape = RectangleShape, + ) { + Box(modifier = Modifier.width(80.dp).fillMaxHeight()) { + Icon( + painter = painterResource(id = icon), + contentDescription = "", + tint = color, + modifier = Modifier.align(Alignment.Center) + ) + } + Text(text, color = color, modifier = Modifier.fillMaxWidth()) } } + + @Composable + fun Card(content: @Composable () -> Unit) { + CardWithPadding(0.dp) { content() } + } + + @Composable + fun CardWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) { + Card( + shape = RoundedCornerShape(16.dp), + elevation = 0.dp, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = 32.dp), + backgroundColor = Color(0xff1b1b1b), + contentColor = Color.White + ) { Box(Modifier.padding(padding)) { content() } } + } + + @Composable + fun titledText(titledText: TitledText, modifier: Modifier = Modifier) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { + Title(titledText.title) + Text(titledText.value) + } + } + @Composable + fun titledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { + Title(title) + content() + } + } + + @Composable + fun Title(text: String) { + Text(text, fontWeight = FontWeight.Bold) + } } \ No newline at end of file From 0c2682fe47354e5940352233b5f2e79d55a42d17 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 29 Jun 2023 17:11:11 +0930 Subject: [PATCH 03/51] Improve compose theming --- .../conversation/v2/MessageDetailActivity.kt | 80 +++++-------------- .../org/thoughtcrime/securesms/ui/Colors.kt | 13 +++ .../thoughtcrime/securesms/ui/Components.kt | 67 ++++++++++++++++ .../org/thoughtcrime/securesms/ui/Themes.kt | 42 ++++++++++ app/src/main/res/values/themes.xml | 6 ++ 5 files changed, 146 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 2c1698ae8..04eeb9005 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,47 +2,40 @@ package org.thoughtcrime.securesms.conversation.v2 import android.os.Bundle import android.view.View -import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Card import androidx.compose.material.Divider -import androidx.compose.material.Icon import androidx.compose.material.Text -import androidx.compose.material.TextButton import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.ui.AppTheme +import org.thoughtcrime.securesms.ui.Cell +import org.thoughtcrime.securesms.ui.CellWithPadding +import org.thoughtcrime.securesms.ui.ItemButton +import org.thoughtcrime.securesms.ui.LocalExtraColors +import org.thoughtcrime.securesms.ui.destructiveButtonColors import java.util.* import javax.inject.Inject + @AndroidEntryPoint class MessageDetailActivity: PassphraseRequiredActionBarActivity() { var messageRecord: MessageRecord? = null @@ -92,11 +85,11 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022 ") val user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") - AppCompatTheme { + AppTheme { Column( modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp)) { - CardWithPadding { + CellWithPadding { FlowRow( verticalArrangement = Arrangement.spacedBy(16.dp), maxItemsInEachRow = 2 @@ -106,13 +99,15 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } } } - CardWithPadding { + CellWithPadding { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { titledText(sent) titledText(received) titledView("From:") { Row { - Box(modifier = Modifier.width(60.dp).height(60.dp)) + Box(modifier = Modifier + .width(60.dp) + .height(60.dp)) Column { titledText(user) } @@ -120,13 +115,13 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } } } - Card { + Cell { Column { ItemButton("Reply", R.drawable.ic_reply) Divider() ItemButton("Resend", R.drawable.ic_reply) Divider() - ItemButton("Delete", R.drawable.ic_delete_24, color = Color.Red) + ItemButton("Delete", R.drawable.ic_delete_24 , colors = destructiveButtonColors()) } } } @@ -135,47 +130,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @Composable fun Divider() { - Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = Color(0xff414141)) - } - - @Composable - fun ItemButton(text: String, @DrawableRes icon: Int, color: Color = Color.White) { - TextButton( - modifier = Modifier - .fillMaxWidth() - .height(60.dp), - onClick = {}, - shape = RectangleShape, - ) { - Box(modifier = Modifier.width(80.dp).fillMaxHeight()) { - Icon( - painter = painterResource(id = icon), - contentDescription = "", - tint = color, - modifier = Modifier.align(Alignment.Center) - ) - } - Text(text, color = color, modifier = Modifier.fillMaxWidth()) - } - } - - @Composable - fun Card(content: @Composable () -> Unit) { - CardWithPadding(0.dp) { content() } - } - - @Composable - fun CardWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) { - Card( - shape = RoundedCornerShape(16.dp), - elevation = 0.dp, - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(horizontal = 32.dp), - backgroundColor = Color(0xff1b1b1b), - contentColor = Color.White - ) { Box(Modifier.padding(padding)) { content() } } + Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = LocalExtraColors.current.divider) } @Composable @@ -185,6 +140,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { Text(titledText.value) } } + @Composable fun titledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { @@ -197,4 +153,4 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { fun Title(text: String) { Text(text, fontWeight = FontWeight.Bold) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt new file mode 100644 index 000000000..0ea7e0714 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt @@ -0,0 +1,13 @@ +package org.thoughtcrime.securesms.ui + +import androidx.compose.material.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +val colorDestructive = Color(0xffFF453A) + +@Composable +fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent) + +@Composable +fun destructiveButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = colorDestructive) \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt new file mode 100644 index 000000000..b5d78e99d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -0,0 +1,67 @@ +package org.thoughtcrime.securesms.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ButtonColors +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun ItemButton( + text: String, + @DrawableRes icon: Int, + colors: ButtonColors = transparentButtonColors(), +) { + TextButton( + modifier = Modifier + .fillMaxWidth() + .height(60.dp), + colors = colors, + onClick = {}, + shape = RectangleShape, + ) { + Box(modifier = Modifier + .width(80.dp) + .fillMaxHeight()) { + Icon( + painter = painterResource(id = icon), + contentDescription = "", + modifier = Modifier.align(Alignment.Center) + ) + } + Text(text, modifier = Modifier.fillMaxWidth()) + } +} + +@Composable +fun Cell(content: @Composable () -> Unit) { + CellWithPadding(0.dp) { content() } +} + +@Composable +fun CellWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) { + androidx.compose.material.Card( + shape = RoundedCornerShape(16.dp), + elevation = 0.dp, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = 32.dp), + backgroundColor = LocalExtraColors.current.settingsBackground + ) { Box(Modifier.padding(padding)) { content() } } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt new file mode 100644 index 000000000..a19082c08 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -0,0 +1,42 @@ +package org.thoughtcrime.securesms.ui + +import android.content.Context +import androidx.annotation.AttrRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import com.google.accompanist.themeadapter.appcompat.AppCompatTheme +import com.google.android.material.color.MaterialColors +import network.loki.messenger.R + +val LocalExtraColors = staticCompositionLocalOf { error("No Custom Attribute value provided") } + +data class ExtraColors( + val cell: Color, + val divider: Color, + val settingsBackground: Color, +) + +fun Context.getColorFromTheme(@AttrRes attr: Int): Color = + MaterialColors.getColor(this, attr, 0).let(::Color) + +@Composable +fun AppTheme( + content: @Composable () -> Unit +) { + val extraColors = LocalContext.current.run { + ExtraColors( + cell = getColorFromTheme(R.attr.colorCellBackground), + divider = getColorFromTheme(R.attr.dividerColor), + settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground) + ) + } + + CompositionLocalProvider(LocalExtraColors provides extraColors) { + AppCompatTheme { + content() + } + } +} diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index edf8108d6..bc8563850 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -342,6 +342,8 @@ ?colorAccent @color/classic_dark_3 + false + #1B1B1B #E5E5E8 @@ -424,6 +426,7 @@ ?colorPrimary true true + true true ?colorPrimary @@ -507,6 +510,8 @@ ?colorAccent @color/ocean_dark_4 + false + @color/ocean_dark_3 @color/ocean_dark_7 @@ -594,6 +599,7 @@ ?colorPrimary true true + true true ?colorPrimary From d5b3d9bcf9b3f2a408079d7db67625b05ba8a5ef Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 29 Jun 2023 19:14:47 +0930 Subject: [PATCH 04/51] Improve theming --- .../conversation/v2/MessageDetailActivity.kt | 13 ++++++++----- .../org/thoughtcrime/securesms/ui/Components.kt | 10 +++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 04eeb9005..34d53acf7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -14,10 +14,13 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider +import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -81,8 +84,8 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { TitledText("Duration:", "N/A"), ) - val sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022 ") - val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022 ") + val sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022") + val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022") val user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") AppTheme { @@ -109,7 +112,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { .width(60.dp) .height(60.dp)) Column { - titledText(user) + titledText(user, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) } } } @@ -134,10 +137,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } @Composable - fun titledText(titledText: TitledText, modifier: Modifier = Modifier) { + fun titledText(titledText: TitledText, modifier: Modifier = Modifier, valueStyle: TextStyle = LocalTextStyle.current) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Title(titledText.title) - Text(titledText.value) + Text(titledText.value, style = valueStyle) } } 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 b5d78e99d..046ba4635 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonColors +import androidx.compose.material.Card import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -55,13 +57,15 @@ fun Cell(content: @Composable () -> Unit) { @Composable fun CellWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) { - androidx.compose.material.Card( + Card( shape = RoundedCornerShape(16.dp), elevation = 0.dp, modifier = Modifier .wrapContentHeight() .fillMaxWidth() .padding(horizontal = 32.dp), - backgroundColor = LocalExtraColors.current.settingsBackground + backgroundColor = LocalExtraColors.current.settingsBackground, + // probably wrong + contentColor = MaterialTheme.colors.onSurface ) { Box(Modifier.padding(padding)) { content() } } -} \ No newline at end of file +} From ac476f4382996ef9ec716c0cffdd5fad9e9d32a2 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 29 Jun 2023 22:11:15 +0930 Subject: [PATCH 05/51] Add icons and respect nullability --- app/build.gradle | 2 + .../conversation/v2/MessageDetailActivity.kt | 139 +++++++++++------- .../drawable/ic_message_details__refresh.xml | 9 ++ .../drawable/ic_message_details__reply.xml | 9 ++ .../drawable/ic_message_details__trash.xml | 9 ++ 5 files changed, 116 insertions(+), 52 deletions(-) create mode 100644 app/src/main/res/drawable/ic_message_details__refresh.xml create mode 100644 app/src/main/res/drawable/ic_message_details__reply.xml create mode 100644 app/src/main/res/drawable/ic_message_details__trash.xml diff --git a/app/build.gradle b/app/build.gradle index 8194ef4b7..c3ae7de49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,6 +164,8 @@ dependencies { implementation 'androidx.compose.ui:ui:1.4.3' implementation 'androidx.compose.ui:ui-tooling:1.4.3' implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.30.1" + implementation "androidx.compose.runtime:runtime-livedata:1.4.3" + implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02' implementation 'androidx.compose.material:material:1.5.0-alpha02' diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 34d53acf7..7421412e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.conversation.v2 import android.os.Bundle -import android.view.View import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,12 +10,15 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.text.TextStyle @@ -24,6 +26,9 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity @@ -60,71 +65,101 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { title = resources.getString(R.string.conversation_context__menu_message_details) - setContentView(createComposeView()) + setContentView(ComposeView(this).apply { + setContent { + MessageDetailsScreen() + } + }) } - private fun createComposeView(): ComposeView = ComposeView(this).apply { - id = View.generateViewId() - setContent { - MessageDetails() - } + class MessageDetailsViewModel: ViewModel() { + private val _details = MutableLiveData(MessageDetails()) + val details: LiveData = _details + } + + @Composable + private fun MessageDetailsScreen(viewModel: MessageDetailsViewModel = MessageDetailsViewModel()) { + val details by viewModel.details.observeAsState(MessageDetails()) + MessageDetails(details) } data class TitledText(val title: String, val value: String) - @OptIn(ExperimentalLayoutApi::class) + data class MessageDetails( + val fileDetails: List? = null, + val sent: TitledText? = null, + val received: TitledText? = null, + val user: TitledText? = null, + ) + @Preview @Composable - fun MessageDetails() { - val fileDetails = listOf( - TitledText("File Id:", "1237896548514214124235985214"), - TitledText("File Type:", ".PNG"), - TitledText("File Size:", "6mb"), - TitledText("Resolution:", "550x550"), - TitledText("Duration:", "N/A"), + fun PreviewMessageDetails() { + MessageDetails( + fileDetails = listOf( + TitledText("File Id:", "1237896548514214124235985214"), + TitledText("File Type:", ".PNG"), + TitledText("File Size:", "6mb"), + TitledText("Resolution:", "550x550"), + TitledText("Duration:", "N/A"), + ), + sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), + received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), + user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") ) + } - val sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022") - val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022") - val user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") - - AppTheme { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp)) { - CellWithPadding { - FlowRow( - verticalArrangement = Arrangement.spacedBy(16.dp), - maxItemsInEachRow = 2 - ) { - fileDetails.forEach { - titledText(it, Modifier.weight(1f)) - } - } - } - CellWithPadding { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - titledText(sent) - titledText(received) - titledView("From:") { - Row { - Box(modifier = Modifier - .width(60.dp) - .height(60.dp)) - Column { - titledText(user, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) + @OptIn(ExperimentalLayoutApi::class) + @Composable + fun MessageDetails(messageDetails: MessageDetails) { + messageDetails.apply { + AppTheme { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + fileDetails?.takeIf { it.isNotEmpty() }?.let { + CellWithPadding { + FlowRow( + verticalArrangement = Arrangement.spacedBy(16.dp), + maxItemsInEachRow = 2 + ) { + it.forEach { + titledText(it, Modifier.weight(1f)) } } } } - } - Cell { - Column { - ItemButton("Reply", R.drawable.ic_reply) - Divider() - ItemButton("Resend", R.drawable.ic_reply) - Divider() - ItemButton("Delete", R.drawable.ic_delete_24 , colors = destructiveButtonColors()) + if (sent != null || received != null || user != null) CellWithPadding { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + sent?.let { titledText(it) } + received?.let { titledText(it) } + user?.let { + titledView("From:") { + Row { + Box(modifier = Modifier + .width(60.dp) + .height(60.dp)) + Column { + titledText(it, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) + } + } + } + } + } + } + Cell { + Column { + ItemButton("Reply", R.drawable.ic_message_details__reply) + Divider() + ItemButton("Resend", R.drawable.ic_message_details__refresh) + Divider() + ItemButton( + "Delete", + R.drawable.ic_message_details__trash, + colors = destructiveButtonColors() + ) + } } } } diff --git a/app/src/main/res/drawable/ic_message_details__refresh.xml b/app/src/main/res/drawable/ic_message_details__refresh.xml new file mode 100644 index 000000000..2aabe6fbe --- /dev/null +++ b/app/src/main/res/drawable/ic_message_details__refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_message_details__reply.xml b/app/src/main/res/drawable/ic_message_details__reply.xml new file mode 100644 index 000000000..c9e1591a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_details__reply.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_message_details__trash.xml b/app/src/main/res/drawable/ic_message_details__trash.xml new file mode 100644 index 000000000..85d421695 --- /dev/null +++ b/app/src/main/res/drawable/ic_message_details__trash.xml @@ -0,0 +1,9 @@ + + + From 676c29ca60b105d9481f985e25e570c3ccd9175c Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 09:48:48 +0930 Subject: [PATCH 06/51] Add click listeners --- .../conversation/v2/MessageDetailActivity.kt | 81 +++++++++++++++---- .../thoughtcrime/securesms/ui/Components.kt | 3 +- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 7421412e5..292b1fa01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.accounts.AccountManager +import android.content.Intent import android.os.Bundle import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -10,7 +12,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider @@ -31,9 +32,12 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellWithPadding @@ -46,22 +50,53 @@ import javax.inject.Inject @AndroidEntryPoint class MessageDetailActivity: PassphraseRequiredActionBarActivity() { + + private var timestamp: Long = 0L + var messageRecord: MessageRecord? = null @Inject lateinit var storage: Storage - // region Settings companion object { // Extras const val MESSAGE_TIMESTAMP = "message_timestamp" + + const val ON_REPLY = 1 + const val ON_RESEND = 2 + const val ON_DELETE = 3 + } + + val viewModel = MessageDetailsViewModel() + + class MessageDetailsViewModel: ViewModel() { + + fun setMessageRecord(value: MessageRecord?) { + _details.value = value?.run { + MessageDetails( + sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, + received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, + user = null + ) + } ?: MessageDetails() + } + + private var _details = MutableLiveData(MessageDetails()) + val details: LiveData = _details } - // endregion override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) - val timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) + timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) + + val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) + messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run { + finish() + return + } + + viewModel.setMessageRecord(messageRecord) title = resources.getString(R.string.conversation_context__menu_message_details) @@ -72,15 +107,25 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { }) } - class MessageDetailsViewModel: ViewModel() { - private val _details = MutableLiveData(MessageDetails()) - val details: LiveData = _details + @Composable + private fun MessageDetailsScreen() { + val details by viewModel.details.observeAsState(MessageDetails()) + MessageDetails( + details, + onReply = { setResultAndFinish(ON_REPLY) }, + onResend = { setResultAndFinish(ON_RESEND) }, + onDelete = { setResultAndFinish(ON_DELETE) } + ) } - @Composable - private fun MessageDetailsScreen(viewModel: MessageDetailsViewModel = MessageDetailsViewModel()) { - val details by viewModel.details.observeAsState(MessageDetails()) - MessageDetails(details) + private fun setResultAndFinish(code: Int) { + setResult(code) + + Bundle().apply { putLong(MESSAGE_TIMESTAMP, timestamp) } + .let(Intent()::putExtras) + .let { setResult(RESULT_OK, it) } + + finish() } data class TitledText(val title: String, val value: String) @@ -111,7 +156,12 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @OptIn(ExperimentalLayoutApi::class) @Composable - fun MessageDetails(messageDetails: MessageDetails) { + fun MessageDetails( + messageDetails: MessageDetails, + onReply: () -> Unit = {}, + onResend: () -> Unit = {}, + onDelete: () -> Unit = {}, + ) { messageDetails.apply { AppTheme { Column( @@ -150,14 +200,15 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } Cell { Column { - ItemButton("Reply", R.drawable.ic_message_details__reply) + ItemButton("Reply", R.drawable.ic_message_details__reply, onClick = onReply) Divider() - ItemButton("Resend", R.drawable.ic_message_details__refresh) + ItemButton("Resend", R.drawable.ic_message_details__refresh, onClick = onResend) Divider() ItemButton( "Delete", R.drawable.ic_message_details__trash, - colors = destructiveButtonColors() + colors = destructiveButtonColors(), + onClick = onDelete ) } } 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 046ba4635..2421c39b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -28,13 +28,14 @@ fun ItemButton( text: String, @DrawableRes icon: Int, colors: ButtonColors = transparentButtonColors(), + onClick: () -> Unit ) { TextButton( modifier = Modifier .fillMaxWidth() .height(60.dp), colors = colors, - onClick = {}, + onClick = onClick, shape = RectangleShape, ) { Box(modifier = Modifier From 6d596226b3b216b70dd4e221344e7cc0652008c4 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 10:33:56 +0930 Subject: [PATCH 07/51] Add user info --- .../securesms/conversation/v2/ConversationActivityV2.kt | 7 ++++--- .../conversation/v2/ConversationReactionOverlay.java | 4 +--- .../securesms/conversation/v2/MessageDetailActivity.kt | 8 ++------ .../v2/menus/ConversationActionModeCallback.kt | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) 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 7844ca4c2..4e55d6510 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 @@ -1773,9 +1773,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun showMessageDetail(messages: Set) { - val intent = Intent(this, MessageDetailActivity::class.java) - intent.putExtra(MessageDetailActivity.MESSAGE_TIMESTAMP, messages.first().timestamp) - push(intent) + Intent(this, MessageDetailActivity::class.java) + .apply { putExtra(MessageDetailActivity.MESSAGE_TIMESTAMP, messages.first().timestamp) } + .let(::push) + endActionMode() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index 20462bef3..eee8b5ecd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -695,9 +695,7 @@ public final class ConversationReactionOverlay extends FrameLayout { items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.conversation_context__menu_ban_and_delete_all), () -> handleActionItemClicked(Action.BAN_AND_DELETE_ALL))); } // Message detail - if (message.isFailed()) { - items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO))); - } + items.add(new ActionItem(R.attr.menu_info_icon, getContext().getResources().getString(R.string.conversation_context__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO))); // Resend if (message.isFailed()) { items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND))); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 292b1fa01..df2e2c6e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.conversation.v2 -import android.accounts.AccountManager import android.content.Intent import android.os.Bundle import androidx.compose.foundation.layout.Arrangement @@ -32,8 +31,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord @@ -76,7 +73,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { MessageDetails( sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, - user = null + user = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } } ) } ?: MessageDetails() } @@ -90,8 +87,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) - val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) - messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run { + messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run { finish() return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index f86920f90..3746aa52e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -67,7 +67,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p menu.findItem(R.id.menu_context_copy_public_key).isVisible = (thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) // Message detail - menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing) + menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1 // Resend menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) // Resync From 876e12c4119170ea5bcfee5de007debe43c254da Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 11:01:57 +0930 Subject: [PATCH 08/51] Refactor ProfilePictureView --- .../components/ProfilePictureView.kt | 5 ++++- .../securesms/contacts/UserView.kt | 6 +++--- .../conversation/start/ContactListAdapter.kt | 6 +++--- .../conversation/v2/ConversationActivityV2.kt | 8 ++++---- .../conversation/v2/MessageDetailActivity.kt | 5 ++++- .../v2/components/MentionCandidateView.kt | 10 +++++----- .../mentions/MentionCandidateView.kt | 10 +++++----- .../v2/messages/VisibleMessageView.kt | 20 +++++++++---------- .../securesms/home/ConversationView.kt | 6 +++--- .../securesms/home/HomeActivity.kt | 16 +++++++-------- .../securesms/home/UserDetailsBottomSheet.kt | 8 ++++---- .../home/search/GlobalSearchAdapter.kt | 6 +++--- .../home/search/GlobalSearchAdapterUtils.kt | 14 ++++++------- .../messagerequests/MessageRequestView.kt | 6 +++--- .../preferences/BlockedContactsAdapter.kt | 4 ++-- .../securesms/preferences/SettingsActivity.kt | 8 ++++---- .../activity_conversation_v2_action_bar.xml | 2 +- app/src/main/res/layout/activity_home.xml | 2 +- app/src/main/res/layout/activity_settings.xml | 2 +- .../res/layout/blocked_contact_layout.xml | 2 +- .../main/res/layout/dialog_change_avatar.xml | 2 +- .../res/layout/fragment_call_bottom_sheet.xml | 2 +- .../fragment_user_details_bottom_sheet.xml | 2 +- ...m_sheet_dialog_fragment_recipient_item.xml | 2 +- app/src/main/res/layout/view_contact.xml | 2 +- app/src/main/res/layout/view_conversation.xml | 2 +- .../res/layout/view_global_search_result.xml | 2 +- .../res/layout/view_mention_candidate.xml | 2 +- .../res/layout/view_mention_candidate_v2.xml | 2 +- .../main/res/layout/view_message_request.xml | 2 +- .../main/res/layout/view_profile_picture.xml | 8 ++------ app/src/main/res/layout/view_user.xml | 2 +- .../main/res/layout/view_visible_message.xml | 3 +-- 33 files changed, 90 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index a827a7d26..f078001af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components import android.content.Context import android.util.AttributeSet +import android.view.LayoutInflater import android.view.View import android.widget.ImageView import android.widget.RelativeLayout @@ -9,6 +10,7 @@ import androidx.annotation.DimenRes import com.bumptech.glide.load.engine.DiskCacheStrategy import network.loki.messenger.R import network.loki.messenger.databinding.ViewProfilePictureBinding +import network.loki.messenger.databinding.ViewUserBinding import org.session.libsession.avatars.ContactColors import org.session.libsession.avatars.PlaceholderAvatarPhoto import org.session.libsession.avatars.ProfileContactPhoto @@ -23,7 +25,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests class ProfilePictureView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : RelativeLayout(context, attrs) { - private val binding: ViewProfilePictureBinding by lazy { ViewProfilePictureBinding.bind(this) } + private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this) lateinit var glide: GlideRequests var publicKey: String? = null var displayName: String? = null @@ -37,6 +39,7 @@ class ProfilePictureView @JvmOverloads constructor( private val unknownOpenGroupDrawable = ResourceContactPhoto(R.drawable.ic_notification) .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) + // endregion // region Updating diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index e88cf1d08..18e43b434 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -54,8 +54,8 @@ class UserView : LinearLayout { val threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(user) MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this val address = user.address.serialize() - binding.profilePictureView.root.glide = glide - binding.profilePictureView.root.update(user) + binding.profilePictureView.glide = glide + binding.profilePictureView.update(user) binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24) binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address) when (actionIndicator) { @@ -87,7 +87,7 @@ class UserView : LinearLayout { } fun unbind() { - binding.profilePictureView.root.recycle() + binding.profilePictureView.recycle() } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt index 99e7c9061..830b5302b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt @@ -32,14 +32,14 @@ class ContactListAdapter( class ContactViewHolder(private val binding: ViewContactBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(contact: ContactListItem.Contact, glide: GlideRequests, listener: (Recipient) -> Unit) { - binding.profilePictureView.root.glide = glide - binding.profilePictureView.root.update(contact.recipient) + binding.profilePictureView.glide = glide + binding.profilePictureView.update(contact.recipient) binding.nameTextView.text = contact.displayName binding.root.setOnClickListener { listener(contact.recipient) } } fun unbind() { - binding.profilePictureView.root.recycle() + binding.profilePictureView.recycle() } } 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 4e55d6510..2514620e8 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 @@ -467,10 +467,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe R.dimen.small_profile_picture_size } val size = resources.getDimension(sizeID).roundToInt() - binding.toolbarContent.profilePictureView.root.layoutParams = LinearLayout.LayoutParams(size, size) - binding.toolbarContent.profilePictureView.root.glide = glide + binding.toolbarContent.profilePictureView.layoutParams = LinearLayout.LayoutParams(size, size) + binding.toolbarContent.profilePictureView.glide = glide MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(viewModel.threadId, this) - val profilePictureView = binding.toolbarContent.profilePictureView.root + val profilePictureView = binding.toolbarContent.profilePictureView viewModel.recipient?.let(profilePictureView::update) } @@ -658,7 +658,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe updateSendAfterApprovalText() showOrHideInputIfNeeded() - binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient) + binding?.toolbarContent?.profilePictureView?.update(threadRecipient) binding?.toolbarContent?.conversationTitleView?.text = when { threadRecipient.isLocalNumber -> getString(R.string.note_to_self) else -> threadRecipient.toShortString() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index df2e2c6e9..c1246f57f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -185,7 +186,9 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { Row { Box(modifier = Modifier .width(60.dp) - .height(60.dp)) + .height(60.dp)) { + + } Column { titledText(it, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt index 834b77ecc..50bf53b76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt @@ -28,11 +28,11 @@ class MentionCandidateView : LinearLayout { private fun update() = with(binding) { mentionCandidateNameTextView.text = mentionCandidate.displayName - profilePictureView.root.publicKey = mentionCandidate.publicKey - profilePictureView.root.displayName = mentionCandidate.displayName - profilePictureView.root.additionalPublicKey = null - profilePictureView.root.glide = glide!! - profilePictureView.root.update() + profilePictureView.publicKey = mentionCandidate.publicKey + profilePictureView.displayName = mentionCandidate.displayName + profilePictureView.additionalPublicKey = null + profilePictureView.glide = glide!! + profilePictureView.update() if (openGroupServer != null && openGroupRoom != null) { val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey) moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt index a21ba1b50..ab002d5e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt @@ -28,11 +28,11 @@ class MentionCandidateView : RelativeLayout { private fun update() = with(binding) { mentionCandidateNameTextView.text = candidate.displayName - profilePictureView.root.publicKey = candidate.publicKey - profilePictureView.root.displayName = candidate.displayName - profilePictureView.root.additionalPublicKey = null - profilePictureView.root.glide = glide!! - profilePictureView.root.update() + profilePictureView.publicKey = candidate.publicKey + profilePictureView.displayName = candidate.displayName + profilePictureView.additionalPublicKey = null + profilePictureView.glide = glide!! + profilePictureView.update() if (openGroupServer != null && openGroupRoom != null) { val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey) moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 319140731..f9366df17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -136,7 +136,7 @@ class VisibleMessageView : LinearLayout { // Show profile picture and sender name if this is a group thread AND // the message is incoming binding.moderatorIconImageView.isVisible = false - binding.profilePictureView.root.visibility = when { + binding.profilePictureView.visibility = when { thread.isGroupRecipient && !message.isOutgoing && isEndOfMessageCluster -> View.VISIBLE thread.isGroupRecipient -> View.INVISIBLE else -> View.GONE @@ -145,22 +145,22 @@ class VisibleMessageView : LinearLayout { val bottomMargin = if (isEndOfMessageCluster) resources.getDimensionPixelSize(R.dimen.small_spacing) else ViewUtil.dpToPx(context,2) - if (binding.profilePictureView.root.visibility == View.GONE) { + if (binding.profilePictureView.visibility == View.GONE) { val expirationParams = binding.messageInnerContainer.layoutParams as MarginLayoutParams expirationParams.bottomMargin = bottomMargin binding.messageInnerContainer.layoutParams = expirationParams } else { - val avatarLayoutParams = binding.profilePictureView.root.layoutParams as MarginLayoutParams + val avatarLayoutParams = binding.profilePictureView.layoutParams as MarginLayoutParams avatarLayoutParams.bottomMargin = bottomMargin - binding.profilePictureView.root.layoutParams = avatarLayoutParams + binding.profilePictureView.layoutParams = avatarLayoutParams } if (isGroupThread && !message.isOutgoing) { if (isEndOfMessageCluster) { - binding.profilePictureView.root.publicKey = senderSessionID - binding.profilePictureView.root.glide = glide - binding.profilePictureView.root.update(message.individualRecipient) - binding.profilePictureView.root.setOnClickListener { + binding.profilePictureView.publicKey = senderSessionID + binding.profilePictureView.glide = glide + binding.profilePictureView.update(message.individualRecipient) + binding.profilePictureView.setOnClickListener { if (thread.isOpenGroupRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { @@ -386,7 +386,7 @@ class VisibleMessageView : LinearLayout { val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing) val iconSize = toPx(24, context.resources) val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing - val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2) + val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.marginBottom - (iconSize / 2) val right = left + iconSize val bottom = top + iconSize swipeToReplyIconRect.left = left @@ -406,7 +406,7 @@ class VisibleMessageView : LinearLayout { } fun recycle() { - binding.profilePictureView.root.recycle() + binding.profilePictureView.recycle() binding.messageContentView.root.recycle() } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index c6a6e1f7f..7574eee9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -58,7 +58,7 @@ class ConversationView : LinearLayout { } else { ContextCompat.getDrawable(context, R.drawable.conversation_view_background) } - binding.profilePictureView.root.glide = glide + binding.profilePictureView.glide = glide val unreadCount = thread.unreadCount if (thread.recipient.isBlocked) { binding.accentView.setBackgroundResource(R.color.destructive) @@ -117,11 +117,11 @@ class ConversationView : LinearLayout { thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) } - binding.profilePictureView.root.update(thread.recipient) + binding.profilePictureView.update(thread.recipient) } fun recycle() { - binding.profilePictureView.root.recycle() + binding.profilePictureView.recycle() } private fun getUserDisplayName(recipient: Recipient): String? { 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 0215040d3..09d3a428e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -151,8 +151,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Set up Glide glide = GlideApp.with(this) // Set up toolbar buttons - binding.profileButton.root.glide = glide - binding.profileButton.root.setOnClickListener { openSettings() } + binding.profileButton.glide = glide + binding.profileButton.setOnClickListener { openSettings() } binding.searchViewContainer.setOnClickListener { binding.globalSearchInputLayout.requestFocus() } @@ -317,8 +317,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(true) if (textSecurePreferences.getLocalNumber() == null) { return; } // This can be the case after a secondary device is auto-cleared IdentityKeyUtil.checkUpdate(this) - binding.profileButton.root.recycle() // clear cached image before update tje profilePictureView - binding.profileButton.root.update() + binding.profileButton.recycle() // clear cached image before update tje profilePictureView + binding.profileButton.update() if (textSecurePreferences.getHasViewedSeed()) { binding.seedReminderView.isVisible = false } @@ -388,10 +388,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } private fun updateProfileButton() { - binding.profileButton.root.publicKey = publicKey - binding.profileButton.root.displayName = textSecurePreferences.getProfileName() - binding.profileButton.root.recycle() - binding.profileButton.root.update() + binding.profileButton.publicKey = publicKey + binding.profileButton.displayName = textSecurePreferences.getProfileName() + binding.profileButton.recycle() + binding.profileButton.update() } // endregion diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index bc9a9bece..fc3627a09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -55,10 +55,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss() with(binding) { - profilePictureView.root.publicKey = publicKey - profilePictureView.root.glide = GlideApp.with(this@UserDetailsBottomSheet) - profilePictureView.root.isLarge = true - profilePictureView.root.update(recipient) + profilePictureView.publicKey = publicKey + profilePictureView.glide = GlideApp.with(this@UserDetailsBottomSheet) + profilePictureView.isLarge = true + profilePictureView.update(recipient) nameTextViewContainer.visibility = View.VISIBLE nameTextViewContainer.setOnClickListener { nameTextViewContainer.visibility = View.INVISIBLE diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index fab8bca99..cab31304b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -83,14 +83,14 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi override fun onViewRecycled(holder: RecyclerView.ViewHolder) { if (holder is ContentView) { - holder.binding.searchResultProfilePicture.root.recycle() + holder.binding.searchResultProfilePicture.recycle() } } class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) { val binding = ViewGlobalSearchResultBinding.bind(view).apply { - searchResultProfilePicture.root.glide = GlideApp.with(root) + searchResultProfilePicture.glide = GlideApp.with(root) } fun bindPayload(newQuery: String, model: Model) { @@ -98,7 +98,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi } fun bind(query: String, model: Model) { - binding.searchResultProfilePicture.root.recycle() + binding.searchResultProfilePicture.recycle() when (model) { is Model.GroupConversation -> bindModel(query, model) is Model.Contact -> bindModel(query, model) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 47e9c7594..0aa62da02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -85,12 +85,12 @@ private fun getHighlight(query: String?, toSearch: String): Spannable? { } fun ContentView.bindModel(query: String?, model: GroupConversation) { - binding.searchResultProfilePicture.root.isVisible = true + binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup binding.searchResultTimestamp.isVisible = false val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false) - binding.searchResultProfilePicture.root.update(threadRecipient) + binding.searchResultProfilePicture.update(threadRecipient) val nameString = model.groupRecord.title binding.searchResultTitle.text = getHighlight(query, nameString) @@ -106,14 +106,14 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) { } fun ContentView.bindModel(query: String?, model: ContactModel) { - binding.searchResultProfilePicture.root.isVisible = true + binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultSubtitle.isVisible = false binding.searchResultTimestamp.isVisible = false binding.searchResultSubtitle.text = null val recipient = Recipient.from(binding.root.context, Address.fromSerialized(model.contact.sessionID), false) - binding.searchResultProfilePicture.root.update(recipient) + binding.searchResultProfilePicture.update(recipient) val nameString = model.contact.getSearchName() binding.searchResultTitle.text = getHighlight(query, nameString) } @@ -122,12 +122,12 @@ fun ContentView.bindModel(model: SavedMessages) { binding.searchResultSubtitle.isVisible = false binding.searchResultTimestamp.isVisible = false binding.searchResultTitle.setText(R.string.note_to_self) - binding.searchResultProfilePicture.root.isVisible = false + binding.searchResultProfilePicture.isVisible = false binding.searchResultSavedMessages.isVisible = true } fun ContentView.bindModel(query: String?, model: Message) { - binding.searchResultProfilePicture.root.isVisible = true + binding.searchResultProfilePicture.isVisible = true binding.searchResultSavedMessages.isVisible = false binding.searchResultTimestamp.isVisible = true // val hasUnreads = model.unread > 0 @@ -136,7 +136,7 @@ fun ContentView.bindModel(query: String?, model: Message) { // binding.unreadCountTextView.text = model.unread.toString() // } binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) - binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient) + binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient) val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { // group chat, bind diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 9a8d06129..5f1afe960 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -34,7 +34,7 @@ class MessageRequestView : LinearLayout { // region Updating fun bind(thread: ThreadRecord, glide: GlideRequests) { this.thread = thread - binding.profilePictureView.root.glide = glide + binding.profilePictureView.glide = glide val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() binding.displayNameTextView.text = senderDisplayName @@ -44,12 +44,12 @@ class MessageRequestView : LinearLayout { binding.snippetTextView.text = snippet post { - binding.profilePictureView.root.update(thread.recipient) + binding.profilePictureView.update(thread.recipient) } } fun recycle() { - binding.profilePictureView.root.recycle() + binding.profilePictureView.recycle() } private fun getUserDisplayName(recipient: Recipient): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index a75d53c4f..66f98a03c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -38,7 +38,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap override fun onViewRecycled(holder: ViewHolder) { super.onViewRecycled(holder) - holder.binding.profilePictureView.root.recycle() + holder.binding.profilePictureView.recycle() } class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { @@ -48,7 +48,7 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) { binding.recipientName.text = selectable.item.name - with (binding.profilePictureView.root) { + with (binding.profilePictureView) { glide = this@ViewHolder.glide update(selectable.item) } 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 5a03cebc3..ece54d7e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -75,8 +75,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { val displayName = getDisplayName() glide = GlideApp.with(this) with(binding) { - setupProfilePictureView(profilePictureView.root) - profilePictureView.root.setOnClickListener { showEditProfilePictureUI() } + setupProfilePictureView(profilePictureView) + profilePictureView.setOnClickListener { showEditProfilePictureUI() } ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) } btnGroupNameDisplay.text = displayName publicKeyTextView.text = hexEncodedPublicKey @@ -231,8 +231,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { binding.btnGroupNameDisplay.text = displayName } if (isUpdatingProfilePicture) { - binding.profilePictureView.root.recycle() // Clear the cached image before updating - binding.profilePictureView.root.update() + binding.profilePictureView.recycle() // Clear the cached image before updating + binding.profilePictureView.update() } binding.loader.isVisible = false } diff --git a/app/src/main/res/layout/activity_conversation_v2_action_bar.xml b/app/src/main/res/layout/activity_conversation_v2_action_bar.xml index fe726f7cf..7322bb7f0 100644 --- a/app/src/main/res/layout/activity_conversation_v2_action_bar.xml +++ b/app/src/main/res/layout/activity_conversation_v2_action_bar.xml @@ -8,7 +8,7 @@ android:orientation="horizontal" android:gravity="center_vertical"> - diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index a18661f89..795ef49ca 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -27,7 +27,7 @@ android:layout_marginLeft="20dp" android:layout_marginRight="20dp"> - - - - - - - - diff --git a/app/src/main/res/layout/view_conversation.xml b/app/src/main/res/layout/view_conversation.xml index d66a1722b..12a7a8ac8 100644 --- a/app/src/main/res/layout/view_conversation.xml +++ b/app/src/main/res/layout/view_conversation.xml @@ -14,7 +14,7 @@ android:layout_height="match_parent" android:background="?colorAccent" /> - - - - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_user.xml b/app/src/main/res/layout/view_user.xml index a9330ae64..177b3ff6c 100644 --- a/app/src/main/res/layout/view_user.xml +++ b/app/src/main/res/layout/view_user.xml @@ -15,7 +15,7 @@ android:gravity="center_vertical" android:paddingHorizontal="@dimen/medium_spacing"> - - Date: Fri, 30 Jun 2023 11:07:27 +0930 Subject: [PATCH 09/51] Add ProfilePictureView to MessageDetailActivity --- .../conversation/v2/MessageDetailActivity.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index c1246f57f..2f4dc4415 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -19,6 +19,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.text.TextStyle @@ -33,6 +34,7 @@ import androidx.lifecycle.ViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity +import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent @@ -184,10 +186,17 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { user?.let { titledView("From:") { Row { - Box(modifier = Modifier - .width(60.dp) - .height(60.dp)) { - + Box( + modifier = Modifier.align(Alignment.CenterVertically) + .width(60.dp) + .height(60.dp) + ) { + AndroidView( + factory = { ProfilePictureView(it) }, + modifier = Modifier.align(Alignment.Center) + .width(46.dp) + .height(46.dp) + ) } Column { titledText(it, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) From f68c01b2ee6ad4b07962e66d8de38b1bd64303b0 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 11:35:22 +0930 Subject: [PATCH 10/51] Populate profile pic --- .../securesms/components/ProfilePictureView.kt | 4 ++-- .../thoughtcrime/securesms/contacts/UserView.kt | 1 - .../conversation/start/ContactListAdapter.kt | 1 - .../conversation/v2/ConversationActivityV2.kt | 1 - .../conversation/v2/MessageDetailActivity.kt | 15 +++++++++------ .../v2/components/MentionCandidateView.kt | 1 - .../v2/input_bar/mentions/MentionCandidateView.kt | 1 - .../v2/messages/VisibleMessageView.kt | 1 - .../securesms/home/ConversationView.kt | 1 - .../thoughtcrime/securesms/home/HomeActivity.kt | 1 - .../securesms/home/UserDetailsBottomSheet.kt | 1 - .../securesms/home/search/GlobalSearchAdapter.kt | 4 +--- .../messagerequests/MessageRequestView.kt | 1 - .../preferences/BlockedContactsAdapter.kt | 1 - .../securesms/preferences/SettingsActivity.kt | 1 - .../reactions/ReactionRecipientsAdapter.java | 1 - 16 files changed, 12 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index f078001af..37a3e764e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -20,13 +20,14 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests class ProfilePictureView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : RelativeLayout(context, attrs) { private val binding = ViewProfilePictureBinding.inflate(LayoutInflater.from(context), this) - lateinit var glide: GlideRequests + private val glide: GlideRequests = GlideApp.with(this) var publicKey: String? = null var displayName: String? = null var additionalPublicKey: String? = null @@ -76,7 +77,6 @@ class ProfilePictureView @JvmOverloads constructor( } fun update() { - if (!this::glide.isInitialized) return val publicKey = publicKey ?: return val additionalPublicKey = additionalPublicKey if (additionalPublicKey != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt index 18e43b434..399dc9b31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/UserView.kt @@ -54,7 +54,6 @@ class UserView : LinearLayout { val threadID = DatabaseComponent.get(context).threadDatabase().getOrCreateThreadIdFor(user) MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(threadID, context) // FIXME: This is a bad place to do this val address = user.address.serialize() - binding.profilePictureView.glide = glide binding.profilePictureView.update(user) binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24) binding.nameTextView.text = if (user.isGroupRecipient) user.name else getUserDisplayName(address) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt index 830b5302b..68e2f975c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/start/ContactListAdapter.kt @@ -32,7 +32,6 @@ class ContactListAdapter( class ContactViewHolder(private val binding: ViewContactBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(contact: ContactListItem.Contact, glide: GlideRequests, listener: (Recipient) -> Unit) { - binding.profilePictureView.glide = glide binding.profilePictureView.update(contact.recipient) binding.nameTextView.text = contact.displayName binding.root.setOnClickListener { listener(contact.recipient) } 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 2514620e8..be65fa32e 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 @@ -468,7 +468,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } val size = resources.getDimension(sizeID).roundToInt() binding.toolbarContent.profilePictureView.layoutParams = LinearLayout.LayoutParams(size, size) - binding.toolbarContent.profilePictureView.glide = glide MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded(viewModel.threadId, this) val profilePictureView = binding.toolbarContent.profilePictureView viewModel.recipient?.let(profilePictureView::update) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 2f4dc4415..820dfd915 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -33,6 +33,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.Storage @@ -76,7 +77,8 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { MessageDetails( sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, - user = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } } + senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, + sender = individualRecipient ) } ?: MessageDetails() } @@ -133,7 +135,8 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val fileDetails: List? = null, val sent: TitledText? = null, val received: TitledText? = null, - val user: TitledText? = null, + val senderInfo: TitledText? = null, + val sender: Recipient? = null ) @Preview @@ -149,7 +152,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { ), sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), - user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") + senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") ) } @@ -179,11 +182,11 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } } } - if (sent != null || received != null || user != null) CellWithPadding { + if (sent != null || received != null || senderInfo != null) CellWithPadding { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } received?.let { titledText(it) } - user?.let { + senderInfo?.let { titledView("From:") { Row { Box( @@ -192,7 +195,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { .height(60.dp) ) { AndroidView( - factory = { ProfilePictureView(it) }, + factory = { ProfilePictureView(it).apply { sender?.let(::update) } }, modifier = Modifier.align(Alignment.Center) .width(46.dp) .height(46.dp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt index 50bf53b76..d54426391 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/MentionCandidateView.kt @@ -31,7 +31,6 @@ class MentionCandidateView : LinearLayout { profilePictureView.publicKey = mentionCandidate.publicKey profilePictureView.displayName = mentionCandidate.displayName profilePictureView.additionalPublicKey = null - profilePictureView.glide = glide!! profilePictureView.update() if (openGroupServer != null && openGroupRoom != null) { val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", mentionCandidate.publicKey) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt index ab002d5e6..2d8f74596 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidateView.kt @@ -31,7 +31,6 @@ class MentionCandidateView : RelativeLayout { profilePictureView.publicKey = candidate.publicKey profilePictureView.displayName = candidate.displayName profilePictureView.additionalPublicKey = null - profilePictureView.glide = glide!! profilePictureView.update() if (openGroupServer != null && openGroupRoom != null) { val isUserModerator = OpenGroupManager.isUserModerator(context, "$openGroupRoom.$openGroupServer", candidate.publicKey) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index f9366df17..40a087ea6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -158,7 +158,6 @@ class VisibleMessageView : LinearLayout { if (isGroupThread && !message.isOutgoing) { if (isEndOfMessageCluster) { binding.profilePictureView.publicKey = senderSessionID - binding.profilePictureView.glide = glide binding.profilePictureView.update(message.individualRecipient) binding.profilePictureView.setOnClickListener { if (thread.isOpenGroupRecipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 7574eee9f..0deab4030 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -58,7 +58,6 @@ class ConversationView : LinearLayout { } else { ContextCompat.getDrawable(context, R.drawable.conversation_view_background) } - binding.profilePictureView.glide = glide val unreadCount = thread.unreadCount if (thread.recipient.isBlocked) { binding.accentView.setBackgroundResource(R.color.destructive) 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 09d3a428e..df70d2f80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -151,7 +151,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Set up Glide glide = GlideApp.with(this) // Set up toolbar buttons - binding.profileButton.glide = glide binding.profileButton.setOnClickListener { openSettings() } binding.searchViewContainer.setOnClickListener { binding.globalSearchInputLayout.requestFocus() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index fc3627a09..a95535e35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -56,7 +56,6 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss() with(binding) { profilePictureView.publicKey = publicKey - profilePictureView.glide = GlideApp.with(this@UserDetailsBottomSheet) profilePictureView.isLarge = true profilePictureView.update(recipient) nameTextViewContainer.visibility = View.VISIBLE diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt index cab31304b..7cf953be2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapter.kt @@ -89,9 +89,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) { - val binding = ViewGlobalSearchResultBinding.bind(view).apply { - searchResultProfilePicture.glide = GlideApp.with(root) - } + val binding = ViewGlobalSearchResultBinding.bind(view) fun bindPayload(newQuery: String, model: Model) { bindQuery(newQuery, model) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 5f1afe960..af3d269c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -34,7 +34,6 @@ class MessageRequestView : LinearLayout { // region Updating fun bind(thread: ThreadRecord, glide: GlideRequests) { this.thread = thread - binding.profilePictureView.glide = glide val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() binding.displayNameTextView.text = senderDisplayName diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index 66f98a03c..e0b92bdbe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -49,7 +49,6 @@ class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdap fun bind(selectable: SelectableRecipient, toggle: (SelectableRecipient) -> Unit) { binding.recipientName.text = selectable.item.name with (binding.profilePictureView) { - glide = this@ViewHolder.glide update(selectable.item) } binding.root.setOnClickListener { toggle(selectable) } 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 ece54d7e0..e72c3e063 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -101,7 +101,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey) private fun setupProfilePictureView(view: ProfilePictureView) { - view.glide = glide view.apply { publicKey = hexEncodedPublicKey displayName = getDisplayName() diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java index f1cbea16c..1c05e68bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/ReactionRecipientsAdapter.java @@ -144,7 +144,6 @@ final class ReactionRecipientsAdapter extends RecyclerView.Adapter Date: Fri, 30 Jun 2023 12:32:56 +0930 Subject: [PATCH 11/51] Fix LiveDataTestUtil --- .../java/org/thoughtcrime/securesms/LiveDataTestUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/sharedTest/java/org/thoughtcrime/securesms/LiveDataTestUtil.kt b/app/src/sharedTest/java/org/thoughtcrime/securesms/LiveDataTestUtil.kt index 03155a910..a8cff6341 100644 --- a/app/src/sharedTest/java/org/thoughtcrime/securesms/LiveDataTestUtil.kt +++ b/app/src/sharedTest/java/org/thoughtcrime/securesms/LiveDataTestUtil.kt @@ -22,7 +22,7 @@ fun LiveData.getOrAwaitValue( var data: T? = null val latch = CountDownLatch(1) val observer = object : Observer { - override fun onChanged(o: T?) { + override fun onChanged(o: T) { data = o latch.countDown() this@getOrAwaitValue.removeObserver(this) From 351b259449c0483b994e144cfc6ba674088b4b57 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 13:25:16 +0930 Subject: [PATCH 12/51] Add error --- .../conversation/v2/MessageDetailActivity.kt | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 820dfd915..d885ade00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellWithPadding import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.LocalExtraColors +import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors import java.util.* import javax.inject.Inject @@ -72,11 +73,12 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { class MessageDetailsViewModel: ViewModel() { - fun setMessageRecord(value: MessageRecord?) { + fun setMessageRecord(value: MessageRecord?, error: String?) { _details.value = value?.run { MessageDetails( sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, + error = error?.let { TitledText("Error:", it) }, senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, sender = individualRecipient ) @@ -97,7 +99,9 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { return } - viewModel.setMessageRecord(messageRecord) + val error = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) + + viewModel.setMessageRecord(messageRecord, error) title = resources.getString(R.string.conversation_context__menu_message_details) @@ -135,6 +139,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val fileDetails: List? = null, val sent: TitledText? = null, val received: TitledText? = null, + val error: TitledText? = null, val senderInfo: TitledText? = null, val sender: Recipient? = null ) @@ -143,16 +148,19 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @Composable fun PreviewMessageDetails() { MessageDetails( - fileDetails = listOf( - TitledText("File Id:", "1237896548514214124235985214"), - TitledText("File Type:", ".PNG"), - TitledText("File Size:", "6mb"), - TitledText("Resolution:", "550x550"), - TitledText("Duration:", "N/A"), - ), - sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), - received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), - senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") + MessageDetails( + fileDetails = listOf( + TitledText("File Id:", "1237896548514214124235985214"), + TitledText("File Type:", ".PNG"), + TitledText("File Size:", "6mb"), + TitledText("Resolution:", "550x550"), + TitledText("Duration:", "N/A"), + ), + sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), + received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), + error = TitledText("Error:", "Message failed to send"), + senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") + ) ) } @@ -186,20 +194,19 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } received?.let { titledText(it) } + error?.let { titledText(it, valueStyle = LocalTextStyle.current.copy(color = colorDestructive)) } senderInfo?.let { titledView("From:") { Row { - Box( - modifier = Modifier.align(Alignment.CenterVertically) - .width(60.dp) - .height(60.dp) - ) { - AndroidView( - factory = { ProfilePictureView(it).apply { sender?.let(::update) } }, - modifier = Modifier.align(Alignment.Center) - .width(46.dp) - .height(46.dp) - ) + sender?.let { + Box(modifier = Modifier.width(60.dp).align(Alignment.CenterVertically)) { + AndroidView( + factory = { ProfilePictureView(it).apply { update(sender) } }, + modifier = Modifier + .width(46.dp) + .height(46.dp) + ) + } } Column { titledText(it, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) @@ -213,14 +220,11 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { Column { ItemButton("Reply", R.drawable.ic_message_details__reply, onClick = onReply) Divider() - ItemButton("Resend", R.drawable.ic_message_details__refresh, onClick = onResend) - Divider() - ItemButton( - "Delete", - R.drawable.ic_message_details__trash, - colors = destructiveButtonColors(), - onClick = onDelete - ) + if (error != null) { + ItemButton("Resend", R.drawable.ic_message_details__refresh, onClick = onResend) + Divider() + } + ItemButton("Delete", R.drawable.ic_message_details__trash, colors = destructiveButtonColors(), onClick = onDelete) } } } From d44dbe089fda60319726c9ca9e23cdacb1066cf0 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 30 Jun 2023 22:49:23 +0930 Subject: [PATCH 13/51] Wire up buttons --- .../conversation/v2/ConversationActivityV2.kt | 23 +++++++++++++++++-- .../conversation/v2/MessageDetailActivity.kt | 21 +++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) 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 be65fa32e..fcc080372 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 @@ -18,6 +18,8 @@ import android.view.* import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog @@ -78,6 +80,10 @@ import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.sele import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.SendSeedDialog @@ -1771,10 +1777,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } + private val handleMessageDetail = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + val message = result.data?.extras?.getLong(MESSAGE_TIMESTAMP) + ?.let(mmsSmsDb::getMessageForTimestamp) + + val set = setOfNotNull(message) + + when (result.resultCode) { + ON_REPLY -> reply(set) + ON_RESEND -> resendMessage(set) + ON_DELETE -> deleteMessages(set) + } + } + override fun showMessageDetail(messages: Set) { Intent(this, MessageDetailActivity::class.java) - .apply { putExtra(MessageDetailActivity.MESSAGE_TIMESTAMP, messages.first().timestamp) } - .let(::push) + .apply { putExtra(MESSAGE_TIMESTAMP, messages.first().timestamp) } + .let { handleMessageDetail.launch(it) } endActionMode() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index d885ade00..68c9c9e7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider @@ -38,6 +40,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Cell @@ -74,6 +77,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { class MessageDetailsViewModel: ViewModel() { fun setMessageRecord(value: MessageRecord?, error: String?) { + val mmsRecord = value as? MmsMessageRecord + + val slides = mmsRecord?.slideDeck?.thumbnailSlides + _details.value = value?.run { MessageDetails( sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, @@ -124,11 +131,9 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } private fun setResultAndFinish(code: Int) { - setResult(code) - Bundle().apply { putLong(MESSAGE_TIMESTAMP, timestamp) } .let(Intent()::putExtras) - .let { setResult(RESULT_OK, it) } + .let { setResult(code, it) } finish() } @@ -164,7 +169,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { ) } - @OptIn(ExperimentalLayoutApi::class) + @OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) @Composable fun MessageDetails( messageDetails: MessageDetails, @@ -178,6 +183,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + HorizontalPager(pageCount = 1) { + + } + fileDetails?.takeIf { it.isNotEmpty() }?.let { CellWithPadding { FlowRow( @@ -199,7 +208,9 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { titledView("From:") { Row { sender?.let { - Box(modifier = Modifier.width(60.dp).align(Alignment.CenterVertically)) { + Box(modifier = Modifier + .width(60.dp) + .align(Alignment.CenterVertically)) { AndroidView( factory = { ProfilePictureView(it).apply { update(sender) } }, modifier = Modifier From 4decce9dde479d4f113c0823e828a63f03d8096c Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 1 Jul 2023 17:38:58 +0930 Subject: [PATCH 14/51] Add images --- app/build.gradle | 1 + .../conversation/v2/MessageDetailActivity.kt | 104 ++++++++++++++---- 2 files changed, 81 insertions(+), 24 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c3ae7de49..d8795b218 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -161,6 +161,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.4' testImplementation 'org.robolectric:shadows-multidex:4.4' + implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.1' implementation 'androidx.compose.ui:ui:1.4.3' implementation 'androidx.compose.ui:ui-tooling:1.4.3' implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.30.1" diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 68c9c9e7a..bf32c2c61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle +import android.view.View import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -23,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -33,15 +36,21 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView +import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellWithPadding @@ -50,6 +59,7 @@ import org.thoughtcrime.securesms.ui.LocalExtraColors import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors import java.util.* +import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -63,6 +73,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @Inject lateinit var storage: Storage + companion object { // Extras const val MESSAGE_TIMESTAMP = "message_timestamp" @@ -75,21 +86,47 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val viewModel = MessageDetailsViewModel() class MessageDetailsViewModel: ViewModel() { + @Inject + lateinit var attachmentDb: AttachmentDatabase fun setMessageRecord(value: MessageRecord?, error: String?) { val mmsRecord = value as? MmsMessageRecord - val slides = mmsRecord?.slideDeck?.thumbnailSlides + val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() _details.value = value?.run { MessageDetails( + attachments = slides.map { slide -> + val duration = slide.takeIf { it.hasAudio() } + ?.let { it.asAttachment() as? DatabaseAttachment } + ?.let { attachment -> + attachmentDb.getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras -> + audioExtras.durationMs.takeIf { it > 0 }?.let { + String.format("%01d:%02d", + TimeUnit.MILLISECONDS.toMinutes(it), + TimeUnit.MILLISECONDS.toSeconds(it) % 60) + } + } + } + + val details = slide.run { + listOfNotNull( + fileName.orNull()?.let { TitledText("File Id:", it) }, + TitledText("File Type:", asAttachment().contentType), + TitledText("File Size:", Util.getPrettyFileSize(fileSize)), + if (slide.hasImage()) { TitledText("Resolution:", slide.asAttachment().run { "${width}x$height" } ) } else null, + duration?.let { TitledText("Duration:", it) }, + ) + } + Attachment(slide, details) + }, sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, error = error?.let { TitledText("Error:", it) }, senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, sender = individualRecipient ) - } ?: MessageDetails() + } } private var _details = MutableLiveData(MessageDetails()) @@ -141,7 +178,8 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { data class TitledText(val title: String, val value: String) data class MessageDetails( - val fileDetails: List? = null, + val attachments: List = emptyList(), +// val fileDetails: List? = null, val sent: TitledText? = null, val received: TitledText? = null, val error: TitledText? = null, @@ -149,27 +187,36 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val sender: Recipient? = null ) + data class Attachment( + val slide: Slide, + val fileDetails: List + ) + @Preview @Composable fun PreviewMessageDetails() { MessageDetails( MessageDetails( - fileDetails = listOf( - TitledText("File Id:", "1237896548514214124235985214"), - TitledText("File Type:", ".PNG"), - TitledText("File Size:", "6mb"), - TitledText("Resolution:", "550x550"), - TitledText("Duration:", "N/A"), - ), + attachments = listOf(), +// fileDetails = listOf( +// TitledText("File Id:", "1237896548514214124235985214"), +// TitledText("File Type:", ".PNG"), +// TitledText("File Size:", "6mb"), +// TitledText("Resolution:", "550x550"), +// TitledText("Duration:", "N/A"), +// ), sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), error = TitledText("Error:", "Message failed to send"), - senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg") + senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg"), ) ) } - @OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) + @OptIn( + ExperimentalLayoutApi::class, + ExperimentalFoundationApi::class, + ExperimentalGlideComposeApi::class) @Composable fun MessageDetails( messageDetails: MessageDetails, @@ -183,18 +230,27 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - HorizontalPager(pageCount = 1) { - - } - - fileDetails?.takeIf { it.isNotEmpty() }?.let { - CellWithPadding { - FlowRow( - verticalArrangement = Arrangement.spacedBy(16.dp), - maxItemsInEachRow = 2 - ) { - it.forEach { - titledText(it, Modifier.weight(1f)) + HorizontalPager(pageCount = attachments.size) {i -> + val attachment = attachments[i] + attachment.apply { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Cell { + if (slide.hasImage()) GlideImage( + contentScale = ContentScale.FillHeight, + modifier = Modifier.fillMaxWidth(), + model = attachment.slide.uri, + contentDescription = attachment.slide.fileName.orNull() ?: "image" + ) + } + fileDetails.takeIf { it.isNotEmpty() }?.let { + CellWithPadding { + FlowRow( + verticalArrangement = Arrangement.spacedBy(16.dp), + maxItemsInEachRow = 2 + ) { + it.forEach { titledText(it, Modifier.weight(1f)) } + } + } } } } From 1303979cdfdb96b047be1bef74cc3da153a08cd3 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 10:35:50 +0930 Subject: [PATCH 15/51] Add image attachments --- .../conversation/v2/MessageDetailActivity.kt | 72 ++++++++++++------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index bf32c2c61..0fd5e6eb9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -10,11 +10,13 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider @@ -230,31 +232,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - HorizontalPager(pageCount = attachments.size) {i -> - val attachment = attachments[i] - attachment.apply { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - Cell { - if (slide.hasImage()) GlideImage( - contentScale = ContentScale.FillHeight, - modifier = Modifier.fillMaxWidth(), - model = attachment.slide.uri, - contentDescription = attachment.slide.fileName.orNull() ?: "image" - ) - } - fileDetails.takeIf { it.isNotEmpty() }?.let { - CellWithPadding { - FlowRow( - verticalArrangement = Arrangement.spacedBy(16.dp), - maxItemsInEachRow = 2 - ) { - it.forEach { titledText(it, Modifier.weight(1f)) } - } - } - } - } - } - } + Attachments(attachments) if (sent != null || received != null || senderInfo != null) CellWithPadding { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } @@ -299,6 +277,50 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } } + @Composable + fun Attachments(attachments: List) { + val slide = attachments.firstOrNull()?.slide ?: return + when { + slide.hasImage() -> ImageAttachments(attachments) + } + } + + @OptIn( + ExperimentalFoundationApi::class, + ExperimentalGlideComposeApi::class, + ExperimentalLayoutApi::class, + ) + @Composable + fun ImageAttachments(attachments: List) { + val pagerState = rememberPagerState() + + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Cell { + val imageAttachments = attachments.filter { it.slide.hasImage() } + HorizontalPager(state = pagerState, pageCount = imageAttachments.size) { i -> + imageAttachments[i].slide.apply { + GlideImage( + contentScale = ContentScale.Crop, + modifier = Modifier.aspectRatio(1f), + model = uri, + contentDescription = fileName.orNull() ?: "image" + ) + } + } + } + attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let { + CellWithPadding { + FlowRow( + verticalArrangement = Arrangement.spacedBy(16.dp), + maxItemsInEachRow = 2 + ) { + it.forEach { titledText(it, Modifier.weight(1f)) } + } + } + } + } + } + @Composable fun Divider() { Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = LocalExtraColors.current.divider) From 1902d4755c09e223692d843d1fbf2de01b8274c0 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 13:00:11 +0930 Subject: [PATCH 16/51] Add pager indicator --- app/build.gradle | 4 +- .../conversation/v2/MessageDetailActivity.kt | 54 +++++++++---------- .../thoughtcrime/securesms/ui/Components.kt | 30 +++++++++-- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d8795b218..4bb54eb20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,10 +164,10 @@ dependencies { implementation 'com.github.bumptech.glide:compose:1.0.0-alpha.1' implementation 'androidx.compose.ui:ui:1.4.3' implementation 'androidx.compose.ui:ui-tooling:1.4.3' - implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.30.1" + implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.31.5-beta" + implementation "com.google.accompanist:accompanist-pager-indicators:0.31.5-beta" implementation "androidx.compose.runtime:runtime-livedata:1.4.3" - implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02' implementation 'androidx.compose.material:material:1.5.0-alpha02' } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 0fd5e6eb9..1c59c8201 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle -import android.view.View import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,7 +10,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -19,6 +17,7 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card import androidx.compose.material.Divider import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text @@ -55,9 +54,11 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Cell -import org.thoughtcrime.securesms.ui.CellWithPadding +import org.thoughtcrime.securesms.ui.CellNoMargin +import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.LocalExtraColors +import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors import java.util.* @@ -181,7 +182,6 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { data class MessageDetails( val attachments: List = emptyList(), -// val fileDetails: List? = null, val sent: TitledText? = null, val received: TitledText? = null, val error: TitledText? = null, @@ -200,13 +200,6 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { MessageDetails( MessageDetails( attachments = listOf(), -// fileDetails = listOf( -// TitledText("File Id:", "1237896548514214124235985214"), -// TitledText("File Type:", ".PNG"), -// TitledText("File Size:", "6mb"), -// TitledText("Resolution:", "550x550"), -// TitledText("Duration:", "N/A"), -// ), sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), error = TitledText("Error:", "Message failed to send"), @@ -215,10 +208,6 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { ) } - @OptIn( - ExperimentalLayoutApi::class, - ExperimentalFoundationApi::class, - ExperimentalGlideComposeApi::class) @Composable fun MessageDetails( messageDetails: MessageDetails, @@ -233,7 +222,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { verticalArrangement = Arrangement.spacedBy(16.dp) ) { Attachments(attachments) - if (sent != null || received != null || senderInfo != null) CellWithPadding { + if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } received?.let { titledText(it) } @@ -292,24 +281,35 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { ) @Composable fun ImageAttachments(attachments: List) { - val pagerState = rememberPagerState() + val imageAttachments = attachments.filter { it.slide.hasImage() } + val pagerState = rememberPagerState { + imageAttachments.size + } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - Cell { - val imageAttachments = attachments.filter { it.slide.hasImage() } - HorizontalPager(state = pagerState, pageCount = imageAttachments.size) { i -> - imageAttachments[i].slide.apply { - GlideImage( - contentScale = ContentScale.Crop, - modifier = Modifier.aspectRatio(1f), - model = uri, - contentDescription = fileName.orNull() ?: "image" + CellNoMargin { + Box { + HorizontalPager(state = pagerState) { i -> + imageAttachments[i].slide.apply { + GlideImage( + contentScale = ContentScale.Crop, + modifier = Modifier.aspectRatio(1f), + model = uri, + contentDescription = fileName.orNull() ?: "image" + ) + } + } + if (imageAttachments.size >= 2) { + SessionHorizontalPagerIndicator( + modifier = Modifier.align(Alignment.BottomCenter), + pagerState = pagerState, + pageCount = imageAttachments.size, ) } } } attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let { - CellWithPadding { + CellWithPaddingAndMargin { FlowRow( verticalArrangement = Arrangement.spacedBy(16.dp), maxItemsInEachRow = 2 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 2421c39b8..9f8be2514 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -8,6 +9,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonColors import androidx.compose.material.Card @@ -18,10 +20,12 @@ import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.google.accompanist.pager.HorizontalPagerIndicator @Composable fun ItemButton( @@ -53,20 +57,40 @@ fun ItemButton( @Composable fun Cell(content: @Composable () -> Unit) { - CellWithPadding(0.dp) { content() } + CellWithPaddingAndMargin(0.dp) { content() } +} +@Composable +fun CellNoMargin(content: @Composable () -> Unit) { + CellWithPaddingAndMargin(0.dp, 0.dp) { content() } } @Composable -fun CellWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) { +fun CellWithPaddingAndMargin( + padding: Dp = 24.dp, + margin: Dp = 32.dp, + content: @Composable () -> Unit +) { Card( shape = RoundedCornerShape(16.dp), elevation = 0.dp, modifier = Modifier .wrapContentHeight() .fillMaxWidth() - .padding(horizontal = 32.dp), + .padding(horizontal = margin), backgroundColor = LocalExtraColors.current.settingsBackground, // probably wrong contentColor = MaterialTheme.colors.onSurface ) { Box(Modifier.padding(padding)) { content() } } } + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun SessionHorizontalPagerIndicator(modifier: Modifier, pagerState: PagerState, pageCount: Int) { + Card(shape = RoundedCornerShape(50.dp), + backgroundColor = Color.Black.copy(alpha = 0.4f), + modifier = Modifier.padding(8.dp).then(modifier)) { + Box(modifier = Modifier.padding(8.dp)) { + HorizontalPagerIndicator(pagerState = pagerState, pageCount = pageCount) + } + } +} From db4ff940844bbea19c759ba44d7c19feaa89c330 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 17:19:33 +0930 Subject: [PATCH 17/51] Add prev and next buttons to carousel --- .../conversation/v2/MessageDetailActivity.kt | 229 +++++++++++++----- .../thoughtcrime/securesms/ui/Components.kt | 4 +- app/src/main/res/drawable/ic_next.xml | 8 + app/src/main/res/drawable/ic_prev.xml | 8 + 4 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 app/src/main/res/drawable/ic_next.xml create mode 100644 app/src/main/res/drawable/ic_prev.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 1c59c8201..1c9997854 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle +import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -9,25 +10,30 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Card import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -40,6 +46,7 @@ import androidx.lifecycle.ViewModel import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util @@ -67,7 +74,7 @@ import javax.inject.Inject @AndroidEntryPoint -class MessageDetailActivity: PassphraseRequiredActionBarActivity() { +class MessageDetailActivity : PassphraseRequiredActionBarActivity() { private var timestamp: Long = 0L @@ -76,7 +83,6 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @Inject lateinit var storage: Storage - companion object { // Extras const val MESSAGE_TIMESTAMP = "message_timestamp" @@ -88,7 +94,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val viewModel = MessageDetailsViewModel() - class MessageDetailsViewModel: ViewModel() { + class MessageDetailsViewModel : ViewModel() { @Inject lateinit var attachmentDb: AttachmentDatabase @@ -103,30 +109,45 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val duration = slide.takeIf { it.hasAudio() } ?.let { it.asAttachment() as? DatabaseAttachment } ?.let { attachment -> - attachmentDb.getAttachmentAudioExtras(attachment.attachmentId)?.let { audioExtras -> - audioExtras.durationMs.takeIf { it > 0 }?.let { - String.format("%01d:%02d", - TimeUnit.MILLISECONDS.toMinutes(it), - TimeUnit.MILLISECONDS.toSeconds(it) % 60) + attachmentDb.getAttachmentAudioExtras(attachment.attachmentId) + ?.let { audioExtras -> + audioExtras.durationMs.takeIf { it > 0 }?.let { + String.format( + "%01d:%02d", + TimeUnit.MILLISECONDS.toMinutes(it), + TimeUnit.MILLISECONDS.toSeconds(it) % 60 + ) + } } - } } - val details = slide.run { - listOfNotNull( - fileName.orNull()?.let { TitledText("File Id:", it) }, - TitledText("File Type:", asAttachment().contentType), - TitledText("File Size:", Util.getPrettyFileSize(fileSize)), - if (slide.hasImage()) { TitledText("Resolution:", slide.asAttachment().run { "${width}x$height" } ) } else null, - duration?.let { TitledText("Duration:", it) }, - ) - } - Attachment(slide, details) + val details = slide.run { + listOfNotNull( + fileName.orNull()?.let { TitledText("File Id:", it) }, + TitledText("File Type:", asAttachment().contentType), + TitledText("File Size:", Util.getPrettyFileSize(fileSize)), + if (slide.hasImage()) { + TitledText( + "Resolution:", + slide.asAttachment().run { "${width}x$height" }) + } else null, + duration?.let { TitledText("Duration:", it) }, + ) + } + Attachment(slide, details) }, sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, - received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, + received = dateReceived.let(::Date).toString() + .let { TitledText("Received:", it) }, error = error?.let { TitledText("Error:", it) }, - senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, + senderInfo = individualRecipient.run { + name?.let { + TitledText( + it, + address.serialize() + ) + } + }, sender = individualRecipient ) } @@ -141,12 +162,14 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) - messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run { - finish() - return - } + messageRecord = + DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run { + finish() + return + } - val error = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) + val error = DatabaseComponent.get(this).lokiMessageDatabase() + .getErrorMessage(messageRecord!!.getId()) viewModel.setMessageRecord(messageRecord, error) @@ -226,16 +249,29 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } received?.let { titledText(it) } - error?.let { titledText(it, valueStyle = LocalTextStyle.current.copy(color = colorDestructive)) } + error?.let { + titledText( + it, + valueStyle = LocalTextStyle.current.copy(color = colorDestructive) + ) + } senderInfo?.let { titledView("From:") { Row { sender?.let { - Box(modifier = Modifier - .width(60.dp) - .align(Alignment.CenterVertically)) { + Box( + modifier = Modifier + .width(60.dp) + .align(Alignment.CenterVertically) + ) { AndroidView( - factory = { ProfilePictureView(it).apply { update(sender) } }, + factory = { + ProfilePictureView(it).apply { + update( + sender + ) + } + }, modifier = Modifier .width(46.dp) .height(46.dp) @@ -243,22 +279,38 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { } } Column { - titledText(it, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) + titledText( + it, + valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) + ) } } - } + } } } } Cell { Column { - ItemButton("Reply", R.drawable.ic_message_details__reply, onClick = onReply) + ItemButton( + "Reply", + R.drawable.ic_message_details__reply, + onClick = onReply + ) Divider() if (error != null) { - ItemButton("Resend", R.drawable.ic_message_details__refresh, onClick = onResend) + ItemButton( + "Resend", + R.drawable.ic_message_details__refresh, + onClick = onResend + ) Divider() } - ItemButton("Delete", R.drawable.ic_message_details__trash, colors = destructiveButtonColors(), onClick = onDelete) + ItemButton( + "Delete", + R.drawable.ic_message_details__trash, + colors = destructiveButtonColors(), + onClick = onDelete + ) } } } @@ -277,26 +329,27 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @OptIn( ExperimentalFoundationApi::class, ExperimentalGlideComposeApi::class, - ExperimentalLayoutApi::class, ) @Composable fun ImageAttachments(attachments: List) { val imageAttachments = attachments.filter { it.slide.hasImage() } - val pagerState = rememberPagerState { - imageAttachments.size - } + val pagerState = rememberPagerState { imageAttachments.size } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - CellNoMargin { - Box { - HorizontalPager(state = pagerState) { i -> - imageAttachments[i].slide.apply { - GlideImage( - contentScale = ContentScale.Crop, - modifier = Modifier.aspectRatio(1f), - model = uri, - contentDescription = fileName.orNull() ?: "image" - ) + Row { + if (imageAttachments.size >= 2) PrevButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically)) + else Spacer(modifier = Modifier.width(32.dp)) + Box(modifier = Modifier.weight(1f)) { + CellNoMargin { + HorizontalPager(state = pagerState) { i -> + imageAttachments[i].slide.apply { + GlideImage( + contentScale = ContentScale.Crop, + modifier = Modifier.aspectRatio(1f), + model = uri, + contentDescription = fileName.orNull() ?: "image" + ) + } } } if (imageAttachments.size >= 2) { @@ -307,15 +360,61 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { ) } } + if (imageAttachments.size >= 2) NextButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically)) + else Spacer(modifier = Modifier.width(32.dp)) } - attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let { - CellWithPaddingAndMargin { - FlowRow( - verticalArrangement = Arrangement.spacedBy(16.dp), - maxItemsInEachRow = 2 - ) { - it.forEach { titledText(it, Modifier.weight(1f)) } - } + + FileDetails(attachments, pagerState) + } + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun PrevButton(pagerState: PagerState, modifier: Modifier = Modifier) { + CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollBackward, id = R.drawable.ic_prev, delta = -1) + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun NextButton(pagerState: PagerState, modifier: Modifier = Modifier) { + CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollForward, id = R.drawable.ic_next, delta = 1) + } + + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun CarouselButton( + pagerState: PagerState, + modifier: Modifier = Modifier, + enabled: Boolean, + @DrawableRes id: Int, + delta: Int + ) { + val animationScope = rememberCoroutineScope() + pagerState.apply { + IconButton( + modifier = Modifier + .width(40.dp) + .then(modifier), + enabled = enabled, + onClick = { animationScope.launch { animateScrollToPage(currentPage + delta) } }) { + Icon( + painter = painterResource(id = id), + contentDescription = "", + ) + } + } + } + + @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) + @Composable + fun FileDetails(attachments: List, pagerState: PagerState) { + attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let { + CellWithPaddingAndMargin { + FlowRow( + verticalArrangement = Arrangement.spacedBy(16.dp), + maxItemsInEachRow = 2 + ) { + it.forEach { titledText(it, Modifier.weight(1f)) } } } } @@ -323,11 +422,19 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { @Composable fun Divider() { - Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = LocalExtraColors.current.divider) + Divider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 1.dp, + color = LocalExtraColors.current.divider + ) } @Composable - fun titledText(titledText: TitledText, modifier: Modifier = Modifier, valueStyle: TextStyle = LocalTextStyle.current) { + fun titledText( + titledText: TitledText, + modifier: Modifier = Modifier, + valueStyle: TextStyle = LocalTextStyle.current + ) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Title(titledText.title) Text(titledText.value, style = valueStyle) 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 9f8be2514..49d419e05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -57,11 +57,11 @@ fun ItemButton( @Composable fun Cell(content: @Composable () -> Unit) { - CellWithPaddingAndMargin(0.dp) { content() } + CellWithPaddingAndMargin(padding = 0.dp) { content() } } @Composable fun CellNoMargin(content: @Composable () -> Unit) { - CellWithPaddingAndMargin(0.dp, 0.dp) { content() } + CellWithPaddingAndMargin(padding = 0.dp, margin = 0.dp) { content() } } @Composable diff --git a/app/src/main/res/drawable/ic_next.xml b/app/src/main/res/drawable/ic_next.xml new file mode 100644 index 000000000..1e72d86cb --- /dev/null +++ b/app/src/main/res/drawable/ic_next.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_prev.xml b/app/src/main/res/drawable/ic_prev.xml new file mode 100644 index 000000000..f72026167 --- /dev/null +++ b/app/src/main/res/drawable/ic_prev.xml @@ -0,0 +1,8 @@ + + + + + + From 1d29b5465f2b14c1dfb82e2e865f040aab640f1b Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 17:41:30 +0930 Subject: [PATCH 18/51] Refactor prev and next buttons --- .../conversation/v2/MessageDetailActivity.kt | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 1c9997854..9836ef486 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -337,8 +337,10 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Row { - if (imageAttachments.size >= 2) PrevButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically)) - else Spacer(modifier = Modifier.width(32.dp)) + CarouselButtonOrSpace( + Direction.PREVIOUS, + pagerState, + modifier = Modifier.align(Alignment.CenterVertically)) Box(modifier = Modifier.weight(1f)) { CellNoMargin { HorizontalPager(state = pagerState) { i -> @@ -360,24 +362,39 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) } } - if (imageAttachments.size >= 2) NextButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically)) - else Spacer(modifier = Modifier.width(32.dp)) + CarouselButtonOrSpace( + Direction.NEXT, + pagerState, + modifier = Modifier.align(Alignment.CenterVertically) + ) } - FileDetails(attachments, pagerState) } } @OptIn(ExperimentalFoundationApi::class) - @Composable - fun PrevButton(pagerState: PagerState, modifier: Modifier = Modifier) { - CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollBackward, id = R.drawable.ic_prev, delta = -1) + enum class Direction constructor( + val enabled: (PagerState) -> Boolean, + @DrawableRes val id: Int, + val delta: Int + ) { + PREVIOUS( + PagerState::canScrollBackward, + R.drawable.ic_prev, + -1 + ), + NEXT( + PagerState::canScrollForward, + R.drawable.ic_next, + 1 + ) } @OptIn(ExperimentalFoundationApi::class) @Composable - fun NextButton(pagerState: PagerState, modifier: Modifier = Modifier) { - CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollForward, id = R.drawable.ic_next, delta = 1) + fun CarouselButtonOrSpace(direction: Direction, pagerState: PagerState, modifier: Modifier = Modifier) { + if (pagerState.pageCount >= 2) CarouselButton(pagerState, modifier = modifier, direction = direction) + else Spacer(modifier = Modifier.width(32.dp)) } @OptIn(ExperimentalFoundationApi::class) @@ -385,9 +402,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { fun CarouselButton( pagerState: PagerState, modifier: Modifier = Modifier, - enabled: Boolean, - @DrawableRes id: Int, - delta: Int + direction: Direction ) { val animationScope = rememberCoroutineScope() pagerState.apply { @@ -395,10 +410,10 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { modifier = Modifier .width(40.dp) .then(modifier), - enabled = enabled, - onClick = { animationScope.launch { animateScrollToPage(currentPage + delta) } }) { + enabled = direction.enabled(pagerState), + onClick = { animationScope.launch { animateScrollToPage(currentPage + direction.delta) } }) { Icon( - painter = painterResource(id = id), + painter = painterResource(id = direction.id), contentDescription = "", ) } From 0ec93e4b36e0ba684692fe57dcad9eed9248ae9d Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 18:30:15 +0930 Subject: [PATCH 19/51] Refactor CarouselButtons --- .../conversation/v2/MessageDetailActivity.kt | 64 ++----------------- .../thoughtcrime/securesms/ui/Components.kt | 47 +++++++++++++- 2 files changed, 52 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 9836ef486..a38ae5d50 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle -import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -21,19 +19,15 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.IconButton import androidx.compose.material.LocalTextStyle import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -46,7 +40,6 @@ import androidx.lifecycle.ViewModel import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util @@ -60,6 +53,8 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme +import org.thoughtcrime.securesms.ui.CarouselNextButton +import org.thoughtcrime.securesms.ui.CarouselPrevButton import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellNoMargin import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin @@ -337,10 +332,10 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Row { - CarouselButtonOrSpace( - Direction.PREVIOUS, + CarouselPrevButton( pagerState, - modifier = Modifier.align(Alignment.CenterVertically)) + modifier = Modifier.align(Alignment.CenterVertically) + ) Box(modifier = Modifier.weight(1f)) { CellNoMargin { HorizontalPager(state = pagerState) { i -> @@ -362,8 +357,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) } } - CarouselButtonOrSpace( - Direction.NEXT, + CarouselNextButton( pagerState, modifier = Modifier.align(Alignment.CenterVertically) ) @@ -372,53 +366,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } - @OptIn(ExperimentalFoundationApi::class) - enum class Direction constructor( - val enabled: (PagerState) -> Boolean, - @DrawableRes val id: Int, - val delta: Int - ) { - PREVIOUS( - PagerState::canScrollBackward, - R.drawable.ic_prev, - -1 - ), - NEXT( - PagerState::canScrollForward, - R.drawable.ic_next, - 1 - ) - } - @OptIn(ExperimentalFoundationApi::class) - @Composable - fun CarouselButtonOrSpace(direction: Direction, pagerState: PagerState, modifier: Modifier = Modifier) { - if (pagerState.pageCount >= 2) CarouselButton(pagerState, modifier = modifier, direction = direction) - else Spacer(modifier = Modifier.width(32.dp)) - } - - @OptIn(ExperimentalFoundationApi::class) - @Composable - fun CarouselButton( - pagerState: PagerState, - modifier: Modifier = Modifier, - direction: Direction - ) { - val animationScope = rememberCoroutineScope() - pagerState.apply { - IconButton( - modifier = Modifier - .width(40.dp) - .then(modifier), - enabled = direction.enabled(pagerState), - onClick = { animationScope.launch { animateScrollToPage(currentPage + direction.delta) } }) { - Icon( - painter = painterResource(id = direction.id), - contentDescription = "", - ) - } - } - } @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) @Composable 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 49d419e05..1819b9a31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -14,10 +15,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonColors import androidx.compose.material.Card import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -26,6 +29,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.google.accompanist.pager.HorizontalPagerIndicator +import kotlinx.coroutines.launch +import network.loki.messenger.R @Composable fun ItemButton( @@ -88,9 +93,49 @@ fun CellWithPaddingAndMargin( fun SessionHorizontalPagerIndicator(modifier: Modifier, pagerState: PagerState, pageCount: Int) { Card(shape = RoundedCornerShape(50.dp), backgroundColor = Color.Black.copy(alpha = 0.4f), - modifier = Modifier.padding(8.dp).then(modifier)) { + modifier = Modifier + .padding(8.dp) + .then(modifier)) { Box(modifier = Modifier.padding(8.dp)) { HorizontalPagerIndicator(pagerState = pagerState, pageCount = pageCount) } } } + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CarouselPrevButton(pagerState: PagerState, modifier: Modifier = Modifier) { + CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_prev, -1, modifier) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CarouselNextButton(pagerState: PagerState, modifier: Modifier = Modifier) { + CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_next, 1, modifier) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CarouselButton( + pagerState: PagerState, + enabled: Boolean, + @DrawableRes id: Int, + delta: Int, + modifier: Modifier = Modifier +) { + if (pagerState.pageCount <= 1) Spacer(modifier = Modifier.width(32.dp)) + else { + val animationScope = rememberCoroutineScope() + IconButton( + modifier = Modifier + .width(40.dp) + .then(modifier), + enabled = enabled, + onClick = { animationScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + delta) } }) { + Icon( + painter = painterResource(id = id), + contentDescription = "", + ) + } + } +} From 70e63a23bc1af88112181554238c125003d0ecf5 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 20:12:23 +0930 Subject: [PATCH 20/51] Fix indicator colors --- .../org/thoughtcrime/securesms/ui/Colors.kt | 19 ++++++++++++++++++- .../thoughtcrime/securesms/ui/Components.kt | 6 +++++- app/src/main/res/drawable/ic_expand.xml | 5 +++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_expand.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt index 0ea7e0714..dedff26cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt @@ -6,8 +6,25 @@ import androidx.compose.ui.graphics.Color val colorDestructive = Color(0xffFF453A) +val classicDark0 = Color(0xff111111) + +val classicDark1 = Color(0xff1B1B1B) +val classicDark2 = Color(0xff2D2D2D) +val classicDark3 = Color(0xff414141) +val classicDark4 = Color(0xff767676) +val classicDark5 = Color(0xffA1A2A1) +val classicDark6 = Color(0xffFFFFFF) + +val classicLight0 = Color(0xff000000) +val classicLight1 = Color(0xff6D6D6D) +val classicLight2 = Color(0xffA1A2A1) +val classicLight3 = Color(0xffDFDFDF) +val classicLight4 = Color(0xffF0F0F0) +val classicLight5 = Color(0xffF9F9F9) +val classicLight6 = Color(0xffFFFFFF) + @Composable fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent) @Composable -fun destructiveButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = colorDestructive) \ No newline at end of file +fun destructiveButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = colorDestructive) 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 1819b9a31..a8848adbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -97,7 +97,11 @@ fun SessionHorizontalPagerIndicator(modifier: Modifier, pagerState: PagerState, .padding(8.dp) .then(modifier)) { Box(modifier = Modifier.padding(8.dp)) { - HorizontalPagerIndicator(pagerState = pagerState, pageCount = pageCount) + HorizontalPagerIndicator( + pagerState = pagerState, + pageCount = pageCount, + activeColor = Color.White, + inactiveColor = classicDark5) } } } diff --git a/app/src/main/res/drawable/ic_expand.xml b/app/src/main/res/drawable/ic_expand.xml new file mode 100644 index 000000000..3b2b816a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand.xml @@ -0,0 +1,5 @@ + + + From 0fcd9972900f5a6fedb09ea4639c7753fde0b9b7 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 20:40:16 +0930 Subject: [PATCH 21/51] Fix divider color --- app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt index a19082c08..67c44c636 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -19,8 +19,8 @@ data class ExtraColors( val settingsBackground: Color, ) -fun Context.getColorFromTheme(@AttrRes attr: Int): Color = - MaterialColors.getColor(this, attr, 0).let(::Color) +fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color = + MaterialColors.getColor(this, attr, defaultValue).let(::Color) @Composable fun AppTheme( @@ -29,7 +29,7 @@ fun AppTheme( val extraColors = LocalContext.current.run { ExtraColors( cell = getColorFromTheme(R.attr.colorCellBackground), - divider = getColorFromTheme(R.attr.dividerColor), + divider = getColorFromTheme(R.attr.dividerHorizontal), settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground) ) } From d719660030e5d8ea881e3565c38ac33ae6874b15 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 3 Jul 2023 21:25:59 +0930 Subject: [PATCH 22/51] Add expand button --- .../conversation/v2/MessageDetailActivity.kt | 90 ++++++++++++------- .../thoughtcrime/securesms/ui/Components.kt | 8 ++ 2 files changed, 64 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index a38ae5d50..247505659 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -17,17 +17,24 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -59,7 +66,6 @@ import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellNoMargin import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.ItemButton -import org.thoughtcrime.securesms.ui.LocalExtraColors import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors @@ -284,35 +290,50 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } } - Cell { - Column { - ItemButton( - "Reply", - R.drawable.ic_message_details__reply, - onClick = onReply - ) - Divider() - if (error != null) { - ItemButton( - "Resend", - R.drawable.ic_message_details__refresh, - onClick = onResend - ) - Divider() - } - ItemButton( - "Delete", - R.drawable.ic_message_details__trash, - colors = destructiveButtonColors(), - onClick = onDelete - ) - } - } + Buttons( + messageDetails.error != null, + onReply, + onResend, + onDelete, + ) } } } } + @Composable + fun Buttons( + hasError: Boolean, + onReply: () -> Unit = {}, + onResend: () -> Unit = {}, + onDelete: () -> Unit = {}, + ) { + Cell { + Column { + ItemButton( + "Reply", + R.drawable.ic_message_details__reply, + onClick = onReply + ) + Divider() + if (hasError) { + ItemButton( + "Resend", + R.drawable.ic_message_details__refresh, + onClick = onResend + ) + Divider() + } + ItemButton( + "Delete", + R.drawable.ic_message_details__trash, + colors = destructiveButtonColors(), + onClick = onDelete + ) + } + } + } + @Composable fun Attachments(attachments: List) { val slide = attachments.firstOrNull()?.slide ?: return @@ -356,6 +377,16 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { pageCount = imageAttachments.size, ) } + Surface( + shape = CircleShape, + color = Color.Black.copy(alpha = 0.4f), + modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.ic_expand), + contentDescription = "" + ) + } } CarouselNextButton( pagerState, @@ -383,15 +414,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } - @Composable - fun Divider() { - Divider( - modifier = Modifier.padding(horizontal = 16.dp), - thickness = 1.dp, - color = LocalExtraColors.current.divider - ) - } - @Composable fun titledText( titledText: TitledText, 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 a8848adbd..7cba0fb94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -143,3 +143,11 @@ fun CarouselButton( } } } + +@Composable +fun Divider() { + androidx.compose.material.Divider( + modifier = Modifier.padding(horizontal = 16.dp), + color = LocalExtraColors.current.divider + ) +} From 6890f5c44884b03b636b22bf50e41d614726a7e8 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 09:31:14 +0930 Subject: [PATCH 23/51] Add image click listener --- .../conversation/v2/MessageDetailActivity.kt | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 247505659..3d9e1fa45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -51,12 +52,14 @@ import network.loki.messenger.R import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord + import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme @@ -69,6 +72,7 @@ import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors +import org.thoughtcrime.securesms.util.ActivityDispatcher import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -106,6 +110,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { _details.value = value?.run { MessageDetails( + mmsRecord = mmsRecord, attachments = slides.map { slide -> val duration = slide.takeIf { it.hasAudio() } ?.let { it.asAttachment() as? DatabaseAttachment } @@ -190,7 +195,11 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { details, onReply = { setResultAndFinish(ON_REPLY) }, onResend = { setResultAndFinish(ON_RESEND) }, - onDelete = { setResultAndFinish(ON_DELETE) } + onDelete = { setResultAndFinish(ON_DELETE) }, + onClickImage = { slide -> + MediaPreviewActivity.getPreviewIntent(this, slide, details.mmsRecord, details.sender) + .let(::startActivity) + } ) } @@ -206,6 +215,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { data class MessageDetails( val attachments: List = emptyList(), + val mmsRecord: MmsMessageRecord? = null, + val sent: TitledText? = null, val received: TitledText? = null, val error: TitledText? = null, @@ -238,6 +249,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { onReply: () -> Unit = {}, onResend: () -> Unit = {}, onDelete: () -> Unit = {}, + onClickImage: (Slide) -> Unit = {}, ) { messageDetails.apply { AppTheme { @@ -245,7 +257,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Attachments(attachments) + Attachments(attachments) { onClickImage(it) } if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { titledText(it) } @@ -335,10 +347,10 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun Attachments(attachments: List) { + fun Attachments(attachments: List, onClick: (Slide) -> Unit) { val slide = attachments.firstOrNull()?.slide ?: return when { - slide.hasImage() -> ImageAttachments(attachments) + slide.hasImage() -> ImageAttachments(attachments) { onClick(it) } } } @@ -347,7 +359,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ExperimentalGlideComposeApi::class, ) @Composable - fun ImageAttachments(attachments: List) { + fun ImageAttachments(attachments: List, onClick: (Slide) -> Unit) { val imageAttachments = attachments.filter { it.slide.hasImage() } val pagerState = rememberPagerState { imageAttachments.size } @@ -360,10 +372,13 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Box(modifier = Modifier.weight(1f)) { CellNoMargin { HorizontalPager(state = pagerState) { i -> - imageAttachments[i].slide.apply { + val slide = imageAttachments[i].slide + slide.apply { GlideImage( contentScale = ContentScale.Crop, - modifier = Modifier.aspectRatio(1f), + modifier = Modifier + .aspectRatio(1f) + .clickable { onClick(slide) }, model = uri, contentDescription = fileName.orNull() ?: "image" ) From 26aed783e84d625f790ca1d0347bd5c52e4ef519 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 11:34:20 +0930 Subject: [PATCH 24/51] Fix Divider padding --- .../securesms/conversation/v2/MessageDetailActivity.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 3d9e1fa45..c6d22e423 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -20,10 +20,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Button -import androidx.compose.material.Divider import androidx.compose.material.Icon -import androidx.compose.material.IconButton import androidx.compose.material.LocalTextStyle import androidx.compose.material.Surface import androidx.compose.material.Text @@ -59,7 +56,6 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord - import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme @@ -68,11 +64,11 @@ import org.thoughtcrime.securesms.ui.CarouselPrevButton import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellNoMargin import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin +import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors -import org.thoughtcrime.securesms.util.ActivityDispatcher import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject From f82ed7718d6fd3e6ed73a83c412e0807f573da97 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 13:26:26 +0930 Subject: [PATCH 25/51] Cleanup CarouselButton --- .../conversation/v2/MessageDetailActivity.kt | 10 ++-------- .../org/thoughtcrime/securesms/ui/Components.kt | 16 ++++++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index c6d22e423..0fddfd7ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -361,10 +361,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Row { - CarouselPrevButton( - pagerState, - modifier = Modifier.align(Alignment.CenterVertically) - ) + CarouselPrevButton(pagerState) Box(modifier = Modifier.weight(1f)) { CellNoMargin { HorizontalPager(state = pagerState) { i -> @@ -399,10 +396,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) } } - CarouselNextButton( - pagerState, - modifier = Modifier.align(Alignment.CenterVertically) - ) + CarouselNextButton(pagerState) } FileDetails(attachments, pagerState) } 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 7cba0fb94..c7720f239 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth @@ -108,24 +109,23 @@ fun SessionHorizontalPagerIndicator(modifier: Modifier, pagerState: PagerState, @OptIn(ExperimentalFoundationApi::class) @Composable -fun CarouselPrevButton(pagerState: PagerState, modifier: Modifier = Modifier) { - CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_prev, -1, modifier) +fun RowScope.CarouselPrevButton(pagerState: PagerState) { + CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_prev, -1) } @OptIn(ExperimentalFoundationApi::class) @Composable -fun CarouselNextButton(pagerState: PagerState, modifier: Modifier = Modifier) { - CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_next, 1, modifier) +fun RowScope.CarouselNextButton(pagerState: PagerState) { + CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_next, 1) } @OptIn(ExperimentalFoundationApi::class) @Composable -fun CarouselButton( +fun RowScope.CarouselButton( pagerState: PagerState, enabled: Boolean, @DrawableRes id: Int, - delta: Int, - modifier: Modifier = Modifier + delta: Int ) { if (pagerState.pageCount <= 1) Spacer(modifier = Modifier.width(32.dp)) else { @@ -133,7 +133,7 @@ fun CarouselButton( IconButton( modifier = Modifier .width(40.dp) - .then(modifier), + .align(Alignment.CenterVertically), enabled = enabled, onClick = { animationScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + delta) } }) { Icon( From ee0141f82d74e2e6d62f4df3ef69ba599e6f202b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 14:09:40 +0930 Subject: [PATCH 26/51] Refactor to Components --- .../conversation/v2/MessageDetailActivity.kt | 76 ++++++++++--------- .../org/thoughtcrime/securesms/ui/Colors.kt | 2 + .../thoughtcrime/securesms/ui/Components.kt | 9 ++- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 0fddfd7ee..bf051a3b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -65,8 +65,9 @@ import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellNoMargin import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Divider +import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator import org.thoughtcrime.securesms.ui.ItemButton -import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator +import org.thoughtcrime.securesms.ui.blackAlpha40 import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors import java.util.* @@ -350,10 +351,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } - @OptIn( - ExperimentalFoundationApi::class, - ExperimentalGlideComposeApi::class, - ) + @OptIn(ExperimentalFoundationApi::class,) @Composable fun ImageAttachments(attachments: List, onClick: (Slide) -> Unit) { val imageAttachments = attachments.filter { it.slide.hasImage() } @@ -363,38 +361,9 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Row { CarouselPrevButton(pagerState) Box(modifier = Modifier.weight(1f)) { - CellNoMargin { - HorizontalPager(state = pagerState) { i -> - val slide = imageAttachments[i].slide - slide.apply { - GlideImage( - contentScale = ContentScale.Crop, - modifier = Modifier - .aspectRatio(1f) - .clickable { onClick(slide) }, - model = uri, - contentDescription = fileName.orNull() ?: "image" - ) - } - } - } - if (imageAttachments.size >= 2) { - SessionHorizontalPagerIndicator( - modifier = Modifier.align(Alignment.BottomCenter), - pagerState = pagerState, - pageCount = imageAttachments.size, - ) - } - Surface( - shape = CircleShape, - color = Color.Black.copy(alpha = 0.4f), - modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.ic_expand), - contentDescription = "" - ) - } + CellPager(pagerState, imageAttachments) { onClick(it) } + HorizontalPagerIndicator(pagerState) + ExpandButton(modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp)) } CarouselNextButton(pagerState) } @@ -402,7 +371,40 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } + @OptIn( + ExperimentalFoundationApi::class, + ExperimentalGlideComposeApi::class + ) + @Composable + private fun CellPager(pagerState: PagerState, imageAttachments: List, onClick: (Slide) -> Unit) { + CellNoMargin { + HorizontalPager(state = pagerState) { i -> + val slide = imageAttachments[i].slide + GlideImage( + contentScale = ContentScale.Crop, + modifier = Modifier + .aspectRatio(1f) + .clickable { onClick(slide) }, + model = slide.uri, + contentDescription = slide.fileName.orNull() ?: "image" + ) + } + } + } + @Composable + fun ExpandButton(modifier: Modifier) { + Surface( + shape = CircleShape, + color = blackAlpha40, + modifier = modifier + ) { + Icon( + painter = painterResource(id = R.drawable.ic_expand), + contentDescription = "" + ) + } + } @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) @Composable diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt index dedff26cd..cace0c6af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt @@ -23,6 +23,8 @@ val classicLight4 = Color(0xffF0F0F0) val classicLight5 = Color(0xffF9F9F9) val classicLight6 = Color(0xffFFFFFF) +val blackAlpha40 = Color.Black.copy(alpha = 0.4f) + @Composable fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent) 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 c7720f239..c70b557ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight @@ -91,16 +92,16 @@ fun CellWithPaddingAndMargin( @OptIn(ExperimentalFoundationApi::class) @Composable -fun SessionHorizontalPagerIndicator(modifier: Modifier, pagerState: PagerState, pageCount: Int) { +fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { Card(shape = RoundedCornerShape(50.dp), backgroundColor = Color.Black.copy(alpha = 0.4f), modifier = Modifier - .padding(8.dp) - .then(modifier)) { + .align(Alignment.BottomCenter) + .padding(8.dp)) { Box(modifier = Modifier.padding(8.dp)) { HorizontalPagerIndicator( pagerState = pagerState, - pageCount = pageCount, + pageCount = pagerState.pageCount, activeColor = Color.White, inactiveColor = classicDark5) } From 6209ae68a8bfed34e49caac7d29bf232abe31994 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 14:46:56 +0930 Subject: [PATCH 27/51] Cleanup --- .../conversation/v2/MessageDetailActivity.kt | 118 +++++++++--------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index bf051a3b7..8d59dea73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource @@ -80,8 +79,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { private var timestamp: Long = 0L - var messageRecord: MessageRecord? = null - @Inject lateinit var storage: Storage @@ -165,24 +162,22 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) - messageRecord = + val messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run { finish() return } val error = DatabaseComponent.get(this).lokiMessageDatabase() - .getErrorMessage(messageRecord!!.getId()) + .getErrorMessage(messageRecord.getId()) viewModel.setMessageRecord(messageRecord, error) title = resources.getString(R.string.conversation_context__menu_message_details) - setContentView(ComposeView(this).apply { - setContent { - MessageDetailsScreen() - } - }) + ComposeView(this) + .apply { setContent { MessageDetailsScreen() } } + .let(::setContentView) } @Composable @@ -255,52 +250,9 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { verticalArrangement = Arrangement.spacedBy(16.dp) ) { Attachments(attachments) { onClickImage(it) } - if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - sent?.let { titledText(it) } - received?.let { titledText(it) } - error?.let { - titledText( - it, - valueStyle = LocalTextStyle.current.copy(color = colorDestructive) - ) - } - senderInfo?.let { - titledView("From:") { - Row { - sender?.let { - Box( - modifier = Modifier - .width(60.dp) - .align(Alignment.CenterVertically) - ) { - AndroidView( - factory = { - ProfilePictureView(it).apply { - update( - sender - ) - } - }, - modifier = Modifier - .width(46.dp) - .height(46.dp) - ) - } - } - Column { - titledText( - it, - valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) - ) - } - } - } - } - } - } + MetaDataCell(messageDetails) Buttons( - messageDetails.error != null, + error != null, onReply, onResend, onDelete, @@ -310,6 +262,50 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } + @Composable + fun MetaDataCell( + messageDetails: MessageDetails, + ) { + if (messageDetails.sent != null || messageDetails.received != null || messageDetails.senderInfo != null) CellWithPaddingAndMargin { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + messageDetails.sent?.let { TitledText(it) } + messageDetails.received?.let { TitledText(it) } + messageDetails.error?.let { + TitledText( + it, + valueStyle = LocalTextStyle.current.copy(color = colorDestructive) + ) + } + messageDetails.senderInfo?.let { + TitledView("From:") { + Row { + messageDetails.sender?.let { sender -> + Box( + modifier = Modifier + .width(60.dp) + .align(Alignment.CenterVertically) + ) { + AndroidView( + factory = { + ProfilePictureView(it).apply { update(sender) } + }, + modifier = Modifier.width(46.dp).height(46.dp) + ) + } + } + Column { + TitledText( + it, + valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) + ) + } + } + } + } + } + } + } + @Composable fun Buttons( hasError: Boolean, @@ -347,13 +343,13 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { fun Attachments(attachments: List, onClick: (Slide) -> Unit) { val slide = attachments.firstOrNull()?.slide ?: return when { - slide.hasImage() -> ImageAttachments(attachments) { onClick(it) } + slide.hasImage() -> Carousel(attachments, onClick) } } @OptIn(ExperimentalFoundationApi::class,) @Composable - fun ImageAttachments(attachments: List, onClick: (Slide) -> Unit) { + fun Carousel(attachments: List, onClick: (Slide) -> Unit) { val imageAttachments = attachments.filter { it.slide.hasImage() } val pagerState = rememberPagerState { imageAttachments.size } @@ -361,7 +357,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Row { CarouselPrevButton(pagerState) Box(modifier = Modifier.weight(1f)) { - CellPager(pagerState, imageAttachments) { onClick(it) } + CellPager(pagerState, imageAttachments, onClick) HorizontalPagerIndicator(pagerState) ExpandButton(modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp)) } @@ -415,14 +411,14 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { verticalArrangement = Arrangement.spacedBy(16.dp), maxItemsInEachRow = 2 ) { - it.forEach { titledText(it, Modifier.weight(1f)) } + it.forEach { TitledText(it, Modifier.weight(1f)) } } } } } @Composable - fun titledText( + fun TitledText( titledText: TitledText, modifier: Modifier = Modifier, valueStyle: TextStyle = LocalTextStyle.current @@ -434,7 +430,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun titledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { + fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Title(title) content() From 7cd2bd0e0d8c1ca4cf98e82d2f40239fd1b45d35 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 15:11:40 +0930 Subject: [PATCH 28/51] Cleanup --- .../components/ProfilePictureView.kt | 4 + .../conversation/v2/MessageDetailActivity.kt | 76 +++++++++++-------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 37a3e764e..4ee4777c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -43,6 +43,10 @@ class ProfilePictureView @JvmOverloads constructor( // endregion + constructor(context: Context, sender: Recipient): this(context) { + update(sender) + } + // region Updating fun update(recipient: Recipient) { fun getUserDisplayName(publicKey: String): String { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 8d59dea73..b8abb7152 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -266,38 +267,17 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { fun MetaDataCell( messageDetails: MessageDetails, ) { - if (messageDetails.sent != null || messageDetails.received != null || messageDetails.senderInfo != null) CellWithPaddingAndMargin { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - messageDetails.sent?.let { TitledText(it) } - messageDetails.received?.let { TitledText(it) } - messageDetails.error?.let { - TitledText( - it, - valueStyle = LocalTextStyle.current.copy(color = colorDestructive) - ) - } - messageDetails.senderInfo?.let { - TitledView("From:") { - Row { - messageDetails.sender?.let { sender -> - Box( - modifier = Modifier - .width(60.dp) - .align(Alignment.CenterVertically) - ) { - AndroidView( - factory = { - ProfilePictureView(it).apply { update(sender) } - }, - modifier = Modifier.width(46.dp).height(46.dp) - ) - } - } - Column { - TitledText( - it, - valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) - ) + messageDetails.apply { + if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + sent?.let { TitledText(it) } + received?.let { TitledText(it) } + error?.let { TitledErrorText(it) } + senderInfo?.let { + TitledView("From:") { + Row { + sender?.let { Avatar(it) } + TitledMonospaceText(it) } } } @@ -306,6 +286,22 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } + @Composable + fun RowScope.Avatar(sender: Recipient) { + Box( + modifier = Modifier + .width(60.dp) + .align(Alignment.CenterVertically) + ) { + AndroidView( + factory = { + ProfilePictureView(it).apply { update(sender) } + }, + modifier = Modifier.width(46.dp).height(46.dp) + ) + } + } + @Composable fun Buttons( hasError: Boolean, @@ -417,6 +413,22 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } + @Composable + fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) { + TitledText( + titledText, + modifier = modifier, + valueStyle = LocalTextStyle.current.copy(color = colorDestructive)) + } + + @Composable + fun TitledMonospaceText(titledText: TitledText, modifier: Modifier = Modifier) { + TitledText( + titledText, + modifier = modifier, + valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) + } + @Composable fun TitledText( titledText: TitledText, From d6b14402175bb3307f51874628c2b89dc7783b58 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 15:27:00 +0930 Subject: [PATCH 29/51] Refactor MessageDetailsViewModel --- .../conversation/v2/MessageDetailActivity.kt | 92 ------------------- .../v2/MessageDetailsViewModel.kt | 87 ++++++++++++++++++ 2 files changed, 87 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index b8abb7152..7a0c24e15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -39,23 +39,15 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment -import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView -import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.database.model.MessageRecord -import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme @@ -70,8 +62,6 @@ import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.blackAlpha40 import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors -import java.util.* -import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -94,70 +84,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { val viewModel = MessageDetailsViewModel() - class MessageDetailsViewModel : ViewModel() { - @Inject - lateinit var attachmentDb: AttachmentDatabase - - fun setMessageRecord(value: MessageRecord?, error: String?) { - val mmsRecord = value as? MmsMessageRecord - - val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() - - _details.value = value?.run { - MessageDetails( - mmsRecord = mmsRecord, - attachments = slides.map { slide -> - val duration = slide.takeIf { it.hasAudio() } - ?.let { it.asAttachment() as? DatabaseAttachment } - ?.let { attachment -> - attachmentDb.getAttachmentAudioExtras(attachment.attachmentId) - ?.let { audioExtras -> - audioExtras.durationMs.takeIf { it > 0 }?.let { - String.format( - "%01d:%02d", - TimeUnit.MILLISECONDS.toMinutes(it), - TimeUnit.MILLISECONDS.toSeconds(it) % 60 - ) - } - } - } - - val details = slide.run { - listOfNotNull( - fileName.orNull()?.let { TitledText("File Id:", it) }, - TitledText("File Type:", asAttachment().contentType), - TitledText("File Size:", Util.getPrettyFileSize(fileSize)), - if (slide.hasImage()) { - TitledText( - "Resolution:", - slide.asAttachment().run { "${width}x$height" }) - } else null, - duration?.let { TitledText("Duration:", it) }, - ) - } - Attachment(slide, details) - }, - sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, - received = dateReceived.let(::Date).toString() - .let { TitledText("Received:", it) }, - error = error?.let { TitledText("Error:", it) }, - senderInfo = individualRecipient.run { - name?.let { - TitledText( - it, - address.serialize() - ) - } - }, - sender = individualRecipient - ) - } - } - - private var _details = MutableLiveData(MessageDetails()) - val details: LiveData = _details - } - override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) @@ -204,24 +130,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { finish() } - data class TitledText(val title: String, val value: String) - - data class MessageDetails( - val attachments: List = emptyList(), - val mmsRecord: MmsMessageRecord? = null, - - val sent: TitledText? = null, - val received: TitledText? = null, - val error: TitledText? = null, - val senderInfo: TitledText? = null, - val sender: Recipient? = null - ) - - data class Attachment( - val slide: Slide, - val fileDetails: List - ) - @Preview @Composable fun PreviewMessageDetails() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt new file mode 100644 index 000000000..80693a167 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -0,0 +1,87 @@ +package org.thoughtcrime.securesms.conversation.v2 + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.utilities.Util +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.database.AttachmentDatabase +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import org.thoughtcrime.securesms.mms.Slide +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +data class TitledText(val title: String, val value: String) + +data class MessageDetails( + val attachments: List = emptyList(), + val mmsRecord: MmsMessageRecord? = null, + val sent: TitledText? = null, + val received: TitledText? = null, + val error: TitledText? = null, + val senderInfo: TitledText? = null, + val sender: Recipient? = null +) + +data class Attachment( + val slide: Slide, + val fileDetails: List +) + +class MessageDetailsViewModel : ViewModel() { + @Inject + lateinit var attachmentDb: AttachmentDatabase + + fun setMessageRecord(value: MessageRecord?, error: String?) { + val mmsRecord = value as? MmsMessageRecord + + val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() + + _details.value = value?.run { + MessageDetails( + mmsRecord = mmsRecord, + attachments = slides.map { Attachment(it, it.details) }, + sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, + received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, + error = error?.let { TitledText("Error:", it) }, + senderInfo = individualRecipient.run { + name?.let { TitledText(it, address.serialize()) } + }, + sender = individualRecipient + ) + } + } + + private var _details = MutableLiveData(MessageDetails()) + val details: LiveData = _details + + private val Slide.details: List + get() = listOfNotNull( + fileName.orNull()?.let { TitledText("File Id:", it) }, + TitledText("File Type:", asAttachment().contentType), + TitledText("File Size:", Util.getPrettyFileSize(fileSize)), + takeIf { it.hasImage() } + .run { asAttachment().run { "${width}x$height" } } + .let { TitledText("Resolution:", it) }, + attachmentDb.duration(this)?.let { TitledText("Duration:", it) }, + ) + + private fun AttachmentDatabase.duration(slide: Slide): String? = + slide.takeIf { it.hasAudio() } + ?.run { asAttachment() as? DatabaseAttachment } + ?.run { + getAttachmentAudioExtras(attachmentId) + ?.let { audioExtras -> + audioExtras.durationMs.takeIf { it > 0 }?.let { + String.format( + "%01d:%02d", + TimeUnit.MILLISECONDS.toMinutes(it), + TimeUnit.MILLISECONDS.toSeconds(it) % 60 + ) + } + } + } +} \ No newline at end of file From 8d66d948ca34d052c5bea08d09aafa783911f075 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 4 Jul 2023 22:10:48 +0930 Subject: [PATCH 30/51] Polish --- app/build.gradle | 15 ++- .../conversation/v2/MessageDetailActivity.kt | 97 +++++++++++++------ .../v2/MessageDetailsViewModel.kt | 9 +- .../v2/messages/VisibleMessageContentView.kt | 11 ++- .../v2/messages/VisibleMessageView.kt | 14 +-- .../thoughtcrime/securesms/ui/Components.kt | 6 +- build.gradle | 5 + 7 files changed, 112 insertions(+), 45 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4bb54eb20..64f641918 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,4 @@ + buildscript { repositories { google() @@ -13,6 +14,11 @@ buildscript { } } +plugins { + id 'kotlin-kapt' + id 'com.google.dagger.hilt.android' +} + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'witness' @@ -29,6 +35,9 @@ configurations.all { dependencies { + implementation("com.google.dagger:hilt-android:2.46.1") + kapt("com.google.dagger:hilt-android-compiler:2.44") + implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "com.google.android.material:material:$materialVersion" @@ -41,7 +50,6 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion" @@ -328,3 +336,8 @@ def autoResConfig() { .collect { matcher -> matcher.group(1) } .sort() } + +// Allow references to generated code +kapt { + correctErrorTypes = true +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 7a0c24e15..c7838047b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Intent import android.os.Bundle +import android.view.LayoutInflater +import androidx.activity.viewModels import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -39,16 +41,24 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.lifecycleScope import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import network.loki.messenger.R +import network.loki.messenger.databinding.ViewVisibleMessageBinding +import network.loki.messenger.databinding.ViewVisibleMessageContentBinding +import org.session.libsession.messaging.jobs.AttachmentDownloadJob +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.CarouselNextButton @@ -64,7 +74,6 @@ import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors import javax.inject.Inject - @AndroidEntryPoint class MessageDetailActivity : PassphraseRequiredActionBarActivity() { @@ -73,6 +82,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { @Inject lateinit var storage: Storage + private val viewModel: MessageDetailsViewModel by viewModels() + companion object { // Extras const val MESSAGE_TIMESTAMP = "message_timestamp" @@ -82,8 +93,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { const val ON_DELETE = 3 } - val viewModel = MessageDetailsViewModel() - override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) @@ -116,7 +125,12 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { onResend = { setResultAndFinish(ON_RESEND) }, onDelete = { setResultAndFinish(ON_DELETE) }, onClickImage = { slide -> - MediaPreviewActivity.getPreviewIntent(this, slide, details.mmsRecord, details.sender) + MediaPreviewActivity.getPreviewIntent( + this, + slide, + details.mmsRecord, + details.sender + ) .let(::startActivity) } ) @@ -144,6 +158,12 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) } + private fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) { + lifecycleScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + } + } + @Composable fun MessageDetails( messageDetails: MessageDetails, @@ -152,27 +172,39 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { onDelete: () -> Unit = {}, onClickImage: (Slide) -> Unit = {}, ) { - messageDetails.apply { - AppTheme { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Attachments(attachments) { onClickImage(it) } - MetaDataCell(messageDetails) - Buttons( - error != null, - onReply, - onResend, - onDelete, + AppTheme { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + messageDetails.mmsRecord?.let { message -> + AndroidView( + modifier = Modifier.padding(32.dp), + factory = { + ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { + bind( + message, + thread = message.individualRecipient, + onAttachmentNeedsDownload = ::onAttachmentNeedsDownload + ) + } + } ) } + Attachments(messageDetails.attachments) { onClickImage(it) } + MetadataCell(messageDetails) + Buttons( + messageDetails.error != null, + onReply, + onResend, + onDelete, + ) } } } @Composable - fun MetaDataCell( + fun MetadataCell( messageDetails: MessageDetails, ) { messageDetails.apply { @@ -205,7 +237,9 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { factory = { ProfilePictureView(it).apply { update(sender) } }, - modifier = Modifier.width(46.dp).height(46.dp) + modifier = Modifier + .width(46.dp) + .height(46.dp) ) } } @@ -245,13 +279,12 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { @Composable fun Attachments(attachments: List, onClick: (Slide) -> Unit) { - val slide = attachments.firstOrNull()?.slide ?: return - when { - slide.hasImage() -> Carousel(attachments, onClick) + when(attachments.firstOrNull()?.slide) { + is ImageSlide -> Carousel(attachments, onClick) } } - @OptIn(ExperimentalFoundationApi::class,) + @OptIn(ExperimentalFoundationApi::class) @Composable fun Carousel(attachments: List, onClick: (Slide) -> Unit) { val imageAttachments = attachments.filter { it.slide.hasImage() } @@ -263,7 +296,11 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { Box(modifier = Modifier.weight(1f)) { CellPager(pagerState, imageAttachments, onClick) HorizontalPagerIndicator(pagerState) - ExpandButton(modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp)) + ExpandButton( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(8.dp) + ) } CarouselNextButton(pagerState) } @@ -276,7 +313,11 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ExperimentalGlideComposeApi::class ) @Composable - private fun CellPager(pagerState: PagerState, imageAttachments: List, onClick: (Slide) -> Unit) { + private fun CellPager( + pagerState: PagerState, + imageAttachments: List, + onClick: (Slide) -> Unit + ) { CellNoMargin { HorizontalPager(state = pagerState) { i -> val slide = imageAttachments[i].slide @@ -326,7 +367,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { TitledText( titledText, modifier = modifier, - valueStyle = LocalTextStyle.current.copy(color = colorDestructive)) + valueStyle = LocalTextStyle.current.copy(color = colorDestructive) + ) } @Composable @@ -334,7 +376,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { TitledText( titledText, modifier = modifier, - valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)) + valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) + ) } @Composable diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 80693a167..b92a39934 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.conversation.v2 import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.HiltViewModel import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient @@ -31,9 +33,10 @@ data class Attachment( val fileDetails: List ) -class MessageDetailsViewModel : ViewModel() { - @Inject - lateinit var attachmentDb: AttachmentDatabase +@HiltViewModel +class MessageDetailsViewModel @Inject constructor( + private val attachmentDb: AttachmentDatabase +): ViewModel() { fun setMessageRecord(value: MessageRecord?, error: String?) { val mmsRecord = value as? MmsMessageRecord diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 75a3c5875..c2133c714 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getInt import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.SmsMessageRecord +import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.getAccentColor @@ -60,12 +61,12 @@ class VisibleMessageContentView : ConstraintLayout { // region Updating fun bind( message: MessageRecord, - isStartOfMessageCluster: Boolean, - isEndOfMessageCluster: Boolean, - glide: GlideRequests, + isStartOfMessageCluster: Boolean = true, + isEndOfMessageCluster: Boolean = true, + glide: GlideRequests = GlideApp.with(this), thread: Recipient, - searchQuery: String?, - contactIsTrusted: Boolean, + searchQuery: String? = null, + contactIsTrusted: Boolean = true, onAttachmentNeedsDownload: (Long, Long) -> Unit ) { // Background diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 40a087ea6..79056ed9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.UserDetailsBottomSheet +import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.disableClipping @@ -70,7 +71,6 @@ class VisibleMessageView : LinearLayout { @Inject lateinit var mmsDb: MmsDatabase private val binding by lazy { ViewVisibleMessageBinding.bind(this) } - private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate() private val swipeToReplyIconRect = Rect() private var dx = 0.0f @@ -119,13 +119,13 @@ class VisibleMessageView : LinearLayout { // region Updating fun bind( message: MessageRecord, - previous: MessageRecord?, - next: MessageRecord?, - glide: GlideRequests, - searchQuery: String?, - contact: Contact?, + previous: MessageRecord? = null, + next: MessageRecord? = null, + glide: GlideRequests = GlideApp.with(this), + searchQuery: String? = null, + contact: Contact? = null, senderSessionID: String, - delegate: VisibleMessageViewDelegate?, + delegate: VisibleMessageViewDelegate? = null, onAttachmentNeedsDownload: (Long, Long) -> Unit ) { val threadID = message.threadId 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 c70b557ee..34c9f0b8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -93,11 +93,13 @@ fun CellWithPaddingAndMargin( @OptIn(ExperimentalFoundationApi::class) @Composable fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { - Card(shape = RoundedCornerShape(50.dp), + if (pagerState.pageCount >= 2) Card( + shape = RoundedCornerShape(50.dp), backgroundColor = Color.Black.copy(alpha = 0.4f), modifier = Modifier .align(Alignment.BottomCenter) - .padding(8.dp)) { + .padding(8.dp) + ) { Box(modifier = Modifier.padding(8.dp)) { HorizontalPagerIndicator( pagerState = pagerState, diff --git a/build.gradle b/build.gradle index 39701076c..e7bc8cae8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,4 @@ + buildscript { repositories { google() @@ -12,6 +13,10 @@ buildscript { } } +plugins{ + id("com.google.dagger.hilt.android") version "2.44" apply false +} + allprojects { repositories { google() From efb5b27191ffcb2114e61913b09a67f7cba3787c Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 12:03:33 +0930 Subject: [PATCH 31/51] Fix divider color --- app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt index 67c44c636..88916e03e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -29,7 +29,7 @@ fun AppTheme( val extraColors = LocalContext.current.run { ExtraColors( cell = getColorFromTheme(R.attr.colorCellBackground), - divider = getColorFromTheme(R.attr.dividerHorizontal), + divider = getColorFromTheme(R.attr.dividerHorizontal).copy(alpha = 0.15f), settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground) ) } From 68684bb8397bfc74dee3d83930f36ae4f671c9e7 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 13:09:00 +0930 Subject: [PATCH 32/51] Refactor VisibleMessageContentView#onContentClick --- .../securesms/conversation/v2/MessageDetailActivity.kt | 1 - .../conversation/v2/messages/VisibleMessageContentView.kt | 7 ++++++- .../conversation/v2/messages/VisibleMessageView.kt | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index c7838047b..96fcb596f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -48,7 +48,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import network.loki.messenger.R -import network.loki.messenger.databinding.ViewVisibleMessageBinding import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index c2133c714..b081d548d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -47,7 +47,6 @@ import kotlin.math.roundToInt class VisibleMessageContentView : ConstraintLayout { private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) } - var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf() var onContentDoubleTap: (() -> Unit)? = null var delegate: VisibleMessageViewDelegate? = null var indexInAdapter: Int = -1 @@ -242,6 +241,12 @@ class VisibleMessageContentView : ConstraintLayout { binding.contentParent.layoutParams = layoutParams } + private val onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf() + + fun onContentClick(event: MotionEvent) { + onContentClick.forEach { clickHandler -> clickHandler.invoke(event) } + } + private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean = listOf(albumThumbnailView.root, linkPreviewView.root, voiceMessageView.root, quoteView.root).none { it.isVisible } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 79056ed9c..55916dc80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context import android.content.Intent -import android.content.res.Resources import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.ColorDrawable @@ -502,7 +501,7 @@ class VisibleMessageView : LinearLayout { } fun onContentClick(event: MotionEvent) { - binding.messageContentView.root.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) } + binding.messageContentView.root.onContentClick(event) } private fun onPress(event: MotionEvent) { From 8be1e8e87e419036c9a2b2c71263cda52f268c41 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 15:20:45 +0930 Subject: [PATCH 33/51] Wire up voiceMessageView --- .../conversation/v2/MessageDetailActivity.kt | 32 ++++++------------- .../thoughtcrime/securesms/ui/Components.kt | 21 ++++++++++++ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 96fcb596f..3dc001f00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -1,8 +1,10 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.MotionEvent.ACTION_UP import androidx.activity.viewModels import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable @@ -12,11 +14,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState @@ -51,15 +50,14 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme +import org.thoughtcrime.securesms.ui.Avatar import org.thoughtcrime.securesms.ui.CarouselNextButton import org.thoughtcrime.securesms.ui.CarouselPrevButton import org.thoughtcrime.securesms.ui.Cell @@ -163,6 +161,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } + @SuppressLint("ClickableViewAccessibility") @Composable fun MessageDetails( messageDetails: MessageDetails, @@ -186,6 +185,11 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { thread = message.individualRecipient, onAttachmentNeedsDownload = ::onAttachmentNeedsDownload ) + + setOnTouchListener { _, event -> + if (event.actionMasked == ACTION_UP) onContentClick(event) + true + } } } ) @@ -225,24 +229,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } } - @Composable - fun RowScope.Avatar(sender: Recipient) { - Box( - modifier = Modifier - .width(60.dp) - .align(Alignment.CenterVertically) - ) { - AndroidView( - factory = { - ProfilePictureView(it).apply { update(sender) } - }, - modifier = Modifier - .width(46.dp) - .height(46.dp) - ) - } - } - @Composable fun Buttons( hasError: Boolean, 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 34c9f0b8b..b7684a22f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -30,9 +30,12 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import com.google.accompanist.pager.HorizontalPagerIndicator import kotlinx.coroutines.launch import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.components.ProfilePictureView @Composable fun ItemButton( @@ -154,3 +157,21 @@ fun Divider() { color = LocalExtraColors.current.divider ) } + +@Composable +fun RowScope.Avatar(recipient: Recipient) { + Box( + modifier = Modifier + .width(60.dp) + .align(Alignment.CenterVertically) + ) { + AndroidView( + factory = { + ProfilePictureView(it).apply { update(recipient) } + }, + modifier = Modifier + .width(46.dp) + .height(46.dp) + ) + } +} From c417b37236b3dbcfd6d98c2a96cf0e3782214479 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 15:23:58 +0930 Subject: [PATCH 34/51] Hide thumbnails --- .../securesms/conversation/v2/MessageDetailActivity.kt | 3 ++- .../conversation/v2/messages/VisibleMessageContentView.kt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 3dc001f00..acba309c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -183,7 +183,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { bind( message, thread = message.individualRecipient, - onAttachmentNeedsDownload = ::onAttachmentNeedsDownload + onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, + suppressThumbnails = true ) setOnTouchListener { _, event -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index b081d548d..d1989a207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -66,7 +66,8 @@ class VisibleMessageContentView : ConstraintLayout { thread: Recipient, searchQuery: String? = null, contactIsTrusted: Boolean = true, - onAttachmentNeedsDownload: (Long, Long) -> Unit + onAttachmentNeedsDownload: (Long, Long) -> Unit, + suppressThumbnails: Boolean = false ) { // Background val background = getBackground(message.isOutgoing) @@ -188,7 +189,7 @@ class VisibleMessageContentView : ConstraintLayout { onContentClick.add { binding.untrustedView.root.showTrustDialog(message.individualRecipient) } } } - message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty() -> { + message is MmsMessageRecord && !suppressThumbnails && message.slideDeck.asAttachments().isNotEmpty() -> { /* * Images / Video attachment */ From 1d1977ca7a2b608e619b0a12c0857e2dc1434fac Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 15:32:51 +0930 Subject: [PATCH 35/51] Make expand button clickable --- .../securesms/conversation/v2/MessageDetailActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index acba309c5..41b049975 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -284,6 +284,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { HorizontalPagerIndicator(pagerState) ExpandButton( modifier = Modifier + .clickable { onClick(imageAttachments[pagerState.currentPage].slide) } .align(Alignment.BottomEnd) .padding(8.dp) ) From fc87ae18f558090a16886ea4fdf1a1eb98b9848e Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 15:48:31 +0930 Subject: [PATCH 36/51] Fix message padding --- .../securesms/conversation/v2/MessageDetailActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 41b049975..07230eade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.MotionEvent.ACTION_UP import androidx.activity.viewModels import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -31,6 +32,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource @@ -177,7 +179,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) { messageDetails.mmsRecord?.let { message -> AndroidView( - modifier = Modifier.padding(32.dp), + modifier = Modifier.padding(horizontal = 32.dp), factory = { ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { bind( From e5b19d4ea4e6e1c52076ac8f46c3259c21626a9f Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 6 Jul 2023 19:05:09 +0930 Subject: [PATCH 37/51] Fix incorrect recipient used as threadRecipient to launch MediaPreview --- .../securesms/MediaPreviewActivity.java | 1 - .../conversation/v2/MessageDetailActivity.kt | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 6544c2ab8..a1648e076 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -531,7 +531,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im @Override public void onLoadFinished(@NonNull Loader> loader, @Nullable Pair data) { if (data != null) { - @SuppressWarnings("ConstantConditions") CursorPagerAdapter adapter = new CursorPagerAdapter(this, GlideApp.with(this), getWindow(), data.first, data.second, leftIsRecent); mediaPager.setAdapter(adapter); adapter.setActive(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 07230eade..d8f8a9d7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -7,7 +7,6 @@ import android.view.LayoutInflater import android.view.MotionEvent.ACTION_UP import androidx.activity.viewModels import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -32,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource @@ -52,6 +50,8 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import org.session.libsession.messaging.jobs.AttachmentDownloadJob import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress +import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage @@ -124,13 +124,19 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { onResend = { setResultAndFinish(ON_RESEND) }, onDelete = { setResultAndFinish(ON_DELETE) }, onClickImage = { slide -> - MediaPreviewActivity.getPreviewIntent( + // only open to downloaded images + if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { + // Restart download here (on IO thread) + (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> + onAttachmentNeedsDownload(attachment.attachmentId.rowId, details.mmsRecord!!.getId()) + } + } + if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( this, slide, details.mmsRecord, - details.sender - ) - .let(::startActivity) + DatabaseComponent.get(this).threadDatabase().getRecipientForThreadId(details.mmsRecord!!.threadId), + ).let(::startActivity) } ) } @@ -184,7 +190,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { bind( message, - thread = message.individualRecipient, + thread = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase().getRecipientForThreadId(message.threadId)!!, onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, suppressThumbnails = true ) @@ -286,10 +292,9 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { HorizontalPagerIndicator(pagerState) ExpandButton( modifier = Modifier - .clickable { onClick(imageAttachments[pagerState.currentPage].slide) } .align(Alignment.BottomEnd) .padding(8.dp) - ) + ) { onClick(imageAttachments[pagerState.currentPage].slide) } } CarouselNextButton(pagerState) } @@ -323,7 +328,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun ExpandButton(modifier: Modifier) { + fun ExpandButton(modifier: Modifier, onClick: () -> Unit) { Surface( shape = CircleShape, color = blackAlpha40, @@ -331,7 +336,8 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ) { Icon( painter = painterResource(id = R.drawable.ic_expand), - contentDescription = "" + contentDescription = "", + modifier = Modifier.clickable { onClick() } ) } } From d8b85768d2240b95755a26267ced1ed978d1a503 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 7 Jul 2023 10:46:43 +0930 Subject: [PATCH 38/51] Fix sms messages --- .../securesms/conversation/v2/MessageDetailActivity.kt | 2 +- .../securesms/conversation/v2/MessageDetailsViewModel.kt | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index d8f8a9d7f..f95f25719 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -183,7 +183,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - messageDetails.mmsRecord?.let { message -> + messageDetails.record?.let { message -> AndroidView( modifier = Modifier.padding(horizontal = 32.dp), factory = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index b92a39934..01864aeb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -20,6 +20,7 @@ data class TitledText(val title: String, val value: String) data class MessageDetails( val attachments: List = emptyList(), + val record: MessageRecord? = null, val mmsRecord: MmsMessageRecord? = null, val sent: TitledText? = null, val received: TitledText? = null, @@ -38,13 +39,14 @@ class MessageDetailsViewModel @Inject constructor( private val attachmentDb: AttachmentDatabase ): ViewModel() { - fun setMessageRecord(value: MessageRecord?, error: String?) { - val mmsRecord = value as? MmsMessageRecord + fun setMessageRecord(record: MessageRecord?, error: String?) { + val mmsRecord = record as? MmsMessageRecord val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() - _details.value = value?.run { + _details.value = record?.run { MessageDetails( + record = record, mmsRecord = mmsRecord, attachments = slides.map { Attachment(it, it.details) }, sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, From bbc9cdfeeb5751ec8dbedce3aed71bacc0a46b42 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 9 Jul 2023 23:31:43 +0930 Subject: [PATCH 39/51] Cleanup ViewModel --- .../v2/MessageDetailsViewModel.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 01864aeb1..5ff1e0517 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2 import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.lifecycle.HiltViewModel import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util @@ -11,6 +10,7 @@ import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.Slide import java.util.* import java.util.concurrent.TimeUnit @@ -32,12 +32,14 @@ data class MessageDetails( data class Attachment( val slide: Slide, val fileDetails: List -) +) { + fun hasImage() = slide is ImageSlide +} @HiltViewModel class MessageDetailsViewModel @Inject constructor( private val attachmentDb: AttachmentDatabase -): ViewModel() { +) : ViewModel() { fun setMessageRecord(record: MessageRecord?, error: String?) { val mmsRecord = record as? MmsMessageRecord @@ -77,16 +79,13 @@ class MessageDetailsViewModel @Inject constructor( private fun AttachmentDatabase.duration(slide: Slide): String? = slide.takeIf { it.hasAudio() } ?.run { asAttachment() as? DatabaseAttachment } - ?.run { - getAttachmentAudioExtras(attachmentId) - ?.let { audioExtras -> - audioExtras.durationMs.takeIf { it > 0 }?.let { - String.format( - "%01d:%02d", - TimeUnit.MILLISECONDS.toMinutes(it), - TimeUnit.MILLISECONDS.toSeconds(it) % 60 - ) - } - } + ?.run { getAttachmentAudioExtras(attachmentId)?.durationMs } + ?.takeIf { it > 0 } + ?.let { + String.format( + "%01d:%02d", + TimeUnit.MILLISECONDS.toMinutes(it), + TimeUnit.MILLISECONDS.toSeconds(it) % 60 + ) } -} \ No newline at end of file +} From 0824713ac5220c1e807b8a34daea5778bf099ad6 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 10 Jul 2023 00:12:42 +0930 Subject: [PATCH 40/51] Fix theming and polish --- .../conversation/v2/MessageDetailActivity.kt | 511 ++++++++++-------- .../org/thoughtcrime/securesms/ui/Colors.kt | 61 ++- .../thoughtcrime/securesms/ui/Components.kt | 51 +- .../org/thoughtcrime/securesms/ui/Themes.kt | 53 +- app/src/main/res/values/strings.xml | 4 + app/src/main/res/values/themes.xml | 2 - 6 files changed, 408 insertions(+), 274 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index f95f25719..46eeb4ecc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -13,9 +13,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState @@ -31,9 +34,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -55,8 +60,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Avatar @@ -68,6 +73,7 @@ import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator import org.thoughtcrime.securesms.ui.ItemButton +import org.thoughtcrime.securesms.ui.Theme import org.thoughtcrime.securesms.ui.blackAlpha40 import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors @@ -118,27 +124,32 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { @Composable private fun MessageDetailsScreen() { val details by viewModel.details.observeAsState(MessageDetails()) - MessageDetails( - details, - onReply = { setResultAndFinish(ON_REPLY) }, - onResend = { setResultAndFinish(ON_RESEND) }, - onDelete = { setResultAndFinish(ON_DELETE) }, - onClickImage = { slide -> - // only open to downloaded images - if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { - // Restart download here (on IO thread) - (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> - onAttachmentNeedsDownload(attachment.attachmentId.rowId, details.mmsRecord!!.getId()) + val threadDb = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase() + AppTheme { + MessageDetails( + threadDb = threadDb, + messageDetails = details, + onReply = { setResultAndFinish(ON_REPLY) }, + onResend = { setResultAndFinish(ON_RESEND) }, + onDelete = { setResultAndFinish(ON_DELETE) }, + onClickImage = { slide -> + // only open to downloaded images + if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { + // Restart download here (on IO thread) + (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> + onAttachmentNeedsDownload(attachment.attachmentId.rowId, details.mmsRecord!!.getId()) + } } - } - if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( - this, - slide, - details.mmsRecord, - DatabaseComponent.get(this).threadDatabase().getRecipientForThreadId(details.mmsRecord!!.threadId), - ).let(::startActivity) - } - ) + if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( + this, + slide, + details.mmsRecord, + threadDb.getRecipientForThreadId(details.mmsRecord!!.threadId), + ).let(::startActivity) + }, + onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, + ) + } } private fun setResultAndFinish(code: Int) { @@ -149,254 +160,276 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { finish() } - @Preview - @Composable - fun PreviewMessageDetails() { - MessageDetails( - MessageDetails( - attachments = listOf(), - sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), - received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), - error = TitledText("Error:", "Message failed to send"), - senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg"), - ) - ) - } - private fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) { lifecycleScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } } - @SuppressLint("ClickableViewAccessibility") - @Composable - fun MessageDetails( - messageDetails: MessageDetails, - onReply: () -> Unit = {}, - onResend: () -> Unit = {}, - onDelete: () -> Unit = {}, - onClickImage: (Slide) -> Unit = {}, - ) { - AppTheme { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - messageDetails.record?.let { message -> - AndroidView( - modifier = Modifier.padding(horizontal = 32.dp), - factory = { - ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { - bind( - message, - thread = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase().getRecipientForThreadId(message.threadId)!!, - onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, - suppressThumbnails = true - ) +} - setOnTouchListener { _, event -> - if (event.actionMasked == ACTION_UP) onContentClick(event) - true - } - } - } - ) - } - Attachments(messageDetails.attachments) { onClickImage(it) } - MetadataCell(messageDetails) - Buttons( - messageDetails.error != null, - onReply, - onResend, - onDelete, - ) - } - } +@Composable +fun PreviewMessageDetails() { + AppTheme { + MessageDetails( + messageDetails = MessageDetails( + attachments = listOf(), + sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), + received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), + error = TitledText("Error:", "Message failed to send"), + senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"), + ) + ) } +} - @Composable - fun MetadataCell( - messageDetails: MessageDetails, +@SuppressLint("ClickableViewAccessibility") +@Composable +fun MessageDetails( + threadDb: ThreadDatabase? = null, + messageDetails: MessageDetails, + onReply: () -> Unit = {}, + onResend: (() -> Unit)? = null, + onDelete: () -> Unit = {}, + onClickImage: (Slide) -> Unit = {}, + onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> } +) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { - messageDetails.apply { - if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - sent?.let { TitledText(it) } - received?.let { TitledText(it) } - error?.let { TitledErrorText(it) } - senderInfo?.let { - TitledView("From:") { - Row { - sender?.let { Avatar(it) } - TitledMonospaceText(it) - } + messageDetails.record?.let { message -> + AndroidView( + modifier = Modifier.padding(horizontal = 32.dp), + factory = { + ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { + bind( + message, + thread = threadDb?.getRecipientForThreadId(message.threadId)!!, + onAttachmentNeedsDownload = onAttachmentNeedsDownload, + suppressThumbnails = true + ) + + setOnTouchListener { _, event -> + if (event.actionMasked == ACTION_UP) onContentClick(event) + true + } + } + } + ) + } + Carousel(messageDetails.attachments) { onClickImage(it) } + MetadataCell(messageDetails) + Buttons( + onReply, + onResend, + onDelete, + ) + } +} + +@Composable +fun MetadataCell( + messageDetails: MessageDetails, +) { + messageDetails.apply { + if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + sent?.let { TitledText(it) } + received?.let { TitledText(it) } + error?.let { TitledErrorText(it) } + senderInfo?.let { + TitledView(stringResource(id = R.string.message_details_header__from)) { + Row { + sender?.let { Avatar(it) } + TitledMonospaceText(it) } } } } } } +} - @Composable - fun Buttons( - hasError: Boolean, - onReply: () -> Unit = {}, - onResend: () -> Unit = {}, - onDelete: () -> Unit = {}, - ) { - Cell { - Column { +@Composable +fun Buttons( + onReply: () -> Unit = {}, + onResend: (() -> Unit)? = null, + onDelete: () -> Unit = {}, +) { + Cell { + Column { + ItemButton( + stringResource(id = R.string.reply), + R.drawable.ic_message_details__reply, + onClick = onReply + ) + Divider() + onResend?.let { ItemButton( - "Reply", - R.drawable.ic_message_details__reply, - onClick = onReply + stringResource(id = R.string.resend), + R.drawable.ic_message_details__refresh, + onClick = it ) Divider() - if (hasError) { - ItemButton( - "Resend", - R.drawable.ic_message_details__refresh, - onClick = onResend - ) - Divider() - } - ItemButton( - "Delete", - R.drawable.ic_message_details__trash, - colors = destructiveButtonColors(), - onClick = onDelete - ) } - } - } - - @Composable - fun Attachments(attachments: List, onClick: (Slide) -> Unit) { - when(attachments.firstOrNull()?.slide) { - is ImageSlide -> Carousel(attachments, onClick) - } - } - - @OptIn(ExperimentalFoundationApi::class) - @Composable - fun Carousel(attachments: List, onClick: (Slide) -> Unit) { - val imageAttachments = attachments.filter { it.slide.hasImage() } - val pagerState = rememberPagerState { imageAttachments.size } - - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - Row { - CarouselPrevButton(pagerState) - Box(modifier = Modifier.weight(1f)) { - CellPager(pagerState, imageAttachments, onClick) - HorizontalPagerIndicator(pagerState) - ExpandButton( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(8.dp) - ) { onClick(imageAttachments[pagerState.currentPage].slide) } - } - CarouselNextButton(pagerState) - } - FileDetails(attachments, pagerState) - } - } - - @OptIn( - ExperimentalFoundationApi::class, - ExperimentalGlideComposeApi::class - ) - @Composable - private fun CellPager( - pagerState: PagerState, - imageAttachments: List, - onClick: (Slide) -> Unit - ) { - CellNoMargin { - HorizontalPager(state = pagerState) { i -> - val slide = imageAttachments[i].slide - GlideImage( - contentScale = ContentScale.Crop, - modifier = Modifier - .aspectRatio(1f) - .clickable { onClick(slide) }, - model = slide.uri, - contentDescription = slide.fileName.orNull() ?: "image" - ) - } - } - } - - @Composable - fun ExpandButton(modifier: Modifier, onClick: () -> Unit) { - Surface( - shape = CircleShape, - color = blackAlpha40, - modifier = modifier - ) { - Icon( - painter = painterResource(id = R.drawable.ic_expand), - contentDescription = "", - modifier = Modifier.clickable { onClick() } + ItemButton( + stringResource(id = R.string.delete), + R.drawable.ic_message_details__trash, + colors = destructiveButtonColors(), + onClick = onDelete ) } } +} - @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) - @Composable - fun FileDetails(attachments: List, pagerState: PagerState) { - attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let { - CellWithPaddingAndMargin { - FlowRow( - verticalArrangement = Arrangement.spacedBy(16.dp), - maxItemsInEachRow = 2 - ) { - it.forEach { TitledText(it, Modifier.weight(1f)) } - } +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun Carousel(attachments: List, onClick: (Slide) -> Unit) { + val imageAttachments = attachments.filter { it.hasImage() }.takeIf { it.isNotEmpty() } ?: return + val pagerState = rememberPagerState { imageAttachments.size } + + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Row { + CarouselPrevButton(pagerState) + Box(modifier = Modifier.weight(1f)) { + CellCarousel(pagerState, imageAttachments, onClick) + HorizontalPagerIndicator(pagerState) + ExpandButton( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(8.dp) + ) { onClick(imageAttachments[pagerState.currentPage].slide) } + } + CarouselNextButton(pagerState) + } + FileDetails(attachments, pagerState) + } +} + +@OptIn( + ExperimentalFoundationApi::class, + ExperimentalGlideComposeApi::class +) +@Composable +private fun CellCarousel( + pagerState: PagerState, + imageAttachments: List, + onClick: (Slide) -> Unit +) { + CellNoMargin { + HorizontalPager(state = pagerState) { i -> + val slide = imageAttachments[i].slide + GlideImage( + contentScale = ContentScale.Crop, + modifier = Modifier + .aspectRatio(1f) + .clickable { onClick(slide) }, + model = slide.uri, + contentDescription = slide.fileName.orNull() ?: stringResource(id = R.string.image) + ) + } + } +} + +@Composable +fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) { + Surface( + shape = CircleShape, + color = blackAlpha40, + modifier = modifier, + contentColor = Color.White, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_expand), + contentDescription = "", + modifier = Modifier.clickable { onClick() }, + ) + } +} + +@Preview +@Composable +fun PreviewFileDetails() { + Theme(R.style.Ocean_Dark) { + FileDetails( + fileDetails = listOf( + TitledText("File Id:", "Screen Shot 2023-07-06 at 11.35.50 am.png"), + TitledText("File Type:", "image/png"), + TitledText("File Size:", "195.6kB"), + TitledText("Resolution:", "342x312"), + ) + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun FileDetails(attachments: List, pagerState: PagerState) { + FileDetails(attachments[pagerState.currentPage].fileDetails) +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun FileDetails(fileDetails: List) { + if (fileDetails.isEmpty()) return + + CellWithPaddingAndMargin { + FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) { + fileDetails.forEach { + TitledText( + it, + modifier = Modifier + .widthIn(min = 100.dp) // set minimum width + .width(IntrinsicSize.Max) // make the text as wide as necessary + .weight(1f) // space evenly + ) } } } +} - @Composable - fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) { - TitledText( - titledText, - modifier = modifier, - valueStyle = LocalTextStyle.current.copy(color = colorDestructive) - ) - } +@Composable +fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) { + TitledText( + titledText, + modifier = modifier, + valueStyle = LocalTextStyle.current.copy(color = colorDestructive) + ) +} - @Composable - fun TitledMonospaceText(titledText: TitledText, modifier: Modifier = Modifier) { - TitledText( - titledText, - modifier = modifier, - valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) - ) - } +@Composable +fun TitledMonospaceText(titledText: TitledText, modifier: Modifier = Modifier) { + TitledText( + titledText, + modifier = modifier, + valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) + ) +} - @Composable - fun TitledText( - titledText: TitledText, - modifier: Modifier = Modifier, - valueStyle: TextStyle = LocalTextStyle.current - ) { - Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { - Title(titledText.title) - Text(titledText.value, style = valueStyle) - } - } - - @Composable - fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { - Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { - Title(title) - content() - } - } - - @Composable - fun Title(text: String) { - Text(text, fontWeight = FontWeight.Bold) +@Composable +fun TitledText( + titledText: TitledText, + modifier: Modifier = Modifier, + valueStyle: TextStyle = LocalTextStyle.current +) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { + Title(titledText.title) + Text(titledText.value, style = valueStyle) } } + +@Composable +fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { + Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { + Title(title) + content() + } +} + +@Composable +fun Title(text: String, modifier: Modifier = Modifier) { + Text(text, modifier = modifier, fontWeight = FontWeight.Bold) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt index cace0c6af..d63b46d8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt @@ -1,27 +1,60 @@ package org.thoughtcrime.securesms.ui import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color val colorDestructive = Color(0xffFF453A) -val classicDark0 = Color(0xff111111) +const val classicDark0 = 0xff111111 +const val classicDark1 = 0xff1B1B1B +const val classicDark2 = 0xff2D2D2D +const val classicDark3 = 0xff414141 +const val classicDark4 = 0xff767676 +const val classicDark5 = 0xffA1A2A1 +const val classicDark6 = 0xffFFFFFF -val classicDark1 = Color(0xff1B1B1B) -val classicDark2 = Color(0xff2D2D2D) -val classicDark3 = Color(0xff414141) -val classicDark4 = Color(0xff767676) -val classicDark5 = Color(0xffA1A2A1) -val classicDark6 = Color(0xffFFFFFF) -val classicLight0 = Color(0xff000000) -val classicLight1 = Color(0xff6D6D6D) -val classicLight2 = Color(0xffA1A2A1) -val classicLight3 = Color(0xffDFDFDF) -val classicLight4 = Color(0xffF0F0F0) -val classicLight5 = Color(0xffF9F9F9) -val classicLight6 = Color(0xffFFFFFF) + +const val classicLight0 = 0xff000000 +const val classicLight1 = 0xff6D6D6D +const val classicLight2 = 0xffA1A2A1 +const val classicLight3 = 0xffDFDFDF +const val classicLight4 = 0xffF0F0F0 +const val classicLight5 = 0xffF9F9F9 +const val classicLight6 = 0xffFFFFFF + +const val oceanDark0 = 0xff000000 +const val oceanDark1 = 0xff1A1C28 +const val oceanDark2 = 0xff252735 +const val oceanDark3 = 0xff2B2D40 +const val oceanDark4 = 0xff3D4A5D +const val oceanDark5 = 0xffA6A9CE +const val oceanDark6 = 0xff5CAACC +const val oceanDark7 = 0xffFFFFFF + +const val oceanLight0 = 0xff000000 +const val oceanLight1 = 0xff19345D +const val oceanLight2 = 0xff6A6E90 +const val oceanLight3 = 0xff5CAACC +const val oceanLight4 = 0xffB3EDF2 +const val oceanLight5 = 0xffE7F3F4 +const val oceanLight6 = 0xffECFAFB +const val oceanLight7 = 0xffFCFFFF + +val ocean_accent = Color(0xff57C9FA) + +val oceanLights = arrayOf(oceanLight0, oceanLight1, oceanLight2, oceanLight3, oceanLight4, oceanLight5, oceanLight6, oceanLight7) +val oceanDarks = arrayOf(oceanDark0, oceanDark1, oceanDark2, oceanDark3, oceanDark4, oceanDark5, oceanDark6, oceanDark7) +val classicLights = arrayOf(classicLight0, classicLight1, classicLight2, classicLight3, classicLight4, classicLight5, classicLight6) +val classicDarks = arrayOf(classicDark0, classicDark1, classicDark2, classicDark3, classicDark4, classicDark5, classicDark6) + +val oceanLightColors = oceanLights.map(::Color) +val oceanDarkColors = oceanDarks.map(::Color) +val classicLightColors = classicLights.map(::Color) +val classicDarkColors = classicDarks.map(::Color) val blackAlpha40 = Color.Black.copy(alpha = 0.4f) 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 b7684a22f..8589f3e7e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -1,32 +1,50 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes +import androidx.appcompat.view.ContextThemeWrapper import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollableDefaults +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.FlowRowScope +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonColors import androidx.compose.material.Card +import androidx.compose.material.Colors import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -36,6 +54,13 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.ProfilePictureView +import kotlin.math.roundToInt + +private val Colors.cellColors: Colors + @Composable + get() = MaterialTheme.colors.copy( + surface = LocalExtraColors.current.settingsBackground, + ) @Composable fun ItemButton( @@ -80,17 +105,18 @@ fun CellWithPaddingAndMargin( margin: Dp = 32.dp, content: @Composable () -> Unit ) { - Card( - shape = RoundedCornerShape(16.dp), - elevation = 0.dp, - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(horizontal = margin), - backgroundColor = LocalExtraColors.current.settingsBackground, - // probably wrong - contentColor = MaterialTheme.colors.onSurface - ) { Box(Modifier.padding(padding)) { content() } } + MaterialTheme(colors = MaterialTheme.colors.cellColors) { + Card( + shape = RoundedCornerShape(16.dp), + elevation = 0.dp, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = margin), + ) { + Box(Modifier.padding(padding)) { content() } + } + } } @OptIn(ExperimentalFoundationApi::class) @@ -108,7 +134,7 @@ fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { pagerState = pagerState, pageCount = pagerState.pageCount, activeColor = Color.White, - inactiveColor = classicDark5) + inactiveColor = classicDarkColors[5]) } } } @@ -154,7 +180,6 @@ fun RowScope.CarouselButton( fun Divider() { androidx.compose.material.Divider( modifier = Modifier.padding(horizontal = 16.dp), - color = LocalExtraColors.current.divider ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt index 88916e03e..59402b681 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -2,25 +2,36 @@ package org.thoughtcrime.securesms.ui import android.content.Context import androidx.annotation.AttrRes +import androidx.annotation.StyleRes +import androidx.appcompat.view.ContextThemeWrapper +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import com.google.android.material.color.MaterialColors import network.loki.messenger.R +import org.thoughtcrime.securesms.conversation.v2.PreviewMessageDetails val LocalExtraColors = staticCompositionLocalOf { error("No Custom Attribute value provided") } data class ExtraColors( - val cell: Color, - val divider: Color, val settingsBackground: Color, ) -fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color = +fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color = try { MaterialColors.getColor(this, attr, defaultValue).let(::Color) +} catch (e: Exception) { + colorDestructive +} @Composable fun AppTheme( @@ -28,9 +39,7 @@ fun AppTheme( ) { val extraColors = LocalContext.current.run { ExtraColors( - cell = getColorFromTheme(R.attr.colorCellBackground), - divider = getColorFromTheme(R.attr.dividerHorizontal).copy(alpha = 0.15f), - settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground) + settingsBackground = getColorFromTheme(R.attr.colorSettingsBackground), ) } @@ -40,3 +49,35 @@ fun AppTheme( } } } + +@Preview +@Composable +fun PreviewMessageDetails( + @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int +) { + Theme(themeResId) { + Box(modifier = Modifier.background(color = MaterialTheme.colors.background)) { + PreviewMessageDetails() + } + } +} + +@Composable +fun Theme(@StyleRes themeResId: Int, content: @Composable () -> Unit) { + CompositionLocalProvider( + LocalContext provides ContextThemeWrapper(LocalContext.current, themeResId) + ) { + AppTheme { + content() + } + } +} + +class ThemeResPreviewParameterProvider : PreviewParameterProvider { + override val values = sequenceOf( + R.style.Classic_Dark, + R.style.Classic_Light, + R.style.Ocean_Dark, + R.style.Ocean_Light, + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7948c4f2..1c718932f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,9 +4,12 @@ Yes No Delete + Resend + Reply Ban Please wait… Save + Image Note to Self Version %s @@ -516,6 +519,7 @@ To: From: With: + Create passphrase Select contacts diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index bc8563850..ad03b3e38 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -490,7 +490,6 @@ ?android:textColorPrimary @color/ocean_dark_5 ?colorPrimary - @color/default_background_start @color/navigation_bar ?colorPrimary ?colorPrimaryDark @@ -575,7 +574,6 @@ @color/ocean_light_6 @color/ocean_light_navigation_bar ?colorPrimary - @color/default_background_start @color/ocean_light_7 @color/ocean_light_6 @color/ocean_light_5 From b26c98af6894e0010f0bd42d59aab1b7b656f3e3 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 10 Jul 2023 00:35:13 +0930 Subject: [PATCH 41/51] Hide Resend if no error --- .../securesms/conversation/v2/MessageDetailActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 46eeb4ecc..95c1a874a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -130,7 +130,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { threadDb = threadDb, messageDetails = details, onReply = { setResultAndFinish(ON_REPLY) }, - onResend = { setResultAndFinish(ON_RESEND) }, + onResend = details.error?.let { { setResultAndFinish(ON_RESEND) } }, onDelete = { setResultAndFinish(ON_DELETE) }, onClickImage = { slide -> // only open to downloaded images From 821327569e8069e46b27ed1d92d1391bd234a637 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 10 Jul 2023 01:05:09 +0930 Subject: [PATCH 42/51] Cleanup Themes --- .../conversation/v2/MessageDetailActivity.kt | 20 +++++++++-- .../org/thoughtcrime/securesms/ui/Themes.kt | 35 +++++++++---------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 95c1a874a..ba94353b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.lifecycleScope @@ -73,7 +74,8 @@ import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator import org.thoughtcrime.securesms.ui.ItemButton -import org.thoughtcrime.securesms.ui.Theme +import org.thoughtcrime.securesms.ui.PreviewTheme +import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider import org.thoughtcrime.securesms.ui.blackAlpha40 import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors @@ -352,8 +354,10 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) { @Preview @Composable -fun PreviewFileDetails() { - Theme(R.style.Ocean_Dark) { +fun PreviewFileDetails( + @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int +) { + PreviewTheme(themeResId) { FileDetails( fileDetails = listOf( TitledText("File Id:", "Screen Shot 2023-07-06 at 11.35.50 am.png"), @@ -365,6 +369,16 @@ fun PreviewFileDetails() { } } +@Preview +@Composable +fun PreviewMessageDetails( + @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int +) { + PreviewTheme(themeResId) { + PreviewMessageDetails() + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable fun FileDetails(attachments: List, pagerState: PagerState) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt index 59402b681..2216c404e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -23,16 +23,14 @@ import org.thoughtcrime.securesms.conversation.v2.PreviewMessageDetails val LocalExtraColors = staticCompositionLocalOf { error("No Custom Attribute value provided") } + data class ExtraColors( val settingsBackground: Color, ) -fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color = try { - MaterialColors.getColor(this, attr, defaultValue).let(::Color) -} catch (e: Exception) { - colorDestructive -} - +/** + * Converts current Theme to Compose Theme. + */ @Composable fun AppTheme( content: @Composable () -> Unit @@ -50,25 +48,24 @@ fun AppTheme( } } -@Preview -@Composable -fun PreviewMessageDetails( - @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int -) { - Theme(themeResId) { - Box(modifier = Modifier.background(color = MaterialTheme.colors.background)) { - PreviewMessageDetails() - } - } -} +fun Context.getColorFromTheme(@AttrRes attr: Int, defaultValue: Int = 0x0): Color = + MaterialColors.getColor(this, attr, defaultValue).let(::Color) +/** + * Set the theme and a background for Compose Previews. + */ @Composable -fun Theme(@StyleRes themeResId: Int, content: @Composable () -> Unit) { +fun PreviewTheme( + themeResId: Int, + content: @Composable () -> Unit +) { CompositionLocalProvider( LocalContext provides ContextThemeWrapper(LocalContext.current, themeResId) ) { AppTheme { - content() + Box(modifier = Modifier.background(color = MaterialTheme.colors.background)) { + content() + } } } } From a1e8ad2c37cf5f60c42440b377facd7dbe200b00 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 10 Jul 2023 13:01:46 +0930 Subject: [PATCH 43/51] Cleanup ViewModel --- .../conversation/v2/MessageDetailActivity.kt | 112 ++++++++---------- .../v2/MessageDetailsViewModel.kt | 50 +++++--- .../org/thoughtcrime/securesms/ui/Colors.kt | 2 - .../thoughtcrime/securesms/ui/Components.kt | 31 +++-- app/src/main/res/values/strings.xml | 1 + 5 files changed, 101 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index ba94353b4..33690da4a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -10,13 +10,16 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.pager.HorizontalPager @@ -61,9 +64,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.Avatar import org.thoughtcrime.securesms.ui.CarouselNextButton @@ -103,21 +103,15 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) - timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) - - val messageRecord = - DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run { - finish() - return - } - - val error = DatabaseComponent.get(this).lokiMessageDatabase() - .getErrorMessage(messageRecord.getId()) - - viewModel.setMessageRecord(messageRecord, error) - title = resources.getString(R.string.conversation_context__menu_message_details) + intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp) + + if (viewModel.details.value == null) { + finish() + return + } + ComposeView(this) .apply { setContent { MessageDetailsScreen() } } .let(::setContentView) @@ -125,28 +119,27 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { @Composable private fun MessageDetailsScreen() { - val details by viewModel.details.observeAsState(MessageDetails()) - val threadDb = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase() + val state by viewModel.details.observeAsState(MessageDetailsState()) AppTheme { MessageDetails( - threadDb = threadDb, - messageDetails = details, + state = state, onReply = { setResultAndFinish(ON_REPLY) }, - onResend = details.error?.let { { setResultAndFinish(ON_RESEND) } }, + onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } }, onDelete = { setResultAndFinish(ON_DELETE) }, - onClickImage = { slide -> + onClickImage = { i -> + val slide = state.attachments[i].slide // only open to downloaded images if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { // Restart download here (on IO thread) (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> - onAttachmentNeedsDownload(attachment.attachmentId.rowId, details.mmsRecord!!.getId()) + onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord!!.getId()) } } if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( this, slide, - details.mmsRecord, - threadDb.getRecipientForThreadId(details.mmsRecord!!.threadId), + state.mmsRecord, + state.thread, ).let(::startActivity) }, onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, @@ -174,7 +167,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { fun PreviewMessageDetails() { AppTheme { MessageDetails( - messageDetails = MessageDetails( + state = MessageDetailsState( attachments = listOf(), sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), @@ -188,12 +181,11 @@ fun PreviewMessageDetails() { @SuppressLint("ClickableViewAccessibility") @Composable fun MessageDetails( - threadDb: ThreadDatabase? = null, - messageDetails: MessageDetails, + state: MessageDetailsState, onReply: () -> Unit = {}, onResend: (() -> Unit)? = null, onDelete: () -> Unit = {}, - onClickImage: (Slide) -> Unit = {}, + onClickImage: (Int) -> Unit = {}, onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> } ) { Column( @@ -202,14 +194,14 @@ fun MessageDetails( .padding(vertical = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - messageDetails.record?.let { message -> + state.record?.let { message -> AndroidView( modifier = Modifier.padding(horizontal = 32.dp), factory = { ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { bind( message, - thread = threadDb?.getRecipientForThreadId(message.threadId)!!, + thread = state.thread!!, onAttachmentNeedsDownload = onAttachmentNeedsDownload, suppressThumbnails = true ) @@ -222,8 +214,9 @@ fun MessageDetails( } ) } - Carousel(messageDetails.attachments) { onClickImage(it) } - MetadataCell(messageDetails) + Carousel(state.imageAttachments) { onClickImage(it) } + state.nonImageAttachment?.fileDetails?.let { FileDetails(it) } + MetadataCell(state) Buttons( onReply, onResend, @@ -234,9 +227,9 @@ fun MessageDetails( @Composable fun MetadataCell( - messageDetails: MessageDetails, + state: MessageDetailsState, ) { - messageDetails.apply { + state.apply { if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { sent?.let { TitledText(it) } @@ -289,25 +282,26 @@ fun Buttons( @OptIn(ExperimentalFoundationApi::class) @Composable -fun Carousel(attachments: List, onClick: (Slide) -> Unit) { - val imageAttachments = attachments.filter { it.hasImage() }.takeIf { it.isNotEmpty() } ?: return - val pagerState = rememberPagerState { imageAttachments.size } +fun Carousel(attachments: List, onClick: (Int) -> Unit) { + if (attachments.isEmpty()) return + + val pagerState = rememberPagerState { attachments.size } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Row { CarouselPrevButton(pagerState) Box(modifier = Modifier.weight(1f)) { - CellCarousel(pagerState, imageAttachments, onClick) + CellCarousel(pagerState, attachments, onClick) HorizontalPagerIndicator(pagerState) ExpandButton( modifier = Modifier .align(Alignment.BottomEnd) .padding(8.dp) - ) { onClick(imageAttachments[pagerState.currentPage].slide) } + ) { onClick(pagerState.currentPage) } } CarouselNextButton(pagerState) } - FileDetails(attachments, pagerState) + attachments.getOrNull(pagerState.currentPage)?.fileDetails?.let { FileDetails(it) } } } @@ -318,19 +312,18 @@ fun Carousel(attachments: List, onClick: (Slide) -> Unit) { @Composable private fun CellCarousel( pagerState: PagerState, - imageAttachments: List, - onClick: (Slide) -> Unit + attachments: List, + onClick: (Int) -> Unit ) { CellNoMargin { HorizontalPager(state = pagerState) { i -> - val slide = imageAttachments[i].slide GlideImage( contentScale = ContentScale.Crop, modifier = Modifier .aspectRatio(1f) - .clickable { onClick(slide) }, - model = slide.uri, - contentDescription = slide.fileName.orNull() ?: stringResource(id = R.string.image) + .clickable { onClick(i) }, + model = attachments[i].uri, + contentDescription = attachments[i].fileName ?: stringResource(id = R.string.image) ) } } @@ -346,7 +339,7 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) { ) { Icon( painter = painterResource(id = R.drawable.ic_expand), - contentDescription = "", + contentDescription = stringResource(id = R.string.expand), modifier = Modifier.clickable { onClick() }, ) } @@ -379,12 +372,6 @@ fun PreviewMessageDetails( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun FileDetails(attachments: List, pagerState: PagerState) { - FileDetails(attachments[pagerState.currentPage].fileDetails) -} - @OptIn(ExperimentalLayoutApi::class) @Composable fun FileDetails(fileDetails: List) { @@ -393,13 +380,14 @@ fun FileDetails(fileDetails: List) { CellWithPaddingAndMargin { FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) { fileDetails.forEach { - TitledText( - it, - modifier = Modifier - .widthIn(min = 100.dp) // set minimum width - .width(IntrinsicSize.Max) // make the text as wide as necessary - .weight(1f) // space evenly - ) + BoxWithConstraints { + TitledText( + it, + modifier = Modifier + .widthIn(min = maxWidth.div(2)) + .width(IntrinsicSize.Max) + ) + } } } } @@ -431,7 +419,7 @@ fun TitledText( ) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Title(titledText.title) - Text(titledText.value, style = valueStyle) + Text(titledText.value, style = valueStyle, modifier = Modifier.fillMaxWidth()) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 5ff1e0517..6bf4223b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -3,11 +3,19 @@ package org.thoughtcrime.securesms.conversation.v2 import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.session.libsession.messaging.jobs.AttachmentDownloadJob +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.AttachmentDatabase +import org.thoughtcrime.securesms.database.LokiMessageDatabase +import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.ImageSlide @@ -18,7 +26,7 @@ import javax.inject.Inject data class TitledText(val title: String, val value: String) -data class MessageDetails( +data class MessageDetailsState( val attachments: List = emptyList(), val record: MessageRecord? = null, val mmsRecord: MmsMessageRecord? = null, @@ -26,44 +34,57 @@ data class MessageDetails( val received: TitledText? = null, val error: TitledText? = null, val senderInfo: TitledText? = null, - val sender: Recipient? = null -) + val sender: Recipient? = null, + val thread: Recipient? = null, +) { + val imageAttachments = attachments.filter { it.hasImage() } + val nonImageAttachment: Attachment? = attachments.firstOrNull { !it.hasImage() } +} data class Attachment( val slide: Slide, val fileDetails: List ) { + val fileName: String? get() = slide.fileName.orNull() + val uri get() = slide.uri + fun hasImage() = slide is ImageSlide } @HiltViewModel class MessageDetailsViewModel @Inject constructor( - private val attachmentDb: AttachmentDatabase + private val attachmentDb: AttachmentDatabase, + private val lokiMessageDatabase: LokiMessageDatabase, + private val mmsSmsDatabase: MmsSmsDatabase, + private val threadDb: ThreadDatabase, ) : ViewModel() { - fun setMessageRecord(record: MessageRecord?, error: String?) { + fun setMessageTimestamp(timestamp: Long) { + mmsSmsDatabase.getMessageForTimestamp(timestamp).let(::setMessageRecord) + } + + fun setMessageRecord(record: MessageRecord?) { val mmsRecord = record as? MmsMessageRecord val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() _details.value = record?.run { - MessageDetails( + MessageDetailsState( + attachments = slides.map { Attachment(it, it.details) }, record = record, mmsRecord = mmsRecord, - attachments = slides.map { Attachment(it, it.details) }, sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, - error = error?.let { TitledText("Error:", it) }, - senderInfo = individualRecipient.run { - name?.let { TitledText(it, address.serialize()) } - }, - sender = individualRecipient + error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText("Error:", it) }, + senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, + sender = individualRecipient, + thread = threadDb.getRecipientForThreadId(threadId)!!, ) } } - private var _details = MutableLiveData(MessageDetails()) - val details: LiveData = _details + private var _details = MutableLiveData(MessageDetailsState()) + val details: LiveData = _details private val Slide.details: List get() = listOfNotNull( @@ -88,4 +109,5 @@ class MessageDetailsViewModel @Inject constructor( TimeUnit.MILLISECONDS.toSeconds(it) % 60 ) } + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt index d63b46d8d..55bc1be62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Colors.kt @@ -16,8 +16,6 @@ const val classicDark4 = 0xff767676 const val classicDark5 = 0xffA1A2A1 const val classicDark6 = 0xffFFFFFF - - const val classicLight0 = 0xff000000 const val classicLight1 = 0xff6D6D6D const val classicLight2 = 0xffA1A2A1 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 8589f3e7e..0b712467f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -56,12 +56,6 @@ import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.ProfilePictureView import kotlin.math.roundToInt -private val Colors.cellColors: Colors - @Composable - get() = MaterialTheme.colors.copy( - surface = LocalExtraColors.current.settingsBackground, - ) - @Composable fun ItemButton( text: String, @@ -105,20 +99,23 @@ fun CellWithPaddingAndMargin( margin: Dp = 32.dp, content: @Composable () -> Unit ) { - MaterialTheme(colors = MaterialTheme.colors.cellColors) { - Card( - shape = RoundedCornerShape(16.dp), - elevation = 0.dp, - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(horizontal = margin), - ) { - Box(Modifier.padding(padding)) { content() } - } + Card( + backgroundColor = MaterialTheme.colors.cellColor, + shape = RoundedCornerShape(16.dp), + elevation = 0.dp, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .padding(horizontal = margin), + ) { + Box(Modifier.padding(padding)) { content() } } } +private val Colors.cellColor: Color + @Composable + get() = LocalExtraColors.current.settingsBackground + @OptIn(ExperimentalFoundationApi::class) @Composable fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c718932f..ff5303e6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,7 @@ Image Note to Self Version %s + Expand Create session ID From 09b321530d8ed0b7954b87d81b305d7ed4fd0a03 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 10 Jul 2023 16:09:38 +0930 Subject: [PATCH 44/51] Add strings --- .../conversation/v2/MessageDetailActivity.kt | 100 +++++++----------- .../v2/MessageDetailsViewModel.kt | 94 ++++++++-------- .../thoughtcrime/securesms/ui/Components.kt | 5 +- .../org/thoughtcrime/securesms/ui/Data.kt | 34 ++++++ .../org/thoughtcrime/securesms/ui/Themes.kt | 4 - app/src/main/res/values/strings.xml | 5 + 6 files changed, 128 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 33690da4a..33ef14824 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.pager.HorizontalPager @@ -72,10 +71,12 @@ import org.thoughtcrime.securesms.ui.Cell import org.thoughtcrime.securesms.ui.CellNoMargin import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Divider +import org.thoughtcrime.securesms.ui.GetString import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator import org.thoughtcrime.securesms.ui.ItemButton import org.thoughtcrime.securesms.ui.PreviewTheme import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider +import org.thoughtcrime.securesms.ui.TitledText import org.thoughtcrime.securesms.ui.blackAlpha40 import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.destructiveButtonColors @@ -160,22 +161,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) } } - -} - -@Composable -fun PreviewMessageDetails() { - AppTheme { - MessageDetails( - state = MessageDetailsState( - attachments = listOf(), - sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), - received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), - error = TitledText("Error:", "Message failed to send"), - senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"), - ) - ) - } } @SuppressLint("ClickableViewAccessibility") @@ -215,9 +200,9 @@ fun MessageDetails( ) } Carousel(state.imageAttachments) { onClickImage(it) } - state.nonImageAttachment?.fileDetails?.let { FileDetails(it) } - MetadataCell(state) - Buttons( + state.nonImageAttachmentFileDetails?.let { FileDetails(it) } + CellMetadata(state) + CellButtons( onReply, onResend, onDelete, @@ -226,17 +211,18 @@ fun MessageDetails( } @Composable -fun MetadataCell( +fun CellMetadata( state: MessageDetailsState, ) { state.apply { - if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { + if (listOfNotNull(sent, received, error, senderInfo).isEmpty()) return + CellWithPaddingAndMargin { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - sent?.let { TitledText(it) } - received?.let { TitledText(it) } - error?.let { TitledErrorText(it) } + TitledText(sent) + TitledText(received) + TitledErrorText(error) senderInfo?.let { - TitledView(stringResource(id = R.string.message_details_header__from)) { + TitledView(state.fromTitle) { Row { sender?.let { Avatar(it) } TitledMonospaceText(it) @@ -249,7 +235,7 @@ fun MetadataCell( } @Composable -fun Buttons( +fun CellButtons( onReply: () -> Unit = {}, onResend: (() -> Unit)? = null, onDelete: () -> Unit = {}, @@ -257,21 +243,21 @@ fun Buttons( Cell { Column { ItemButton( - stringResource(id = R.string.reply), + stringResource(R.string.reply), R.drawable.ic_message_details__reply, onClick = onReply ) Divider() onResend?.let { ItemButton( - stringResource(id = R.string.resend), + stringResource(R.string.resend), R.drawable.ic_message_details__refresh, onClick = it ) Divider() } ItemButton( - stringResource(id = R.string.delete), + stringResource(R.string.delete), R.drawable.ic_message_details__trash, colors = destructiveButtonColors(), onClick = onDelete @@ -345,22 +331,6 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) { } } -@Preview -@Composable -fun PreviewFileDetails( - @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int -) { - PreviewTheme(themeResId) { - FileDetails( - fileDetails = listOf( - TitledText("File Id:", "Screen Shot 2023-07-06 at 11.35.50 am.png"), - TitledText("File Type:", "image/png"), - TitledText("File Size:", "195.6kB"), - TitledText("Resolution:", "342x312"), - ) - ) - } -} @Preview @Composable @@ -368,7 +338,20 @@ fun PreviewMessageDetails( @PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int ) { PreviewTheme(themeResId) { - PreviewMessageDetails() + MessageDetails( + state = MessageDetailsState( + nonImageAttachmentFileDetails = listOf( + TitledText(R.string.message_details_header__file_id, "Screen Shot 2023-07-06 at 11.35.50 am.png"), + TitledText(R.string.message_details_header__file_type, "image/png"), + TitledText(R.string.message_details_header__file_size, "195.6kB"), + TitledText(R.string.message_details_header__resolution, "342x312"), + ), + sent = TitledText(R.string.message_details_header__sent, "6:12 AM Tue, 09/08/2022"), + received = TitledText(R.string.message_details_header__received, "6:12 AM Tue, 09/08/2022"), + error = TitledText(R.string.message_details_header__error, "Message failed to send"), + senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54"), + ) + ) } } @@ -394,37 +377,36 @@ fun FileDetails(fileDetails: List) { } @Composable -fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) { +fun TitledErrorText(titledText: TitledText?) { TitledText( titledText, - modifier = modifier, valueStyle = LocalTextStyle.current.copy(color = colorDestructive) ) } @Composable -fun TitledMonospaceText(titledText: TitledText, modifier: Modifier = Modifier) { +fun TitledMonospaceText(titledText: TitledText?) { TitledText( titledText, - modifier = modifier, valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace) ) } @Composable fun TitledText( - titledText: TitledText, + titledText: TitledText?, modifier: Modifier = Modifier, - valueStyle: TextStyle = LocalTextStyle.current + valueStyle: TextStyle = LocalTextStyle.current, ) { - Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { - Title(titledText.title) - Text(titledText.value, style = valueStyle, modifier = Modifier.fillMaxWidth()) + titledText?.apply { + TitledView(title, modifier) { + Text(text, style = valueStyle, modifier = Modifier.fillMaxWidth()) + } } } @Composable -fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) { +fun TitledView(title: GetString, modifier: Modifier = Modifier, content: @Composable () -> Unit) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Title(title) content() @@ -432,6 +414,6 @@ fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composabl } @Composable -fun Title(text: String, modifier: Modifier = Modifier) { - Text(text, modifier = modifier, fontWeight = FontWeight.Bold) +fun Title(title: GetString) { + Text(title.string(), fontWeight = FontWeight.Bold) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 6bf4223b4..890c82c0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -3,12 +3,8 @@ package org.thoughtcrime.securesms.conversation.v2 import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.lifecycleScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.JobQueue +import network.loki.messenger.R import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient @@ -20,37 +16,12 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.Slide -import java.util.* +import org.thoughtcrime.securesms.ui.GetString +import org.thoughtcrime.securesms.ui.TitledText +import java.util.Date import java.util.concurrent.TimeUnit import javax.inject.Inject -data class TitledText(val title: String, val value: String) - -data class MessageDetailsState( - val attachments: List = emptyList(), - val record: MessageRecord? = null, - val mmsRecord: MmsMessageRecord? = null, - val sent: TitledText? = null, - val received: TitledText? = null, - val error: TitledText? = null, - val senderInfo: TitledText? = null, - val sender: Recipient? = null, - val thread: Recipient? = null, -) { - val imageAttachments = attachments.filter { it.hasImage() } - val nonImageAttachment: Attachment? = attachments.firstOrNull { !it.hasImage() } -} - -data class Attachment( - val slide: Slide, - val fileDetails: List -) { - val fileName: String? get() = slide.fileName.orNull() - val uri get() = slide.uri - - fun hasImage() = slide is ImageSlide -} - @HiltViewModel class MessageDetailsViewModel @Inject constructor( private val attachmentDb: AttachmentDatabase, @@ -60,22 +31,18 @@ class MessageDetailsViewModel @Inject constructor( ) : ViewModel() { fun setMessageTimestamp(timestamp: Long) { - mmsSmsDatabase.getMessageForTimestamp(timestamp).let(::setMessageRecord) - } - - fun setMessageRecord(record: MessageRecord?) { + val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) ?: return val mmsRecord = record as? MmsMessageRecord - val slides: List = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() + _details.value = record.run { + val slides = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() - _details.value = record?.run { MessageDetailsState( attachments = slides.map { Attachment(it, it.details) }, record = record, - mmsRecord = mmsRecord, - sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, - received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, - error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText("Error:", it) }, + sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) }, + received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) }, + error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) }, senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, sender = individualRecipient, thread = threadDb.getRecipientForThreadId(threadId)!!, @@ -88,13 +55,14 @@ class MessageDetailsViewModel @Inject constructor( private val Slide.details: List get() = listOfNotNull( - fileName.orNull()?.let { TitledText("File Id:", it) }, - TitledText("File Type:", asAttachment().contentType), - TitledText("File Size:", Util.getPrettyFileSize(fileSize)), - takeIf { it.hasImage() } - .run { asAttachment().run { "${width}x$height" } } - .let { TitledText("Resolution:", it) }, - attachmentDb.duration(this)?.let { TitledText("Duration:", it) }, + fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) }, + TitledText(R.string.message_details_header__file_type, asAttachment().contentType), + TitledText(R.string.message_details_header__file_size, Util.getPrettyFileSize(fileSize)), + takeIf { it is ImageSlide } + ?.let(Slide::asAttachment) + ?.run { "${width}x$height" } + ?.let { TitledText(R.string.message_details_header__resolution, it) }, + attachmentDb.duration(this)?.let { TitledText(R.string.message_details_header__duration, it) }, ) private fun AttachmentDatabase.duration(slide: Slide): String? = @@ -111,3 +79,29 @@ class MessageDetailsViewModel @Inject constructor( } } + +data class MessageDetailsState( + val attachments: List = emptyList(), + val imageAttachments: List = attachments.filter { it.hasImage() }, + val nonImageAttachmentFileDetails: List? = attachments.firstOrNull { !it.hasImage() }?.fileDetails, + val record: MessageRecord? = null, + val mmsRecord: MmsMessageRecord? = record as? MmsMessageRecord, + val sent: TitledText? = null, + val received: TitledText? = null, + val error: TitledText? = null, + val senderInfo: TitledText? = null, + val sender: Recipient? = null, + val thread: Recipient? = null, +) { + val fromTitle = GetString(R.string.message_details_header__from) +} + +data class Attachment( + val slide: Slide, + val fileDetails: List +) { + val fileName: String? get() = slide.fileName.orNull() + val uri get() = slide.uri + + fun hasImage() = slide is ImageSlide +} 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 0b712467f..883e3a209 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.appcompat.view.ContextThemeWrapper import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.FlingBehavior @@ -46,6 +47,7 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -61,6 +63,7 @@ fun ItemButton( text: String, @DrawableRes icon: Int, colors: ButtonColors = transparentButtonColors(), + contentDescription: String = text, onClick: () -> Unit ) { TextButton( @@ -76,7 +79,7 @@ fun ItemButton( .fillMaxHeight()) { Icon( painter = painterResource(id = icon), - contentDescription = "", + contentDescription = contentDescription, modifier = Modifier.align(Alignment.Center) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt new file mode 100644 index 000000000..44ff4a42d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Data.kt @@ -0,0 +1,34 @@ +package org.thoughtcrime.securesms.ui + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource + +/** + * Compatibility class to allow ViewModels to use strings and string resources interchangeably. + */ +sealed class GetString { + @Composable + abstract fun string(): String + data class FromString(val string: String): GetString() { + @Composable + override fun string(): String = string + } + data class FromResId(@StringRes val resId: Int): GetString() { + @Composable + override fun string(): String = stringResource(resId) + + } +} + +fun GetString(@StringRes resId: Int) = GetString.FromResId(resId) +fun GetString(string: String) = GetString.FromString(string) + + +/** + * Represents some text with an associated title. + */ +data class TitledText(val title: GetString, val text: String) { + constructor(title: String, text: String): this(GetString(title), text) + constructor(@StringRes title: Int, text: String): this(GetString(title), text) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt index 2216c404e..64bbd21d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Themes.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.ui import android.content.Context import androidx.annotation.AttrRes -import androidx.annotation.StyleRes import androidx.appcompat.view.ContextThemeWrapper import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -13,13 +12,10 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import com.google.android.material.color.MaterialColors import network.loki.messenger.R -import org.thoughtcrime.securesms.conversation.v2.PreviewMessageDetails val LocalExtraColors = staticCompositionLocalOf { error("No Custom Attribute value provided") } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff5303e6c..5afcb40b7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -520,6 +520,11 @@ To: From: With: + File Id: + File Type: + File Size: + Resolution: + Duration: Create passphrase From fb68aaede63f6b43ed11770b7007a9834b7a3139 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 11 Jul 2023 01:29:44 +0930 Subject: [PATCH 45/51] Cleanup Components imports --- .../thoughtcrime/securesms/ui/Components.kt | 20 ------------------- 1 file changed, 20 deletions(-) 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 883e3a209..1724bde8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -1,32 +1,17 @@ package org.thoughtcrime.securesms.ui import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.appcompat.view.ContextThemeWrapper import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.ScrollableDefaults -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.FlowRowScope -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonColors @@ -38,16 +23,12 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -56,7 +37,6 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.ProfilePictureView -import kotlin.math.roundToInt @Composable fun ItemButton( From 172f85ae4f5264bbccf6510c2266dd016fec818b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 11 Jul 2023 01:30:03 +0930 Subject: [PATCH 46/51] Fix some edge cases in fileDetails --- .../securesms/conversation/v2/MessageDetailActivity.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 33ef14824..5397c3073 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -360,14 +361,18 @@ fun PreviewMessageDetails( fun FileDetails(fileDetails: List) { if (fileDetails.isEmpty()) return - CellWithPaddingAndMargin { - FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) { + CellWithPaddingAndMargin(padding = 0.dp) { + FlowRow( + modifier = Modifier.padding(vertical = 24.dp, horizontal = 12.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { fileDetails.forEach { BoxWithConstraints { TitledText( it, modifier = Modifier .widthIn(min = maxWidth.div(2)) + .padding(horizontal = 12.dp) .width(IntrinsicSize.Max) ) } From 1845b60dace0f389bdfef5704ce06672847eb9a5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 11 Jul 2023 12:28:04 +0930 Subject: [PATCH 47/51] Refactor slide out of MessageDetailsActivity --- .../securesms/MediaPreviewActivity.java | 4 ++ .../securesms/MediaPreviewArgs.kt | 11 ++++ .../conversation/v2/MessageDetailActivity.kt | 42 +++--------- .../v2/MessageDetailsViewModel.kt | 66 ++++++++++++++----- 4 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index a1648e076..e9ee120e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -146,6 +146,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im } }; + public static Intent getPreviewIntent(Context context, MediaPreviewArgs args) { + return getPreviewIntent(context, args.getSlide(), args.getMmsRecord(), args.getThread()); + } + public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms, Recipient threadRecipient) { Intent previewIntent = null; if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt new file mode 100644 index 000000000..00e2c3d6d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewArgs.kt @@ -0,0 +1,11 @@ +package org.thoughtcrime.securesms + +import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.database.model.MmsMessageRecord +import org.thoughtcrime.securesms.mms.Slide + +data class MediaPreviewArgs( + val slide: Slide, + val mmsRecord: MmsMessageRecord?, + val thread: Recipient?, +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 5397c3073..8f2310195 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -49,18 +48,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.lifecycleScope +import androidx.core.content.ContextCompat import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding -import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage @@ -109,7 +102,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp) - if (viewModel.details.value == null) { + if (viewModel.state.value == null) { finish() return } @@ -117,34 +110,23 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { ComposeView(this) .apply { setContent { MessageDetailsScreen() } } .let(::setContentView) + + viewModel.event.observe(this) { + startActivity(MediaPreviewActivity.getPreviewIntent(this, it)) + } } @Composable private fun MessageDetailsScreen() { - val state by viewModel.details.observeAsState(MessageDetailsState()) + val state by viewModel.state.observeAsState(MessageDetailsState()) AppTheme { MessageDetails( state = state, onReply = { setResultAndFinish(ON_REPLY) }, onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } }, onDelete = { setResultAndFinish(ON_DELETE) }, - onClickImage = { i -> - val slide = state.attachments[i].slide - // only open to downloaded images - if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { - // Restart download here (on IO thread) - (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> - onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord!!.getId()) - } - } - if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( - this, - slide, - state.mmsRecord, - state.thread, - ).let(::startActivity) - }, - onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, + onClickImage = { viewModel.onClickImage(it) }, + onAttachmentNeedsDownload = viewModel::onAttachmentNeedsDownload, ) } } @@ -156,12 +138,6 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { finish() } - - private fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) { - lifecycleScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) - } - } } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 890c82c0a..ad4bf24d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -1,13 +1,21 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import network.loki.messenger.R +import org.session.libsession.messaging.jobs.AttachmentDownloadJob +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient +import org.thoughtcrime.securesms.MediaPreviewArgs import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase @@ -30,15 +38,21 @@ class MessageDetailsViewModel @Inject constructor( private val threadDb: ThreadDatabase, ) : ViewModel() { + private var _state = MutableLiveData(MessageDetailsState()) + val state: LiveData = _state + + private var _event = MutableLiveData() + val event: LiveData = _event + fun setMessageTimestamp(timestamp: Long) { val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) ?: return val mmsRecord = record as? MmsMessageRecord - _details.value = record.run { - val slides = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() + _state.value = record.run { + val slides = mmsRecord?.slideDeck?.slides ?: emptyList() MessageDetailsState( - attachments = slides.map { Attachment(it, it.details) }, + attachments = slides.map(::Attachment), record = record, sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) }, received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) }, @@ -50,9 +64,6 @@ class MessageDetailsViewModel @Inject constructor( } } - private var _details = MutableLiveData(MessageDetailsState()) - val details: LiveData = _details - private val Slide.details: List get() = listOfNotNull( fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) }, @@ -78,12 +89,38 @@ class MessageDetailsViewModel @Inject constructor( ) } + fun Attachment(slide: Slide): Attachment = + Attachment(slide.details, slide.fileName.orNull(), slide.uri, slide is ImageSlide) + + fun onClickImage(index: Int) { + val state = state.value ?: return + val mmsRecord = state.mmsRecord ?: return + val slide = mmsRecord.slideDeck.slides[index] ?: return + // only open to downloaded images + if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { + // Restart download here (on IO thread) + (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> + onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord.getId()) + } + } + + if (slide.isInProgress) return + + _event.value = MediaPreviewArgs(slide, state.mmsRecord, state.thread) + } + + + fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) { + viewModelScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + } + } } data class MessageDetailsState( val attachments: List = emptyList(), - val imageAttachments: List = attachments.filter { it.hasImage() }, - val nonImageAttachmentFileDetails: List? = attachments.firstOrNull { !it.hasImage() }?.fileDetails, + val imageAttachments: List = attachments.filter { it.hasImage }, + val nonImageAttachmentFileDetails: List? = attachments.firstOrNull { !it.hasImage }?.fileDetails, val record: MessageRecord? = null, val mmsRecord: MmsMessageRecord? = record as? MmsMessageRecord, val sent: TitledText? = null, @@ -97,11 +134,8 @@ data class MessageDetailsState( } data class Attachment( - val slide: Slide, - val fileDetails: List -) { - val fileName: String? get() = slide.fileName.orNull() - val uri get() = slide.uri - - fun hasImage() = slide is ImageSlide -} + val fileDetails: List, + val fileName: String?, + val uri: Uri?, + val hasImage: Boolean +) From d83532b6af85c28523bcd8592b667d35b8c8fbee Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 11 Jul 2023 13:28:44 +0930 Subject: [PATCH 48/51] Use Channel and Flow to avoid duplicate events --- .../conversation/v2/MessageDetailActivity.kt | 25 +++++++------ .../v2/MessageDetailsViewModel.kt | 36 +++++++++++++------ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 8f2310195..0ce83f9af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -32,8 +32,8 @@ import androidx.compose.material.LocalTextStyle import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -48,13 +48,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.GlideImage import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding -import org.thoughtcrime.securesms.MediaPreviewActivity +import org.thoughtcrime.securesms.MediaPreviewActivity.getPreviewIntent import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.ui.AppTheme @@ -102,23 +103,25 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp) - if (viewModel.state.value == null) { - finish() - return - } - ComposeView(this) .apply { setContent { MessageDetailsScreen() } } .let(::setContentView) - viewModel.event.observe(this) { - startActivity(MediaPreviewActivity.getPreviewIntent(this, it)) + lifecycleScope.launch { + viewModel.eventFlow.collect { + when (it) { + Event.Finish -> finish() + is Event.StartMediaPreview -> startActivity( + getPreviewIntent(this@MessageDetailActivity, it.args) + ) + } + } } } @Composable private fun MessageDetailsScreen() { - val state by viewModel.state.observeAsState(MessageDetailsState()) + val state by viewModel.stateFlow.collectAsState() AppTheme { MessageDetails( state = state, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index ad4bf24d4..7f97645b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -1,12 +1,14 @@ package org.thoughtcrime.securesms.conversation.v2 import android.net.Uri -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.messaging.jobs.AttachmentDownloadJob @@ -38,17 +40,23 @@ class MessageDetailsViewModel @Inject constructor( private val threadDb: ThreadDatabase, ) : ViewModel() { - private var _state = MutableLiveData(MessageDetailsState()) - val state: LiveData = _state + private val state = MutableStateFlow(MessageDetailsState()) + val stateFlow = state.asStateFlow() - private var _event = MutableLiveData() - val event: LiveData = _event + private val event = Channel() + val eventFlow = event.receiveAsFlow() fun setMessageTimestamp(timestamp: Long) { - val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) ?: return + val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) + + if (record == null) { + viewModelScope.launch { event.send(Event.Finish) } + return + } + val mmsRecord = record as? MmsMessageRecord - _state.value = record.run { + state.value = record.run { val slides = mmsRecord?.slideDeck?.slides ?: emptyList() MessageDetailsState( @@ -106,10 +114,13 @@ class MessageDetailsViewModel @Inject constructor( if (slide.isInProgress) return - _event.value = MediaPreviewArgs(slide, state.mmsRecord, state.thread) + viewModelScope.launch { + MediaPreviewArgs(slide, state.mmsRecord, state.thread) + .let(Event::StartMediaPreview) + .let { event.send(it) } + } } - fun onAttachmentNeedsDownload(attachmentId: Long, mmsId: Long) { viewModelScope.launch(Dispatchers.IO) { JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) @@ -139,3 +150,8 @@ data class Attachment( val uri: Uri?, val hasImage: Boolean ) + +sealed class Event { + object Finish: Event() + data class StartMediaPreview(val args: MediaPreviewArgs): Event() +} \ No newline at end of file From 452db6dfa371ecf7f92e594e164a31664b018c18 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 18 Jul 2023 13:36:08 +0930 Subject: [PATCH 49/51] Make reply safer --- .../securesms/conversation/v2/ConversationActivityV2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5e78d9e1c..233d43eae 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 @@ -1982,7 +1982,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun reply(messages: Set) { val recipient = viewModel.recipient ?: return - binding?.inputBar?.draftQuote(recipient, messages.first(), glide) + messages.firstOrNull()?.let { binding?.inputBar?.draftQuote(recipient, it, glide) } endActionMode() } From fc8a92998cc5d4f19b9f9c8c9e4fb974350643f7 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 19 Jul 2023 15:04:30 +0930 Subject: [PATCH 50/51] Fix message clipping --- .../v2/messages/VisibleMessageView.kt | 16 +++++--- .../main/res/layout/view_visible_message.xml | 41 +++++++++++-------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 00c5c08a4..90cb58cdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -9,9 +9,11 @@ import android.graphics.drawable.ColorDrawable import android.os.Handler import android.os.Looper import android.util.AttributeSet +import android.view.Gravity import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View +import android.widget.FrameLayout import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.annotation.DrawableRes @@ -114,6 +116,7 @@ class VisibleMessageView : LinearLayout { binding.root.disableClipping() binding.mainContainer.disableClipping() binding.messageInnerContainer.disableClipping() + binding.messageInnerLayout.disableClipping() binding.messageContentView.root.disableClipping() } // endregion @@ -342,11 +345,14 @@ class VisibleMessageView : LinearLayout { private fun updateExpirationTimer(message: MessageRecord) { val container = binding.messageInnerContainer - val content = binding.messageContentView.root - val expiration = binding.expirationTimerView - container.removeAllViewsInLayout() - container.addView(if (message.isOutgoing) expiration else content) - container.addView(if (message.isOutgoing) content else expiration) + val layout = binding.messageInnerLayout + + if (message.isOutgoing) binding.messageContentView.root.bringToFront() + else binding.expirationTimerView.bringToFront() + + layout.layoutParams = layout.layoutParams.let { it as FrameLayout.LayoutParams } + .apply { gravity = if (message.isOutgoing) Gravity.END else Gravity.START } + val containerParams = container.layoutParams as ConstraintLayout.LayoutParams containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f container.layoutParams = containerParams diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 11155ccc2..37b65c8fc 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -107,35 +107,40 @@ app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/full_names" /> - - + android:orientation="horizontal"> - + - + + + + Date: Fri, 21 Jul 2023 11:50:42 +0930 Subject: [PATCH 51/51] Fix buttons --- .../conversation/v2/MessageDetailActivity.kt | 6 +-- .../v2/MessageDetailsViewModel.kt | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 0ce83f9af..61732827f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -80,8 +80,6 @@ import javax.inject.Inject @AndroidEntryPoint class MessageDetailActivity : PassphraseRequiredActionBarActivity() { - private var timestamp: Long = 0L - @Inject lateinit var storage: Storage @@ -101,7 +99,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { title = resources.getString(R.string.conversation_context__menu_message_details) - intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp) + viewModel.timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L) ComposeView(this) .apply { setContent { MessageDetailsScreen() } } @@ -135,7 +133,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() { } private fun setResultAndFinish(code: Int) { - Bundle().apply { putLong(MESSAGE_TIMESTAMP, timestamp) } + Bundle().apply { putLong(MESSAGE_TIMESTAMP, viewModel.timestamp) } .let(Intent()::putExtras) .let { setResult(code, it) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt index 7f97645b6..a73fe4113 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailsViewModel.kt @@ -46,32 +46,34 @@ class MessageDetailsViewModel @Inject constructor( private val event = Channel() val eventFlow = event.receiveAsFlow() - fun setMessageTimestamp(timestamp: Long) { - val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) + var timestamp: Long = 0L + set(value) { + field = value + val record = mmsSmsDatabase.getMessageForTimestamp(timestamp) - if (record == null) { - viewModelScope.launch { event.send(Event.Finish) } - return + if (record == null) { + viewModelScope.launch { event.send(Event.Finish) } + return + } + + val mmsRecord = record as? MmsMessageRecord + + state.value = record.run { + val slides = mmsRecord?.slideDeck?.slides ?: emptyList() + + MessageDetailsState( + attachments = slides.map(::Attachment), + record = record, + sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) }, + received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) }, + error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) }, + senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, + sender = individualRecipient, + thread = threadDb.getRecipientForThreadId(threadId)!!, + ) + } } - val mmsRecord = record as? MmsMessageRecord - - state.value = record.run { - val slides = mmsRecord?.slideDeck?.slides ?: emptyList() - - MessageDetailsState( - attachments = slides.map(::Attachment), - record = record, - sent = dateSent.let(::Date).toString().let { TitledText(R.string.message_details_header__sent, it) }, - received = dateReceived.let(::Date).toString().let { TitledText(R.string.message_details_header__received, it) }, - error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText(R.string.message_details_header__error, it) }, - senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } }, - sender = individualRecipient, - thread = threadDb.getRecipientForThreadId(threadId)!!, - ) - } - } - private val Slide.details: List get() = listOfNotNull( fileName.orNull()?.let { TitledText(R.string.message_details_header__file_id, it) },