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 '- \n'
- if not os.path.isfile(APPFILTER_PATH):
- print_error(f"appfilter.xml file does not exist. {reply_reason}")
+ return result
-def find_logic(mode):
- root = ET.parse(APPFILTER_PATH).getroot()
- svgs = os.listdir(SVGS_FOLDER)
- def find_duplicates():
- packages = [i.attrib["component"] for i in root]
- duplicates = {i for i in packages if (packages.count(i) > 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 @@
+
+