From f249a6edd56f2f4bb7f8836afdf03ae1f808d85f Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 30 Mar 2023 16:16:57 -0300 Subject: [PATCH] Add StatusBarColorNestedScrollConnection. --- .../pnp/PhoneNumberPrivacySettingsFragment.kt | 157 +++++++++++++++++- .../StatusBarColorNestedScrollConnection.kt | 80 +++++++++ .../main/java/org/signal/core/ui/Scaffolds.kt | 16 +- 3 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorNestedScrollConnection.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt index cd0926aa26..dcb08d997a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/pnp/PhoneNumberPrivacySettingsFragment.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.components.settings.app.privacy.pnp +import android.os.Bundle import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding @@ -10,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -22,12 +24,24 @@ import org.signal.core.ui.Scaffolds import org.signal.core.ui.Texts import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.compose.ComposeFragment +import org.thoughtcrime.securesms.compose.StatusBarColorNestedScrollConnection import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberListingMode import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberSharingMode class PhoneNumberPrivacySettingsFragment : ComposeFragment() { private val viewModel: PhoneNumberPrivacySettingsViewModel by viewModels() + private lateinit var statusBarNestedScrollConnection: StatusBarColorNestedScrollConnection + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + statusBarNestedScrollConnection = StatusBarColorNestedScrollConnection(requireActivity()) + } + + override fun onResume() { + super.onResume() + statusBarNestedScrollConnection.setColorImmediate() + } @Composable override fun SheetContent() { @@ -40,7 +54,8 @@ class PhoneNumberPrivacySettingsFragment : ComposeFragment() { title = stringResource(id = R.string.preferences_app_protection__phone_number), onNavigationClick = onNavigationClick, navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24), - navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close) + navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close), + modifier = Modifier.nestedScroll(statusBarNestedScrollConnection) ) { contentPadding -> Box(modifier = Modifier.padding(contentPadding)) { LazyColumn { @@ -120,6 +135,146 @@ class PhoneNumberPrivacySettingsFragment : ComposeFragment() { modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) ) } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } + + item { + Text( + text = stringResource( + id = when (state.findMeByPhoneNumber) { + PhoneNumberListingMode.UNLISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal + PhoneNumberListingMode.LISTED -> R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has + } + ), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter), vertical = 16.dp) + ) + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorNestedScrollConnection.kt b/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorNestedScrollConnection.kt new file mode 100644 index 0000000000..e918bde64c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/compose/StatusBarColorNestedScrollConnection.kt @@ -0,0 +1,80 @@ +package org.thoughtcrime.securesms.compose + +import android.animation.ValueAnimator +import android.app.Activity +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.unit.Velocity +import androidx.core.content.ContextCompat +import com.google.android.material.animation.ArgbEvaluatorCompat +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.util.WindowUtil +import kotlin.math.abs + +/** + * Controls status-bar color based off a nested scroll + */ +class StatusBarColorNestedScrollConnection( + private val activity: Activity +) : NestedScrollConnection { + private var animator: ValueAnimator? = null + + private val normalColor = ContextCompat.getColor(activity, R.color.signal_colorBackground) + private val scrollColor = ContextCompat.getColor(activity, R.color.signal_colorSurface2) + + private var contentOffset = 0f + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + return Velocity.Zero + } + + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + val oldContentOffset = contentOffset + if (consumed.y == 0f && available.y > 0f) { + contentOffset = 0f + } else { + contentOffset += consumed.y + } + + if (oldContentOffset.isNearZero() xor contentOffset.isNearZero()) { + applyState() + } + + return Offset.Zero + } + + fun setColorImmediate() { + val end = when { + contentOffset.isNearZero() -> normalColor + else -> scrollColor + } + + animator?.cancel() + WindowUtil.setStatusBarColor( + activity.window, + end + ) + } + + private fun applyState() { + val (start, end) = when { + contentOffset.isNearZero() -> scrollColor to normalColor + else -> normalColor to scrollColor + } + + animator?.cancel() + animator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 200 + addUpdateListener { + WindowUtil.setStatusBarColor( + activity.window, + ArgbEvaluatorCompat.getInstance().evaluate(it.animatedFraction, start, end) + ) + } + start() + } + } + + private fun Float.isNearZero(): Boolean = abs(this) < 0.001 +} diff --git a/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt index ebc113dc9b..558d31a398 100644 --- a/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt +++ b/core-ui/src/main/java/org/signal/core/ui/Scaffolds.kt @@ -11,12 +11,14 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.signal.core.ui.theme.SignalTheme @@ -32,6 +34,8 @@ object Scaffolds { navigationContentDescription: String? = null, content: @Composable (PaddingValues) -> Unit ) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + Scaffold( topBar = { TopAppBar( @@ -51,10 +55,14 @@ object Scaffolds { contentDescription = navigationContentDescription ) } - } + }, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.smallTopAppBarColors( + scrolledContainerColor = SignalTheme.colors.colorSurface2 + ) ) }, - modifier = modifier, + modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), content = content ) } @@ -71,7 +79,9 @@ private fun SettingsScaffoldPreview() { ) { paddingValues -> Box( contentAlignment = Alignment.Center, - modifier = Modifier.padding(paddingValues).fillMaxSize() + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() ) { Text("Content") }