diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt index 5e8e9fef..07d28e3c 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt @@ -50,7 +50,6 @@ import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.theme.LocalUser import dev.meloda.fast.ui.util.isNeedToEnableDarkMode import org.koin.androidx.compose.koinViewModel -import org.koin.compose.KoinContext import org.koin.compose.koinInject import dev.meloda.fast.ui.R as UiR @@ -89,169 +88,167 @@ class MainActivity : AppCompatActivity() { requestNotificationPermissions() setContent { - KoinContext { - val context = LocalContext.current + val context = LocalContext.current - val userSettings: UserSettings = koinInject() - val longPollController: LongPollController = koinInject() + val userSettings: UserSettings = koinInject() + val longPollController: LongPollController = koinInject() - val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle() - val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle() + val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle() + val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle() - val viewModel: MainViewModel = koinViewModel() + val viewModel: MainViewModel = koinViewModel() - val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle() + val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle() - LifecycleResumeEffect(true) { - viewModel.onAppResumed(intent) - onPauseOrDispose {} - } + LifecycleResumeEffect(true) { + viewModel.onAppResumed(intent) + onPauseOrDispose {} + } - val permissionState = - rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS) + val permissionState = + rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS) - val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle() - val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle() + val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle() + val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle() - LaunchedEffect(isNeedToCheckPermission) { - if (isNeedToCheckPermission) { - viewModel.onPermissionCheckStatus(permissionState.status) + LaunchedEffect(isNeedToCheckPermission) { + if (isNeedToCheckPermission) { + viewModel.onPermissionCheckStatus(permissionState.status) - if (permissionState.status.isGranted) { - if (longPollCurrentState == LongPollState.InApp) { - toggleLongPollService(false) - } - - toggleLongPollService( - enable = true, - inBackground = true - ) - } - } - } - - LaunchedEffect(isNeedToRequestPermission) { - if (isNeedToRequestPermission) { - viewModel.onPermissionsRequested() - permissionState.launchPermissionRequest() - } - } - - LifecycleResumeEffect(longPollStateToApply) { - Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply") - if (longPollStateToApply != LongPollState.Background) { - if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched() - && longPollCurrentState != longPollStateToApply - ) { + if (permissionState.status.isGranted) { + if (longPollCurrentState == LongPollState.InApp) { toggleLongPollService(false) - Log.d("LongPoll", "recreate()") } toggleLongPollService( - enable = longPollStateToApply.isLaunched(), - inBackground = longPollStateToApply == LongPollState.Background + enable = true, + inBackground = true ) } - - onPauseOrDispose {} } + } - val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle() - LifecycleResumeEffect(sendOnline) { - toggleOnlineService(sendOnline) + LaunchedEffect(isNeedToRequestPermission) { + if (isNeedToRequestPermission) { + viewModel.onPermissionsRequested() + permissionState.launchPermissionRequest() + } + } - onPauseOrDispose { - toggleOnlineService(false) + LifecycleResumeEffect(longPollStateToApply) { + Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply") + if (longPollStateToApply != LongPollState.Background) { + if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched() + && longPollCurrentState != longPollStateToApply + ) { + toggleLongPollService(false) + Log.d("LongPoll", "recreate()") } - } - val deviceWidthDp = remember(true) { - context.resources.displayMetrics.widthPixels.pxToDp() - } - val deviceHeightDp = remember(true) { - context.resources.displayMetrics.heightPixels.pxToDp() - } - - val deviceWidthSize by remember(deviceWidthDp) { - derivedStateOf { - when { - deviceWidthDp <= 360 -> DeviceSize.Small - deviceWidthDp <= 600 -> DeviceSize.Compact - deviceWidthDp <= 840 -> DeviceSize.Medium - else -> DeviceSize.Expanded - } - } - } - - val deviceHeightSize by remember(deviceHeightDp) { - derivedStateOf { - when { - deviceHeightDp <= 480 -> DeviceSize.Small - deviceHeightDp <= 700 -> DeviceSize.Compact - deviceHeightDp <= 900 -> DeviceSize.Medium - else -> DeviceSize.Expanded - } - } - } - - val sizeConfig by remember(deviceWidthSize, deviceHeightSize) { - mutableStateOf( - SizeConfig( - widthSize = deviceWidthSize, - heightSize = deviceHeightSize - ) + toggleLongPollService( + enable = longPollStateToApply.isLaunched(), + inBackground = longPollStateToApply == LongPollState.Background ) } - val darkMode by userSettings.darkMode.collectAsStateWithLifecycle() - val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle() - val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle() - val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle() - val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle() - val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle() - val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle() + onPauseOrDispose {} + } - val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode) + val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle() + LifecycleResumeEffect(sendOnline) { + toggleOnlineService(sendOnline) - val themeConfig by remember( - darkMode, - dynamicColors, - amoledDark, - enableBlur, - enableMultiline, - setDarkMode, - useSystemFont - ) { - derivedStateOf { - ThemeConfig( - darkMode = setDarkMode, - dynamicColors = dynamicColors, - selectedColorScheme = 0, - amoledDark = amoledDark, - enableBlur = enableBlur, - enableMultiline = enableMultiline, - useSystemFont = useSystemFont, - enableAnimations = enableAnimations - ) + onPauseOrDispose { + toggleOnlineService(false) + } + } + + val deviceWidthDp = remember(true) { + context.resources.displayMetrics.widthPixels.pxToDp() + } + val deviceHeightDp = remember(true) { + context.resources.displayMetrics.heightPixels.pxToDp() + } + + val deviceWidthSize by remember(deviceWidthDp) { + derivedStateOf { + when { + deviceWidthDp <= 360 -> DeviceSize.Small + deviceWidthDp <= 600 -> DeviceSize.Compact + deviceWidthDp <= 840 -> DeviceSize.Medium + else -> DeviceSize.Expanded } } + } - CompositionLocalProvider( - LocalThemeConfig provides themeConfig, - LocalSizeConfig provides sizeConfig, - LocalUser provides currentUser - ) { - AppTheme( - useDarkTheme = themeConfig.darkMode, - useDynamicColors = themeConfig.dynamicColors, - selectedColorScheme = themeConfig.selectedColorScheme, - useAmoledBackground = themeConfig.amoledDark, - useSystemFont = themeConfig.useSystemFont - ) { - RootScreen(viewModel = viewModel) + val deviceHeightSize by remember(deviceHeightDp) { + derivedStateOf { + when { + deviceHeightDp <= 480 -> DeviceSize.Small + deviceHeightDp <= 700 -> DeviceSize.Compact + deviceHeightDp <= 900 -> DeviceSize.Medium + else -> DeviceSize.Expanded } } } + + val sizeConfig by remember(deviceWidthSize, deviceHeightSize) { + mutableStateOf( + SizeConfig( + widthSize = deviceWidthSize, + heightSize = deviceHeightSize + ) + ) + } + + val darkMode by userSettings.darkMode.collectAsStateWithLifecycle() + val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle() + val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle() + val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle() + val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle() + val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle() + val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle() + + val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode) + + val themeConfig by remember( + darkMode, + dynamicColors, + amoledDark, + enableBlur, + enableMultiline, + setDarkMode, + useSystemFont + ) { + derivedStateOf { + ThemeConfig( + darkMode = setDarkMode, + dynamicColors = dynamicColors, + selectedColorScheme = 0, + amoledDark = amoledDark, + enableBlur = enableBlur, + enableMultiline = enableMultiline, + useSystemFont = useSystemFont, + enableAnimations = enableAnimations + ) + } + } + + CompositionLocalProvider( + LocalThemeConfig provides themeConfig, + LocalSizeConfig provides sizeConfig, + LocalUser provides currentUser + ) { + AppTheme( + useDarkTheme = themeConfig.darkMode, + useDynamicColors = themeConfig.dynamicColors, + selectedColorScheme = themeConfig.selectedColorScheme, + useAmoledBackground = themeConfig.amoledDark, + useSystemFont = themeConfig.useSystemFont + ) { + RootScreen(viewModel = viewModel) + } + } } } diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/IconButton.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/IconButton.kt index 0148d85d..95e0c610 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/IconButton.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/components/IconButton.kt @@ -1,19 +1,16 @@ package dev.meloda.fast.ui.components import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Indication import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.IconButtonColors import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.LocalUseFallbackRippleImplementation import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.ripple import androidx.compose.runtime.Composable @@ -23,10 +20,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun IconButton( modifier: Modifier = Modifier, @@ -39,21 +35,18 @@ fun IconButton( ) { Box( modifier = - modifier - .minimumInteractiveComponentSize() - .size(IconButtonTokens.StateLayerSize) - .clip(IconButtonTokens.StateLayerShape) - .background(color = colors.containerColor(enabled)) - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - enabled = enabled, - interactionSource = interactionSource, - indication = rippleOrFallbackImplementation( - bounded = false, - radius = IconButtonTokens.StateLayerSize / 2 - ) - ), + modifier + .minimumInteractiveComponentSize() + .size(IconButtonTokens.StateLayerSize) + .clip(IconButtonTokens.StateLayerShape) + .background(color = colors.containerColor(enabled)) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + enabled = enabled, + interactionSource = interactionSource, + indication = ripple() + ), contentAlignment = Alignment.Center ) { val contentColor = colors.contentColor(enabled) @@ -61,21 +54,6 @@ fun IconButton( } } -@Suppress("DEPRECATION_ERROR") -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun rippleOrFallbackImplementation( - bounded: Boolean = true, - radius: Dp = Dp.Unspecified, - color: Color = Color.Unspecified -): Indication { - return if (LocalUseFallbackRippleImplementation.current) { - rememberRipple(bounded, radius, color) - } else { - ripple(bounded, radius, color) - } -} - internal object IconButtonTokens { val StateLayerShape = CircleShape val StateLayerSize = 40.0.dp diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt index 5ec33e7f..8b168200 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt @@ -1,11 +1,6 @@ package dev.meloda.fast.messageshistory.presentation -import android.os.Build -import android.view.Menu -import android.view.MenuItem -import android.view.View import androidx.activity.compose.BackHandler -import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.animateColorAsState @@ -41,6 +36,8 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.contextmenu.builder.item +import androidx.compose.foundation.text.contextmenu.modifier.addTextContextMenuComponents import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.outlined.MoreVert @@ -62,7 +59,6 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -74,15 +70,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalTextToolbar import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.platform.TextToolbar -import androidx.compose.ui.platform.TextToolbarStatus import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue @@ -149,6 +142,7 @@ fun MessagesHistoryScreen( onItalicRequested: () -> Unit = {}, onUnderlineRequested: () -> Unit = {}, ) { + val context = LocalContext.current val view = LocalView.current val coroutineScope = rememberCoroutineScope() val theme = LocalThemeConfig.current @@ -574,37 +568,59 @@ fun MessagesHistoryScreen( } } - val view = LocalView.current - val textToolbar = remember { - CustomTextToolbar( - view = view, - onBoldRequested = onBoldRequested, - onItalicRequested = onItalicRequested, - onUnderlineRequested = onUnderlineRequested, - onLinkRequested = {} - ) - } + TextField( + modifier = Modifier + .weight(1f) + .addTextContextMenuComponents { + separator() + + item( + key = "Bold", + label = context.getString(UiR.string.bold) + ) { + onBoldRequested() + close() + } + item( + key = "Italic", + label = context.getString(UiR.string.italic) + ) { + onItalicRequested() + close() + } + item( + key = "Underline", + label = context.getString(UiR.string.underline) + ) { + onUnderlineRequested() + close() + } + item( + key = "Link", + label = context.getString(UiR.string.link) + ) { + close() + } + + separator() + }, + value = screenState.message, + onValueChange = onMessageInputChanged, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + ), + placeholder = { + Text( + text = stringResource(id = UiR.string.message_input_hint), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + ) - CompositionLocalProvider(LocalTextToolbar provides textToolbar) { - TextField( - modifier = Modifier.weight(1f), - value = screenState.message, - onValueChange = onMessageInputChanged, - colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, - focusedContainerColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - ), - placeholder = { - Text( - text = stringResource(id = UiR.string.message_input_hint), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - ) - } val scope = rememberCoroutineScope() val attachmentRotation = remember { Animatable(0f) } @@ -717,242 +733,3 @@ fun MessagesHistoryScreen( } } } - -class CustomTextToolbar( - private val view: View, - private var onBoldRequested: (() -> Unit)? = null, - private var onItalicRequested: (() -> Unit)? = null, - private var onUnderlineRequested: (() -> Unit)? = null, - private var onLinkRequested: (() -> Unit)? = null -) : TextToolbar { - private var actionMode: android.view.ActionMode? = null - private val textActionModeCallback: TextActionModeCallback = - TextActionModeCallback(onActionModeDestroy = { actionMode = null }) - override var status: TextToolbarStatus = TextToolbarStatus.Hidden - private set - - override fun showMenu( - rect: Rect, - onCopyRequested: (() -> Unit)?, - onPasteRequested: (() -> Unit)?, - onCutRequested: (() -> Unit)?, - onSelectAllRequested: (() -> Unit)?, - onAutofillRequested: (() -> Unit)? - ) { - textActionModeCallback.rect = rect - textActionModeCallback.onCopyRequested = onCopyRequested - textActionModeCallback.onCutRequested = onCutRequested - textActionModeCallback.onPasteRequested = onPasteRequested - textActionModeCallback.onSelectAllRequested = onSelectAllRequested - textActionModeCallback.onAutofillRequested = onAutofillRequested - textActionModeCallback.onBoldRequested = onBoldRequested - textActionModeCallback.onItalicRequested = onItalicRequested - textActionModeCallback.onUnderlineRequested = onUnderlineRequested - textActionModeCallback.onLinkRequested = onLinkRequested - - if (actionMode == null) { - status = TextToolbarStatus.Shown - actionMode = - TextToolbarHelperMethods.startActionMode( - view, - FloatingTextActionModeCallback(textActionModeCallback), - android.view.ActionMode.TYPE_FLOATING - ) - } else { - actionMode?.invalidate() - } - } - - override fun showMenu( - rect: Rect, - onCopyRequested: (() -> Unit)?, - onPasteRequested: (() -> Unit)?, - onCutRequested: (() -> Unit)?, - onSelectAllRequested: (() -> Unit)? - ) { - showMenu( - rect = rect, - onCopyRequested = onCopyRequested, - onPasteRequested = onPasteRequested, - onCutRequested = onCutRequested, - onSelectAllRequested = onSelectAllRequested, - onAutofillRequested = null - ) - } - - override fun hide() { - status = TextToolbarStatus.Hidden - actionMode?.finish() - actionMode = null - } -} - -/** - * This class is here to ensure that the classes that use this API will get verified and can be AOT - * compiled. It is expected that this class will soft-fail verification, but the classes which use - * this method will pass. - */ -internal object TextToolbarHelperMethods { - fun startActionMode( - view: View, - actionModeCallback: android.view.ActionMode.Callback, - type: Int - ): android.view.ActionMode? { - return view.startActionMode(actionModeCallback, type) - } - - fun invalidateContentRect(actionMode: android.view.ActionMode) { - actionMode.invalidateContentRect() - } -} - - -class FloatingTextActionModeCallback(private val callback: TextActionModeCallback) : - android.view.ActionMode.Callback2() { - override fun onActionItemClicked(mode: android.view.ActionMode?, item: MenuItem?): Boolean { - return callback.onActionItemClicked(mode, item) - } - - override fun onCreateActionMode(mode: android.view.ActionMode?, menu: Menu?): Boolean { - return callback.onCreateActionMode(mode, menu) - } - - override fun onPrepareActionMode(mode: android.view.ActionMode?, menu: Menu?): Boolean { - return callback.onPrepareActionMode(mode, menu) - } - - override fun onDestroyActionMode(mode: android.view.ActionMode?) { - callback.onDestroyActionMode() - } - - override fun onGetContentRect( - mode: android.view.ActionMode?, - view: View?, - outRect: android.graphics.Rect? - ) { - val rect = callback.rect - outRect?.set(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt()) - } -} - -class TextActionModeCallback( - val onActionModeDestroy: (() -> Unit)? = null, - var rect: Rect = Rect.Zero, - var onCopyRequested: (() -> Unit)? = null, - var onPasteRequested: (() -> Unit)? = null, - var onCutRequested: (() -> Unit)? = null, - var onSelectAllRequested: (() -> Unit)? = null, - var onAutofillRequested: (() -> Unit)? = null, - var onBoldRequested: (() -> Unit)? = null, - var onItalicRequested: (() -> Unit)? = null, - var onUnderlineRequested: (() -> Unit)? = null, - var onLinkRequested: (() -> Unit)? = null -) { - fun onCreateActionMode(mode: android.view.ActionMode?, menu: Menu?): Boolean { - requireNotNull(menu) { "onCreateActionMode requires a non-null menu" } - requireNotNull(mode) { "onCreateActionMode requires a non-null mode" } - - onCopyRequested?.let { addMenuItem(menu, MenuItemOption.Copy) } - onPasteRequested?.let { addMenuItem(menu, MenuItemOption.Paste) } - onCutRequested?.let { addMenuItem(menu, MenuItemOption.Cut) } - onSelectAllRequested?.let { addMenuItem(menu, MenuItemOption.SelectAll) } - if (onAutofillRequested != null && Build.VERSION.SDK_INT >= 26) { - addMenuItem(menu, MenuItemOption.Autofill) - } - onBoldRequested?.let { addMenuItem(menu, MenuItemOption.Bold) } - onItalicRequested?.let { addMenuItem(menu, MenuItemOption.Italic) } - onUnderlineRequested?.let { addMenuItem(menu, MenuItemOption.Underline) } - onLinkRequested?.let { addMenuItem(menu, MenuItemOption.Link) } - return true - } - - // this method is called to populate new menu items when the actionMode was invalidated - fun onPrepareActionMode(mode: android.view.ActionMode?, menu: Menu?): Boolean { - if (mode == null || menu == null) return false - updateMenuItems(menu) - // should return true so that new menu items are populated - return true - } - - fun onActionItemClicked(mode: android.view.ActionMode?, item: MenuItem?): Boolean { - when (item!!.itemId) { - MenuItemOption.Copy.ordinal -> onCopyRequested?.invoke() - MenuItemOption.Paste.ordinal -> onPasteRequested?.invoke() - MenuItemOption.Cut.ordinal -> onCutRequested?.invoke() - MenuItemOption.SelectAll.ordinal -> onSelectAllRequested?.invoke() - MenuItemOption.Autofill.ordinal -> onAutofillRequested?.invoke() - MenuItemOption.Bold.ordinal -> onBoldRequested?.invoke() - MenuItemOption.Italic.ordinal -> onItalicRequested?.invoke() - MenuItemOption.Underline.ordinal -> onUnderlineRequested?.invoke() - MenuItemOption.Link.ordinal -> onLinkRequested?.invoke() - else -> return false - } - mode?.finish() - return true - } - - fun onDestroyActionMode() { - onActionModeDestroy?.invoke() - } - - @VisibleForTesting - internal fun updateMenuItems(menu: Menu) { - addOrRemoveMenuItem(menu, MenuItemOption.Copy, onCopyRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Paste, onPasteRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Cut, onCutRequested) - addOrRemoveMenuItem(menu, MenuItemOption.SelectAll, onSelectAllRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Autofill, onAutofillRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Bold, onBoldRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Italic, onItalicRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Underline, onUnderlineRequested) - addOrRemoveMenuItem(menu, MenuItemOption.Link, onLinkRequested) - } - - private fun addMenuItem(menu: Menu, item: MenuItemOption) { - menu - .add(0, item.ordinal, item.order, item.titleResource) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) - } - - private fun addOrRemoveMenuItem(menu: Menu, item: MenuItemOption, callback: (() -> Unit)?) { - when { - callback != null && menu.findItem(item.ordinal) == null -> addMenuItem(menu, item) - callback == null && menu.findItem(item.ordinal) != null -> menu.removeItem(item.ordinal) - } - } -} - -internal enum class MenuItemOption { - Copy, - Paste, - Cut, - SelectAll, - Autofill, - Bold, - Italic, - Underline, - Link; - - val titleResource: Int - get() = - when (this) { - Copy -> android.R.string.copy - Paste -> android.R.string.paste - Cut -> android.R.string.cut - SelectAll -> android.R.string.selectAll - Autofill -> - if (Build.VERSION.SDK_INT <= 26) { - UiR.string.autofill - } else { - android.R.string.autofill - } - - Bold -> UiR.string.bold - Italic -> UiR.string.italic - Underline -> UiR.string.underline - Link -> UiR.string.link - } - - /** This item will be shown before all items that have order greater than this value. */ - val order = ordinal -} diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt index 7ae4665e..2b12ef40 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/presentation/SettingsScreen.kt @@ -33,8 +33,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.LayoutDirection import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.haze -import dev.chrisbanes.haze.hazeChild +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.fast.data.UserConfig @@ -159,7 +159,7 @@ fun SettingsScreen( modifier = Modifier .then( if (themeConfig.enableBlur) { - Modifier.hazeChild( + Modifier.hazeEffect( state = hazeState, style = HazeMaterials.thick() ) @@ -175,7 +175,7 @@ fun SettingsScreen( modifier = Modifier .then( if (themeConfig.enableBlur) { - Modifier.haze(state = hazeState) + Modifier.hazeSource(state = hazeState) } else Modifier ) .fillMaxWidth() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f289706..d3111ad9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ haze = "1.6.4" kotlin = "2.1.21" ksp = "2.1.21-2.0.2" -compose-bom = "2025.06.01" +compose-bom = "2025.06.00" koin = "4.1.0" accompanist = "0.37.3" @@ -68,7 +68,8 @@ nanokt-jvm = { module = "com.conena.nanokt:nanokt-jvm", version.ref = "nanokt" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } -compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" } +compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "compose-bom" } +compose-material-icons = { module = "androidx.compose.material:material-icons-core" } compose-material3 = { module = "androidx.compose.material3:material3" } compose-ui = { module = "androidx.compose.ui:ui" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } @@ -95,6 +96,7 @@ room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", vers [bundles] compose = [ + "compose-material-icons", "compose-material3", "compose-material3-windowsize", "compose-ui",