diff --git a/app/src/main/java/dev/dimension/flare/data/model/AppearanceSettings.kt b/app/src/main/java/dev/dimension/flare/data/model/AppearanceSettings.kt index 51c45d89a..0409cbd60 100644 --- a/app/src/main/java/dev/dimension/flare/data/model/AppearanceSettings.kt +++ b/app/src/main/java/dev/dimension/flare/data/model/AppearanceSettings.kt @@ -25,7 +25,7 @@ data class AppearanceSettings( val colorSeed: ULong = Color.Blue.value, val avatarShape: AvatarShape = AvatarShape.CIRCLE, val showActions: Boolean = true, - val pureColorMode: Boolean = false, + val pureColorMode: Boolean = true, val showNumbers: Boolean = true, val showLinkPreview: Boolean = true, val showMedia: Boolean = true, diff --git a/app/src/main/java/dev/dimension/flare/ui/component/BackButton.kt b/app/src/main/java/dev/dimension/flare/ui/component/BackButton.kt index 0488dde16..fa77304d4 100644 --- a/app/src/main/java/dev/dimension/flare/ui/component/BackButton.kt +++ b/app/src/main/java/dev/dimension/flare/ui/component/BackButton.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import compose.icons.FontAwesomeIcons import dev.dimension.flare.R @Composable @@ -33,13 +34,13 @@ fun BackButton( ), ) { FAIcon( - imageVector = arrow, + imageVector = FontAwesomeIcons.BackArrow, contentDescription = stringResource(id = R.string.navigate_back), ) } } -private val arrow by lazy { +val FontAwesomeIcons.BackArrow by lazy { Builder( name = "Arrow", defaultWidth = 448.0.dp, diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt index 393f9ec37..891d0d527 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourceEditSheet.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.delete import androidx.compose.foundation.text.input.rememberTextFieldState @@ -32,6 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import compose.icons.FontAwesomeIcons import compose.icons.fontawesomeicons.Solid @@ -42,6 +44,8 @@ import dev.dimension.flare.R import dev.dimension.flare.data.model.RssTimelineTabItem import dev.dimension.flare.data.repository.SettingsRepository import dev.dimension.flare.ui.component.FAIcon +import dev.dimension.flare.ui.component.NetworkImage +import dev.dimension.flare.ui.component.listCard import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.model.flatMap @@ -148,6 +152,11 @@ internal fun RssSourceEditSheet( label = { Text(text = stringResource(id = R.string.rss_sources_title_label)) }, modifier = Modifier.fillMaxWidth(), lineLimits = TextFieldLineLimits.SingleLine, + keyboardOptions = + KeyboardOptions( + imeAction = ImeAction.Done, + autoCorrectEnabled = false, + ), ) } @@ -216,26 +225,37 @@ internal fun RssSourceEditSheet( Text(text = stringResource(id = R.string.rss_sources_rss_hub_host_hint)) }, ) - publicRssHubServer.forEach { + publicRssHubServer.forEachIndexed { index, it -> ListItem( headlineContent = { Text(text = it) }, modifier = - Modifier.clickable { - state.rssHubHostText.edit { - delete(0, state.rssHubHostText.text.length) - append(it) - } - }, + Modifier + .listCard( + index = index, + totalCount = publicRssHubServer.size, + ).clickable { + state.rssHubHostText.edit { + delete(0, state.rssHubHostText.text.length) + append(it) + } + }, ) } } is CheckRssSourcePresenter.State.RssState.RssSources -> { Text(stringResource(R.string.rss_sources_discovered_rss_sources)) - rssState.sources.forEach { source -> + rssState.sources.forEachIndexed { index, source -> ListItem( + leadingContent = { + NetworkImage( + source.favIcon, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, headlineContent = { Text(text = source.title.orEmpty()) }, @@ -251,9 +271,13 @@ internal fun RssSourceEditSheet( ) }, modifier = - Modifier.clickable { - state.selectSource(source) - }, + Modifier + .listCard( + index = index, + totalCount = rssState.sources.size, + ).clickable { + state.selectSource(source) + }, ) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourcesScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourcesScreen.kt index ea2128151..7d68ef937 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourcesScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/rss/RssSourcesScreen.kt @@ -132,12 +132,12 @@ internal fun RssSourcesScreen( ListItem( modifier = Modifier - .clickable { - onClicked.invoke(it) - }.listCard( + .listCard( index = index, totalCount = itemCount, - ), + ).clickable { + onClicked.invoke(it) + }, headlineContent = { it.title?.let { Text(text = it) diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/ServiceSelectScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/ServiceSelectScreen.kt index 71ab3c144..c3ba17cb6 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/ServiceSelectScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/ServiceSelectScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material3.Button @@ -42,6 +43,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import compose.icons.FontAwesomeIcons @@ -143,6 +145,11 @@ internal fun ServiceSelectScreen( ) OutlinedTextField( state = state.instanceInputState, + keyboardOptions = + KeyboardOptions( + imeAction = ImeAction.Done, + autoCorrectEnabled = false, + ), placeholder = { Text( text = stringResource(id = R.string.service_select_instance_input_placeholder), @@ -236,6 +243,11 @@ internal fun ServiceSelectScreen( Modifier .width(300.dp), lineLimits = TextFieldLineLimits.SingleLine, + keyboardOptions = + KeyboardOptions( + imeAction = ImeAction.Done, + autoCorrectEnabled = false, + ), ) AnimatedVisibility(state.blueskyInputState.usePasswordLogin) { OutlinedSecureTextField( @@ -260,6 +272,11 @@ internal fun ServiceSelectScreen( Modifier .width(300.dp), lineLimits = TextFieldLineLimits.SingleLine, + keyboardOptions = + KeyboardOptions( + imeAction = ImeAction.Done, + autoCorrectEnabled = false, + ), ) } AnimatedVisibility(!state.blueskyInputState.usePasswordLogin) { diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/VVOLoginScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/VVOLoginScreen.kt index 428371762..aa80fe207 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/VVOLoginScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/VVOLoginScreen.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.ui.screen.serviceselect import android.graphics.Color -import android.view.View import android.view.ViewGroup.LayoutParams import android.webkit.CookieManager import android.widget.FrameLayout @@ -59,7 +58,7 @@ internal fun VVOLoginScreen(toHome: () -> Unit) { .padding(it) .fillMaxSize(), onCreated = { - it.setLayerType(View.LAYER_TYPE_SOFTWARE, null) +// it.setLayerType(View.LAYER_TYPE_SOFTWARE, null) it.setBackgroundColor(Color.TRANSPARENT) // clea all cookies CookieManager.getInstance().removeAllCookies(null) diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/XQTLoginScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/XQTLoginScreen.kt index 4a11f9544..be7b9ec39 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/XQTLoginScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/serviceselect/XQTLoginScreen.kt @@ -1,7 +1,6 @@ package dev.dimension.flare.ui.screen.serviceselect import android.graphics.Color -import android.view.View import android.view.ViewGroup.LayoutParams import android.webkit.CookieManager import android.webkit.WebSettings @@ -60,7 +59,7 @@ internal fun XQTLoginScreen(toHome: () -> Unit) { .padding(it) .fillMaxSize(), onCreated = { - it.setLayerType(View.LAYER_TYPE_SOFTWARE, null) +// it.setLayerType(View.LAYER_TYPE_SOFTWARE, null) it.setBackgroundColor(Color.TRANSPARENT) // clea all cookies CookieManager.getInstance().removeAllCookies(null) diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt index 9fc28581e..764028f62 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AccountsScreen.kt @@ -140,7 +140,7 @@ internal fun AccountsScreen( LaunchedEffect(swipeState.settledValue) { if (swipeState.settledValue != SwipeToDismissBoxValue.Settled) { delay(AnimationConstants.DefaultDurationMillis.toLong()) - swipeState.reset() + swipeState.snapTo(SwipeToDismissBoxValue.Settled) state.logout(account.accountKey) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AiConfigScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AiConfigScreen.kt index 98aa1b562..a3dca1873 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AiConfigScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AiConfigScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -51,7 +50,8 @@ import dev.dimension.flare.ui.model.onLoading import dev.dimension.flare.ui.model.onSuccess import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.settings.FlareServerProviderPresenter -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer +import dev.dimension.flare.ui.theme.listCardItem import dev.dimension.flare.ui.theme.screenHorizontalPadding import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest @@ -89,17 +89,16 @@ internal fun AiConfigScreen(onBack: () -> Unit) { .verticalScroll(rememberScrollState()) .padding(it) .padding(horizontal = screenHorizontalPadding) - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( modifier = Modifier + .listCardItem() .clickable { state.setShowServerDialog(true) - }.clip( - ListCardShapes.item(), - ), + }, overlineContent = { Text( text = stringResource(id = R.string.settings_ai_config_server), @@ -142,13 +141,12 @@ internal fun AiConfigScreen(onBack: () -> Unit) { }, modifier = Modifier + .listCardItem() .clickable { state.update { copy(translation = !state.aiConfig.translation) } - }.clip( - ListCardShapes.item(), - ), + }, ) ListItem( headlineContent = { @@ -173,13 +171,12 @@ internal fun AiConfigScreen(onBack: () -> Unit) { }, modifier = Modifier + .listCardItem() .clickable { state.update { copy(tldr = !state.aiConfig.tldr) } - }.clip( - ListCardShapes.item(), - ), + }, ) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppLoggingScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppLoggingScreen.kt index ee4414e7f..0d7b81baf 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppLoggingScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppLoggingScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -41,7 +40,7 @@ import dev.dimension.flare.ui.component.listCard import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.settings.DevModePresenter import dev.dimension.flare.ui.screen.media.saveByteArrayToDownloads -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer import dev.dimension.flare.ui.theme.screenHorizontalPadding import moe.tlaster.precompose.molecule.producePresenter import kotlin.time.Clock @@ -124,9 +123,10 @@ internal fun AppLoggingScreen(onBack: () -> Unit) { }, modifier = Modifier + .listCardContainer() .clickable { state.setEnabled(!state.enabled) - }.clip(ListCardShapes.container()), + }, ) } item { @@ -139,9 +139,10 @@ internal fun AppLoggingScreen(onBack: () -> Unit) { }, modifier = Modifier + .listCard(index = index, totalCount = state.messages.size) .clickable { selectedMessage = it - }.listCard(index = index, totalCount = state.messages.size), + }, ) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceScreen.kt index 83c10c26a..355bfd5a7 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/AppearanceScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -52,7 +51,8 @@ import dev.dimension.flare.ui.presenter.home.UserState import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.settings.AppearancePresenter import dev.dimension.flare.ui.presenter.settings.AppearanceState -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer +import dev.dimension.flare.ui.theme.listCardItem import dev.dimension.flare.ui.theme.screenHorizontalPadding import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.launch @@ -95,7 +95,7 @@ internal fun AppearanceScreen( Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { BoxWithConstraints { @@ -103,11 +103,12 @@ internal fun AppearanceScreen( ListItem( modifier = Modifier + .listCardItem() .clickable { if (maxWidth < 400.dp) { showMenu = true } - }.clip(ListCardShapes.item()), + }, headlineContent = { Text(text = stringResource(id = R.string.settings_appearance_theme)) }, @@ -213,11 +214,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(pureColorMode = !pureColorMode) } - }.clip(ListCardShapes.item()), + }, ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { ListItem( @@ -239,11 +241,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(dynamicTheme = !dynamicTheme) } - }.clip(ListCardShapes.item()), + }, ) } AnimatedVisibility(visible = !appearanceSettings.dynamicTheme || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { @@ -266,9 +269,10 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { toColorPicker.invoke() - }.clip(ListCardShapes.item()), + }, ) } } @@ -276,7 +280,7 @@ internal fun AppearanceScreen( Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { state.sampleStatus.onSuccess { @@ -284,8 +288,8 @@ internal fun AppearanceScreen( it, modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .clip(ListCardShapes.item()), + .listCardItem() + .background(MaterialTheme.colorScheme.surface), ) } BoxWithConstraints { @@ -293,11 +297,12 @@ internal fun AppearanceScreen( ListItem( modifier = Modifier + .listCardItem() .clickable { if (maxWidth < 400.dp) { showMenu = true } - }.clip(ListCardShapes.item()), + }, headlineContent = { Text(text = stringResource(id = R.string.settings_appearance_avatar_shape)) }, @@ -388,11 +393,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(showActions = !showActions) } - }.clip(ListCardShapes.item()), + }, ) AnimatedVisibility(appearanceSettings.showActions) { ListItem( @@ -414,11 +420,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(showNumbers = !showNumbers) } - }.clip(ListCardShapes.item()), + }, ) } ListItem( @@ -440,11 +447,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(showLinkPreview = !showLinkPreview) } - }.clip(ListCardShapes.item()), + }, ) AnimatedVisibility(visible = appearanceSettings.showLinkPreview) { ListItem( @@ -466,11 +474,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(compatLinkPreview = !compatLinkPreview) } - }.clip(ListCardShapes.item()), + }, ) } ListItem( @@ -492,11 +501,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(showMedia = !showMedia) } - }.clip(ListCardShapes.item()), + }, ) AnimatedVisibility(appearanceSettings.showMedia) { ListItem( @@ -518,11 +528,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(showSensitiveContent = !showSensitiveContent) } - }.clip(ListCardShapes.item()), + }, ) } AnimatedVisibility(appearanceSettings.showMedia) { @@ -545,11 +556,12 @@ internal fun AppearanceScreen( }, modifier = Modifier + .listCardItem() .clickable { state.updateSettings { copy(expandMediaSize = !expandMediaSize) } - }.clip(ListCardShapes.item()), + }, ) } AnimatedVisibility(appearanceSettings.showMedia) { @@ -558,11 +570,12 @@ internal fun AppearanceScreen( ListItem( modifier = Modifier + .listCardItem() .clickable { if (maxWidth < 400.dp) { showMenu = true } - }.clip(ListCardShapes.item()), + }, headlineContent = { Text(text = stringResource(id = R.string.settings_appearance_video_autoplay)) }, diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalCacheSearchScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalCacheSearchScreen.kt index 29d3d60b0..e4667a227 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalCacheSearchScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalCacheSearchScreen.kt @@ -30,7 +30,7 @@ import compose.icons.fontawesomeicons.Solid import compose.icons.fontawesomeicons.solid.Xmark import dev.dimension.flare.R import dev.dimension.flare.ui.common.itemsIndexed -import dev.dimension.flare.ui.component.BackButton +import dev.dimension.flare.ui.component.BackArrow import dev.dimension.flare.ui.component.FAIcon import dev.dimension.flare.ui.component.FlareScaffold import dev.dimension.flare.ui.component.status.AdaptiveCard @@ -78,15 +78,20 @@ internal fun LocalCacheSearchScreen(onBack: () -> Unit) { Text(stringResource(R.string.local_history_search_placeholder)) }, leadingIcon = { - BackButton( - onBack = { + IconButton( + onClick = { if (state.searchBarExpanded) { state.setSearchBarExpanded(false) } else { onBack() } }, - ) + ) { + FAIcon( + imageVector = FontAwesomeIcons.BackArrow, + contentDescription = stringResource(id = R.string.navigate_back), + ) + } }, trailingIcon = { if (text.isNotEmpty()) { diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalFilterEditDialog.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalFilterEditDialog.kt index bcb0be40a..79fc1ff70 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalFilterEditDialog.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/LocalFilterEditDialog.kt @@ -23,7 +23,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -40,7 +39,8 @@ import dev.dimension.flare.ui.model.UiKeywordFilter import dev.dimension.flare.ui.model.onSuccess import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.settings.LocalFilterPresenter -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer +import dev.dimension.flare.ui.theme.listCardItem import dev.dimension.flare.ui.theme.screenHorizontalPadding import moe.tlaster.precompose.molecule.producePresenter @@ -111,7 +111,7 @@ internal fun LocalFilterEditDialog( Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -126,9 +126,10 @@ internal fun LocalFilterEditDialog( }, modifier = Modifier + .listCardItem() .clickable { state.setForTimeline(!state.forTimeline) - }.clip(ListCardShapes.item()), + }, ) ListItem( headlineContent = { @@ -142,9 +143,10 @@ internal fun LocalFilterEditDialog( }, modifier = Modifier + .listCardItem() .clickable { state.setForNotification(!state.forNotification) - }.clip(ListCardShapes.item()), + }, ) ListItem( headlineContent = { @@ -158,9 +160,10 @@ internal fun LocalFilterEditDialog( }, modifier = Modifier + .listCardItem() .clickable { state.setForSearch(!state.forSearch) - }.clip(ListCardShapes.item()), + }, ) } Spacer(modifier = Modifier.weight(1f)) @@ -181,10 +184,11 @@ internal fun LocalFilterEditDialog( }, modifier = Modifier + .listCardItem() .clickable { state.delete() onBack() - }.clip(ListCardShapes.container()), + }, ) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt index 77b3b600d..e7d96f691 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -47,7 +46,8 @@ import dev.dimension.flare.ui.model.onSuccess import dev.dimension.flare.ui.presenter.home.ActiveAccountPresenter import dev.dimension.flare.ui.presenter.home.UserState import dev.dimension.flare.ui.presenter.invoke -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer +import dev.dimension.flare.ui.theme.listCardItem import dev.dimension.flare.ui.theme.screenHorizontalPadding import moe.tlaster.precompose.molecule.producePresenter @@ -120,73 +120,94 @@ internal fun SettingsScreen( ) { state.user .onSuccess { - AccountItem( - userState = state.user, - avatarSize = 40.dp, - onClick = { - toAccounts.invoke() - }, - supportingContent = { - Text(text = stringResource(id = R.string.settings_accounts_title)) - }, - toLogin = { - toAccounts.invoke() - }, + Column( modifier = Modifier - .clip(shape = ListCardShapes.container()), - ) + .listCardContainer(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + AccountItem( + userState = state.user, + avatarSize = 40.dp, + onClick = { + toAccounts.invoke() + }, + supportingContent = { + Text(text = stringResource(id = R.string.settings_accounts_title)) + }, + toLogin = { + toAccounts.invoke() + }, + modifier = + Modifier + .listCardItem(), + ) + } }.onError { - ListItem( - headlineContent = { - Text(text = stringResource(id = R.string.settings_accounts_title)) - }, + Column( modifier = Modifier - .clickable { - toAccounts.invoke() - }.clip(shape = ListCardShapes.container()), - leadingContent = { - ThemedIcon( - imageVector = FontAwesomeIcons.Solid.CircleUser, - contentDescription = null, - color = ThemeIconData.Color.ImperialMagenta, - ) - }, - supportingContent = { - Text(text = stringResource(id = R.string.settings_accounts_title)) - }, - ) + .listCardContainer(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + ListItem( + headlineContent = { + Text(text = stringResource(id = R.string.settings_accounts_title)) + }, + modifier = + Modifier + .listCardItem() + .clickable { + toAccounts.invoke() + }, + leadingContent = { + ThemedIcon( + imageVector = FontAwesomeIcons.Solid.CircleUser, + contentDescription = null, + color = ThemeIconData.Color.ImperialMagenta, + ) + }, + supportingContent = { + Text(text = stringResource(id = R.string.settings_accounts_title)) + }, + ) + } } state.user .onError { - ListItem( - headlineContent = { - Text(text = stringResource(id = R.string.settings_guest_setting_title)) - }, + Column( modifier = Modifier - .clickable { - toGuestSettings.invoke() - }, - leadingContent = { - ThemedIcon( - imageVector = FontAwesomeIcons.Solid.Globe, - contentDescription = stringResource(id = R.string.settings_guest_setting_title), - color = ThemeIconData.Color.SapphireBlue, - ) - }, - supportingContent = { - Text(text = stringResource(id = R.string.settings_guest_setting_description)) - }, - ) + .listCardContainer(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + ListItem( + headlineContent = { + Text(text = stringResource(id = R.string.settings_guest_setting_title)) + }, + modifier = + Modifier + .listCardItem() + .clickable { + toGuestSettings.invoke() + }, + leadingContent = { + ThemedIcon( + imageVector = FontAwesomeIcons.Solid.Globe, + contentDescription = stringResource(id = R.string.settings_guest_setting_title), + color = ThemeIconData.Color.SapphireBlue, + ) + }, + supportingContent = { + Text(text = stringResource(id = R.string.settings_guest_setting_description)) + }, + ) + } } - Column( modifier = - Modifier - .clip(ListCardShapes.container()), + Modifier.listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -205,9 +226,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toAppearance.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) state.user.onSuccess { ListItem( @@ -226,9 +248,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toTabCustomization.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) } } @@ -237,7 +260,7 @@ internal fun SettingsScreen( Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -256,9 +279,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toLocalFilter.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) ListItem( headlineContent = { @@ -266,9 +290,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toLocalHistory.invoke() - }.clip(shape = ListCardShapes.item()), + }, leadingContent = { ThemedIcon( imageVector = FontAwesomeIcons.Solid.ClockRotateLeft, @@ -296,9 +321,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toStorage.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) } // ListItem( @@ -323,7 +349,7 @@ internal fun SettingsScreen( Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -342,15 +368,16 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toAiConfig.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) } Column( modifier = Modifier - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { if (BuildConfig.DEBUG) { @@ -367,9 +394,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toColorSpace.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) } ListItem( @@ -388,9 +416,10 @@ internal fun SettingsScreen( }, modifier = Modifier + .listCardItem() .clickable { toAbout.invoke() - }.clip(shape = ListCardShapes.item()), + }, ) } } diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/settings/StorageScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/settings/StorageScreen.kt index 77505472e..f25507cd5 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/settings/StorageScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/settings/StorageScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -37,7 +36,8 @@ import dev.dimension.flare.ui.component.ThemedIcon import dev.dimension.flare.ui.presenter.invoke import dev.dimension.flare.ui.presenter.settings.StoragePresenter import dev.dimension.flare.ui.presenter.settings.StorageState -import dev.dimension.flare.ui.theme.ListCardShapes +import dev.dimension.flare.ui.theme.listCardContainer +import dev.dimension.flare.ui.theme.listCardItem import dev.dimension.flare.ui.theme.screenHorizontalPadding import moe.tlaster.precompose.molecule.producePresenter @@ -72,7 +72,7 @@ internal fun StorageScreen( .verticalScroll(rememberScrollState()) .padding(it) .padding(horizontal = screenHorizontalPadding) - .clip(ListCardShapes.container()), + .listCardContainer(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -90,11 +90,10 @@ internal fun StorageScreen( }, modifier = Modifier + .listCardItem() .clickable { state.clearImageCache() - }.clip( - ListCardShapes.item(), - ), + }, leadingContent = { ThemedIcon( FontAwesomeIcons.Solid.Images, @@ -119,11 +118,10 @@ internal fun StorageScreen( }, modifier = Modifier + .listCardItem() .clickable { state.clearCacheDatabase() - }.clip( - ListCardShapes.item(), - ), + }, leadingContent = { ThemedIcon( FontAwesomeIcons.Solid.Database, @@ -144,11 +142,10 @@ internal fun StorageScreen( }, modifier = Modifier + .listCardItem() .clickable { toAppLog.invoke() - }.clip( - ListCardShapes.item(), - ), + }, leadingContent = { ThemedIcon( FontAwesomeIcons.Solid.Envelope, diff --git a/app/src/main/java/dev/dimension/flare/ui/theme/Theme.kt b/app/src/main/java/dev/dimension/flare/ui/theme/Theme.kt index 8a0cd488a..1c64f9263 100644 --- a/app/src/main/java/dev/dimension/flare/ui/theme/Theme.kt +++ b/app/src/main/java/dev/dimension/flare/ui/theme/Theme.kt @@ -48,6 +48,7 @@ private fun ColorScheme.withPureColorLightMode(): ColorScheme = surfaceContainerLowest = Color.White, surfaceContainerHighest = Color.White, onSurfaceVariant = MoreColors.Gray800, + outlineVariant = MoreColors.Gray400, ) private fun ColorScheme.withPureColorDarkMode(): ColorScheme = @@ -62,6 +63,7 @@ private fun ColorScheme.withPureColorDarkMode(): ColorScheme = surfaceContainerLowest = Color.Black, surfaceContainerHighest = Color.Black, onSurfaceVariant = MoreColors.Gray400, + outlineVariant = MoreColors.Gray800, ) @OptIn(ExperimentalMaterial3ExpressiveApi::class) diff --git a/shared/ui/component/src/androidMain/kotlin/dev/dimension/flare/ui/theme/PlatformShapes.android.kt b/shared/ui/component/src/androidMain/kotlin/dev/dimension/flare/ui/theme/PlatformShapes.android.kt index f783ebb2b..5fe05554c 100644 --- a/shared/ui/component/src/androidMain/kotlin/dev/dimension/flare/ui/theme/PlatformShapes.android.kt +++ b/shared/ui/component/src/androidMain/kotlin/dev/dimension/flare/ui/theme/PlatformShapes.android.kt @@ -1,10 +1,19 @@ package dev.dimension.flare.ui.theme +import android.os.Build import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection internal actual object PlatformShapes { actual val extraSmall: Shape @@ -50,7 +59,7 @@ internal actual object PlatformShapes { public object ListCardShapes { @Composable - public fun container(): Shape = PlatformShapes.listCardContainerShape + public fun container(): CornerBasedShape = PlatformShapes.listCardContainerShape @Composable public fun item(): Shape = PlatformShapes.listCardItemShape @@ -61,3 +70,39 @@ public object ListCardShapes { @Composable public fun bottomCard(): Shape = PlatformShapes.bottomCardShape } + +@Composable +public fun Modifier.listCardContainer(): Modifier = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + this.clip(PlatformShapes.listCardContainerShape) + } else { + this.compatClip(PlatformShapes.listCardContainerShape) + } + +@Composable +public fun Modifier.listCardItem(): Modifier = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + this.clip(PlatformShapes.listCardItemShape) + } else { + this.compatClip(PlatformShapes.listCardItemShape) + } + +@Composable +private fun Modifier.compatClip(shape: Shape): Modifier { + val layoutDirection = LocalLayoutDirection.current + val density = LocalDensity.current + return this.drawWithContent { + val outline = shape.createOutline(size, layoutDirection, density) + + val clipPath = + when (outline) { + is Outline.Rectangle -> Path().apply { addRect(outline.rect) } + is Outline.Rounded -> Path().apply { addRoundRect(outline.roundRect) } + is Outline.Generic -> outline.path + } + + clipPath(path = clipPath) { + this@drawWithContent.drawContent() + } + } +} diff --git a/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/CompatClip.kt b/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/CompatClip.kt new file mode 100644 index 000000000..e2795c449 --- /dev/null +++ b/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/CompatClip.kt @@ -0,0 +1,31 @@ +package dev.dimension.flare.ui.component + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +public fun Modifier.compatClip(shape: Shape): Modifier { + val layoutDirection = LocalLayoutDirection.current + val density = LocalDensity.current + return this.drawWithContent { + val outline = shape.createOutline(size, layoutDirection, density) + + val clipPath = + when (outline) { + is Outline.Rectangle -> Path().apply { addRect(outline.rect) } + is Outline.Rounded -> Path().apply { addRoundRect(outline.roundRect) } + is Outline.Generic -> outline.path + } + + clipPath(path = clipPath) { + this@drawWithContent.drawContent() + } + } +} diff --git a/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/settings/AboutScreenContent.kt b/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/settings/AboutScreenContent.kt index ad47e0925..15a34ef11 100644 --- a/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/settings/AboutScreenContent.kt +++ b/shared/ui/src/commonMain/kotlin/dev/dimension/flare/ui/screen/settings/AboutScreenContent.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -41,6 +40,7 @@ import dev.dimension.flare.settings_about_source_code import dev.dimension.flare.settings_about_telegram import dev.dimension.flare.settings_about_telegram_description import dev.dimension.flare.settings_privacy_policy +import dev.dimension.flare.ui.component.compatClip import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -84,7 +84,7 @@ public fun AboutScreenContent( Column( modifier = Modifier - .clip(MaterialTheme.shapes.medium), + .compatClip(MaterialTheme.shapes.medium), verticalArrangement = Arrangement.spacedBy(2.dp), ) { ListItem( @@ -98,11 +98,10 @@ public fun AboutScreenContent( }, modifier = Modifier + .compatClip(MaterialTheme.shapes.extraSmall) .clickable { uriHandler.openUri("https://github.com/DimensionDev/Flare") - }.clip( - MaterialTheme.shapes.extraSmall, - ), + }, leadingContent = { Icon( imageVector = FontAwesomeIcons.Brands.Github, @@ -122,11 +121,10 @@ public fun AboutScreenContent( }, modifier = Modifier + .compatClip(MaterialTheme.shapes.extraSmall) .clickable { uriHandler.openUri("https://t.me/+0UtcP6_qcDoyOWE1") - }.clip( - MaterialTheme.shapes.extraSmall, - ), + }, leadingContent = { Icon( imageVector = FontAwesomeIcons.Brands.Telegram, @@ -146,11 +144,10 @@ public fun AboutScreenContent( }, modifier = Modifier + .compatClip(MaterialTheme.shapes.extraSmall) .clickable { uriHandler.openUri("https://line.me/ti/g/hf95HyGJ9k") - }.clip( - MaterialTheme.shapes.extraSmall, - ), + }, leadingContent = { Icon( imageVector = FontAwesomeIcons.Brands.Line, @@ -170,11 +167,10 @@ public fun AboutScreenContent( }, modifier = Modifier + .compatClip(MaterialTheme.shapes.extraSmall) .clickable { uriHandler.openUri("https://crowdin.com/project/flareapp") - }.clip( - MaterialTheme.shapes.extraSmall, - ), + }, leadingContent = { Icon( imageVector = FontAwesomeIcons.Solid.Language, @@ -194,11 +190,10 @@ public fun AboutScreenContent( }, modifier = Modifier + .compatClip(MaterialTheme.shapes.extraSmall) .clickable { uriHandler.openUri("https://legal.mask.io/maskbook/") - }.clip( - MaterialTheme.shapes.extraSmall, - ), + }, leadingContent = { Icon( imageVector = FontAwesomeIcons.Solid.Lock,