From 5a1560e513088da80f7c2862265d38b800fd22af Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Sun, 24 Aug 2025 11:11:39 -0500 Subject: [PATCH] System bar scrim improvements --- .../todoroo/astrid/activity/MainActivity.kt | 9 +- .../astrid/activity/TaskListFragment.kt | 18 +- .../java/org/tasks/compose/home/HomeScreen.kt | 309 ++++++++++-------- .../org/tasks/compose/home/SystemBarScrim.kt | 22 ++ .../main/res/layout/fragment_task_list.xml | 7 + .../tasks/compose/drawer/TaskListDrawer.kt | 2 +- 6 files changed, 217 insertions(+), 150 deletions(-) create mode 100644 app/src/main/java/org/tasks/compose/home/SystemBarScrim.kt diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt index 1cec40f89..50fd828da 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt @@ -123,10 +123,11 @@ class MainActivity : AppCompatActivity() { lightScrim = Color.TRANSPARENT, darkScrim = Color.TRANSPARENT ), - navigationBarStyle = SystemBarStyle.auto( - lightScrim = Color.TRANSPARENT, - darkScrim = Color.TRANSPARENT - ) + navigationBarStyle = if (theme.themeBase.isDarkTheme(this)) { + SystemBarStyle.dark(Color.TRANSPARENT) + } else { + SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT) + } ) setContent { diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index deea25ad8..eeac563da 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -22,7 +22,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LAYOUT_DIRECTION_LTR -import android.view.ViewGroup.MarginLayoutParams import androidx.activity.OnBackPressedCallback import androidx.activity.compose.BackHandler import androidx.activity.result.contract.ActivityResultContracts @@ -338,7 +337,9 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL right = endInset, ) binding.bottomAppBar.updatePadding(bottom = bottomInset) - (binding.fab.layoutParams as MarginLayoutParams).bottomMargin = bottomInset / 2 + val scrimLayoutParams = binding.systemBarScrim.layoutParams + scrimLayoutParams.height = bottomInset + binding.systemBarScrim.layoutParams = scrimLayoutParams } @OptIn(ExperimentalPermissionsApi::class) @@ -365,6 +366,14 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL listViewModel.setFilter(filter) (recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false recyclerView.layoutManager = LinearLayoutManager(context) + + val baseFooterHeight = resources.getDimensionPixelSize(R.dimen.task_list_footer_height) + val additionalFabSpace = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 56f, + resources.displayMetrics + ).toInt() + recyclerView.updatePadding(bottom = baseFooterHeight + additionalFabSpace) lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { listViewModel.updateBannerState() @@ -394,6 +403,11 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL binding.bottomAppBar.performShow() } } + + val typedValue = TypedValue() + requireContext().theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true) + val scrimColor = typedValue.data + binding.systemBarScrim.setBackgroundColor((scrimColor and 0x00FFFFFF) or 0xCC000000.toInt()) // 80% opacity with (binding.fab) { backgroundTintList = ColorStateList.valueOf(themeColor.primaryColor) imageTintList = ColorStateList.valueOf(themeColor.colorOnPrimary) diff --git a/app/src/main/java/org/tasks/compose/home/HomeScreen.kt b/app/src/main/java/org/tasks/compose/home/HomeScreen.kt index 784a29099..287552183 100644 --- a/app/src/main/java/org/tasks/compose/home/HomeScreen.kt +++ b/app/src/main/java/org/tasks/compose/home/HomeScreen.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.material3.DrawerState import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -121,173 +123,194 @@ fun HomeScreen( ) { val context = LocalContext.current val scope = rememberCoroutineScope() - TaskListDrawer( - arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom, - filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems, - onClick = { - when (it) { - is DrawerItem.Filter -> { - viewModel.setFilter(it.filter) - scope.launch { - drawerState.close() - keyboard?.hide() + Box(modifier = Modifier.fillMaxSize()) { + TaskListDrawer( + arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom, + filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems, + onClick = { + when (it) { + is DrawerItem.Filter -> { + viewModel.setFilter(it.filter) + scope.launch { + drawerState.close() + keyboard?.hide() + } } - } - is DrawerItem.Header -> { - viewModel.toggleCollapsed(it.header) + is DrawerItem.Header -> { + viewModel.toggleCollapsed(it.header) + } } - } - }, - onAddClick = { - scope.launch { - drawerState.close() - when (it.header.addIntentRc) { - FilterProvider.REQUEST_NEW_FILTER -> - showNewFilterDialog() + }, + onAddClick = { + scope.launch { + drawerState.close() + when (it.header.addIntentRc) { + FilterProvider.REQUEST_NEW_FILTER -> + showNewFilterDialog() - REQUEST_NEW_PLACE -> - newList.launch(Intent(context, LocationPickerActivity::class.java)) + REQUEST_NEW_PLACE -> + newList.launch(Intent(context, LocationPickerActivity::class.java)) - REQUEST_NEW_TAGS -> - newList.launch(Intent(context, TagSettingsActivity::class.java)) + REQUEST_NEW_TAGS -> + newList.launch(Intent(context, TagSettingsActivity::class.java)) - REQUEST_NEW_LIST -> - when (it.header.subheaderType) { - NavigationDrawerSubheader.SubheaderType.CALDAV, - NavigationDrawerSubheader.SubheaderType.TASKS -> - viewModel - .getAccount(it.header.id.toLong()) - ?.let { - newList.launch( - Intent(context, it.listSettingsClass()) - .putExtra(EXTRA_CALDAV_ACCOUNT, it) - ) - } + REQUEST_NEW_LIST -> + when (it.header.subheaderType) { + NavigationDrawerSubheader.SubheaderType.CALDAV, + NavigationDrawerSubheader.SubheaderType.TASKS -> + viewModel + .getAccount(it.header.id.toLong()) + ?.let { + newList.launch( + Intent(context, it.listSettingsClass()) + .putExtra(EXTRA_CALDAV_ACCOUNT, it) + ) + } - else -> {} - } + else -> {} + } - else -> Timber.e("Unhandled request code: $it") + else -> Timber.e("Unhandled request code: $it") + } } - } - }, - onErrorClick = { - context.startActivity(Intent(context, MainPreferences::class.java)) - }, - searchBar = { - MenuSearchBar( - begForMoney = state.begForMoney, - onDrawerAction = { - scope.launch { - drawerState.close() - when (it) { - DrawerAction.PURCHASE -> - if (TasksApplication.IS_GENERIC) - context.openUri(R.string.url_donate) - else + }, + onErrorClick = { + context.startActivity(Intent(context, MainPreferences::class.java)) + }, + searchBar = { + MenuSearchBar( + begForMoney = state.begForMoney, + onDrawerAction = { + scope.launch { + drawerState.close() + when (it) { + DrawerAction.PURCHASE -> + if (TasksApplication.IS_GENERIC) + context.openUri(R.string.url_donate) + else + context.startActivity( + Intent( + context, + PurchaseActivity::class.java + ) + ) + + DrawerAction.HELP_AND_FEEDBACK -> context.startActivity( Intent( context, - PurchaseActivity::class.java + HelpAndFeedback::class.java ) ) - - DrawerAction.HELP_AND_FEEDBACK -> - context.startActivity( - Intent( - context, - HelpAndFeedback::class.java - ) - ) + } } - } - }, - query = state.menuQuery, - onQueryChange = { viewModel.queryMenu(it) }, - ) - }, - ) + }, + query = state.menuQuery, + onQueryChange = { viewModel.queryMenu(it) }, + ) + }, + ) + + SystemBarScrim( + modifier = Modifier + .windowInsetsTopHeight(WindowInsets.systemBars) + .align(Alignment.TopCenter) + ) + SystemBarScrim( + modifier = Modifier + .windowInsetsBottomHeight(WindowInsets.systemBars) + .align(Alignment.BottomCenter), + ) + } } } ) { - val scope = rememberCoroutineScope() - ListDetailPaneScaffold( - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, - listPane = { - key (state.filter) { - val fragment = remember { mutableStateOf(null) } - val keyboardOpen = rememberImeState() - AndroidFragment( - fragmentState = rememberFragmentState(), - arguments = remember(state.filter) { - Bundle() - .apply { putParcelable(EXTRA_FILTER, state.filter) } - }, - modifier = Modifier - .fillMaxSize() - .imePadding(), - ) { tlf -> - fragment.value = tlf - tlf.applyInsets(windowInsets.value) - tlf.setNavigationClickListener { - scope.launch { drawerState.open() } + Box(modifier = Modifier.fillMaxSize()) { + val scope = rememberCoroutineScope() + ListDetailPaneScaffold( + directive = navigator.scaffoldDirective, + value = navigator.scaffoldValue, + listPane = { + key (state.filter) { + val fragment = remember { mutableStateOf(null) } + val keyboardOpen = rememberImeState() + AndroidFragment( + fragmentState = rememberFragmentState(), + arguments = remember(state.filter) { + Bundle() + .apply { putParcelable(EXTRA_FILTER, state.filter) } + }, + modifier = Modifier + .fillMaxSize() + .imePadding(), + ) { tlf -> + fragment.value = tlf + tlf.applyInsets(windowInsets.value) + tlf.setNavigationClickListener { + scope.launch { drawerState.open() } + } + } + LaunchedEffect(fragment, windowInsets, keyboardOpen.value) { + fragment.value?.applyInsets( + if (keyboardOpen.value) { + PaddingValues( + top = windowInsets.value.calculateTopPadding(), + ) + } else { + windowInsets.value + } + ) } } - LaunchedEffect(fragment, windowInsets, keyboardOpen.value) { - fragment.value?.applyInsets( - if (keyboardOpen.value) { - PaddingValues( - top = windowInsets.value.calculateTopPadding(), + }, + detailPane = { + val direction = LocalLayoutDirection.current + Box( + modifier = Modifier + .fillMaxSize() + .padding( + top = windowInsets.value.calculateTopPadding(), + start = windowInsets.value.calculateStartPadding(direction), + end = windowInsets.value.calculateEndPadding(direction), + bottom = if (rememberImeState().value) + WindowInsets.ime.asPaddingValues().calculateBottomPadding() + else + windowInsets.value.calculateBottomPadding() + ), + contentAlignment = Alignment.Center, + ) { + if (state.task == null) { + if (isListVisible && isDetailVisible) { + Icon( + painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground), + contentDescription = null, + modifier = Modifier.size(192.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } else { + key(state.task) { + AndroidFragment( + fragmentState = rememberFragmentState(), + arguments = remember(state.task) { + Bundle() + .apply { putParcelable(EXTRA_TASK, state.task) } + }, + modifier = Modifier.fillMaxSize(), ) - } else { - windowInsets.value } - ) - } - } - }, - detailPane = { - val direction = LocalLayoutDirection.current - Box( - modifier = Modifier - .fillMaxSize() - .padding( - top = windowInsets.value.calculateTopPadding(), - start = windowInsets.value.calculateStartPadding(direction), - end = windowInsets.value.calculateEndPadding(direction), - bottom = if (rememberImeState().value) - WindowInsets.ime.asPaddingValues().calculateBottomPadding() - else - windowInsets.value.calculateBottomPadding() - ), - contentAlignment = Alignment.Center, - ) { - if (state.task == null) { - if (isListVisible && isDetailVisible) { - Icon( - painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground), - contentDescription = null, - modifier = Modifier.size(192.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } else { - key(state.task) { - AndroidFragment( - fragmentState = rememberFragmentState(), - arguments = remember(state.task) { - Bundle() - .apply { putParcelable(EXTRA_TASK, state.task) } - }, - modifier = Modifier.fillMaxSize(), - ) } } - } - }, - ) + }, + ) + + SystemBarScrim( + modifier = Modifier + .windowInsetsTopHeight(WindowInsets.systemBars) + .align(Alignment.TopCenter), + ) + } } } } diff --git a/app/src/main/java/org/tasks/compose/home/SystemBarScrim.kt b/app/src/main/java/org/tasks/compose/home/SystemBarScrim.kt new file mode 100644 index 000000000..b414ebd74 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/home/SystemBarScrim.kt @@ -0,0 +1,22 @@ +package org.tasks.compose.home + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun SystemBarScrim( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.background, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(color.copy(alpha = 0.8f)) + .then(modifier) + ) +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_task_list.xml b/app/src/main/res/layout/fragment_task_list.xml index 2d6a9ca93..807697c55 100644 --- a/app/src/main/res/layout/fragment_task_list.xml +++ b/app/src/main/res/layout/fragment_task_list.xml @@ -68,6 +68,13 @@ + +