mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Update bank mandate CTA UX.
This commit is contained in:
committed by
Greyson Parrelli
parent
0659edb762
commit
6e856a7648
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user