diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/mandate/BankTransferMandateFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/mandate/BankTransferMandateFragment.kt
index ec1de0ac3d..41445a7370 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/mandate/BankTransferMandateFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/transfer/mandate/BankTransferMandateFragment.kt
@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.transfer.mandate
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.compose.animation.AnimatedVisibility
@@ -12,22 +13,33 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
-import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
@@ -37,16 +49,16 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
+import kotlinx.coroutines.launch
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dividers
-import org.signal.core.ui.Scaffolds
import org.signal.core.ui.Texts
import org.signal.core.ui.theme.SignalTheme
import org.signal.donations.PaymentSourceType
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
import org.thoughtcrime.securesms.compose.ComposeFragment
-import org.thoughtcrime.securesms.compose.StatusBarColorNestedScrollConnection
+import org.thoughtcrime.securesms.compose.StatusBarColorAnimator
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.viewModel
@@ -61,15 +73,15 @@ class BankTransferMandateFragment : ComposeFragment() {
BankTransferMandateViewModel(PaymentSourceType.Stripe.SEPADebit)
}
- private lateinit var statusBarColorNestedScrollConnection: StatusBarColorNestedScrollConnection
+ private lateinit var statusBarColorAnimator: StatusBarColorAnimator
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- statusBarColorNestedScrollConnection = StatusBarColorNestedScrollConnection(requireActivity())
+ statusBarColorAnimator = StatusBarColorAnimator(requireActivity())
}
override fun onResume() {
super.onResume()
- statusBarColorNestedScrollConnection.setColorImmediate()
+ statusBarColorAnimator.setColorImmediate()
}
@Composable
@@ -83,7 +95,7 @@ class BankTransferMandateFragment : ComposeFragment() {
onNavigationClick = this::onNavigationClick,
onContinueClick = this::onContinueClick,
onLearnMoreClick = this::onLearnMoreClick,
- modifier = Modifier.nestedScroll(statusBarColorNestedScrollConnection)
+ onCanScrollUp = statusBarColorAnimator::setCanScrollUp
)
}
@@ -119,11 +131,14 @@ fun BankTransferScreenPreview() {
failedToLoadMandate = false,
onNavigationClick = {},
onContinueClick = {},
- onLearnMoreClick = {}
+ onLearnMoreClick = {},
+ onCanScrollUp = {}
)
}
}
+@OptIn(ExperimentalMaterial3Api::class)
+@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun BankTransferScreen(
bankMandate: String,
@@ -131,91 +146,123 @@ fun BankTransferScreen(
onNavigationClick: () -> Unit,
onContinueClick: () -> Unit,
onLearnMoreClick: () -> Unit,
- modifier: Modifier = Modifier
+ onCanScrollUp: (Boolean) -> Unit
) {
- Scaffolds.Settings(
- title = stringResource(id = R.string.BankTransferMandateFragment__bank_transfer),
- onNavigationClick = onNavigationClick,
- navigationIconPainter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24)),
- titleContent = { contentOffset, title ->
- AnimatedVisibility(
- visible = contentOffset < 0f,
- enter = fadeIn(),
- exit = fadeOut()
- ) {
- Text(text = title, style = MaterialTheme.typography.titleLarge)
- }
- },
- modifier = modifier
- ) {
- LazyColumn(
- horizontalAlignment = CenterHorizontally,
- modifier = Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .padding(top = 64.dp)
- ) {
- item {
- Image(
- painter = painterResource(id = R.drawable.bank_transfer),
- contentScale = ContentScale.Inside,
- contentDescription = null,
- modifier = Modifier
- .size(72.dp)
- .background(
- SignalTheme.colors.colorSurface2,
- CircleShape
+ val listState = rememberLazyListState()
+ val scope = rememberCoroutineScope()
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = {
+ AnimatedVisibility(
+ visible = listState.canScrollBackward,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Text(text = stringResource(id = R.string.BankTransferMandateFragment__bank_transfer), style = MaterialTheme.typography.titleLarge)
+ }
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = onNavigationClick,
+ Modifier.padding(end = 16.dp)
+ ) {
+ Icon(
+ painter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24)),
+ contentDescription = null
)
- )
- }
+ }
+ },
+ colors = if (listState.canScrollBackward) TopAppBarDefaults.topAppBarColors(containerColor = SignalTheme.colors.colorSurface2) else TopAppBarDefaults.topAppBarColors()
+ )
+ }
+ ) {
+ onCanScrollUp(listState.canScrollBackward)
- item {
- Text(
- text = stringResource(id = R.string.BankTransferMandateFragment__bank_transfer),
- style = MaterialTheme.typography.headlineMedium,
- modifier = Modifier.padding(top = 12.dp, bottom = 15.dp)
- )
- }
+ Column(horizontalAlignment = CenterHorizontally, modifier = Modifier.fillMaxSize()) {
+ LazyColumn(
+ state = listState,
+ horizontalAlignment = CenterHorizontally,
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f, true)
+ .padding(top = 64.dp)
+ ) {
+ item {
+ Image(
+ painter = painterResource(id = R.drawable.bank_transfer),
+ contentScale = ContentScale.Inside,
+ contentDescription = null,
+ modifier = Modifier
+ .size(72.dp)
+ .background(
+ SignalTheme.colors.colorSurface2,
+ CircleShape
+ )
+ )
+ }
- item {
- val learnMore = stringResource(id = R.string.BankTransferMandateFragment__learn_more)
- val fullString = stringResource(id = R.string.BankTransferMandateFragment__stripe_processes_donations, learnMore)
+ item {
+ Text(
+ text = stringResource(id = R.string.BankTransferMandateFragment__bank_transfer),
+ style = MaterialTheme.typography.headlineMedium,
+ modifier = Modifier.padding(top = 12.dp, bottom = 15.dp)
+ )
+ }
- Texts.LinkifiedText(
- textWithUrlSpans = SpanUtil.urlSubsequence(fullString, learnMore, stringResource(id = R.string.donate_url)), // TODO [alex] -- final URL
- onUrlClick = {
- onLearnMoreClick()
- },
- style = MaterialTheme.typography.bodyLarge.copy(
- color = MaterialTheme.colorScheme.onSurfaceVariant
- ),
- modifier = Modifier
- .padding(bottom = 12.dp)
- .padding(horizontal = dimensionResource(id = R.dimen.bank_transfer_mandate_gutter))
- )
- }
+ item {
+ val learnMore = stringResource(id = R.string.BankTransferMandateFragment__learn_more)
+ val fullString = stringResource(id = R.string.BankTransferMandateFragment__stripe_processes_donations, learnMore)
- item {
- Dividers.Default()
- }
+ Texts.LinkifiedText(
+ textWithUrlSpans = SpanUtil.urlSubsequence(fullString, learnMore, stringResource(id = R.string.donate_url)), // TODO [alex] -- final URL
+ onUrlClick = {
+ onLearnMoreClick()
+ },
+ style = MaterialTheme.typography.bodyLarge.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ ),
+ modifier = Modifier
+ .padding(bottom = 12.dp)
+ .padding(horizontal = dimensionResource(id = R.dimen.bank_transfer_mandate_gutter))
+ )
+ }
- item {
- Text(
- text = if (failedToLoadMandate) stringResource(id = R.string.BankTransferMandateFragment__failed_to_load_mandate) else bankMandate,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.bank_transfer_mandate_gutter), vertical = 16.dp)
- )
+ item {
+ Dividers.Default()
+ }
+
+ item {
+ Text(
+ text = if (failedToLoadMandate) stringResource(id = R.string.BankTransferMandateFragment__failed_to_load_mandate) else bankMandate,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.bank_transfer_mandate_gutter), vertical = 16.dp)
+ )
+ }
}
if (!failedToLoadMandate) {
- item {
+ Surface(
+ shadowElevation = if (listState.canScrollForward) 8.dp else 0.dp,
+ modifier = Modifier.fillMaxWidth()
+ ) {
Buttons.LargeTonal(
- onClick = onContinueClick,
+ onClick = {
+ if (!listState.canScrollForward) {
+ onContinueClick()
+ } else {
+ scope.launch {
+ listState.animateScrollBy(value = 1000f)
+ }
+ }
+ },
modifier = Modifier
- .padding(top = 16.dp, bottom = 46.dp)
+ .wrapContentWidth()
+ .padding(top = 16.dp, bottom = 16.dp)
.defaultMinSize(minWidth = 220.dp)
) {
- Text(text = stringResource(id = R.string.BankTransferMandateFragment__continue))
+ Text(text = if (listState.canScrollForward) stringResource(id = R.string.BankTransferMandateFragment__read_more) else stringResource(id = R.string.BankTransferMandateFragment__continue))
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorAnimator.kt b/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorAnimator.kt
new file mode 100644
index 0000000000..ff1ef7567d
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorAnimator.kt
@@ -0,0 +1,62 @@
+package org.thoughtcrime.securesms.compose
+
+import android.animation.ValueAnimator
+import android.app.Activity
+import androidx.core.content.ContextCompat
+import com.google.android.material.animation.ArgbEvaluatorCompat
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.util.WindowUtil
+
+/**
+ * Controls status-bar color based off ability to scroll up
+ */
+class StatusBarColorAnimator(
+ private val activity: Activity
+) {
+ private var animator: ValueAnimator? = null
+ private var previousCanScrollUp: Boolean = false
+
+ private val normalColor = ContextCompat.getColor(activity, R.color.signal_colorBackground)
+ private val scrollColor = ContextCompat.getColor(activity, R.color.signal_colorSurface2)
+
+ fun setCanScrollUp(canScrollUp: Boolean) {
+ if (previousCanScrollUp == canScrollUp) {
+ return
+ }
+
+ previousCanScrollUp = canScrollUp
+ applyState(canScrollUp)
+ }
+
+ fun setColorImmediate() {
+ val end = when {
+ previousCanScrollUp -> scrollColor
+ else -> normalColor
+ }
+
+ animator?.cancel()
+ WindowUtil.setStatusBarColor(
+ activity.window,
+ end
+ )
+ }
+
+ private fun applyState(canScrollUp: Boolean) {
+ val (start, end) = when {
+ canScrollUp -> normalColor to scrollColor
+ else -> scrollColor to normalColor
+ }
+
+ animator?.cancel()
+ animator = ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 200
+ addUpdateListener {
+ WindowUtil.setStatusBarColor(
+ activity.window,
+ ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, start, end)
+ )
+ }
+ start()
+ }
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3172c85613..469594f508 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5890,6 +5890,8 @@
Learn more
Continue
+
+ Read more
Failed to load mandate