switch to compose-bom-alpha

This commit is contained in:
2025-06-20 21:25:17 +03:00
parent a916dc649c
commit 5aa1f21183
5 changed files with 208 additions and 454 deletions
@@ -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,7 +88,6 @@ class MainActivity : AppCompatActivity() {
requestNotificationPermissions()
setContent {
KoinContext {
val context = LocalContext.current
val userSettings: UserSettings = koinInject()
@@ -253,7 +251,6 @@ class MainActivity : AppCompatActivity() {
}
}
}
}
private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -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,
@@ -49,10 +45,7 @@ fun IconButton(
onLongClick = onLongClick,
enabled = enabled,
interactionSource = interactionSource,
indication = rippleOrFallbackImplementation(
bounded = false,
radius = IconButtonTokens.StateLayerSize / 2
)
indication = ripple()
),
contentAlignment = Alignment.Center
) {
@@ -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
@@ -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,20 +568,42 @@ 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()
}
CompositionLocalProvider(LocalTextToolbar provides textToolbar) {
TextField(
modifier = Modifier.weight(1f),
separator()
},
value = screenState.message,
onValueChange = onMessageInputChanged,
colors = TextFieldDefaults.colors(
@@ -604,7 +620,7 @@ fun MessagesHistoryScreen(
)
}
)
}
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
}
@@ -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()
+4 -2
View File
@@ -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",