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 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/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/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/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/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 - - - - - - - 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/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 = [ 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,