mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-08-05 19:59:51 +00:00
feat: add adaptive dialog bottom sheet component and enhance plugin installation UI
This commit is contained in:
parent
7d1a6750c3
commit
c3852a1c32
@ -29,7 +29,10 @@
|
||||
- `plugin_interfaces` exports `zipline.core` and `semver` as API. `composeApp` depends on it for the plugin system.
|
||||
- `plugin_interfaces` also has a JS target (`browser()`), used by the plugin system.
|
||||
|
||||
## Desktop JVM specifics
|
||||
## UI component patterns
|
||||
- **AdaptiveDropdownBottomSheet** (`commonMain/.../core/ui/component/AdaptiveDropdownBottomSheet.kt`): switches between `DropdownMenu` (large screen) and `ModalBottomSheet` (small screen) via `currentWindowAdaptiveInfo()`. Do NOT use expect/actual — all adaptive components that rely ONLY on Compose/Material3 APIs belong in commonMain.
|
||||
- **AdaptiveDialogBottomSheet** (`commonMain/.../core/ui/component/AdaptiveDialogBottomSheet.kt`): switches between `ThemedDialog` (large screen) and `ModalBottomSheet` (small screen) via `currentWindowAdaptiveInfo()`. Same rule — keep in commonMain unless platform-specific APIs are required.
|
||||
- Use `expect`/`actual` only when the component MUST use platform-specific APIs (e.g. `WindowState` for desktop window controls, native scrollbars). Pure Compose/Material3 adaptivity stays in commonMain.
|
||||
- JavaFX is required; `--add-opens` flags in `compose.desktop.application.jvmArgs` must be preserved: `javafx.graphics/javafx.scene`, `javafx.graphics/com.sun.javafx.sg.prism`, `javafx.graphics/com.sun.javafx.scene`, `javafx.web/com.sun.webkit`, `javafx.media/com.sun.media.jfxmedia`, `javafx.media/com.sun.media.jfxmedia.events`.
|
||||
- JavaFX dependencies are loaded from OpenJFX with platform classifiers (win/mac/linux) resolved at configuration time via `System.getProperty("os.name")`.
|
||||
|
||||
|
||||
@ -126,6 +126,7 @@
|
||||
<string name="settings_plugins_plugin_content_description">%1$s plugin</string>
|
||||
<string name="settings_plugins_clear_content_description">Clear</string>
|
||||
|
||||
<string name="plugin_configure_title">Configure</string>
|
||||
<string name="plugin_screen_title">Plugin Manager</string>
|
||||
<string name="plugin_error_enter_url">Please enter a URL</string>
|
||||
<string name="plugin_error_url_scheme">URL must start with http:// or https://</string>
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package dev.krtirtho.spotube.core.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.krtirtho.spotube.core.ui.base.ThemedDialog
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AdaptiveDialogBottomSheet(
|
||||
onDismiss: () -> Unit,
|
||||
title: @Composable (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
breakpointDp: Float = 600f,
|
||||
actions: @Composable (() -> Unit)? = null,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val adaptiveInfo = currentWindowAdaptiveInfo()
|
||||
val isLargeScreen = adaptiveInfo.windowSizeClass.minWidthDp >= breakpointDp
|
||||
|
||||
if (isLargeScreen) {
|
||||
ThemedDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = title,
|
||||
actions = actions,
|
||||
modifier = modifier,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
} else {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
dragHandle = null,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
title?.let {
|
||||
it()
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
content()
|
||||
actions?.let {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
it()
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,9 +26,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@ -55,22 +57,30 @@ import dev.krtirtho.spotube.core.ui.base.Card
|
||||
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
||||
import dev.krtirtho.spotube.PlatformType
|
||||
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
||||
import dev.krtirtho.spotube.core.ui.base.SecondaryIconButton
|
||||
import dev.krtirtho.spotube.core.ui.base.TextField
|
||||
import dev.krtirtho.spotube.core.ui.component.AdaptiveDropdownBottomSheet
|
||||
import dev.krtirtho.spotube.core.ui.component.AdaptiveDialogBottomSheet
|
||||
import dev.krtirtho.spotube.core.ui.component.AdaptiveMenuItem
|
||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||
import dev.krtirtho.spotube.core.ui.component.HeaderDisplayMode
|
||||
import dev.krtirtho.spotube.core.webview.WebViewController
|
||||
import dev.krtirtho.spotube.getPlatform
|
||||
import dev.krtirtho.spotube.modules.plugin.components.InstallSection
|
||||
|
||||
import dev.krtirtho.spotube.modules.plugin.components.PluginCard
|
||||
import dev.krtirtho.spotube.modules.plugin.components.PluginPermissionDialog
|
||||
import dev.krtirtho.spotube.modules.shell.LocalAppShellBottomInset
|
||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxAdd
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxAddSquare
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxArrowDown4
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxBox
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxCheckSquare
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxDocumentText
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxEdit
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxExportArrowBulk
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxImportArrow2Bulk
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxLink
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusic
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxSound
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxTextalignLeft
|
||||
@ -83,6 +93,9 @@ import org.koin.compose.koinInject
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import spotube.composeapp.generated.resources.Res
|
||||
import spotube.composeapp.generated.resources.plugin_empty_subtitle
|
||||
import spotube.composeapp.generated.resources.plugin_action_download
|
||||
import spotube.composeapp.generated.resources.plugin_action_install_from_file
|
||||
import spotube.composeapp.generated.resources.plugin_configure_title
|
||||
import spotube.composeapp.generated.resources.plugin_empty_title
|
||||
import spotube.composeapp.generated.resources.plugin_error_download_failed
|
||||
import spotube.composeapp.generated.resources.plugin_error_enter_url
|
||||
@ -90,7 +103,9 @@ import spotube.composeapp.generated.resources.plugin_error_url_scheme
|
||||
import spotube.composeapp.generated.resources.plugin_installed_count
|
||||
import spotube.composeapp.generated.resources.plugin_installed_plural
|
||||
import spotube.composeapp.generated.resources.plugin_installed_singular
|
||||
import spotube.composeapp.generated.resources.plugin_install_section_title
|
||||
import spotube.composeapp.generated.resources.plugin_screen_title
|
||||
import spotube.composeapp.generated.resources.plugin_url_placeholder
|
||||
import spotube.composeapp.generated.resources.settings_plugins_action_change
|
||||
import spotube.composeapp.generated.resources.settings_plugins_action_select
|
||||
import spotube.composeapp.generated.resources.settings_plugins_ability_audio
|
||||
@ -120,6 +135,7 @@ fun PluginScreen(
|
||||
var urlInput by remember { mutableStateOf("") }
|
||||
var urlError by remember { mutableStateOf<String?>(null) }
|
||||
var isLoadingUrl by remember { mutableStateOf(false) }
|
||||
var showInstallSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val pleaseEnterUrl = stringResource(Res.string.plugin_error_enter_url)
|
||||
val urlSchemeError = stringResource(Res.string.plugin_error_url_scheme)
|
||||
@ -175,6 +191,90 @@ fun PluginScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (showInstallSheet) {
|
||||
AdaptiveDialogBottomSheet(
|
||||
onDismiss = { showInstallSheet = false },
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
Iconsax.IconsaxImportArrow2Bulk,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
stringResource(Res.string.plugin_install_section_title),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TextField(
|
||||
value = urlInput,
|
||||
onValueChange = { urlInput = it; urlError = null },
|
||||
modifier = Modifier.weight(1f),
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(Res.string.plugin_url_placeholder),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Iconsax.IconsaxLink,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
},
|
||||
isError = urlError != null,
|
||||
singleLine = true,
|
||||
)
|
||||
SecondaryIconButton(
|
||||
onClick = { submitUrl() },
|
||||
enabled = !isLoadingUrl,
|
||||
) {
|
||||
if (isLoadingUrl) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
Iconsax.IconsaxImportArrow2Bulk,
|
||||
contentDescription = stringResource(Res.string.plugin_action_download),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
|
||||
|
||||
OutlineButton(
|
||||
onClick = { launcher.launch() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(
|
||||
Iconsax.IconsaxExportArrowBulk,
|
||||
contentDescription = stringResource(Res.string.plugin_action_install_from_file)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(Res.string.plugin_action_install_from_file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
ApplicationMainBar(title = { Text(stringResource(Res.string.plugin_screen_title)) })
|
||||
@ -205,16 +305,28 @@ fun PluginScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
)
|
||||
{
|
||||
// ── Install section ───────────────────────────────────
|
||||
// ── Configure header ──────────────────────────────
|
||||
item {
|
||||
InstallSection(
|
||||
urlInput = urlInput,
|
||||
onUrlChange = { urlInput = it; urlError = null },
|
||||
urlError = urlError,
|
||||
isLoadingUrl = isLoadingUrl,
|
||||
onSubmitUrl = { submitUrl() },
|
||||
onPickFile = { launcher.launch() }
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
stringResource(Res.string.plugin_configure_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
PrimaryButton(onClick = { showInstallSheet = true }) {
|
||||
Icon(
|
||||
Iconsax.IconsaxAdd,
|
||||
contentDescription = "Install a plugin",
|
||||
)
|
||||
Text(stringResource(Res.string.plugin_install_section_title))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Default ability plugin selectors ─────────────────
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package dev.krtirtho.spotube.modules.plugin.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.krtirtho.spotube.core.ui.base.Card
|
||||
import dev.krtirtho.spotube.core.ui.base.TextField
|
||||
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
||||
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
||||
import dev.krtirtho.spotube.core.ui.base.SecondaryIconButton
|
||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxExportArrowBulk
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxImportArrow2Bulk
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxLink
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import spotube.composeapp.generated.resources.Res
|
||||
import spotube.composeapp.generated.resources.plugin_action_download
|
||||
import spotube.composeapp.generated.resources.plugin_action_install_from_file
|
||||
import spotube.composeapp.generated.resources.plugin_install_section_title
|
||||
import spotube.composeapp.generated.resources.plugin_url_placeholder
|
||||
|
||||
@Composable
|
||||
internal fun InstallSection(
|
||||
urlInput: String,
|
||||
onUrlChange: (String) -> Unit,
|
||||
urlError: String?,
|
||||
isLoadingUrl: Boolean,
|
||||
onSubmitUrl: () -> Unit,
|
||||
onPickFile: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
Iconsax.IconsaxImportArrow2Bulk,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
stringResource(Res.string.plugin_install_section_title),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
// URL row
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TextField(
|
||||
value = urlInput,
|
||||
onValueChange = onUrlChange,
|
||||
modifier = Modifier.weight(1f),
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(Res.string.plugin_url_placeholder),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Iconsax.IconsaxLink,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
},
|
||||
isError = urlError != null,
|
||||
// supportingText = urlError?.let { { Text(it) } },
|
||||
// shape = RoundedCornerShape(10.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
SecondaryIconButton(
|
||||
onClick = onSubmitUrl,
|
||||
enabled = !isLoadingUrl,
|
||||
) {
|
||||
if (isLoadingUrl) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
Iconsax.IconsaxImportArrow2Bulk,
|
||||
contentDescription = stringResource(Res.string.plugin_action_download),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
|
||||
|
||||
// File picker
|
||||
OutlineButton(
|
||||
onClick = onPickFile,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Icon(
|
||||
Iconsax.IconsaxExportArrowBulk,
|
||||
contentDescription = stringResource(Res.string.plugin_action_install_from_file)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(Res.string.plugin_action_install_from_file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package dev.krtirtho.spotube.resources.iconsax
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.PathData
|
||||
import androidx.compose.ui.graphics.vector.group
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Iconsax.IconsaxAdd: ImageVector
|
||||
get() {
|
||||
if (_IncosaxAdd != null) {
|
||||
return _IncosaxAdd!!
|
||||
}
|
||||
_IncosaxAdd = ImageVector.Builder(
|
||||
name = "IncosaxAdd",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f
|
||||
).apply {
|
||||
group(
|
||||
clipPathData = PathData {
|
||||
moveTo(0f, 0f)
|
||||
horizontalLineToRelative(24f)
|
||||
verticalLineToRelative(24f)
|
||||
horizontalLineToRelative(-24f)
|
||||
close()
|
||||
}
|
||||
) {
|
||||
path(
|
||||
fillAlpha = 0.4f,
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeAlpha = 0.4f,
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(6f, 12f)
|
||||
horizontalLineTo(18f)
|
||||
}
|
||||
path(
|
||||
stroke = SolidColor(Color.White),
|
||||
strokeLineWidth = 1.5f,
|
||||
strokeLineCap = StrokeCap.Round,
|
||||
strokeLineJoin = StrokeJoin.Round
|
||||
) {
|
||||
moveTo(12f, 18f)
|
||||
verticalLineTo(6f)
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
return _IncosaxAdd!!
|
||||
}
|
||||
|
||||
@Suppress("ObjectPropertyName")
|
||||
private var _IncosaxAdd: ImageVector? = null
|
||||
Loading…
Reference in New Issue
Block a user