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 1481d8ff12..5902c0dfa9 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 @@ -19,10 +19,7 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.provider.MediaStore -import android.text.SpannableString -import android.text.Spanned import android.text.TextUtils -import android.text.style.StyleSpan import android.util.Pair import android.util.TypedValue import android.view.ActionMode @@ -36,6 +33,10 @@ import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.ContextCompat import androidx.core.view.drawToBitmap import androidx.core.view.isGone @@ -170,6 +171,8 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.showSessionDialog +import org.thoughtcrime.securesms.ui.OpenURLAlertDialog +import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme import org.thoughtcrime.securesms.util.ActivityDispatcher import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.DateUtils @@ -195,6 +198,7 @@ import kotlin.math.roundToInt import kotlin.math.sqrt import kotlin.time.Duration.Companion.minutes + private const val TAG = "ConversationActivityV2" // Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually @@ -237,6 +241,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe .get(LinkPreviewViewModel::class.java) } + private var openLinkDialogUrl: String? by mutableStateOf(null) + private val threadId: Long by lazy { var threadId = intent.getLongExtra(THREAD_ID, -1L) if (threadId == -1L) { @@ -400,12 +406,33 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } // endregion + fun showOpenUrlDialog(url: String){ + openLinkDialogUrl = url + } + // region Lifecycle override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) binding = ActivityConversationV2Binding.inflate(layoutInflater) setContentView(binding.root) + // set the compose dialog content + binding.dialogOpenUrl.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + SessionMaterialTheme { + if(!openLinkDialogUrl.isNullOrEmpty()){ + OpenURLAlertDialog( + url = openLinkDialogUrl, + onDismissRequest = { + openLinkDialogUrl = null + } + ) + } + } + } + } + // messageIdToScroll messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1)) messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) 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 f5773b27db..157d459127 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 @@ -11,47 +11,32 @@ import android.text.util.Linkify import android.util.AttributeSet import android.view.MotionEvent import android.view.View -import android.widget.Toast import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.graphics.ColorUtils import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.view.children import androidx.core.view.isVisible +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding -import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.modifyLayoutParams import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.SmsMessageRecord -import com.bumptech.glide.Glide -import com.bumptech.glide.RequestManager -import org.thoughtcrime.securesms.copyURLToClipboard -import org.thoughtcrime.securesms.openUrl import org.thoughtcrime.securesms.showOpenUrlDialog -import org.thoughtcrime.securesms.ui.AlertDialog -import org.thoughtcrime.securesms.ui.DialogButtonModel -import org.thoughtcrime.securesms.ui.GetString -import org.thoughtcrime.securesms.ui.OpenURLAlertDialog -import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.getAccentColor @@ -71,7 +56,6 @@ class VisibleMessageContentView : ConstraintLayout { // endregion // region Updating - @Composable fun bind( message: MessageRecord, isStartOfMessageCluster: Boolean = true, @@ -285,7 +269,6 @@ class VisibleMessageContentView : ConstraintLayout { // region Convenience companion object { - @Composable fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable { var body = message.body.toSpannable() @@ -306,14 +289,8 @@ class VisibleMessageContentView : ConstraintLayout { body.getSpans(0, body.length).toList().forEach { urlSpan -> val updatedUrl = urlSpan.url.let { it.toHttpUrlOrNull().toString() } val replacementSpan = ModalURLSpan(updatedUrl) { url -> - - // @Thomas - PREVIOUS CODE WAS: - //val activity = context as AppCompatActivity - //activity.showOpenUrlDialog(url) - - // Now attempting to use compose for the dialog - but it's not happy =/ - OpenURLWarningDialog(url) - + val activity = context as ConversationActivityV2 + activity.showOpenUrlDialog(url) } val start = body.getSpanStart(urlSpan) val end = body.getSpanEnd(urlSpan) @@ -324,35 +301,6 @@ class VisibleMessageContentView : ConstraintLayout { return body } - @Composable - private fun OpenURLWarningDialog(url: String) { - val context = LocalContext.current - - OpenURLAlertDialog( - title = stringResource(R.string.urlOpen), - text = stringResource(R.string.urlOpenDescription), - showCloseButton = true, // display the 'x' button - buttons = listOf( - DialogButtonModel( - text = GetString(R.string.open), - contentDescription = GetString(R.string.AccessibilityId_urlOpenBrowser), - color = LocalColors.current.danger, - onClick = { context.openUrl(url) } - ), - DialogButtonModel( - text = GetString(android.R.string.copyUrl), - contentDescription = GetString(R.string.AccessibilityId_copy), - onClick = { - context.copyURLToClipboard(url) - Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() - } - ) - ), - url = url, - onDismissRequest = {} - ) - } - @ColorInt fun getTextColor(context: Context, message: MessageRecord): Int = context.getColorFromAttr( if (message.isOutgoing) R.attr.message_sent_text_color else R.attr.message_received_text_color diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt index 9c16f7119b..fed01640e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/AlertDialog.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.ui +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -12,6 +13,8 @@ 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.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -25,14 +28,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max +import androidx.compose.ui.unit.times +import com.squareup.phrase.Phrase import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY +import org.thoughtcrime.securesms.copyURLToClipboard +import org.thoughtcrime.securesms.openUrl import org.thoughtcrime.securesms.ui.components.CircularProgressIndicator +import org.thoughtcrime.securesms.ui.components.annotatedStringResource import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalType @@ -48,17 +61,40 @@ class DialogButtonModel( val onClick: () -> Unit = {}, ) -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AlertDialog( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, title: String? = null, text: String? = null, + maxLines: Int? = null, buttons: List? = null, showCloseButton: Boolean = false, - content: @Composable () -> Unit = {}, - optionalURL: String = "" + content: @Composable () -> Unit = {} +) { + AlertDialog( + onDismissRequest = onDismissRequest, + modifier = modifier, + title = if(title != null) AnnotatedString(title) else null, + text = if(text != null) AnnotatedString(text) else null, + maxLines = maxLines, + buttons = buttons, + showCloseButton = showCloseButton, + content = content + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AlertDialog( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: AnnotatedString? = null, + text: AnnotatedString? = null, + maxLines: Int? = null, + buttons: List? = null, + showCloseButton: Boolean = false, + content: @Composable () -> Unit = {} ) { BasicAlertDialog( modifier = modifier, @@ -96,25 +132,26 @@ fun AlertDialog( ) } text?.let { - if (optionalURL.isNotEmpty()) { - // If this is an open URL dialog it should have a maximum height of 5 lines and truncate long URLs with an ellipsis - Text( - text = it, - textAlign = TextAlign.Center, - style = LocalType.current.large, - modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing), - maxLines = 5, - overflow = TextOverflow.Ellipsis - ) - } else { - // Otherwise it should be a regular, non-open-URL dialog - Text( - text = it, - textAlign = TextAlign.Center, - style = LocalType.current.large, - modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) - ) + val textStyle = LocalType.current.large + var textModifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) + + // if we have a maxLines, make the text scrollable + if(maxLines != null) { + val textHeight = with(LocalDensity.current) { + textStyle.lineHeight.toDp() + } * maxLines + + textModifier = textModifier + .height(textHeight) + .verticalScroll(rememberScrollState()) } + + Text( + text = it, + textAlign = TextAlign.Center, + style = textStyle, + modifier = textModifier + ) } content() } @@ -141,27 +178,42 @@ fun AlertDialog( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun OpenURLAlertDialog( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, - title: String? = null, - text: String? = null, - buttons: List? = null, - showCloseButton: Boolean = false, - content: @Composable () -> Unit = {}, - url: String = "" + url: String, + content: @Composable () -> Unit = {} ) { + val context = LocalContext.current + val unformattedText = Phrase.from(context.getText(R.string.urlOpenDescription)) + .put(URL_KEY, url).format() + + AlertDialog( - onDismissRequest = onDismissRequest, modifier = modifier, - title = title, - text = text, - buttons = buttons, - showCloseButton = showCloseButton, - content = content, - optionalURL = url + title = AnnotatedString(stringResource(R.string.urlOpen)), + text = annotatedStringResource(text = unformattedText), + maxLines = 5, + showCloseButton = true, // display the 'x' button + buttons = listOf( + DialogButtonModel( + text = GetString(R.string.open), + contentDescription = GetString(R.string.AccessibilityId_urlOpenBrowser), + color = LocalColors.current.danger, + onClick = { context.openUrl(url) } + ), + DialogButtonModel( + text = GetString(android.R.string.copyUrl), + contentDescription = GetString(R.string.AccessibilityId_copy), + onClick = { + context.copyURLToClipboard(url) + Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() + } + ) + ), + onDismissRequest = onDismissRequest, + content = content ) } @@ -255,15 +307,14 @@ fun PreviewSimpleDialog() { text = stringResource(R.string.onboardingBackAccountCreation), buttons = listOf( DialogButtonModel( - GetString(stringResource(R.string.quit)), + GetString(stringResource(R.string.cancel)), color = LocalColors.current.danger, onClick = { } ), DialogButtonModel( - GetString(stringResource(R.string.cancel)) + GetString(stringResource(R.string.ok)) ) - ), - optionalURL = "https://slashdot.org" + ) ) } } @@ -298,23 +349,7 @@ fun PreviewXCloseDialog() { fun PreviewOpenURLDialog() { PreviewTheme { OpenURLAlertDialog( - title = stringResource(R.string.urlOpen), - text = stringResource(R.string.urlOpenDescription), - showCloseButton = true, // display the 'x' button - buttons = listOf( - DialogButtonModel( - text = GetString(R.string.open), - contentDescription = GetString(R.string.AccessibilityId_urlOpenBrowser), - color = LocalColors.current.danger, - onClick = {} - ), - DialogButtonModel( - text = GetString(android.R.string.copyUrl), - contentDescription = GetString(R.string.AccessibilityId_copy), - onClick = {} - ) - ), - url = "http://slashdot.org", + url = "https://getsession.org/", onDismissRequest = {} ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt index 951db1816e..15f0292853 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Html.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -26,6 +27,9 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.core.text.HtmlCompat +import com.squareup.phrase.Phrase +import network.loki.messenger.R +import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY // TODO Remove this file once we update to composeVersion=1.7.0-alpha06 fixes https://issuetracker.google.com/issues/139320238?pli=1 // which allows Stylized string in string resources @@ -71,6 +75,14 @@ fun annotatedStringResource(@StringRes id: Int): AnnotatedString { } } +@Composable +fun annotatedStringResource(text: CharSequence): AnnotatedString { + val density = LocalDensity.current + return remember(text.hashCode()) { + spannableStringToAnnotatedString(text, density) + } +} + private fun spannableStringToAnnotatedString( text: CharSequence, density: Density diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 94250f1b0a..d8e6b1fe73 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -346,4 +346,9 @@ + + diff --git a/libsession/src/main/res/values/strings.xml b/libsession/src/main/res/values/strings.xml index 69c87854b2..5070f180ff 100644 --- a/libsession/src/main/res/values/strings.xml +++ b/libsession/src/main/res/values/strings.xml @@ -792,7 +792,7 @@ NOTE: Strings with blank lines have manually been replaced with '\n\n' - this wi Copy URL Open URL This will open in your browser. - Are you sure you want to open this URL in your browser?\n\n{url} + Are you sure you want to open this URL in your browser?\n\n{url} Use Fast Mode Video Unable to play video.