diff --git a/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt b/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt index fa4ea4d48..e90e01ca7 100644 --- a/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt +++ b/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt @@ -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 { diff --git a/app/src/main/java/org/tasks/caldav/BaseCaldavCalendarSettingsActivity.kt b/app/src/main/java/org/tasks/caldav/BaseCaldavCalendarSettingsActivity.kt index ef5a530e1..cb41587ed 100644 --- a/app/src/main/java/org/tasks/caldav/BaseCaldavCalendarSettingsActivity.kt +++ b/app/src/main/java/org/tasks/caldav/BaseCaldavCalendarSettingsActivity.kt @@ -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) } diff --git a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt index 19efd5fd0..483c85c16 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavCalendarSettingsActivity.kt @@ -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 ) } - } + ) } } } diff --git a/app/src/main/java/org/tasks/compose/PrincipalList.kt b/app/src/main/java/org/tasks/compose/PrincipalList.kt index b2fb33378..5175a7980 100644 --- a/app/src/main/java/org/tasks/compose/PrincipalList.kt +++ b/app/src/main/java/org/tasks/compose/PrincipalList.kt @@ -158,7 +158,6 @@ object ListSettingsComposables { imageVector = Icons.Outlined.Clear, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.alpha(ICON_ALPHA) ) } } diff --git a/app/src/main/java/org/tasks/compose/settings/BaseSettingsContent.kt b/app/src/main/java/org/tasks/compose/settings/BaseSettingsContent.kt index 28c107d60..91c00a970 100644 --- a/app/src/main/java/org/tasks/compose/settings/BaseSettingsContent.kt +++ b/app/src/main/java/org/tasks/compose/settings/BaseSettingsContent.kt @@ -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() } } diff --git a/app/src/main/java/org/tasks/compose/settings/ListSettingsScaffold.kt b/app/src/main/java/org/tasks/compose/settings/ListSettingsScaffold.kt new file mode 100644 index 000000000..e6085d123 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/settings/ListSettingsScaffold.kt @@ -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() }, + ) + } +} diff --git a/app/src/main/java/org/tasks/compose/settings/SettingsSurface.kt b/app/src/main/java/org/tasks/compose/settings/SettingsSurface.kt deleted file mode 100644 index 2e4ce951c..000000000 --- a/app/src/main/java/org/tasks/compose/settings/SettingsSurface.kt +++ /dev/null @@ -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() } - } - } -} diff --git a/app/src/main/java/org/tasks/opentasks/OpenTasksListSettingsActivity.kt b/app/src/main/java/org/tasks/opentasks/OpenTasksListSettingsActivity.kt index 20b5dc673..b32e18276 100644 --- a/app/src/main/java/org/tasks/opentasks/OpenTasksListSettingsActivity.kt +++ b/app/src/main/java/org/tasks/opentasks/OpenTasksListSettingsActivity.kt @@ -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) diff --git a/kmp/src/commonMain/kotlin/org/tasks/themes/TasksTheme.kt b/kmp/src/commonMain/kotlin/org/tasks/themes/TasksTheme.kt index eb09fdd0d..821fb88f9 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/themes/TasksTheme.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/themes/TasksTheme.kt @@ -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),