Convert list settings to use scaffold

pull/3207/head
Alex Baker 12 months ago
parent b3f29dc0fb
commit ece1fd4ef3

@ -1,6 +1,7 @@
package org.tasks.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
@ -11,18 +12,20 @@ import org.tasks.compose.DeleteButton
import org.tasks.compose.IconPickerActivity.Companion.launchIconPicker
import org.tasks.compose.IconPickerActivity.Companion.registerForIconPickerResult
import org.tasks.compose.settings.BaseSettingsContent
import org.tasks.compose.settings.ListSettingsScaffold
import org.tasks.dialogs.ColorPalettePicker
import org.tasks.dialogs.ColorPalettePicker.Companion.newColorPalette
import org.tasks.dialogs.ColorPickerAdapter.Palette
import org.tasks.dialogs.ColorWheelPicker
import org.tasks.extensions.addBackPressedCallback
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.themes.ColorProvider
import org.tasks.themes.Theme
import org.tasks.themes.ThemeColor
import javax.inject.Inject
abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), ColorPalettePicker.ColorPickedCallback, ColorWheelPicker.ColorPickedCallback {
abstract class BaseListSettingsActivity : AppCompatActivity(), ColorPalettePicker.ColorPickedCallback, ColorWheelPicker.ColorPickedCallback {
@Inject lateinit var tasksTheme: Theme
@Inject lateinit var colorProvider: ColorProvider
protected abstract val defaultIcon: String
protected var selectedColor = 0
@ -94,7 +97,7 @@ abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), Co
protected fun updateTheme() {
val themeColor: ThemeColor =
if (selectedColor == 0) this.themeColor
if (selectedColor == 0) tasksTheme.themeColor
else colorProvider.getThemeColor(selectedColor, true)
colorState.value =
@ -114,30 +117,39 @@ abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), Co
optionButton: @Composable () -> Unit = {
if (!isNew) DeleteButton(toolbarTitle ?: "") { delete() }
},
extensionContent: @Composable ColumnScope.() -> Unit = {}
extensionContent: @Composable ColumnScope.() -> Unit = {},
fab: @Composable () -> Unit = {},
) {
BaseSettingsContent(
ListSettingsScaffold(
title = title,
color = colorState.value,
icon = selectedIcon.value ?: defaultIcon,
text = textState.value,
error = errorState.value,
requestKeyboard = requestKeyboard,
theme = if (colorState.value == Color.Unspecified)
Color(tasksTheme.themeColor.primaryColor)
else
colorState.value,
promptDiscard = promptDiscard.value,
showProgress = showProgress.value,
dismissDiscardPrompt = { promptDiscard.value = false },
setText = {
textState.value = it
errorState.value = ""
},
save = { lifecycleScope.launch { save() } },
pickColor = { showThemePicker() },
clearColor = { clearColor() },
pickIcon = { showIconPicker() },
discard = { finish() },
optionButton = optionButton,
extensionContent = extensionContent,
)
actions = optionButton,
fab = fab,
) {
BaseSettingsContent(
color = colorState.value,
icon = selectedIcon.value ?: defaultIcon,
text = textState.value,
error = errorState.value,
requestKeyboard = requestKeyboard,
setText = {
textState.value = it
errorState.value = ""
},
pickColor = { showThemePicker() },
clearColor = { clearColor() },
pickIcon = { showIconPicker() },
extensionContent = extensionContent,
)
}
}
companion object {

@ -200,11 +200,13 @@ abstract class BaseCaldavCalendarSettingsActivity : BaseListSettingsActivity() {
@Composable
fun BaseCaldavSettingsContent (
optionButton: @Composable () -> Unit = { if (!isNew) DeleteButton(caldavCalendar?.name ?: "") { delete() } },
extensionContent: @Composable ColumnScope.() -> Unit = {}
fab: @Composable () -> Unit = {},
extensionContent: @Composable ColumnScope.() -> Unit = {},
) {
BaseSettingsContent (
optionButton = optionButton,
extensionContent = extensionContent
extensionContent = extensionContent,
fab = fab,
)
Toaster(state = snackbar)
}

@ -3,8 +3,6 @@ package org.tasks.caldav
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.PersonAdd
@ -16,8 +14,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
@ -37,12 +35,15 @@ import org.tasks.data.entity.CaldavAccount.Companion.SERVER_TASKS
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.themes.TasksTheme
import org.tasks.themes.ThemeAccent
import org.tasks.themes.colorOn
import javax.inject.Inject
@AndroidEntryPoint
class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
@Inject lateinit var principalDao: PrincipalDao
@Inject lateinit var accent: ThemeAccent
private val viewModel: CaldavCalendarViewModel by viewModels()
@ -67,83 +68,79 @@ class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
setContent {
TasksTheme {
Box(contentAlignment = Alignment.TopStart) {// Box to layout FAB over main content
BaseCaldavSettingsContent {
caldavCalendar?.takeIf { it.id > 0 }?.let { calendar->
val principals = principalDao.getPrincipals(calendar.id).collectAsStateWithLifecycle(initialValue = emptyList()).value
PrincipalList(
principals = principals,
onRemove = if (canRemovePrincipals) { { onRemove(it) } } else null,
)
}
if (principalsList.value.isNotEmpty())
PrincipalList(
principalsList.value,
onRemove = if (canRemovePrincipals) ::onRemove else null
)
}
removeDialog.value?.let { principal ->
AlertDialog(
onDismissRequest = { removeDialog.value = null },
confirmButton = {
Constants.TextButton(text = R.string.ok) {
removePrincipal(principal)
removeDialog.value = null
BaseCaldavSettingsContent(
fab = {
if (caldavAccount.canShare && (isNew || caldavCalendar?.access == ACCESS_OWNER)) {
val openDialog = rememberSaveable { mutableStateOf(false) }
ShareInviteDialog(
openDialog,
email = caldavAccount.serverType != SERVER_OWNCLOUD
) { input ->
lifecycleScope.launch {
share(input)
openDialog.value = false
}
},
dismissButton = {
Constants.TextButton(text = R.string.cancel) {
removeDialog.value = null
}
},
title = {
Text(
stringResource(id = R.string.remove_user),
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Text(
text = stringResource(
R.string.remove_user_confirmation,
principal.name,
caldavCalendar?.name ?: ""
),
style = MaterialTheme.typography.bodyMedium
}
val accentColor = Color(accent.accentColor)
FloatingActionButton(
onClick = { openDialog.value = true },
modifier = Modifier.padding(Constants.KEYLINE_FIRST),
containerColor = Color(accent.accentColor)
) {
Icon(
imageVector = Icons.Outlined.PersonAdd,
contentDescription = null,
tint = colorOn(accentColor),
)
}
}
},
) {
caldavCalendar?.takeIf { it.id > 0 }?.let { calendar->
val principals = principalDao.getPrincipals(calendar.id).collectAsStateWithLifecycle(initialValue = emptyList()).value
PrincipalList(
principals = principals,
onRemove = if (canRemovePrincipals) { { onRemove(it) } } else null,
)
}
if (principalsList.value.isNotEmpty())
PrincipalList(
principalsList.value,
onRemove = if (canRemovePrincipals) ::onRemove else null
)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomEnd
) {
if (caldavAccount.canShare && (isNew || caldavCalendar?.access == ACCESS_OWNER)) {
val openDialog = rememberSaveable { mutableStateOf(false) }
ShareInviteDialog(
openDialog,
email = caldavAccount.serverType != SERVER_OWNCLOUD
) { input ->
lifecycleScope.launch {
share(input)
openDialog.value = false
removeDialog.value?.let { principal ->
AlertDialog(
onDismissRequest = { removeDialog.value = null },
confirmButton = {
Constants.TextButton(text = R.string.ok) {
removePrincipal(principal)
removeDialog.value = null
}
}
FloatingActionButton(
onClick = { openDialog.value = true },
modifier = Modifier.padding(Constants.KEYLINE_FIRST),
containerColor = MaterialTheme.colorScheme.secondary
) {
Icon(
imageVector = Icons.Outlined.PersonAdd,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondary,
},
dismissButton = {
Constants.TextButton(text = R.string.cancel) {
removeDialog.value = null
}
},
title = {
Text(
stringResource(id = R.string.remove_user),
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Text(
text = stringResource(
R.string.remove_user_confirmation,
principal.name,
caldavCalendar?.name ?: ""
),
style = MaterialTheme.typography.bodyMedium
)
}
}
)
}
}
}

@ -158,7 +158,6 @@ object ListSettingsComposables {
imageVector = Icons.Outlined.Clear,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.alpha(ICON_ALPHA)
)
}
}

@ -2,42 +2,30 @@ package org.tasks.compose.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import org.tasks.R
import org.tasks.compose.Constants
@Composable
fun BaseSettingsContent(
title: String,
color: Color,
icon: String,
text: String,
error: String,
requestKeyboard: Boolean,
promptDiscard: Boolean,
showProgress: Boolean,
dismissDiscardPrompt: () -> Unit,
setText: (String) -> Unit,
save: () -> Unit,
pickColor: () -> Unit,
clearColor: () -> Unit,
pickIcon: () -> Unit,
discard: () -> Unit,
optionButton: @Composable () -> Unit,
extensionContent: @Composable ColumnScope.() -> Unit,
) {
SettingsSurface {
Toolbar(
title = title,
save = { save() },
optionButton = optionButton
)
ProgressBar(showProgress)
Column(
modifier = Modifier
.fillMaxSize(),
) {
TitleInput(
text = text,
error = error,
@ -45,24 +33,15 @@ fun BaseSettingsContent(
modifier = Modifier.padding(horizontal = Constants.KEYLINE_FIRST),
setText = { setText(it) },
)
Column(modifier = Modifier.fillMaxWidth()) {
SelectColorRow(
color = color,
selectColor = { pickColor() },
clearColor = { clearColor() },
)
SelectIconRow(
icon = icon,
selectIcon = { pickIcon() },
)
extensionContent()
PromptAction(
showDialog = promptDiscard,
title = stringResource(id = R.string.discard_changes),
onAction = { discard() },
onCancel = { dismissDiscardPrompt() },
)
}
SelectColorRow(
color = color,
selectColor = { pickColor() },
clearColor = { clearColor() },
)
SelectIconRow(
icon = icon,
selectIcon = { pickIcon() },
)
extensionContent()
}
}

@ -0,0 +1,97 @@
package org.tasks.compose.settings
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Save
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import org.tasks.R
import org.tasks.extensions.Context.findActivity
import org.tasks.themes.colorOn
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ListSettingsScaffold(
title: String,
theme: Color,
promptDiscard: Boolean,
showProgress: Boolean,
dismissDiscardPrompt: () -> Unit,
save: () -> Unit,
discard: () -> Unit,
actions: @Composable () -> Unit = {},
fab: @Composable () -> Unit = {},
content: @Composable () -> Unit,
) {
Scaffold(
topBar = {
Column {
val context = LocalContext.current
val contentColor = colorOn(theme)
LaunchedEffect(theme, contentColor, context) {
val systemBarStyle = if (contentColor == Color.White) {
SystemBarStyle.dark(0)
} else {
SystemBarStyle.light(0, 0)
}
(context.findActivity() as? AppCompatActivity)?.enableEdgeToEdge(
statusBarStyle = systemBarStyle,
navigationBarStyle = systemBarStyle
)
}
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = theme,
navigationIconContentColor = contentColor,
titleContentColor = contentColor,
actionIconContentColor = contentColor,
),
title = {
Text(text = title)
},
navigationIcon = {
IconButton(
onClick = save,
) {
Icon(
imageVector = Icons.Outlined.Save,
contentDescription = stringResource(R.string.save),
)
}
},
actions = {
actions()
}
)
ProgressBar(showProgress)
}
},
floatingActionButton = { fab() },
) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues)) {
content()
}
PromptAction(
showDialog = promptDiscard,
title = stringResource(id = R.string.discard_changes),
onAction = { discard() },
onCancel = { dismissDiscardPrompt() },
)
}
}

@ -1,25 +0,0 @@
package org.tasks.compose.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.sp
import org.tasks.R
@Composable
fun SettingsSurface(content: @Composable ColumnScope.() -> Unit) {
ProvideTextStyle(LocalTextStyle.current.copy(fontSize = 18.sp)) {
Surface(
color = colorResource(id = R.color.window_background),
contentColor = colorResource(id = R.color.text_primary)
) {
Column(modifier = Modifier.fillMaxSize()) { content() }
}
}
}

@ -2,15 +2,14 @@ package org.tasks.opentasks
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.compose.settings.ProgressBar
import org.tasks.compose.settings.ListSettingsScaffold
import org.tasks.compose.settings.SelectIconRow
import org.tasks.compose.settings.SettingsSurface
import org.tasks.compose.settings.Toaster
import org.tasks.compose.settings.Toolbar
import org.tasks.data.entity.CaldavAccount
import org.tasks.data.entity.CaldavCalendar
import org.tasks.themes.TasksTheme
@ -23,13 +22,18 @@ class OpenTasksListSettingsActivity : BaseCaldavCalendarSettingsActivity() {
setContent {
TasksTheme {
SettingsSurface {
Toolbar(
title = toolbarTitle,
save = { lifecycleScope.launch { save() } },
optionButton = { },
)
ProgressBar(showProgress.value)
ListSettingsScaffold(
title = toolbarTitle,
theme = if (colorState.value == Color.Unspecified)
Color(tasksTheme.themeColor.primaryColor)
else
colorState.value,
promptDiscard = promptDiscard.value,
showProgress = showProgress.value,
dismissDiscardPrompt = { promptDiscard.value = false },
save = { lifecycleScope.launch { save() } },
discard = { finish() },
) {
SelectIconRow(icon = selectedIcon.value?: defaultIcon) { showIconPicker() }
}
Toaster(state = snackbar)

@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
const val BLUE = -14575885
const val WHITE = -1
@ -36,6 +37,21 @@ private val wallpaperScheme = darkColorScheme.copy(
surface = Color(0x99000000),
)
@Composable
fun colorOn(color: Color) = colorOn(color.toArgb())
@Composable
fun colorOn(color: Int) =
remember (color) {
if (color == 0) {
Color.White
} else if (calculateContrast(WHITE, color) < 3) {
Color.Black
} else {
Color.White
}
}
@Composable
fun TasksTheme(
theme: Int = 5,
@ -49,13 +65,7 @@ fun TasksTheme(
3 -> wallpaperScheme
else -> if (isSystemInDarkTheme()) darkColorScheme else lightColorScheme
}
val colorOnPrimary = remember(primary) {
if (calculateContrast(WHITE, primary) < 3) {
Color.Black
} else {
Color.White
}
}
val colorOnPrimary = colorOn(primary)
MaterialTheme(
colorScheme = colorScheme.copy(
primary = Color(primary),

Loading…
Cancel
Save