switch to compose-bom-alpha
This commit is contained in:
@@ -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
|
||||
|
||||
+39
-262
@@ -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
|
||||
}
|
||||
|
||||
+4
-4
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user