diff --git a/.github/ISSUE_TEMPLATE/icon_rebrand.yml b/.github/ISSUE_TEMPLATE/icon_rebrand.yml index 65dfccbf3..90fef3c53 100644 --- a/.github/ISSUE_TEMPLATE/icon_rebrand.yml +++ b/.github/ISSUE_TEMPLATE/icon_rebrand.yml @@ -11,8 +11,8 @@ body: You can find the current icon and application name in [the appfilter.xml](https://github.com/LawnchairLauncher/lawnicons/blob/develop/app/assets/appfilter.xml). For example, you found "...**drawable="ladb" name="ADB Shell" />**". The icon is [ladb](https://github.com/LawnchairLauncher/lawnicons/tree/develop/svgs/ladb.svg) and the application name is **ADB Shell**. **Sample response** - Now: Twitter, [twitter.svg](https://github.com/LawnchairLauncher/lawnicons/tree/develop/svgs/) - New: X, [fresh icon](https://cdn.cms-twdigitalassets.com/content/dam/about-twitter/x/brand-toolkit/logo-black.png.twimg.2560.png) + Now: Twitter, https://github.com/LawnchairLauncher/lawnicons/tree/develop/svgs/twitter.svg + New: X, https://cdn.cms-twdigitalassets.com/content/dam/about-twitter/x/brand-toolkit/logo-black.png.twimg.2560.png placeholder: Tell us what should be changed and how validations: diff --git a/README.md b/README.md index 7adfaf751..c4ee1c011 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ Please see our guidelines for information on contributing icons or code, it will [Lawnicons guidelines](CONTRIBUTING.md) • [Icon samples in Figma](https://www.figma.com/community/file/1227718471680779613) • [Popular icon requests](https://docs.google.com/spreadsheets/d/1AXc9EDXA6udZeGROtB5nuABjM33VluGY_V24tIzHaKc/edit?resourcekey=&gid=609680142#gid=609680142) • [Development issues](https://github.com/LawnchairLauncher/lawnicons/issues) ## Requesting icons +> [!NOTE] +> Request acceptance is currently closed. + `Open Lawnicons 2.10+ → Tap "Request icons" → Submit the response` -[Icon request form](https://forms.gle/xt7sJhgWEasuo9TR9) • [Report outdated icons](https://github.com/LawnchairLauncher/lawnicons/issues/new?assignees=&labels=icon+update&projects=&template=icon_rebrand.yml) +[Report outdated icons](https://github.com/LawnchairLauncher/lawnicons/issues/new?assignees=&labels=icon+update&projects=&template=icon_rebrand.yml) diff --git a/app/assets/appfilter.xml b/app/assets/appfilter.xml index 8d869c18d..3e2eab776 100644 --- a/app/assets/appfilter.xml +++ b/app/assets/appfilter.xml @@ -130,8 +130,8 @@ + - @@ -298,6 +298,8 @@ + + @@ -728,6 +730,14 @@ + + + + + + + + @@ -1117,6 +1127,7 @@ + @@ -2957,6 +2968,7 @@ + @@ -4057,6 +4069,7 @@ + @@ -4516,6 +4529,7 @@ + @@ -5039,7 +5053,7 @@ - + @@ -7371,6 +7385,7 @@ + @@ -7436,6 +7451,7 @@ + @@ -7858,6 +7874,7 @@ + @@ -8874,8 +8891,6 @@ - - @@ -10249,6 +10264,7 @@ + @@ -11066,23 +11082,6 @@ - - - - - - - - - - - - - - - - - @@ -11331,6 +11330,23 @@ + + + + + + + + + + + + + + + + + @@ -12638,6 +12654,8 @@ + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b40f1576d..ed7932b24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,6 +40,7 @@ + diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/model/IconInfo.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/model/IconInfo.kt index 6661e3118..c1ad53e99 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/model/IconInfo.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/model/IconInfo.kt @@ -1,5 +1,7 @@ package app.lawnchair.lawnicons.model +import kotlinx.serialization.Serializable + /** * Data class to hold information about an icon. * @@ -73,6 +75,7 @@ fun IconInfo.getFirstLabelAndComponent(): LabelAndComponent { * @property label The user-facing label associated with the component. * @property componentName The name of the component, typically a fully qualified class name. */ +@Serializable data class LabelAndComponent( val label: String, val componentName: String, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/ExternalLinkRow.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/ExternalLinkRow.kt index 7f8735784..590a33249 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/ExternalLinkRow.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/ExternalLinkRow.kt @@ -18,6 +18,7 @@ fun ExternalLinkRow( background: Boolean = false, first: Boolean = false, last: Boolean = false, + startIcon: @Composable (() -> Unit)? = null, ) { val context = LocalContext.current val onClick = @@ -35,6 +36,7 @@ fun ExternalLinkRow( divider = divider, label = name, onClick = onClick, + startIcon = startIcon, ) } diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/LawniconsScaffold.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/LawniconsScaffold.kt index 4bd3d20a0..c70e1612e 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/LawniconsScaffold.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/LawniconsScaffold.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp -import app.lawnchair.lawnicons.ui.components.home.ClickableIcon +import app.lawnchair.lawnicons.ui.components.home.NavigationIconButton import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons import app.lawnchair.lawnicons.ui.util.toPaddingValues @@ -39,7 +39,7 @@ fun LawniconsScaffold( scrollBehavior = scrollBehavior, title = title, navigationIcon = { - ClickableIcon( + NavigationIconButton( onClick = onBack, imageVector = Icons.AutoMirrored.Rounded.ArrowBack, size = 40.dp, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/ListRow.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/ListRow.kt index 9b5cb14dd..a74e737b2 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/ListRow.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/ListRow.kt @@ -20,7 +20,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import app.lawnchair.lawnicons.ui.util.thenIf +import app.lawnchair.lawnicons.ui.util.thenIfNotNull private val basePadding = 16.dp @@ -47,55 +50,47 @@ fun ListRow( val bottomCornerRadius = if (last) 16.dp else 0.dp val basePaddingPx = with(LocalDensity.current) { basePadding.toPx() } + val backgroundColor = MaterialTheme.colorScheme.surfaceContainer + Box( modifier = modifier .fillMaxWidth() - .then( - if (enforceHeight) { - Modifier.height(if (tall) 72.dp else 56.dp) - } else { - Modifier - }, - ) - .then( - if (background) { - Modifier - .padding(horizontal = basePadding) - .clip( - RoundedCornerShape( - topStart = topCornerRadius, - topEnd = topCornerRadius, - bottomStart = bottomCornerRadius, - bottomEnd = bottomCornerRadius, - ), - ) - .background( - MaterialTheme.colorScheme.surfaceContainer, - ) - } else { - Modifier - }, - ) - .then( - if (divider) { - Modifier.drawBehind { - drawLine( - strokeWidth = dividerHeightPx, - color = dividerColor, - start = Offset( - x = basePaddingPx, - y = size.height - dividerHeightPx / 2, - ), - end = Offset( - x = size.width - basePaddingPx, - y = size.height - dividerHeightPx / 2, - ), - ) - } - } else { - Modifier - }, - ), + .semantics( + mergeDescendants = true, + ) {} + .thenIf(enforceHeight) { + Modifier.height(if (tall) 72.dp else 56.dp) + } + .thenIf(background) { + padding(horizontal = basePadding) + .clip( + RoundedCornerShape( + topStart = topCornerRadius, + topEnd = topCornerRadius, + bottomStart = bottomCornerRadius, + bottomEnd = bottomCornerRadius, + ), + ) + .background( + backgroundColor, + ) + } + .thenIf(divider) { + drawBehind { + drawLine( + strokeWidth = dividerHeightPx, + color = dividerColor, + start = Offset( + x = basePaddingPx, + y = size.height - dividerHeightPx / 2, + ), + end = Offset( + x = size.width - basePaddingPx, + y = size.height - dividerHeightPx / 2, + ), + ) + } + }, ) { Content( label = label, @@ -121,13 +116,7 @@ private fun Content( verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxSize() - .then( - if (onClick != null) { - Modifier.clickable(onClick = onClick) - } else { - Modifier - }, - ) + .thenIfNotNull(onClick) { clickable(onClick = it) } .padding(horizontal = basePadding), ) { if (icon != null) { diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/TopAppBar.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/TopAppBar.kt index 79feb3f25..ea95032f5 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/TopAppBar.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/core/TopAppBar.kt @@ -12,7 +12,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import app.lawnchair.lawnicons.ui.components.home.ClickableIcon +import app.lawnchair.lawnicons.ui.components.home.NavigationIconButton import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons @@ -53,7 +53,7 @@ private fun SmallTopAppBarPreview() { LawniconsTheme { TopAppBar( navigationIcon = { - ClickableIcon( + NavigationIconButton( onClick = {}, imageVector = Icons.AutoMirrored.Rounded.ArrowBack, size = 40.dp, @@ -74,7 +74,7 @@ private fun LargeTopAppBarPreview() { LawniconsTheme { TopAppBar( navigationIcon = { - ClickableIcon( + NavigationIconButton( onClick = {}, imageVector = Icons.AutoMirrored.Rounded.ArrowBack, size = 40.dp, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/ClickableIcon.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/ClickableIcon.kt index be3b3d5e5..a8ed95cf1 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/ClickableIcon.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/ClickableIcon.kt @@ -20,7 +20,7 @@ import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons @Composable -fun ClickableIcon( +fun NavigationIconButton( onClick: () -> Unit, imageVector: ImageVector, modifier: Modifier = Modifier, @@ -44,9 +44,9 @@ fun ClickableIcon( @PreviewLawnicons @Composable -private fun ClickableIconPreview() { +private fun NavigationIconButtonPreview() { LawniconsTheme { - ClickableIcon( + NavigationIconButton( imageVector = Icons.Rounded.Clear, size = 52.dp, onClick = {}, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestHandler.kt similarity index 100% rename from app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestFAB.kt rename to app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconRequestHandler.kt diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/PlaceholderUI.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/PlaceholderUI.kt index 36301c012..06f43c80a 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/PlaceholderUI.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/PlaceholderUI.kt @@ -47,6 +47,8 @@ import app.lawnchair.lawnicons.ui.components.core.placeholder.PlaceholderHighlig import app.lawnchair.lawnicons.ui.components.core.placeholder.fade import app.lawnchair.lawnicons.ui.components.core.placeholder.placeholder import app.lawnchair.lawnicons.ui.components.core.placeholder.shimmer +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreviewGridPadding +import app.lawnchair.lawnicons.ui.components.home.iconpreview.iconColor import app.lawnchair.lawnicons.ui.util.toPaddingValues @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconInfoSheet.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconInfoSheet.kt similarity index 90% rename from app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconInfoSheet.kt rename to app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconInfoSheet.kt index c79a5685d..c5ccac6e6 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconInfoSheet.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconInfoSheet.kt @@ -1,4 +1,20 @@ -package app.lawnchair.lawnicons.ui.components.home +/* + * Copyright 2024 Lawnchair Launcher + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.lawnchair.lawnicons.ui.components.home.iconpreview import android.content.Intent import androidx.compose.foundation.layout.Arrangement @@ -214,19 +230,17 @@ private fun IconInfoListRow( label: String, componentNames: List, ) { - ListRow( - label = { - SelectionContainer { + SelectionContainer { + ListRow( + label = { Text( text = label, maxLines = 2, overflow = TextOverflow.Ellipsis, ) - } - }, - description = { - Spacer(Modifier.height(4.dp)) - SelectionContainer { + }, + description = { + Spacer(Modifier.height(4.dp)) Column { componentNames.forEach { Text( @@ -238,12 +252,12 @@ private fun IconInfoListRow( Spacer(Modifier.height(6.dp)) } } - } - }, - divider = false, - enforceHeight = false, - ) - Spacer(Modifier.height(16.dp)) + }, + divider = false, + enforceHeight = false, + ) + Spacer(Modifier.height(16.dp)) + } } @PreviewLawnicons diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreview.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreview.kt similarity index 86% rename from app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreview.kt rename to app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreview.kt index 8847e7801..1b5810871 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreview.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreview.kt @@ -1,4 +1,20 @@ -package app.lawnchair.lawnicons.ui.components.home +/* + * Copyright 2024 Lawnchair Launcher + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.lawnchair.lawnicons.ui.components.home.iconpreview import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreviewGrid.kt similarity index 93% rename from app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt rename to app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreviewGrid.kt index 715798045..aa2002e9e 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/IconPreviewGrid.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/iconpreview/IconPreviewGrid.kt @@ -1,4 +1,20 @@ -package app.lawnchair.lawnicons.ui.components.home +/* + * Copyright 2024 Lawnchair Launcher + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.lawnchair.lawnicons.ui.components.home.iconpreview import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt index 00bf71628..21586cbed 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchBar.kt @@ -44,10 +44,11 @@ import androidx.compose.ui.zIndex import app.lawnchair.lawnicons.R import app.lawnchair.lawnicons.model.IconInfoModel import app.lawnchair.lawnicons.model.SearchMode -import app.lawnchair.lawnicons.ui.components.home.ClickableIcon +import app.lawnchair.lawnicons.ui.components.home.NavigationIconButton import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons import app.lawnchair.lawnicons.ui.util.SampleData +import app.lawnchair.lawnicons.ui.util.thenIf import app.lawnchair.lawnicons.ui.util.toPaddingValues @Composable @@ -112,24 +113,17 @@ fun LawniconsSearchBar( content: @Composable (() -> Unit), ) { var active by rememberSaveable { mutableStateOf(false) } + val padding = WindowInsets.navigationBars.toPaddingValues( + additionalStart = 16.dp, + additionalEnd = 16.dp, + ) Box( modifier = modifier .animateContentSize() - .then( - if (isExpandedScreen) { - Modifier - .padding( - WindowInsets.navigationBars.toPaddingValues( - additionalStart = 16.dp, - additionalEnd = 16.dp, - ), - ) - .statusBarsPadding() - } else { - Modifier - }, - ) + .thenIf(isExpandedScreen) { + padding(padding).statusBarsPadding() + } .semantics { isTraversalGroup = true } @@ -262,7 +256,7 @@ internal fun SearchIcon( onButtonClick: () -> Unit, ) { if (active) { - ClickableIcon( + NavigationIconButton( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, onClick = onButtonClick, ) @@ -282,7 +276,7 @@ internal fun SearchActionButton( if (it) { navigateContent(onNavigate) } else { - ClickableIcon( + NavigationIconButton( onClick = onClear, imageVector = Icons.Rounded.Close, ) diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchContents.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchContents.kt index 62b18cf9e..755ff0a12 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchContents.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/components/home/search/SearchContents.kt @@ -41,8 +41,8 @@ import app.lawnchair.lawnicons.R import app.lawnchair.lawnicons.model.IconInfo import app.lawnchair.lawnicons.model.SearchMode import app.lawnchair.lawnicons.model.getFirstLabelAndComponent -import app.lawnchair.lawnicons.ui.components.home.IconInfoSheet -import app.lawnchair.lawnicons.ui.components.home.IconPreview +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconInfoSheet +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreview @Composable fun SearchContents( diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Contributors.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Contributors.kt index 1a677077f..e96890bfc 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Contributors.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Contributors.kt @@ -3,19 +3,28 @@ package app.lawnchair.lawnicons.ui.destination import android.content.Intent import android.net.Uri import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -125,6 +134,22 @@ private fun ContributorList( last = true, divider = false, url = CONTRIBUTOR_URL, + startIcon = { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(R.drawable.github_foreground), + contentDescription = null, + modifier = Modifier + .size(24.dp), + ) + } + }, ) } item { diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt index 8a4981aae..4daa4346a 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/Home.kt @@ -23,16 +23,16 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import app.lawnchair.lawnicons.model.IconInfo import app.lawnchair.lawnicons.repository.preferenceManager -import app.lawnchair.lawnicons.ui.components.home.AppBarListItem import app.lawnchair.lawnicons.ui.components.home.DebugMenu import app.lawnchair.lawnicons.ui.components.home.HomeBottomBar import app.lawnchair.lawnicons.ui.components.home.HomeTopBar import app.lawnchair.lawnicons.ui.components.home.HomeTopBarUiState -import app.lawnchair.lawnicons.ui.components.home.IconPreviewGrid -import app.lawnchair.lawnicons.ui.components.home.IconPreviewGridPadding import app.lawnchair.lawnicons.ui.components.home.IconRequestFAB import app.lawnchair.lawnicons.ui.components.home.NewIconsCard import app.lawnchair.lawnicons.ui.components.home.PlaceholderUI +import app.lawnchair.lawnicons.ui.components.home.iconpreview.AppBarListItem +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreviewGrid +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreviewGridPadding import app.lawnchair.lawnicons.ui.components.home.search.PlaceholderSearchBar import app.lawnchair.lawnicons.ui.theme.LawniconsTheme import app.lawnchair.lawnicons.ui.util.PreviewLawnicons @@ -85,6 +85,7 @@ private fun Home( val snackbarHostState = remember { SnackbarHostState() } val focusRequester = remember { FocusRequester() } + val prefs = preferenceManager(context) Crossfade( modifier = modifier, @@ -163,7 +164,7 @@ private fun Home( if (isExpandedScreen) { PlaceholderSearchBar() } else { - PlaceholderUI(newIconsInfoModel.iconCount != 0) + PlaceholderUI(prefs.showNewIconsCard.asState().value) } } } @@ -172,7 +173,7 @@ private fun Home( focusRequester.requestFocus() } } - val prefs = preferenceManager(context) + if (prefs.showDebugMenu.asState().value) { DebugMenu( iconInfoModel, diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/NewIcons.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/NewIcons.kt index 50ecbed8c..ecd7b957c 100644 --- a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/NewIcons.kt +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/destination/NewIcons.kt @@ -28,8 +28,8 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import app.lawnchair.lawnicons.R import app.lawnchair.lawnicons.ui.components.core.LawniconsScaffold -import app.lawnchair.lawnicons.ui.components.home.IconPreviewGrid -import app.lawnchair.lawnicons.ui.components.home.IconPreviewGridPadding +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreviewGrid +import app.lawnchair.lawnicons.ui.components.home.iconpreview.IconPreviewGridPadding import app.lawnchair.lawnicons.viewmodel.NewIconsViewModel import kotlinx.serialization.Serializable diff --git a/app/src/main/kotlin/app/lawnchair/lawnicons/ui/util/ModifierExtensions.kt b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/util/ModifierExtensions.kt new file mode 100644 index 000000000..790969cc0 --- /dev/null +++ b/app/src/main/kotlin/app/lawnchair/lawnicons/ui/util/ModifierExtensions.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Lawnchair Launcher + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.lawnchair.lawnicons.ui.util + +import androidx.compose.ui.Modifier + +/** + * Conditionally apply a [Modifier] if the [condition] is true. + * + * @param condition the condition to check + * @param other the modifier to apply if the condition is true + * @return the original modifier if the condition is false, otherwise the result of applying the [other] modifier + */ +inline fun Modifier.thenIf( + condition: Boolean, + crossinline other: Modifier.() -> Modifier, +) = if (condition) other() else this + +/** + * Conditionally apply a [Modifier] if the [value] is not null. + * + * @param value the value to check for null + * @param other the modifier to apply if the value is not null + * @return the original modifier if the value is null, otherwise the result of applying the [other] modifier with the value + */ +inline fun Modifier.thenIfNotNull( + value: T?, + crossinline other: Modifier.(T) -> Modifier, +) = if (value != null) other(value) else this diff --git a/docs/icontool_guide.md b/docs/icontool_guide.md index 681b0341b..536adb1fb 100644 --- a/docs/icontool_guide.md +++ b/docs/icontool_guide.md @@ -5,7 +5,7 @@ For Mac/Linux users, you can simply type `./icontool` to run the program. Otherw ## Summary of usage ```console -./icontool [-m] [-h] +./icontool [-h] add (a) svg component name link (l) svg component name remove (r, d) component [-d] @@ -13,17 +13,6 @@ For Mac/Linux users, you can simply type `./icontool` to run the program. Otherw find (f) {duplicates, unused} ``` -## General syntax -Adding the flag `-m` will generate a list item (depending on the subcommand) that looks like this: -``` -* App Name (component info) -``` - -Note that you should add it *before* the other parameters: - -:x: `python3 icontool.py add ... -m`
-:white_check_mark: `python3 icontool.py -m add ...` - ## Adding icons ```console python3 icontool.py add /path/to/icon com.app.app/com.app.app.appActivity "App Name" @@ -62,12 +51,6 @@ Note that the SVG file's name is based on the `drawable` attribute of the first Some common utilities are described below. ### Sorting appfilter.xml -> [!WARNING] -> At the moment, the sorting works with errors: duplicates and extra spaces are added. It is recommended not to use it. -```console -python3 ./icontool.py sort -``` - This will sort the `appfilter.xml` file via the `name` attribute. ### Finding duplicate entries in appfilter.xml diff --git a/icontool.py b/icontool.py index 47e2edd88..8291def65 100755 --- a/icontool.py +++ b/icontool.py @@ -1,351 +1,269 @@ #!/usr/bin/env python3 -import xml.etree.ElementTree as ET import argparse -import shutil import os import re - -# -# Global Variables -# -# See https://regex101.com/r/xC9Kh3/2 -PATTERN = re.compile(r"([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)+)\/([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)+)", re.IGNORECASE) - -APPFILTER_PATH = "app/assets/appfilter.xml" -SVGS_FOLDER = "svgs/" -CALENDAR_REGEX = r"(?s) .*? " - -# Helper Functions +import shutil +import xml.etree.ElementTree as ElementTree + +##### +# Global variables +##### +PATTERN = re.compile(r"([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)+)/([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)+)", + re.IGNORECASE) +APPFILTER_PATH = "./app/assets/appfilter.xml" +SVGS_FOLDER = "./svgs/" +CALENDARS_PATTERN = r'( .*?)' +LAWNICONS_PATTERN = r'\s*(.*?)\s*' + + +##### +# Helper functions +##### def print_error(msg): - print("\033[91merror:\033[0m " + msg + "\n") + print("\033[91m" + "error:\033[0m " + msg + "\n") exit() -def print_success(): - print("\033[96msuccessfully completed task(s)\033[0m") - exit() - -# -# Logic -# -def sort_components(xml_file): - """Sorts XML data by 'name' attribute.""" - def check_calendar_components(): - calendar_stuff = re.findall(CALENDAR_REGEX, xml_file) - return True if calendar_stuff == None else False - - def get_calendar_components(): - # Always get the original file for safety - original_file = open(APPFILTER_PATH, "r", encoding="utf-8").read() - calendar_stuff = re.findall(CALENDAR_REGEX, original_file) - return calendar_stuff - - def remove_calendar_components(has_calendar): - return re.sub(CALENDAR_REGEX, "", xml_file) if has_calendar else xml_file - def read_calendar_components(xml_data, calendar_stuff): - return xml_data[:52] + calendar_stuff[0] + "\n" + xml_data[52:] + "\n" - - calendar_stuff = get_calendar_components() - pure_xml_file = remove_calendar_components(check_calendar_components()) +def check_file_existence(): + if not os.path.exists(SVGS_FOLDER): + print_error("svgs folder does not exist. Please ensure the path is correct.") + if not os.path.isfile(APPFILTER_PATH): + print_error("appfilter.xml file does not exist. Please ensure the path is correct.") - # Sort the XML by the name tag, thanks https://stackoverflow.com/a/25339725 - xml_data = ET.fromstring(pure_xml_file) - xml_data[:] = sorted(xml_data, key=lambda child: child.get("name").casefold()) - sorted_xml_data = ET.tostring(xml_data, encoding="unicode") - sorted_xml_data = '\n\n' + sorted_xml_data +def validate_component_format(component): + if not PATTERN.match(component): + print_error( + "Invalid component entry. Must be in format [PACKAGE_NAME]/[APP_ACTIVITY_NAME], e.g., package.name/component.name") + + +##### +# Primary logic +##### +def sort_xml_file(file, new_item): + # The use of strings instead of XML parsers/ElementTree is to preserve all newlines and comments in the file. + dynamic_calendars_section = re.search(CALENDARS_PATTERN, file, re.DOTALL) + if dynamic_calendars_section: + x = dynamic_calendars_section.group(0) + else: + x = "" - sorted_xml_data = read_calendar_components(sorted_xml_data, calendar_stuff) + lawnicons_section = re.search(LAWNICONS_PATTERN, file, re.DOTALL) + lawnicons_items = lawnicons_section.group(1).strip().splitlines() if lawnicons_section else [] - return sorted_xml_data + if new_item: + lawnicons_items.append(new_item) -def check_lawnicons_corruption(): - reply_reason = "this may be due to a broken local copy of lawnicons. please clone lawnicons again." + lawnicons_items_sorted = sorted( + [" " + line.strip() for line in lawnicons_items if ' 1) and not ("calendar" in i)} - - if len(duplicates) == 0: - print("no duplicates found") - return - - print("duplicates:") - for i in duplicates: - print("* " + i) - - def find_unused_icons(): - drawables = [] - - for i in root: - drawable = str(i.attrib.get("drawable", None)) + ".svg" - drawables.append(drawable) - - unused_list = [] - - for i in svgs: - if i not in drawables: - if not i.startswith("themed_icon_calendar_"): - unused_list.append(i) - - if len(unused_list) == 0: - print("no unused svg files found") - return - - print("unused svg files:") - for i in unused_list: print("* " + i) - - match mode: - case "duplicates": - find_duplicates() - - case "unused": - find_unused_icons() - - case _: - print_error("you must specify a mode {duplicates,unused}") +def add_or_link_component(link_mode, svg, component, name): + validate_component_format(component) -def sort_logic(): - # print("sorting icons...") - print("due to several issues, sorting icons manually is temporalily disabled. an alternative is to add an icon first to automatically sort appfilter.xml") - exit() - # xml_file = open(APPFILTER_PATH, "r", encoding="utf-8").read() - # sorted_data = sort_components(xml_file) + def is_readable(file_path): + return os.path.isfile(file_path) and os.access(file_path, os.R_OK) - # with open(APPFILTER_PATH, "w", encoding="utf-8") as f: - # f.write(sorted_data) + def prompt_user_for_replacement(svg_file): + options = ["y", "n", "l"] + while True: + user_input = input( + f"\033[93mwarning\033[0m: svg \033[4m{svg_file}\033[0m already exists in the svgs directory. Replace? (Yes/No/Link) [N] ").lower() + if user_input in options: + return user_input + print("Invalid input, please enter 'y', 'n', or 'l'.") -def parse_component(link_mode, svg, component, name, show_message): - # - # Initialization - # + def copy_svg_to_svgs_folder(svg_file, svg_file_in_folder): + try: + shutil.copyfile(svg_file, svg_file_in_folder) + except shutil.SameFileError: + print_error( + f"The source and destination SVG are identical: '{os.path.basename(svg_file)}'") + except (PermissionError, FileNotFoundError) as e: + print_error(f"Error copying file: {str(e)}") + except Exception as e: + print_error(f"Unexpected error while copying file: {str(e)}") + + # SVG checks if not svg.endswith(".svg"): svg += ".svg" - basename = os.path.basename(svg) - if not PATTERN.match(component): - print_error("invalid component entry. must be in format \033[4m[PACKAGE_NAME]/[APP_ACTIVITY_NAME]\033[0m, i.e: package.name/component.name") - - # Link mode true if link_mode: - if not os.path.isfile(SVGS_FOLDER + svg): - print_error(f"svg '{svg}' doesn't exist in the svgs directory.") - - # Link mode false + if not is_readable(os.path.join(SVGS_FOLDER, svg)): + print_error(f"SVG '{svg}' doesn't exist in the svgs directory.") else: - if not os.path.isfile(svg): - path = f"directory `{os.path.dirname(svg)}/`" - print(path) - if path == "directory `.`" or path == "directory ``": - path = "current directory" - - print_error(f"svg '{basename}' doesn't exist in {path}. check if the file exists and try again.") - - if os.path.isfile(SVGS_FOLDER + basename): - user_input = input(f"\033[93mwarning\033[0m: svg \033[4m{basename}\033[0m already exists in the svgs directory. replace? (Yes/No/Link) [N] ").lower() - if user_input == "" or user_input[0] == "n": + if not is_readable(svg): + print_error(f"SVG '{basename}' doesn't exist in the current directory.") + + # Handle file replacement + if is_readable(os.path.join(SVGS_FOLDER, basename)): + prompt_input = prompt_user_for_replacement(basename) + if prompt_input == "n": exit() - - if user_input[0] == "l": - parse_component(True, basename, component, name, show_message) + elif prompt_input == "l": + add_or_link_component(True, basename, component, name) return - - if user_input[0] != "y": exit() - - print(f"replacing {basename} in svgs folder...") - - svg_in_svgs_folder = SVGS_FOLDER + basename - - try: - shutil.copyfile(svg, svg_in_svgs_folder) - except shutil.SameFileError: - print_error(f"\033[4m{basename}\033[0m has the same contents as \033[4m{svg_in_svgs_folder}\033[0m. cannot continue") + elif prompt_input == "y": + print(f"Replacing {basename} in svgs folder...") - # - # Writing to file - # + svg_in_svgs_folder = os.path.join(SVGS_FOLDER, basename) + copy_svg_to_svgs_folder(svg, svg_in_svgs_folder) - # Remove .svg extension drawable = basename[:-4] - # xml_file the actual xml file - # line the actual xml element in string form - # pure_xml_file the xml file without the calendar declarations - xml_file = open(APPFILTER_PATH, "r", encoding="utf-8").read() - line = f' ' - pure_xml_file = re.sub(CALENDAR_REGEX, "", xml_file) - - # Add the line - edited_xml_file = pure_xml_file[:52] + line + pure_xml_file[52:] - - # Sort - sorted_xml_data = sort_components(edited_xml_file) - with open(APPFILTER_PATH, "w", encoding="utf-8") as f: - f.write(sorted_xml_data) + with open(APPFILTER_PATH, "r", encoding="utf-8") as file: + sorted_lines = sort_xml_file(file.read(), line) + with open(APPFILTER_PATH, "w", encoding="utf-8") as f: + f.write(sorted_lines) - # - # Print success message - # - with open(APPFILTER_PATH, encoding="utf-8") as file: + with open(APPFILTER_PATH, "r", encoding="utf-8") as file: lines = file.readlines() - - for number, line in enumerate(lines, 1): - if component in line: - if link_mode: - action = "linked" - else: - action = "added" - print( - f"{action} \033[92m{name}\033[0m app to \033[92m`@drawable/{drawable}`\033[0m in line \033[92m{number}\033[0m of appfilter.xml" - ) - - if show_message: - if link_mode: - print( - f"* {name} (linked `{component}` to `@drawable/{drawable}`)" - ) - continue - + for number, line in enumerate(lines, 1): + if component in line: + action = "Link" if link_mode else "Add" print( - f"* {name} (`{component}`)" + f"{action}ed \033[92m{name}\033[0m app to \033[92m`@drawable/{drawable}`\033[0m in line \033[92m{number}\033[0m of appfilter.xml" ) -def remove_component(component, do_delete, message): - pattern1 = re.compile(r"([A-Za-z0-9_]+(\.[A-Za-z0-9_]+)+)", re.IGNORECASE) - pattern2 = PATTERN - error_msg = "invalid component name. format must be either \033[4m[PACKAGE_NAME]\033[0m or \033[4m[PACKAGE_NAME]/[APP_ACTIVITY_NAME]\033[0m" - if not pattern1.match(component): - print_error(error_msg) - elif not pattern2.match(component): - if ("/" in component): - print_error(error_msg) +def remove_component(component, delete_svg): + validate_component_format(component) with open(APPFILTER_PATH, "r", encoding="utf-8") as file: lines = file.readlines() + component_found = False with open(APPFILTER_PATH, "w", encoding="utf-8") as f: for linenumber, line in enumerate(lines, 1): if component not in line: f.write(line) - - elif component in line: + else: deleted_line = line - number = linenumber + component_found = True print( - f"removed \033[92m{component}\033[0m icon in line \033[92m{number}\033[0m" + f"Removed \033[92m{component}\033[0m icon in line \033[92m{linenumber}\033[0m") + + if not component_found: + print_error(f"Component {component} not found.") + + if delete_svg: + drawable = ElementTree.fromstring(deleted_line).get("drawable") + ".svg" + try: + os.remove(os.path.join(SVGS_FOLDER, drawable)) + print(f"Deleted \033[92m{drawable}\033[0m") + except FileNotFoundError: + print_error(f"SVG file \033[92m{drawable}\033[0m not found for deletion.") + + +def find_logic(mode): + root = ElementTree.parse(APPFILTER_PATH).getroot() + svgs = os.listdir(SVGS_FOLDER) + + def find_duplicates(root_file): + packages = [item.attrib["component"] for item in root_file] + duplicate_elements = {pkg for pkg in packages if + packages.count(pkg) > 1 and "calendar" not in pkg} + return duplicate_elements + + def find_unused_icons(root_file, svgs_list): + drawables = [f"{item.attrib.get('drawable', None)}.svg" for item in root_file] + unused_list = [ + svg_item for svg_item in svgs_list + if (svg_item not in drawables and not svg_item.startswith("themed_icon_calendar_") ) + ] + return unused_list - if message: - print(f"* {component} (removed)") - - if do_delete: - deleted_file = ET.fromstring(deleted_line).get("drawable") + ".svg" - os.remove(SVGS_FOLDER + deleted_file) - print(f"deleted \033[92m{deleted_file}\033[0m") - -# Parser Logic -def add_parser(args): - parse_component(False, args.svg, args.component, args.name, args.message) - -def link_parser(args): - parse_component(True, args.svg, args.component, args.name, args.message) - -def remove_parser(args): - remove_component(args.component, args.delete, args.message) - -def sort_parser(): - sort_logic() - -def find_parser(args): - find_logic(args.mode) - -# -# Parser Initialization -# -parser = argparse.ArgumentParser( - prog="icontool", description="a CLI tool to help contributors with adding icons. requires the use of subcommands {add,link,remove,sort,find}. for help with a specific subcommand, type 'icontool.py -h'") -parser.add_argument( - "-m", "--message", action="store_true", help="shows a list item for use in a pull request") - -subparsers = parser.add_subparsers( - help='what action icontool should use', dest="subcommand") - -parser_add = subparsers.add_parser( - "add", help='adds an icon SVG and an entry to appfilter.xml, links SVG to component', aliases=['a']) -parser_link = subparsers.add_parser( - "link", help='adds an entry to appfilter.xml, links SVG to component', aliases=['l']) -parser_remove = subparsers.add_parser( - "remove", help='removes an entry on appfilter.xml, can optionally delete SVG', aliases=['d', 'r']) -parser_sort = subparsers.add_parser( - "sort", help='sorts the appfilter.xml file by component name', aliases=['s']) -parser_find = subparsers.add_parser( - "find", help='find either duplicates in appfilter.xml or unlinked SVGs', aliases=['f']) - -# Remove parser -parser_remove.add_argument( - "component", help="the component to remove. can be [PACKAGE_NAME]/[APP_ACTIVITY_NAME] or [PACKAGE_NAME]") -parser_remove.add_argument( - "-d", "--delete", help="enables SVG deletion. SVG derived from `drawable` entry in each ``", action="store_true") - -# Add parser -parser_add.add_argument( - "svg", help="the path of the SVG file that will be added") -parser_add.add_argument( - "component", help="the component name. format must be `[PACKAGE_NAME]/[APP_ACTIVITY_NAME]`") -parser_add.add_argument( - "name", help="the displayed name of the app. if multiple lines, use a string like ``\"App Name\"`") - -# Link parser -parser_link.add_argument("svg", help="the file name of the SVG icon.") -parser_link.add_argument( - "component", help="the component name. format must be `[PACKAGE_NAME]/[APP_ACTIVITY_NAME]`") -parser_link.add_argument( - "name", help="the displayed name of the app. if multiple lines, use a string like ``\"App Name\"`") - -# No additional arguments for `sort` parser - -# Find parser -parser_find.add_argument("mode", help="the mode to use {duplicates, unused}.`duplicates` = find duplicates in appfilter.xml.`unused` = find unused files in svgs/") - -args = parser.parse_args() - -# -# Main Functions -# -check_lawnicons_corruption() - -# Match proper subcommand -match args.subcommand: - case "add" | "a": - add_parser(args) + match mode: + case "duplicates": + duplicates = find_duplicates(root) + if duplicates: + print("Duplicates found:") + for dup in duplicates: + print("* " + dup) + else: + print("No duplicates found.") - case "link" | "l": - link_parser(args) + case "unused": + unused = find_unused_icons(root, svgs) + if unused: + print("Unused SVG files:") + for svg in unused: + print("* " + svg) + else: + print("No unused SVG files found.") - case "remove" | "r" | "d": - remove_parser(args) - case "sort" | "s": - sort_parser() - - case "find" | "f": - find_parser(args) +def sort_logic(): + with open(APPFILTER_PATH, "r", encoding="utf-8") as file: + sorted_lines = sort_xml_file(file.read(), False) + with open(APPFILTER_PATH, "w", encoding="utf-8") as f: + f.write(sorted_lines) - case _: - print_error("you must specify a subcommand {add,link,remove,sort,find}") -print_success() +##### +# Parser logic +##### + +def parse_args(): + parser = argparse.ArgumentParser(prog="icontool", description="A CLI tool for managing icons.") + subparsers = parser.add_subparsers(dest="command") + + add_parser = subparsers.add_parser("add", help="Add a new component.", aliases=['a']) + add_parser.add_argument("svg", help="The name of the SVG file (without .svg extension).") + add_parser.add_argument("component", + help="Component name in format PACKAGE_NAME/ACTIVITY_NAME.") + add_parser.add_argument("name", help="Display name for the component.") + + link_parser = subparsers.add_parser("link", help="Link an existing SVG to a component.", + aliases=['l']) + link_parser.add_argument("svg", help="The name of the SVG file (without .svg extension).") + link_parser.add_argument("component", + help="Component name in format PACKAGE_NAME/ACTIVITY_NAME.") + link_parser.add_argument("name", help="Display name for the component.") + + remove_parser = subparsers.add_parser("remove", help="Remove a component.", aliases=['r', 'd']) + remove_parser.add_argument("component", help="Component name to remove.") + remove_parser.add_argument("--delete", action="store_true", + help="Delete the associated SVG file.") + + find_parser = subparsers.add_parser("find", help="Find duplicates or unused SVGs.", + aliases=['f']) + find_parser.add_argument("mode", choices=["duplicates", "unused"], + help="Mode to find duplicates or unused SVGs.") + + subparsers.add_parser("sort", help='sorts the appfilter.xml file by component name', + aliases=['s']) + + return parser.parse_args() + + +# Main Logic +check_file_existence() +args = parse_args() + +match args.command: + case "add" | "a": + add_or_link_component(False, args.svg, args.component, args.name) + case "link" | "l": + add_or_link_component(True, args.svg, args.component, args.name) + case "remove" | "r" | "d": + remove_component(args.component, args.delete) + case "find" | "f": + find_logic(args.mode) + case "sort" | "s": + sort_logic() + case _: + print_error("Invalid command. Please use add, link, remove, sort, or find.") diff --git a/svg-processor/src/main/kotlin/app/lawnchair/lawnicons/helper/AppfilterDiffCreator.kt b/svg-processor/src/main/kotlin/app/lawnchair/lawnicons/helper/AppfilterDiffCreator.kt index 1e49e4d3a..eaed717ea 100644 --- a/svg-processor/src/main/kotlin/app/lawnchair/lawnicons/helper/AppfilterDiffCreator.kt +++ b/svg-processor/src/main/kotlin/app/lawnchair/lawnicons/helper/AppfilterDiffCreator.kt @@ -87,7 +87,7 @@ object AppfilterDiffCreator { val schema = "" if (diff.isEmpty()) { - outputFile.writeText("$schema\n") + outputFile.writeText("$schema\n") return } diff --git a/svgs/a101.svg b/svgs/a101.svg new file mode 100644 index 000000000..f71ec40fb --- /dev/null +++ b/svgs/a101.svg @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/svgs/a101_yeni.svg b/svgs/a101_yeni.svg deleted file mode 100644 index 93fede664..000000000 --- a/svgs/a101_yeni.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/svgs/radarbox.svg b/svgs/airnav_radar.svg similarity index 100% rename from svgs/radarbox.svg rename to svgs/airnav_radar.svg diff --git a/svgs/image_toolbox.svg b/svgs/image_toolbox.svg index ebb131b0a..2eecd4664 100644 --- a/svgs/image_toolbox.svg +++ b/svgs/image_toolbox.svg @@ -1 +1,41 @@ - \ No newline at end of file + + diff --git a/svgs/innertune_fork.svg b/svgs/innertune_fork.svg new file mode 100644 index 000000000..f67836805 --- /dev/null +++ b/svgs/innertune_fork.svg @@ -0,0 +1,24 @@ + + diff --git a/svgs/nothing_gallery.svg b/svgs/nothing_gallery.svg new file mode 100644 index 000000000..45fba73f8 --- /dev/null +++ b/svgs/nothing_gallery.svg @@ -0,0 +1 @@ + diff --git a/svgs/ph_diver.svg b/svgs/ph_diver.svg new file mode 100644 index 000000000..0f573e395 --- /dev/null +++ b/svgs/ph_diver.svg @@ -0,0 +1,3 @@ + + + diff --git a/svgs/skycards.svg b/svgs/skycards.svg new file mode 100644 index 000000000..52dc19abe --- /dev/null +++ b/svgs/skycards.svg @@ -0,0 +1,25 @@ + + diff --git a/svgs/telegram_monet.svg b/svgs/telegram_monet.svg deleted file mode 100644 index cfd252505..000000000 --- a/svgs/telegram_monet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/svgs/tgmonet_theme.svg b/svgs/tgmonet_theme.svg new file mode 100644 index 000000000..24ad6a834 --- /dev/null +++ b/svgs/tgmonet_theme.svg @@ -0,0 +1,64 @@ + + + + + + + + +