diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/export/ExportAccountDataFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/export/ExportAccountDataFragment.kt
index d567a9b53e..d5808b8d91 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/export/ExportAccountDataFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/export/ExportAccountDataFragment.kt
@@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -36,10 +37,19 @@ import org.signal.core.ui.Buttons
import org.signal.core.ui.Dialogs
import org.signal.core.ui.Rows
import org.signal.core.ui.Scaffolds
+import org.signal.core.ui.Texts
+import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
+import org.thoughtcrime.securesms.util.CommunicationActions
+import org.thoughtcrime.securesms.util.SpanUtil
class ExportAccountDataFragment : ComposeFragment() {
+
+ companion object {
+ val TAG = Log.tag(ExportAccountDataFragment::class.java)
+ }
+
private val viewModel: ExportAccountDataViewModel by viewModels()
private fun deleteReport() {
@@ -105,10 +115,15 @@ class ExportAccountDataFragment : ComposeFragment() {
}
item {
- Text(
- text = stringResource(id = R.string.ExportAccountDataFragment__export_explanation, stringResource(id = R.string.ExportAccountDataFragment__learn_more)),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(top = 12.dp, start = 32.dp, end = 32.dp, bottom = 20.dp)
+ val learnMore = stringResource(R.string.ExportAccountDataFragment__learn_more)
+ val explanation = stringResource(R.string.ExportAccountDataFragment__export_explanation, learnMore)
+ Texts.LinkifiedText(
+ textWithUrlSpans = SpanUtil.urlSubsequence(explanation, learnMore, stringResource(R.string.export_account_data_url)),
+ onUrlClick = { url ->
+ CommunicationActions.openBrowserLink(requireContext(), url)
+ },
+ modifier = Modifier.padding(top = 12.dp, start = 32.dp, end = 32.dp, bottom = 20.dp),
+ style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center)
)
}
@@ -255,7 +270,7 @@ class ExportAccountDataFragment : ComposeFragment() {
}
Text(
- text = stringResource(id = R.string.ExportAccountDataFragment__report_deletion_disclaimer, stringResource(id = R.string.ExportAccountDataFragment__learn_more)),
+ text = stringResource(id = R.string.ExportAccountDataFragment__report_deletion_disclaimer),
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Start,
modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 28.dp, bottom = 20.dp)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java
index 2b2f53f06e..37873dc22d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java
@@ -24,6 +24,7 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
import android.view.View;
import androidx.annotation.ColorInt;
@@ -155,6 +156,17 @@ public final class SpanUtil {
return clickSubstring(text, text, onLearnMoreClicked, color);
}
+ public static Spanned urlSubsequence(@NonNull CharSequence fullString, @NonNull CharSequence substring, @NonNull String url) {
+ SpannableString spannable = new SpannableString(fullString);
+ int start = TextUtils.indexOf(fullString, substring);
+ int end = start + substring.length();
+
+ if (start >= 0 && end <= fullString.length()) {
+ spannable.setSpan(new URLSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ return spannable;
+ }
+
/**
* Takes two resources:
* - one resource that has a single string placeholder
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0ebc67c1ec..0e54b07dec 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -15,6 +15,7 @@
https://signal.me/#u/%1$s
signal.me/#u/%1$s
https://support.signal.org/hc/articles/5389476324250
+ https://support.signal.org/hc/articles/5538911756954
Yes
No
diff --git a/core-ui/src/main/java/org/signal/core/ui/Texts.kt b/core-ui/src/main/java/org/signal/core/ui/Texts.kt
index 662abea6f2..0d5ee51028 100644
--- a/core-ui/src/main/java/org/signal/core/ui/Texts.kt
+++ b/core-ui/src/main/java/org/signal/core/ui/Texts.kt
@@ -1,13 +1,21 @@
package org.signal.core.ui
+import android.text.Spanned
+import android.text.style.URLSpan
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.core.text.getSpans
import org.signal.core.ui.theme.SignalTheme
object Texts {
@@ -29,6 +37,43 @@ object Texts {
.padding(top = 16.dp, bottom = 12.dp)
)
}
+
+ @Composable
+ fun LinkifiedText(
+ textWithUrlSpans: Spanned,
+ onUrlClick: (String) -> Unit,
+ modifier: Modifier = Modifier,
+ style: TextStyle = LocalTextStyle.current
+ ) {
+ val annotatedText = annotatedStringFromUrlSpans(urlSpanText = textWithUrlSpans)
+ ClickableText(
+ text = annotatedText,
+ style = style,
+ modifier = modifier,
+ onClick = { offset ->
+ annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).firstOrNull()?.let { annotation ->
+ onUrlClick(annotation.item)
+ }
+ }
+ )
+ }
+
+ @Composable
+ private fun annotatedStringFromUrlSpans(urlSpanText: Spanned): AnnotatedString {
+ val builder = AnnotatedString.Builder(urlSpanText.toString())
+ val urlSpans = urlSpanText.getSpans()
+ for (urlSpan in urlSpans) {
+ val spanStart = urlSpanText.getSpanStart(urlSpan)
+ val spanEnd = urlSpanText.getSpanEnd(urlSpan)
+ builder.addStyle(
+ style = SpanStyle(color = MaterialTheme.colorScheme.primary),
+ start = spanStart,
+ end = spanEnd
+ )
+ builder.addStringAnnotation("URL", urlSpan.url, spanStart, spanEnd)
+ }
+ return builder.toAnnotatedString()
+ }
}
@Preview