diff --git a/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt index 0dab6c6a04..8d40dd2785 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/SessionDialogBuilder.kt @@ -4,25 +4,16 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent -import android.graphics.Typeface import android.net.Uri -import android.text.Spannable -import android.text.SpannableString -import android.text.style.StyleSpan -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.Button -import android.widget.ImageButton import android.widget.LinearLayout import android.widget.LinearLayout.VERTICAL -import android.widget.RelativeLayout -import android.widget.ScrollView import android.widget.Space import android.widget.TextView -import android.widget.Toast import androidx.annotation.AttrRes import androidx.annotation.LayoutRes import androidx.annotation.StringRes @@ -31,9 +22,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.text.HtmlCompat import androidx.core.view.updateMargins import androidx.fragment.app.Fragment -import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY import org.thoughtcrime.securesms.util.toPx @DslMarker @@ -199,127 +188,6 @@ public fun Context.copyURLToClipboard(url: String) { clipboard.setPrimaryClip(clip) } -// Method to show a dialog used to open or copy a URL -fun Context.showOpenUrlDialog(url: String, showCloseButton: Boolean = true): AlertDialog { - - return SessionDialogBuilder(this).apply { - // If we're not showing a close button we can just use a simple title.. - if (!showCloseButton) { - title(R.string.urlOpen) - } else { - // ..otherwise we have to jump through some hoops to add a close button. - - // Create a RelativeLayout as the container for the custom title - val titleLayout = RelativeLayout(context).apply { - layoutParams = RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.MATCH_PARENT, - RelativeLayout.LayoutParams.WRAP_CONTENT - ) - } - - // Create a TextView for the title - val titleTextView = TextView(context).apply { - // Set the text and display it in the correct 'title' style - text = context.getString(R.string.urlOpen) - setTextAppearance(R.style.TextAppearance_AppCompat_Title) - - layoutParams = RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT - ).apply { - addRule(RelativeLayout.CENTER_HORIZONTAL) - addRule(RelativeLayout.CENTER_VERTICAL) - } - } - - // Create an ImageButton for the close button - val closeButton = ImageButton(context).apply { - setImageResource(android.R.drawable.ic_menu_close_clear_cancel) // Use a standard Android close icon - background = null // Remove the background to make it look like an icon - layoutParams = RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.WRAP_CONTENT, - RelativeLayout.LayoutParams.WRAP_CONTENT - ).apply { - addRule(RelativeLayout.ALIGN_PARENT_END) // Place the close button on the "right" side - addRule(RelativeLayout.CENTER_VERTICAL) - } - contentDescription = context.getString(R.string.close) - } - - // // Close the dialog when the button is clicked - closeButton.setOnClickListener { dismiss() } - - // Add the TextView and ImageButton to the RelativeLayout.. - titleLayout.addView(titleTextView) - titleLayout.addView(closeButton) - - // ..and then add that layout to the contentView. - contentView.addView(titleLayout) - } - - // Create a TextView for the "Are you sure you want to open this URL?" - val txtView = TextView(context).apply { - layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) - .apply { updateMargins(dp40, 0, dp40, 0) } - - // Substitute the URL into the string then make it bold - val txt = Phrase.from(context, R.string.urlOpenDescription).put(URL_KEY, url).format().toString() - val txtWithBoldedURL = SpannableString(txt) - val urlStart = txt.indexOf(url) - if (urlStart >= 0) { - txtWithBoldedURL.setSpan( - StyleSpan(Typeface.BOLD), - urlStart, - urlStart + url.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - text = txtWithBoldedURL - - gravity = Gravity.CENTER // Center the text - } - - // Create a ScrollView and add the TextView to it - val scrollView = ScrollView(context).apply { - addView(txtView) - - // Apply padding to the ScrollView so that the scroll bar isn't right up against the edge. - // We'll apply the same padding to both sides to keep the text centered. - setPadding(dp20, 0, dp20, 0) - - // Place the scroll bar inside the container. - // See the following for how different options look: https://stackoverflow.com/questions/3103132/android-listview-scrollbarstyle - scrollBarStyle = ScrollView.SCROLLBARS_INSIDE_INSET - } - - // If the textView takes up 5 lines or more then show the scroll bar, force it to remain visible, - // and set the ScrollView height accordingly. - txtView.viewTreeObserver.addOnGlobalLayoutListener { - // Only display the vertical scroll bar if the text takes up 5 lines or more - val maxLines = 5 - if (txtView.lineCount >= maxLines) { - scrollView.isVerticalScrollBarEnabled = true - // Note: `scrollView.isScrollbarFadingEnabled = false` does NOT - // work to prevent the scroll bar from fading away - so a hacky - // way to fix this is to allow it to fade out... after an hour, lol. - scrollView.scrollBarFadeDuration = 1000 * 60 * 60 // Value is in milliseconds - scrollView.isVerticalFadingEdgeEnabled = false - val lineHeight = txtView.lineHeight - scrollView.layoutParams.height = lineHeight * maxLines - } - } - - // Add the ScrollView to the contentView and then add the 'Open' and 'Copy URL' buttons. - // Note: The text and contentDescription are set on the `copyUrlButton` by the function. - contentView.addView(scrollView) - dangerButton(R.string.open, R.string.AccessibilityId_urlOpenBrowser) { openUrl(url) } - copyUrlButton { - context.copyURLToClipboard(url) - Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() - } - }.show() -} - // Method to actually open a given URL via an Intent that will use the default browser fun Context.openUrl(url: String) = Intent(Intent.ACTION_VIEW, Uri.parse(url)).let(::startActivity) 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 348537ca79..b12baec2a8 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 @@ -171,6 +172,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 @@ -196,6 +199,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 @@ -238,6 +242,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) { @@ -401,12 +407,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/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 68da078e55..d064d02872 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -6,7 +6,6 @@ import android.graphics.Rect import android.util.AttributeSet import android.view.MotionEvent import android.widget.LinearLayout -import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.bumptech.glide.RequestManager import network.loki.messenger.R @@ -14,10 +13,10 @@ import network.loki.messenger.databinding.ViewLinkPreviewBinding import org.session.libsession.utilities.getColorFromAttr import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.components.CornerMask +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.showOpenUrlDialog class LinkPreviewView : LinearLayout { private val binding: ViewLinkPreviewBinding by lazy { ViewLinkPreviewBinding.bind(this) } @@ -88,8 +87,8 @@ class LinkPreviewView : LinearLayout { // Method to show the open or copy URL dialog private fun openURL() { val url = this.url ?: return Log.w("LinkPreviewView", "Cannot open a null URL") - val activity = context as AppCompatActivity - activity.showOpenUrlDialog(url) + val activity = context as? ConversationActivityV2 + activity?.showOpenUrlDialog(url) } // endregion } \ No newline at end of file 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 1d193bd745..d62cc532c4 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 @@ -12,35 +12,29 @@ import android.util.AttributeSet import android.view.MotionEvent import android.view.View import androidx.annotation.ColorInt -import androidx.appcompat.app.AppCompatActivity 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.showOpenUrlDialog import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.getAccentColor @@ -293,8 +287,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 -> - val activity = context as AppCompatActivity - activity.showOpenUrlDialog(url) + val activity = context as? ConversationActivityV2 + activity?.showOpenUrlDialog(url) } val start = body.getSpanStart(urlSpan) val end = body.getSpanEnd(urlSpan) 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 1dd1df46cb..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,7 +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.layout.width +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 @@ -26,13 +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,13 +61,37 @@ 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 = {} +) { + 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 = {} @@ -88,18 +125,32 @@ fun AlertDialog( ) { title?.let { Text( - it, + text = it, textAlign = TextAlign.Center, style = LocalType.current.h7, modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) ) } text?.let { + 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( - it, + text = it, textAlign = TextAlign.Center, - style = LocalType.current.large, - modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) + style = textStyle, + modifier = textModifier ) } content() @@ -127,6 +178,45 @@ fun AlertDialog( ) } +@Composable +fun OpenURLAlertDialog( + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + url: String, + content: @Composable () -> Unit = {} +) { + val context = LocalContext.current + val unformattedText = Phrase.from(context.getText(R.string.urlOpenDescription)) + .put(URL_KEY, url).format() + + + AlertDialog( + modifier = modifier, + 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 + ) +} + @Composable fun DialogButton( text: String, @@ -217,12 +307,12 @@ 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)) ) ) ) @@ -254,6 +344,17 @@ fun PreviewXCloseDialog() { } } +@Preview +@Composable +fun PreviewOpenURLDialog() { + PreviewTheme { + OpenURLAlertDialog( + url = "https://getsession.org/", + onDismissRequest = {} + ) + } +} + @Preview @Composable fun PreviewLoadingDialog() { 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.