Refactor username link share screen to enable previews.
This commit is contained in:
parent
252aa3714e
commit
7530d44d28
2 changed files with 277 additions and 214 deletions
|
@ -1,7 +1,8 @@
|
|||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
|
@ -11,6 +12,7 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -45,6 +47,8 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.core.app.ShareCompat
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
|
@ -52,6 +56,7 @@ import com.google.accompanist.permissions.PermissionState
|
|||
import com.google.accompanist.permissions.PermissionStatus
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Dialogs
|
||||
|
@ -59,6 +64,9 @@ import org.signal.core.ui.Snackbars
|
|||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeData
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeState
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameLinkSettingsState.ActiveTab
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
|
@ -80,19 +88,66 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state
|
||||
val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
|
||||
val scope: CoroutineScope = rememberCoroutineScope()
|
||||
val navController: NavController by remember { mutableStateOf(findNavController()) }
|
||||
var showResetDialog: Boolean by remember { mutableStateOf(false) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val linkCopiedEvent: UUID? by viewModel.linkCopiedEvent
|
||||
val helpText = stringResource(id = R.string.UsernameLinkSettings_scan_this_qr_code)
|
||||
|
||||
val cameraPermissionState: PermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA) {
|
||||
viewModel.onTabSelected(ActiveTab.Scan)
|
||||
}
|
||||
val linkCopiedEvent: UUID? by viewModel.linkCopiedEvent
|
||||
|
||||
MainScreen(
|
||||
state = state,
|
||||
navController = navController,
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
disposables = disposables.disposables,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
onCodeTabSelected = { viewModel.onTabSelected(ActiveTab.Code) },
|
||||
onScanTabSelected = { viewModel.onTabSelected(ActiveTab.Scan) },
|
||||
onUsernameLinkResetResultHandled = { viewModel.onUsernameLinkResetResultHandled() },
|
||||
onShareBadge = { shareQrBadge(requireActivity(), viewModel.generateQrCodeImage(helpText)) },
|
||||
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
|
||||
onQrResultHandled = { viewModel.onQrResultHandled() },
|
||||
onLinkReset = { viewModel.onUsernameLinkReset() },
|
||||
onBackNavigationPressed = { requireActivity().onBackPressed() },
|
||||
linkCopiedEvent = linkCopiedEvent
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.onResume()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainScreen(
|
||||
state: UsernameLinkSettingsState,
|
||||
navController: NavController? = null,
|
||||
lifecycleOwner: LifecycleOwner = previewLifecycleOwner,
|
||||
disposables: CompositeDisposable = CompositeDisposable(),
|
||||
cameraPermissionState: PermissionState = previewPermissionState(),
|
||||
onCodeTabSelected: () -> Unit = {},
|
||||
onScanTabSelected: () -> Unit = {},
|
||||
onUsernameLinkResetResultHandled: () -> Unit = {},
|
||||
onShareBadge: () -> Unit = {},
|
||||
onQrCodeScanned: (String) -> Unit = {},
|
||||
onQrResultHandled: () -> Unit = {},
|
||||
onLinkReset: () -> Unit = {},
|
||||
onBackNavigationPressed: () -> Unit = {},
|
||||
linkCopiedEvent: UUID? = null
|
||||
) {
|
||||
val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
|
||||
val scope: CoroutineScope = rememberCoroutineScope()
|
||||
var showResetDialog: Boolean by remember { mutableStateOf(false) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
|
||||
val linkCopiedString = stringResource(R.string.UsernameLinkSettings_link_copied_toast)
|
||||
|
||||
|
@ -108,9 +163,10 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
TopAppBarContent(
|
||||
activeTab = state.activeTab,
|
||||
scrollBehavior = scrollBehavior,
|
||||
onCodeTabSelected = { viewModel.onTabSelected(ActiveTab.Code) },
|
||||
onScanTabSelected = { viewModel.onTabSelected(ActiveTab.Scan) },
|
||||
cameraPermissionState = cameraPermissionState
|
||||
onCodeTabSelected = onCodeTabSelected,
|
||||
onScanTabSelected = onScanTabSelected,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
onBackNavigationPressed = onBackNavigationPressed
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
|
@ -125,18 +181,15 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
enter = slideInHorizontally(initialOffsetX = { fullWidth -> -fullWidth }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { fullWidth -> -fullWidth })
|
||||
) {
|
||||
val helpText = stringResource(id = R.string.UsernameLinkSettings_scan_this_qr_code)
|
||||
UsernameLinkShareScreen(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
scope = scope,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
navController = navController,
|
||||
onShareBadge = {
|
||||
shareQrBadge(viewModel.generateQrCodeImage(helpText))
|
||||
},
|
||||
onShareBadge = onShareBadge,
|
||||
onResetClicked = { showResetDialog = true },
|
||||
onLinkResultHandled = { viewModel.onUsernameLinkResetResultHandled() }
|
||||
onLinkResultHandled = onUsernameLinkResetResultHandled
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -146,11 +199,11 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
exit = slideOutHorizontally(targetOffsetX = { fullWidth -> fullWidth })
|
||||
) {
|
||||
UsernameQrScanScreen(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
disposables = disposables.disposables,
|
||||
lifecycleOwner = lifecycleOwner,
|
||||
disposables = disposables,
|
||||
qrScanResult = state.qrScanResult,
|
||||
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
|
||||
onQrResultHandled = { viewModel.onQrResultHandled() },
|
||||
onQrCodeScanned = onQrCodeScanned,
|
||||
onQrResultHandled = onQrResultHandled,
|
||||
modifier = Modifier.padding(contentPadding)
|
||||
)
|
||||
}
|
||||
|
@ -159,32 +212,23 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
if (showResetDialog) {
|
||||
ResetDialog(
|
||||
onConfirm = {
|
||||
viewModel.onUsernameLinkReset()
|
||||
onLinkReset()
|
||||
showResetDialog = false
|
||||
},
|
||||
onDismiss = { showResetDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.onResume()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopAppBarContent(
|
||||
@Composable
|
||||
private fun TopAppBarContent(
|
||||
activeTab: ActiveTab,
|
||||
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
|
||||
onCodeTabSelected: () -> Unit = {},
|
||||
onScanTabSelected: () -> Unit = {},
|
||||
cameraPermissionState: PermissionState = previewPermissionState()
|
||||
) {
|
||||
cameraPermissionState: PermissionState = previewPermissionState(),
|
||||
onBackNavigationPressed: () -> Unit = {}
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
|
@ -217,7 +261,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = { requireActivity().onBackPressed() }
|
||||
onClick = onBackNavigationPressed
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_x_24),
|
||||
|
@ -227,10 +271,10 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabButton(label: String, active: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
private fun TabButton(label: String, active: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val colors = if (active) {
|
||||
ButtonDefaults.filledTonalButtonColors()
|
||||
} else {
|
||||
|
@ -247,10 +291,10 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
) {
|
||||
Text(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
@Composable
|
||||
private fun ResetDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(id = R.string.UsernameLinkSettings_reset_link_dialog_title),
|
||||
body = stringResource(id = R.string.UsernameLinkSettings_reset_link_dialog_body),
|
||||
|
@ -259,44 +303,64 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewAppBar() {
|
||||
@Preview(name = "Light Theme", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AppBarPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Column {
|
||||
TopAppBarContent(activeTab = ActiveTab.Code)
|
||||
TopAppBarContent(activeTab = ActiveTab.Scan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewAll() {
|
||||
FragmentContent()
|
||||
@Preview(name = "Light Theme", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun MainScreenPreview() {
|
||||
SignalTheme {
|
||||
MainScreen(
|
||||
state = UsernameLinkSettingsState(
|
||||
activeTab = ActiveTab.Code,
|
||||
username = "PeterParker.42",
|
||||
usernameLinkState = UsernameLinkState.Present("https://signal.org"),
|
||||
qrCodeState = QrCodeState.Present(QrCodeData.forData("PeterParker.42", 64)),
|
||||
qrCodeColorScheme = UsernameQrCodeColorScheme.Orange
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewResetDialog() {
|
||||
@Preview(name = "Light Theme", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ResetDialogPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
ResetDialog(onConfirm = {}, onDismiss = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun previewPermissionState(): PermissionState {
|
||||
private fun previewPermissionState(): PermissionState {
|
||||
return object : PermissionState {
|
||||
override val permission: String = ""
|
||||
override val status: PermissionStatus = PermissionStatus.Granted
|
||||
override fun launchPermissionRequest() = Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareQrBadge(badge: Bitmap?) {
|
||||
private val previewLifecycleOwner: LifecycleOwner = object : LifecycleOwner {
|
||||
override val lifecycle: Lifecycle
|
||||
get() = TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
private fun shareQrBadge(activity: Activity, badge: Bitmap?) {
|
||||
if (badge == null) {
|
||||
return
|
||||
}
|
||||
|
@ -312,16 +376,15 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
.withFileName("SignalGroupQr.png")
|
||||
.createForSingleSessionInMemory()
|
||||
|
||||
val intent = ShareCompat.IntentBuilder.from(requireActivity())
|
||||
val intent = ShareCompat.IntentBuilder(activity)
|
||||
.setType("image/png")
|
||||
.setStream(shareUri)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
startActivity(intent)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
} finally {
|
||||
badge.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ fun UsernameLinkShareScreen(
|
|||
onLinkResultHandled: () -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
scope: CoroutineScope,
|
||||
navController: NavController,
|
||||
navController: NavController?,
|
||||
onShareBadge: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onResetClicked: () -> Unit
|
||||
|
@ -93,9 +93,9 @@ fun UsernameLinkShareScreen(
|
|||
|
||||
ButtonBar(
|
||||
onShareClicked = onShareBadge,
|
||||
onColorClicked = { navController.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkQrColorPickerFragment()) },
|
||||
onColorClicked = { navController?.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkQrColorPickerFragment()) },
|
||||
onLinkClicked = {
|
||||
navController.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkShareBottomSheet())
|
||||
navController?.safeNavigate(UsernameLinkSettingsFragmentDirections.actionUsernameLinkSettingsFragmentToUsernameLinkShareBottomSheet())
|
||||
},
|
||||
linkState = state.usernameLinkState
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue