More 'Add account' design updates

renovate/fastlane-2.x-lockfile
Alex Baker 2 days ago
parent 744715a658
commit 71450aa782

@ -164,9 +164,11 @@ class MainActivity : AppCompatActivity() {
Timber.d("hasAccount=$hasAccount")
when (hasAccount) {
false -> {
wasInOnboarding = true
navController.navigate(WelcomeDestination) {
popUpTo(0) { inclusive = true }
if (!wasInOnboarding) {
wasInOnboarding = true
navController.navigate(WelcomeDestination) {
popUpTo(0) { inclusive = true }
}
}
}
true -> {
@ -182,6 +184,7 @@ class MainActivity : AppCompatActivity() {
true
)
}
wasInOnboarding = false
}
navController.navigate(HomeDestination) {
popUpTo(0) { inclusive = true }
@ -310,6 +313,9 @@ class MainActivity : AppCompatActivity() {
firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to platform.name)
addAccountViewModel.openUrl(this@MainActivity, platform)
},
onNameYourPriceInfo = {
firebase.logEvent(R.string.event_onboarding_name_your_price)
},
)
}
composable<HomeDestination> {

@ -33,7 +33,11 @@ fun TosUpdateDialog(
)
},
text = {
LegalDisclosure(openLegalUrl = openUrl, textAlign = TextAlign.Start)
LegalDisclosure(
prefixRes = R.string.legal_disclosure_prefix_using,
openLegalUrl = openUrl,
textAlign = TextAlign.Start,
)
},
dismissButton = {
TextButton(onClick = onExit) {

@ -1,5 +1,6 @@
package org.tasks.compose
import androidx.annotation.StringRes
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -131,6 +132,7 @@ private fun WelcomeContent(
horizontalAlignment = Alignment.CenterHorizontally,
) {
LegalDisclosure(
prefixRes = R.string.legal_disclosure_prefix_continuing,
openLegalUrl = openLegalUrl,
modifier = Modifier.widthIn(max = 400.dp),
)
@ -141,7 +143,11 @@ private fun WelcomeContent(
onClick = onSignIn,
modifier = buttonModifier
) {
Text(text = stringResource(R.string.add_account))
Text(
text = stringResource(R.string.add_account),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
Spacer(modifier = Modifier.height(8.dp))
@ -150,25 +156,31 @@ private fun WelcomeContent(
onClick = onContinueWithoutSync,
modifier = buttonModifier
) {
Text(text = stringResource(R.string.continue_without_sync))
Text(
text = stringResource(R.string.continue_without_sync),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.returning_user),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.height(8.dp))
TextButton(onClick = onImportBackup) {
Text(text = stringResource(R.string.backup_BAc_import))
OutlinedButton(
onClick = onImportBackup,
modifier = buttonModifier
) {
Text(
text = stringResource(R.string.returning_user_import_backup),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
)
}
}
}
@Composable
internal fun LegalDisclosure(
@StringRes prefixRes: Int,
openLegalUrl: (String) -> Unit,
modifier: Modifier = Modifier,
textAlign: TextAlign = TextAlign.Center,
@ -181,11 +193,12 @@ internal fun LegalDisclosure(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline,
)
val prefixText = stringResource(prefixRes)
val tosText = stringResource(R.string.terms_of_service_proper)
val privacyText = stringResource(R.string.privacy_policy_proper)
val licenseText = stringResource(R.string.gplv3_license)
val template = stringResource(R.string.legal_disclosure)
val formatted = String.format(template, tosText, privacyText, licenseText)
val formatted = String.format(template, prefixText, tosText, privacyText, licenseText)
val tosStart = formatted.indexOf(tosText)
val tosEnd = tosStart + tosText.length

@ -115,6 +115,9 @@ class AddAccountActivity : ComponentActivity() {
)
viewModel.openUrl(this, platform)
},
onNameYourPriceInfo = {
firebase.logEvent(R.string.event_onboarding_name_your_price)
},
)
}
}

@ -3,29 +3,44 @@ package org.tasks.compose.accounts
import androidx.activity.compose.BackHandler
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material3.Card
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -47,7 +62,23 @@ fun AddAccountScreen(
onBack: () -> Unit,
signIn: (Platform) -> Unit,
openUrl: (Platform) -> Unit,
onNameYourPriceInfo: () -> Unit = {},
) {
var showNameYourPriceInfo by remember { mutableStateOf(false) }
if (showNameYourPriceInfo) {
AlertDialog(
onDismissRequest = { showNameYourPriceInfo = false },
title = { Text(stringResource(R.string.name_your_price)) },
text = { Text(stringResource(R.string.name_your_price_blurb)) },
confirmButton = {
TextButton(onClick = { showNameYourPriceInfo = false }) {
Text(stringResource(R.string.ok))
}
},
)
}
BackHandler(onBack = onBack)
Scaffold(
topBar = {
@ -62,7 +93,10 @@ fun AddAccountScreen(
},
title = {
Text(text = stringResource(R.string.add_account))
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background,
),
)
}
) { paddingValues ->
@ -78,7 +112,6 @@ fun AddAccountScreen(
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (!hasTasksAccount) {
SectionHeader(R.string.upgrade_to_pro)
@ -89,70 +122,126 @@ fun AddAccountScreen(
onClick = { signIn(Platform.TASKS_ORG) }
)
if (hasPro) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
}
}
if (!hasPro) {
SectionHeader(R.string.cost_free)
}
AccountTypeCard(
title = R.string.microsoft,
icon = R.drawable.ic_microsoft_tasks,
description = if (IS_GOOGLE_PLAY)
R.string.microsoft_selection_description_googleplay
else
R.string.microsoft_selection_description,
onClick = { signIn(Platform.MICROSOFT) }
)
AccountTypeCard(
title = R.string.gtasks_GPr_header,
icon = R.drawable.ic_google,
description = R.string.google_tasks_selection_description,
onClick = { signIn(Platform.GOOGLE_TASKS) }
)
CardGroup {
AccountTypeRow(
title = R.string.microsoft,
icon = R.drawable.ic_microsoft_tasks,
description = if (IS_GOOGLE_PLAY)
R.string.microsoft_selection_description_googleplay
else
R.string.microsoft_selection_description,
onClick = { signIn(Platform.MICROSOFT) }
)
HorizontalDivider()
AccountTypeRow(
title = R.string.gtasks_GPr_header,
icon = R.drawable.ic_google,
description = R.string.google_tasks_selection_description,
onClick = { signIn(Platform.GOOGLE_TASKS) }
)
}
if (!hasPro) {
SectionHeader(R.string.name_your_price)
SectionHeader(
title = R.string.name_your_price,
onInfoClick = {
onNameYourPriceInfo()
showNameYourPriceInfo = true
},
)
} else {
Spacer(modifier = Modifier.height(16.dp))
}
CardGroup {
AccountTypeRow(
title = R.string.davx5,
icon = R.drawable.ic_davx5_icon_green_bg,
description = R.string.davx5_selection_description,
onClick = { openUrl(Platform.DAVX5) }
)
HorizontalDivider()
AccountTypeRow(
title = R.string.caldav,
icon = R.drawable.ic_webdav_logo,
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = .8f),
description = R.string.caldav_selection_description,
onClick = { signIn(Platform.CALDAV) }
)
HorizontalDivider()
AccountTypeRow(
title = R.string.etesync,
icon = R.drawable.ic_etesync,
description = R.string.etesync_selection_description,
onClick = { signIn(Platform.ETESYNC) }
)
HorizontalDivider()
AccountTypeRow(
title = R.string.decsync,
icon = R.drawable.ic_decsync,
description = R.string.decsync_selection_description,
onClick = { openUrl(Platform.DECSYNC_CC) }
)
}
AccountTypeCard(
title = R.string.davx5,
icon = R.drawable.ic_davx5_icon_green_bg,
description = R.string.davx5_selection_description,
onClick = { openUrl(Platform.DAVX5) }
)
AccountTypeCard(
title = R.string.caldav,
icon = R.drawable.ic_webdav_logo,
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = .8f),
description = R.string.caldav_selection_description,
onClick = { signIn(Platform.CALDAV) }
)
AccountTypeCard(
title = R.string.etesync,
icon = R.drawable.ic_etesync,
description = R.string.etesync_selection_description,
onClick = { signIn(Platform.ETESYNC) }
)
AccountTypeCard(
title = R.string.decsync,
icon = R.drawable.ic_decsync,
description = R.string.decsync_selection_description,
onClick = { openUrl(Platform.DECSYNC_CC) }
)
}
}
}
}
@Composable
private fun SectionHeader(@StringRes title: Int) {
Text(
text = stringResource(id = title).uppercase(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(top = 8.dp),
)
private fun SectionHeader(
@StringRes title: Int,
onInfoClick: (() -> Unit)? = null,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(48.dp)
.then(
if (onInfoClick != null)
Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onInfoClick,
)
else
Modifier
),
) {
Text(
text = stringResource(id = title).uppercase(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
)
if (onInfoClick != null) {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = stringResource(R.string.help),
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(start = 4.dp)
.size(24.dp),
)
}
}
}
@Composable
private fun CardGroup(
content: @Composable ColumnScope.() -> Unit,
) {
OutlinedCard(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
) {
Column(content = content)
}
}
@Composable
@ -160,52 +249,76 @@ private fun AccountTypeCard(
@StringRes title: Int,
@DrawableRes icon: Int,
@StringRes description: Int,
@StringRes price: Int? = null,
tint: Color? = null,
onClick: () -> Unit,
) {
Card(
OutlinedCard(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Icon(
painter = painterResource(id = icon),
contentDescription = null,
tint = tint ?: Color.Unspecified,
modifier = Modifier.size(40.dp)
AccountTypeRowContent(
title = title,
icon = icon,
description = description,
tint = tint,
)
}
}
@Composable
private fun AccountTypeRow(
@StringRes title: Int,
@DrawableRes icon: Int,
@StringRes description: Int,
tint: Color? = null,
onClick: () -> Unit,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
) {
AccountTypeRowContent(
title = title,
icon = icon,
description = description,
tint = tint,
)
}
}
@Composable
private fun AccountTypeRowContent(
@StringRes title: Int,
@DrawableRes icon: Int,
@StringRes description: Int,
tint: Color? = null,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Icon(
painter = painterResource(id = icon),
contentDescription = null,
tint = tint ?: Color.Unspecified,
modifier = Modifier.size(40.dp)
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(id = title),
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = stringResource(id = description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
)
Column(modifier = Modifier.weight(1f)) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = title),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
price?.let {
Text(
text = stringResource(id = it),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
)
}
}
Text(
text = stringResource(id = description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
)
}
}
}
}
@ -225,3 +338,20 @@ fun AddAccountPreview() {
)
}
}
@PreviewLightDark
@Composable
fun NameYourPriceDialogPreview() {
TasksTheme {
AlertDialog(
onDismissRequest = {},
title = { Text(stringResource(R.string.name_your_price)) },
text = { Text(stringResource(R.string.name_your_price_blurb)) },
confirmButton = {
TextButton(onClick = {}) {
Text(stringResource(R.string.ok))
}
},
)
}
}

@ -429,6 +429,7 @@
<string name="event_accept_tos">accept_tos</string>
<string name="event_accept_tos_update">accept_tos_update</string>
<string name="event_onboarding_complete">onboarding_complete</string>
<string name="event_onboarding_name_your_price">onboarding_name_your_price</string>
<string name="event_screen_settings">screen_settings</string>
<string name="event_create_tag">create_tag</string>
<string name="event_create_filter">create_filter</string>

@ -513,6 +513,7 @@ File %1$s contained %2$s.\n\n
<string name="invalid_backup_file">Invalid backup file</string>
<string name="google_tasks_add_to_top">New tasks on top</string>
<string name="name_your_price">Name your price</string>
<string name="name_your_price_blurb">Pay what you want to unlock themes, icons, and these advanced sync options</string>
<string name="expand_subtasks">Expand subtasks</string>
<string name="collapse_subtasks">Collapse subtasks</string>
<string name="subtasks_multilevel_google_task">Multi-level subtasks not supported by Google Tasks</string>
@ -707,7 +708,9 @@ File %1$s contained %2$s.\n\n
<string name="enable_alarms">Get notified at the right time</string>
<string name="enable_alarms_description">To make sure you\'re notified at the right time, grant permission to set alarms and reminders in Settings</string>
<string name="sign_in">Sign in</string>
<string name="legal_disclosure">By using Tasks.org, you agree to the %1$s and %2$s. Tasks is open source software provided under the %3$s.</string>
<string name="legal_disclosure">%1$s, you agree to the %2$s and %3$s. Tasks is open source software provided under the %4$s.</string>
<string name="legal_disclosure_prefix_using">By using Tasks.org</string>
<string name="legal_disclosure_prefix_continuing">By continuing with any button below</string>
<string name="terms_of_service">Terms of service</string>
<string name="terms_of_service_proper">Terms of Service</string>
<string name="privacy_policy_proper">Privacy Policy</string>
@ -717,6 +720,7 @@ File %1$s contained %2$s.\n\n
<string name="accept">Accept</string>
<string name="exit">Exit</string>
<string name="returning_user">Returning user?</string>
<string name="returning_user_import_backup">Import Tasks.org backup</string>
<string name="sort_sorting">Sorting</string>
<string name="sort_grouping">Grouping</string>
<string name="sort_ascending">Ascending</string>

Loading…
Cancel
Save