From 0cc655e6da76dbdd9821a0252389b92932cd1fcd Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Sat, 15 Nov 2025 18:16:35 +0100 Subject: [PATCH 1/6] enable `iconics-core` publishing again --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89648049..9df537b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') run: | ./gradlew build -x test -x lint - # ./gradlew :iconics-core:publishToMavenCentral -x test -x lint --no-configure-on-demand --no-parallel + ./gradlew :iconics-core:publishToMavenCentral -x test -x lint --no-configure-on-demand --no-parallel ./gradlew :iconics-typeface-api:publishToMavenCentral -x test -x lint --no-configure-on-demand --no-parallel ./gradlew :iconics-views:publishToMavenCentral -x test -x lint --no-configure-on-demand --no-parallel ./gradlew :iconics-compose:publishToMavenCentral -x test -x lint --no-configure-on-demand --no-parallel From 1ce4bc9d9fec9d1bbc0367bd744ea18e12e5f3bf Mon Sep 17 00:00:00 2001 From: PrOF-kk Date: Thu, 20 Aug 2026 16:40:27 +0200 Subject: [PATCH 2/6] IconicsMenuInflaterUtil: don't unset non-iconics menu item icons --- .../mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt index 651c6711..b2d94762 100644 --- a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt +++ b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt @@ -168,10 +168,18 @@ object IconicsMenuInflaterUtil { menu: Menu ) { val attrsMap = mutableMapOf() + var hasIconicsAttrs = false repeat(attrs.attributeCount) { - attrsMap[attrs.getAttributeName(it)] = attrs.getAttributeValue(it) + val name = attrs.getAttributeName(it) + attrsMap[name] = attrs.getAttributeValue(it) + if (name.startsWith("ico_")) { + hasIconicsAttrs = true + } } + // Don't unset non-iconics menu item icon + if (!hasIconicsAttrs) return + attrsMap["id"] ?.replace("@", "") ?.removePrefix("+id/") From 44dd17876e8110f0872f8f018549deba42a88d4a Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Thu, 20 Aug 2026 18:21:40 +0200 Subject: [PATCH 3/6] fix(sample): repair toolbar theming and do an edge-to-edge pass - toolbar used a white-on-?colorPrimary style, which is invisible under Theme.Material3.DayNight in dark mode; use Widget.Material3.Toolbar.Surface and let M3 resolve the content colors - menu icons hardcoded Color.WHITE, same problem in light mode - drop values-v21/themes.xml: windowTranslucentStatus, statusBarColor and navigationBarColor are no-ops under Android 15 edge-to-edge, and the window transitions it declared were never used - enable edge-to-edge in both activities and apply the insets explicitly --- .../mikepenz/iconics/sample/MainActivity.kt | 9 +++-- .../iconics/sample/PlaygroundActivity.kt | 4 ++ .../mikepenz/iconics/sample/utils/Insets.kt | 38 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 2 + .../main/res/layout/activity_playground.xml | 4 +- app/src/main/res/values-v21/themes.xml | 32 ---------------- app/src/main/res/values/themes.xml | 18 +-------- 7 files changed, 52 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/com/mikepenz/iconics/sample/utils/Insets.kt delete mode 100644 app/src/main/res/values-v21/themes.xml diff --git a/app/src/main/java/com/mikepenz/iconics/sample/MainActivity.kt b/app/src/main/java/com/mikepenz/iconics/sample/MainActivity.kt index 67c27d86..7ff0d70a 100644 --- a/app/src/main/java/com/mikepenz/iconics/sample/MainActivity.kt +++ b/app/src/main/java/com/mikepenz/iconics/sample/MainActivity.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.content.Intent import android.content.res.ColorStateList import android.content.res.Configuration -import android.graphics.Color import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -34,6 +33,8 @@ import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.iconics.Iconics import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.sample.databinding.ActivityMainBinding +import androidx.activity.enableEdgeToEdge +import com.mikepenz.iconics.sample.utils.applyEdgeToEdgeInsets import com.mikepenz.iconics.sample.utils.getThemeColor import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.ITypeface @@ -64,10 +65,12 @@ class MainActivity : AppCompatActivity() { private var currentSearch: String? = null override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater).also { setContentView(it.root) } + applyEdgeToEdgeInsets(binding.insetsRoot, binding.toolbar, binding.content) // Handle Toolbar setSupportActionBar(binding.toolbar) @@ -142,7 +145,7 @@ class MainActivity : AppCompatActivity() { // menu.findItem(R.id.search).icon = IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_search).apply { - colorInt = Color.WHITE + colorInt = getThemeColor(android.R.attr.textColorPrimary) sizeDp = 24 respectFontBounds = true } @@ -176,7 +179,7 @@ class MainActivity : AppCompatActivity() { val menuItem = menu.findItem(R.id.action_opensource) menuItem.icon = IconicsDrawable(this, FontAwesomeBrand.Icon.fab_github).apply { actionBar() - colorInt = Color.WHITE + colorInt = getThemeColor(android.R.attr.textColorPrimary) } menu.findItem(R.id.action_respect_bounds).isChecked = Iconics.respectFontBoundsDefault diff --git a/app/src/main/java/com/mikepenz/iconics/sample/PlaygroundActivity.kt b/app/src/main/java/com/mikepenz/iconics/sample/PlaygroundActivity.kt index cd4c731c..daf275a9 100644 --- a/app/src/main/java/com/mikepenz/iconics/sample/PlaygroundActivity.kt +++ b/app/src/main/java/com/mikepenz/iconics/sample/PlaygroundActivity.kt @@ -46,6 +46,8 @@ import com.mikepenz.iconics.IconicsArrayBuilder import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsSize import com.mikepenz.iconics.sample.databinding.ActivityPlaygroundBinding +import androidx.activity.enableEdgeToEdge +import com.mikepenz.iconics.sample.utils.applyEdgeToEdgeInsets import com.mikepenz.iconics.sample.utils.getThemeColor import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.fontawesome.FontAwesome @@ -66,10 +68,12 @@ import com.mikepenz.iconics.utils.sizeY class PlaygroundActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) val binding: ActivityPlaygroundBinding = DataBindingUtil.setContentView(this, R.layout.activity_playground) + applyEdgeToEdgeInsets(binding.root, binding.toolbar, binding.list) // Handle Toolbar setSupportActionBar(binding.toolbar) diff --git a/app/src/main/java/com/mikepenz/iconics/sample/utils/Insets.kt b/app/src/main/java/com/mikepenz/iconics/sample/utils/Insets.kt new file mode 100644 index 00000000..105119e2 --- /dev/null +++ b/app/src/main/java/com/mikepenz/iconics/sample/utils/Insets.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026 Mike Penz + * + * 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 com.mikepenz.iconics.sample.utils + +import android.view.View +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding + +/** + * Applies edge-to-edge insets: [topView] takes the status bar, [bottomView] the navigation bar, + * both take the horizontal cutout/bar insets. Listener is installed on [root] so it also survives + * `DrawerLayout` consuming the insets for its slider. + */ +fun applyEdgeToEdgeInsets(root: View, topView: View, bottomView: View) { + ViewCompat.setOnApplyWindowInsetsListener(root) { _, windowInsets -> + val insets = windowInsets.getInsets( + WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() + ) + topView.updatePadding(left = insets.left, top = insets.top, right = insets.right) + bottomView.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom) + windowInsets + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 84cde29f..5795da69 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,8 +22,10 @@ android:fitsSystemWindows="true"> @@ -612,6 +609,7 @@ android:id="@+id/list" android:layout_width="0dp" android:layout_height="112dp" + android:clipToPadding="false" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/content" diff --git a/app/src/main/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml deleted file mode 100644 index 3a485752..00000000 --- a/app/src/main/res/values-v21/themes.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 03035423..d33cba80 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -22,22 +22,6 @@ true - @style/CustomToolbarStyle - @android:color/white - - - - - - - From 2bdf50d1ff6f43d7639d4df2185b2c2118e2a25d Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Thu, 20 Aug 2026 18:29:29 +0200 Subject: [PATCH 4/6] fix(core): don't overwrite existing icons when no ico_ attribute is set IconicsAttrsApplier.getIconicsDrawable() always returned a drawable, even for an AttributeSet carrying no `ico_*` attribute at all, because the extractor falls back to creating an empty IconicsDrawable. Every caller treats a non-null result as "there is iconics data here" and assigns it, so a menu item's `android:icon` or an ImageView's `android:src` got replaced by an empty drawable. Return null when the styled attributes are empty, which is what all three call sites (IconicsMenuInflaterUtil, and the ActionMenuItemView / ImageView branches of IconicsFactory) already handle. Adds regression tests over `menu_playground`, which covers an item with a plain `android:icon`, an item defined via `ico_*` attributes and an item with no icon at all. Fixes #666 Supersedes #667 Co-authored-by: PrOF-kk --- app/build.gradle | 8 ++ app/src/main/res/menu/menu_playground.xml | 6 ++ .../sample/IconicsMenuInflaterUtilTest.kt | 76 +++++++++++++++++++ .../iconics/context/IconicsAttrsApplier.kt | 5 ++ 4 files changed, 95 insertions(+) create mode 100644 app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt diff --git a/app/build.gradle b/app/build.gradle index 6293e3ee..016a0801 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,12 @@ android { abortOnError false } + testOptions { + unitTests { + includeAndroidResources = true + } + } + packagingOptions { exclude 'META-INF/library-core_release.kotlin_module' exclude 'META-INF/library_release.kotlin_module' @@ -139,4 +145,6 @@ dependencies { implementation project(':weather-icons-typeface-library') testImplementation 'junit:junit:4.13.2' + testImplementation 'org.robolectric:robolectric:4.16' + testImplementation 'androidx.test:core:1.7.0' } diff --git a/app/src/main/res/menu/menu_playground.xml b/app/src/main/res/menu/menu_playground.xml index 7ad01d40..8c0b7af5 100644 --- a/app/src/main/res/menu/menu_playground.xml +++ b/app/src/main/res/menu/menu_playground.xml @@ -31,4 +31,10 @@ app:ico_color="@android:color/holo_blue_bright" app:ico_icon="gmd_star" app:ico_size="24dp" /> + + + diff --git a/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt b/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt new file mode 100644 index 00000000..a8abf767 --- /dev/null +++ b/app/src/test/java/com/mikepenz/iconics/sample/IconicsMenuInflaterUtilTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 Mike Penz + * + * 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 com.mikepenz.iconics.sample + +import android.app.Activity +import android.view.Menu +import androidx.appcompat.view.menu.MenuBuilder +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.IconicsMenuInflaterUtil +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner + +/** + * `menu_playground` covers the three relevant cases: + * - `menu_item_1` has a plain `android:icon` and no `ico_*` attributes + * - `menu_item_2` is defined via `ico_*` attributes + * - `menu_item_3` has no icon at all + */ +@RunWith(RobolectricTestRunner::class) +class IconicsMenuInflaterUtilTest { + + private lateinit var activity: Activity + private lateinit var menu: Menu + + @Before fun setUp() { + activity = Robolectric.buildActivity(PlaygroundActivity::class.java).get() + menu = MenuBuilder(activity) + } + + @Test fun `iconics item gets an IconicsDrawable`() { + IconicsMenuInflaterUtil.inflate(activity.menuInflater, activity, R.menu.menu_playground, menu) + + assertTrue(menu.findItem(R.id.menu_item_2).icon is IconicsDrawable) + } + + @Test fun `non-iconics item keeps its android icon`() { + activity.menuInflater.inflate(R.menu.menu_playground, menu) + val original = menu.findItem(R.id.menu_item_1).icon + assertNotNull("menu_item_1 is expected to declare an android:icon", original) + + IconicsMenuInflaterUtil.parseXmlAndSetIconicsDrawables(activity, R.menu.menu_playground, menu) + + assertSame( + "an item without ico_* attributes must keep the drawable of its android:icon", + original, + menu.findItem(R.id.menu_item_1).icon + ) + } + + @Test fun `item without any icon stays without one`() { + IconicsMenuInflaterUtil.inflate(activity.menuInflater, activity, R.menu.menu_playground, menu) + + assertNull(menu.findItem(R.id.menu_item_3).icon) + } +} diff --git a/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt b/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt index b337a9fd..b51316ae 100644 --- a/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt +++ b/iconics-core/src/main/java/com/mikepenz/iconics/context/IconicsAttrsApplier.kt @@ -45,6 +45,11 @@ object IconicsAttrsApplier { @JvmStatic fun getIconicsDrawable(res: Resources, theme: Theme?, attrs: AttributeSet?): IconicsDrawable? { return theme?.obtainStyledAttributes(attrs, R.styleable.Iconics, 0, 0)?.use { + // no `ico_*` attribute at all -> there is nothing to build an icon from. Returning an + // empty drawable here would overwrite whatever icon the target already has, e.g. the + // `android:icon` of a menu item or the `android:src` of an `ImageView`. + if (it.indexCount == 0) return@use null + IconicsAttrsExtractor( res = res, theme = theme, From ae354b75fb9d045d9c599180d16ab6d09f48d1c8 Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Thu, 20 Aug 2026 18:34:06 +0200 Subject: [PATCH 5/6] refactor(core): drop the ico_ prefix check in the menu inflater Superseded by the null return in IconicsAttrsApplier: matching attribute names by their `ico_` prefix breaks if an attribute is ever renamed, and it only covered the menu inflater while IconicsFactory had the same problem. --- .../mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt index b2d94762..651c6711 100644 --- a/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt +++ b/iconics-core/src/main/java/com/mikepenz/iconics/utils/IconicsMenuInflaterUtil.kt @@ -168,18 +168,10 @@ object IconicsMenuInflaterUtil { menu: Menu ) { val attrsMap = mutableMapOf() - var hasIconicsAttrs = false repeat(attrs.attributeCount) { - val name = attrs.getAttributeName(it) - attrsMap[name] = attrs.getAttributeValue(it) - if (name.startsWith("ico_")) { - hasIconicsAttrs = true - } + attrsMap[attrs.getAttributeName(it)] = attrs.getAttributeValue(it) } - // Don't unset non-iconics menu item icon - if (!hasIconicsAttrs) return - attrsMap["id"] ?.replace("@", "") ?.removePrefix("+id/") From a5d26fa732c4bd22c8b536b7d94cd62ba2779dc3 Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Thu, 20 Aug 2026 19:22:01 +0200 Subject: [PATCH 6/6] - [release] v5.6.0 --- README.md | 2 +- build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cf357ee0..0f037fde 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ ## Latest releases 🛠 -- Kotlin | [v5.5.0](https://github.com/mikepenz/Android-Iconics/tree/v5.5.0) +- Kotlin | [v5.6.0](https://github.com/mikepenz/Android-Iconics/tree/v5.6.0) ## 1. Provide the gradle dependency ```gradle diff --git a/build.gradle b/build.gradle index bb2f1226..b6b55915 100644 --- a/build.gradle +++ b/build.gradle @@ -18,8 +18,8 @@ buildscript { ext { release = [ - versionName: "5.5.0", - versionCode: 50500 + versionName: "5.6.0", + versionCode: 50600 ] setup = [