Merge branch 'compose-open-url-dialog' into strings-squashed

This commit is contained in:
alansley 2024-08-28 16:31:43 +10:00
commit 2a8f010369
8 changed files with 164 additions and 158 deletions

View File

@ -4,25 +4,16 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Typeface
import android.net.Uri 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.Button import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.VERTICAL import android.widget.LinearLayout.VERTICAL
import android.widget.RelativeLayout
import android.widget.ScrollView
import android.widget.Space import android.widget.Space
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -31,9 +22,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.updateMargins import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.squareup.phrase.Phrase
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY
import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.util.toPx
@DslMarker @DslMarker
@ -199,127 +188,6 @@ public fun Context.copyURLToClipboard(url: String) {
clipboard.setPrimaryClip(clip) 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 // 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) fun Context.openUrl(url: String) = Intent(Intent.ACTION_VIEW, Uri.parse(url)).let(::startActivity)

View File

@ -19,10 +19,7 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.MediaStore import android.provider.MediaStore
import android.text.SpannableString
import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.text.style.StyleSpan
import android.util.Pair import android.util.Pair
import android.util.TypedValue import android.util.TypedValue
import android.view.ActionMode import android.view.ActionMode
@ -36,6 +33,10 @@ import android.widget.Toast
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels 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.content.ContextCompat
import androidx.core.view.drawToBitmap import androidx.core.view.drawToBitmap
import androidx.core.view.isGone 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.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
import org.thoughtcrime.securesms.showSessionDialog 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.ActivityDispatcher
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
@ -196,6 +199,7 @@ import kotlin.math.roundToInt
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
private const val TAG = "ConversationActivityV2" private const val TAG = "ConversationActivityV2"
// Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually // 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) .get(LinkPreviewViewModel::class.java)
} }
private var openLinkDialogUrl: String? by mutableStateOf(null)
private val threadId: Long by lazy { private val threadId: Long by lazy {
var threadId = intent.getLongExtra(THREAD_ID, -1L) var threadId = intent.getLongExtra(THREAD_ID, -1L)
if (threadId == -1L) { if (threadId == -1L) {
@ -401,12 +407,33 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
// endregion // endregion
fun showOpenUrlDialog(url: String){
openLinkDialogUrl = url
}
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
binding = ActivityConversationV2Binding.inflate(layoutInflater) binding = ActivityConversationV2Binding.inflate(layoutInflater)
setContentView(binding.root) 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 // messageIdToScroll
messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1)) messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1))
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))

View File

@ -6,7 +6,6 @@ import android.graphics.Rect
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import network.loki.messenger.R import network.loki.messenger.R
@ -14,10 +13,10 @@ import network.loki.messenger.databinding.ViewLinkPreviewBinding
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.components.CornerMask 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.conversation.v2.utilities.MessageBubbleUtilities
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.ImageSlide
import org.thoughtcrime.securesms.showOpenUrlDialog
class LinkPreviewView : LinearLayout { class LinkPreviewView : LinearLayout {
private val binding: ViewLinkPreviewBinding by lazy { ViewLinkPreviewBinding.bind(this) } 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 // Method to show the open or copy URL dialog
private fun openURL() { private fun openURL() {
val url = this.url ?: return Log.w("LinkPreviewView", "Cannot open a null URL") val url = this.url ?: return Log.w("LinkPreviewView", "Cannot open a null URL")
val activity = context as AppCompatActivity val activity = context as? ConversationActivityV2
activity.showOpenUrlDialog(url) activity?.showOpenUrlDialog(url)
} }
// endregion // endregion
} }

View File

@ -12,35 +12,29 @@ import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.text.getSpans import androidx.core.text.getSpans
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull 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.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.getColorFromAttr
import org.session.libsession.utilities.modifyLayoutParams import org.session.libsession.utilities.modifyLayoutParams
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 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.MentionUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.SmsMessageRecord 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.GlowViewUtilities
import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.SearchUtil
import org.thoughtcrime.securesms.util.getAccentColor import org.thoughtcrime.securesms.util.getAccentColor
@ -293,8 +287,8 @@ class VisibleMessageContentView : ConstraintLayout {
body.getSpans<URLSpan>(0, body.length).toList().forEach { urlSpan -> body.getSpans<URLSpan>(0, body.length).toList().forEach { urlSpan ->
val updatedUrl = urlSpan.url.let { it.toHttpUrlOrNull().toString() } val updatedUrl = urlSpan.url.let { it.toHttpUrlOrNull().toString() }
val replacementSpan = ModalURLSpan(updatedUrl) { url -> val replacementSpan = ModalURLSpan(updatedUrl) { url ->
val activity = context as AppCompatActivity val activity = context as? ConversationActivityV2
activity.showOpenUrlDialog(url) activity?.showOpenUrlDialog(url)
} }
val start = body.getSpanStart(urlSpan) val start = body.getSpanStart(urlSpan)
val end = body.getSpanEnd(urlSpan) val end = body.getSpanEnd(urlSpan)

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.ui package org.thoughtcrime.securesms.ui
import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box 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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding 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.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon 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.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.takeOrElse 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.painterResource
import androidx.compose.ui.res.stringResource 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.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp 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 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.CircularProgressIndicator
import org.thoughtcrime.securesms.ui.components.annotatedStringResource
import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType import org.thoughtcrime.securesms.ui.theme.LocalType
@ -48,13 +61,37 @@ class DialogButtonModel(
val onClick: () -> Unit = {}, val onClick: () -> Unit = {},
) )
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AlertDialog( fun AlertDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String? = null, title: String? = null,
text: String? = null, text: String? = null,
maxLines: Int? = null,
buttons: List<DialogButtonModel>? = 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<DialogButtonModel>? = null, buttons: List<DialogButtonModel>? = null,
showCloseButton: Boolean = false, showCloseButton: Boolean = false,
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
@ -88,18 +125,32 @@ fun AlertDialog(
) { ) {
title?.let { title?.let {
Text( Text(
it, text = it,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = LocalType.current.h7, style = LocalType.current.h7,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing)
) )
} }
text?.let { 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( Text(
it, text = it,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = LocalType.current.large, style = textStyle,
modifier = Modifier.padding(bottom = LocalDimensions.current.xxsSpacing) modifier = textModifier
) )
} }
content() 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 @Composable
fun DialogButton( fun DialogButton(
text: String, text: String,
@ -217,12 +307,12 @@ fun PreviewSimpleDialog() {
text = stringResource(R.string.onboardingBackAccountCreation), text = stringResource(R.string.onboardingBackAccountCreation),
buttons = listOf( buttons = listOf(
DialogButtonModel( DialogButtonModel(
GetString(stringResource(R.string.quit)), GetString(stringResource(R.string.cancel)),
color = LocalColors.current.danger, color = LocalColors.current.danger,
onClick = { } onClick = { }
), ),
DialogButtonModel( 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 @Preview
@Composable @Composable
fun PreviewLoadingDialog() { fun PreviewLoadingDialog() {

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString 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.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import androidx.core.text.HtmlCompat 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 // 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 // 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( private fun spannableStringToAnnotatedString(
text: CharSequence, text: CharSequence,
density: Density density: Density

View File

@ -346,4 +346,9 @@
</LinearLayout> </LinearLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/dialog_open_url"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -792,7 +792,7 @@ NOTE: Strings with blank lines have manually been replaced with '\n\n' - this wi
<string name="urlCopy">Copy URL</string> <string name="urlCopy">Copy URL</string>
<string name="urlOpen">Open URL</string> <string name="urlOpen">Open URL</string>
<string name="urlOpenBrowser">This will open in your browser.</string> <string name="urlOpenBrowser">This will open in your browser.</string>
<string name="urlOpenDescription">Are you sure you want to open this URL in your browser?\n\n{url}</string> <string name="urlOpenDescription">Are you sure you want to open this URL in your browser?\n\n<b>{url}</b></string>
<string name="useFastMode">Use Fast Mode</string> <string name="useFastMode">Use Fast Mode</string>
<string name="video">Video</string> <string name="video">Video</string>
<string name="videoErrorPlay">Unable to play video.</string> <string name="videoErrorPlay">Unable to play video.</string>