Skip to content

#1586 issue: add custom app icon option in Appearance settings#1587

Open
iRahul360 wants to merge 1 commit into
komikku-app:masterfrom
iRahul360:rahulburman459/issue1586
Open

#1586 issue: add custom app icon option in Appearance settings#1587
iRahul360 wants to merge 1 commit into
komikku-app:masterfrom
iRahul360:rahulburman459/issue1586

Conversation

@iRahul360

@iRahul360 iRahul360 commented Apr 11, 2026

Copy link
Copy Markdown

Closes #1586

Added custom app icon feature:

  • Added activity-alias entries in AndroidManifest
  • Added AppIconManager to handle icon switching
  • Added App Icon preference in Appearance settings
  • Added default and alternative 1 icon options

Summary by Sourcery

Add a selectable app icon setting and wire it to launcher icon aliases on Android.

New Features:

  • Introduce an App Icon preference in Appearance settings backed by a new UI preference.
  • Allow switching between default and alternative launcher icons using activity aliases and an AppIconManager utility.

Enhancements:

  • Add localized strings for the new app icon setting and options.

@sourcery-ai

sourcery-ai Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements a user-selectable app icon feature by adding launcher activity aliases, a preference-backed app icon selector in Appearance settings, and an AppIconManager utility to enable/disable the corresponding launcher components at runtime.

Sequence diagram for applying a new app icon selection

sequenceDiagram
  actor User
  participant SettingsAppearanceScreen
  participant UiPreferences
  participant AppIconManager
  participant PackageManager

  User->>SettingsAppearanceScreen: Open Appearance settings
  SettingsAppearanceScreen->>UiPreferences: Read appIcon preference
  UiPreferences-->>SettingsAppearanceScreen: Current icon id

  User->>SettingsAppearanceScreen: Change App Icon preference
  SettingsAppearanceScreen->>UiPreferences: Persist new icon id
  SettingsAppearanceScreen->>AppIconManager: switchIcon(context, AppIcon.fromId(newIconId))
  AppIconManager->>AppIconManager: Resolve AppIcon from id

  loop Disable all aliases
    AppIconManager->>PackageManager: setComponentEnabledSetting(MainActivityDefault, DISABLED)
    AppIconManager->>PackageManager: setComponentEnabledSetting(MainActivityAlt1, DISABLED)
  end

  AppIconManager->>PackageManager: setComponentEnabledSetting(selected_alias, ENABLED)
  PackageManager-->>User: Updated launcher icon for app
Loading

Updated class diagram for app icon preferences and manager

classDiagram

  class UiPreferences {
    - preferenceStore
    + appIcon() String
  }

  class SettingsAppearanceScreen {
    + getAppIconGroup(uiPreferences UiPreferences) PreferenceGroup
  }

  class AppIconManager {
    + switchIcon(context Context, icon AppIcon)
  }

  class AppIcon {
    <<sealed>>
    + id String
    + alias String
    + AppIcon(id String, alias String)
    + icons List~AppIcon~
    + fromId(id String) AppIcon
  }

  class Default {
    + Default()
  }

  class Alt1 {
    + Alt1()
  }

  UiPreferences <.. SettingsAppearanceScreen : uses
  AppIconManager <.. SettingsAppearanceScreen : uses

  AppIconManager *-- AppIcon : manages
  AppIcon <|-- Default
  AppIcon <|-- Alt1
Loading

File-Level Changes

Change Details Files
Add Appearance settings group for choosing the app icon and wiring it to a new preference.
  • Introduce getAppIconGroup composable that renders a ListPreference bound to uiPreferences.appIcon() with default and alt1 options.
  • Invoke getAppIconGroup in the Appearance settings groups list so it shows up near other UI-related preferences.
  • On preference change, call AppIconManager.switchIcon with the selected icon id mapped via AppIconManager.AppIcon.fromId.
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt
app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
Define launcher activity aliases for each supported app icon and register them in the manifest.
  • Add MainActivityDefault activity-alias that is enabled by default and uses the primary launcher icon resource.
  • Add MainActivityAlt1 activity-alias that is initially disabled and uses the alternative launcher icon resource.
  • Ensure both aliases target .ui.main.MainActivity and declare MAIN/LAUNCHER intent filters so they show as launcher entries when enabled.
app/src/main/AndroidManifest.xml
Introduce AppIconManager utility to manage enabling/disabling launcher aliases based on the selected icon.
  • Create sealed AppIcon class with Default and Alt1 objects, each mapping a stable id to the corresponding activity-alias suffix.
  • Provide fromId helper to resolve a string preference value into an AppIcon, defaulting safely to Default.
  • Implement switchIcon to first disable all known aliases, then enable only the selected alias using PackageManager.setComponentEnabledSetting without killing the app.
app/src/main/java/eu/kanade/tachiyomi/AppIconManager.kt
Add localized strings for the new App Icon preference and option labels.
  • Define pref_app_icon, pref_app_icon_default, and pref_app_icon_alt1 string resources for use in the Appearance settings UI.
i18n-kmk/src/commonMain/moko-resources/base/strings.xml

Assessment against linked issues

Issue Objective Addressed Explanation
#1586 Add an option in Settings → Appearance to change the app icon by choosing between multiple built-in/preset icons.
#1586 Allow users to upload any image from gallery/files to use as a custom app logo/icon. The PR only adds a ListPreference for selecting between two predefined icons (default and alt1) and does not include any functionality for picking or storing user-provided images from gallery/files.
#1586 Ensure the selected logo is applied consistently across the launcher icon, splash screen, and in-app logo. The changes use activity-alias components and AppIconManager to switch the launcher icon only. There are no modifications related to the splash screen or in-app logo resources or logic.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a feature that allows users to customize the application icon directly from the Appearance settings. It leverages Android's activity-alias system to swap between different launcher icons and provides a new management class to handle the state changes and UI integration.

Highlights

  • Android Manifest Updates: Added activity-alias entries to support multiple app icon configurations.
  • App Icon Management: Introduced AppIconManager to handle the programmatic switching of application icons.
  • Settings UI: Added a new preference group in the Appearance settings to allow users to select their preferred app icon.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


An icon choice for all to see, A simple switch for you and me. From default look to something new, The app now wears a different hue.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The string IDs for icons ("default", "alt1") are duplicated between UiPreferences.appIcon, the settings entries, and AppIconManager.AppIcon; consider referencing AppIconManager.AppIcon IDs/constants everywhere to avoid mismatches when adding or renaming icons.
  • In AppIconManager.switchIcon, all component enabling/disabling is done without error handling or user feedback; consider wrapping the PackageManager calls to handle failures gracefully (e.g., logging or surfacing an error to the user) so a bad state doesn’t silently persist.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The string IDs for icons ("default", "alt1") are duplicated between `UiPreferences.appIcon`, the settings entries, and `AppIconManager.AppIcon`; consider referencing `AppIconManager.AppIcon` IDs/constants everywhere to avoid mismatches when adding or renaming icons.
- In `AppIconManager.switchIcon`, all component enabling/disabling is done without error handling or user feedback; consider wrapping the PackageManager calls to handle failures gracefully (e.g., logging or surfacing an error to the user) so a bad state doesn’t silently persist.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a feature to change the application icon via settings. It adds activity aliases to the manifest, updates UI preferences, and introduces an AppIconManager. Feedback focuses on preventing duplicate launcher icons, optimizing PackageManager calls, utilizing data object for icon definitions, and using remember in the UI to avoid redundant map allocations.

android:resource="@xml/shortcuts" />
</activity>
<!-- KMK -->
<activity-alias

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Adding these activity-alias entries with the LAUNCHER category while the base MainActivity (lines 65-74) still retains its own launcher intent filter will result in duplicate icons appearing in the app drawer. You should remove the <intent-filter> containing android.intent.category.LAUNCHER from the MainActivity declaration to ensure only the selected alias is used as the entry point.

Comment on lines +19 to +38
fun switchIcon(context: Context, icon: AppIcon) {
val pm = context.packageManager
val pkg = context.packageName

// Disable all first
AppIcon.icons.forEach {
pm.setComponentEnabledSetting(
ComponentName(pkg, "$pkg${it.alias}"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP,
)
}

// Enable selected
pm.setComponentEnabledSetting(
ComponentName(pkg, "$pkg${icon.alias}"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP,
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation performs redundant PackageManager calls by disabling all icons (including the one about to be enabled) and then enabling the target. It's more efficient to check if the target icon is already enabled and only disable the others. Also, note that PackageManager.DONT_KILL_APP might delay the icon update on some launchers until the app is manually restarted or the launcher process refreshes.

    fun switchIcon(context: Context, icon: AppIcon) {
        val pm = context.packageManager
        val pkg = context.packageName
        val targetComponent = ComponentName(pkg, "$pkg${icon.alias}")

        // Avoid redundant work if the icon is already active
        if (pm.getComponentEnabledSetting(targetComponent) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
            return
        }

        // Disable all other icons
        AppIcon.icons.filter { it != icon }.forEach {
            pm.setComponentEnabledSetting(
                ComponentName(pkg, "$pkg${it.alias}"),
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP,
            )
        }

        // Enable selected icon
        pm.setComponentEnabledSetting(
            targetComponent,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP,
        )
    }

Comment on lines +10 to +11
object Default : AppIcon("default", ".MainActivityDefault")
object Alt1 : AppIcon("alt1", ".MainActivityAlt1")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Since these objects are part of a sealed class and do not hold state, it is recommended to use data object (available in Kotlin 1.9+) for better toString(), equals(), and hashCode() implementations.

Suggested change
object Default : AppIcon("default", ".MainActivityDefault")
object Alt1 : AppIcon("alt1", ".MainActivityAlt1")
data object Default : AppIcon("default", ".MainActivityDefault")
data object Alt1 : AppIcon("alt1", ".MainActivityAlt1")

Comment on lines +354 to +378
private fun getAppIconGroup(
uiPreferences: UiPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
return Preference.PreferenceGroup(
title = stringResource(KMR.strings.pref_app_icon),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.appIcon(),
entries = mapOf(
"default" to stringResource(KMR.strings.pref_app_icon_default),
"alt1" to stringResource(KMR.strings.pref_app_icon_alt1),
).toImmutableMap(),
title = stringResource(KMR.strings.pref_app_icon),
onValueChanged = { newIcon ->
AppIconManager.switchIcon(
context,
AppIconManager.AppIcon.fromId(newIcon),
)
true
},
),
),
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The entries map is currently recreated on every recomposition of getAppIconGroup. It is better to remember the map to avoid unnecessary allocations, especially since it depends on localized strings.

    @Composable
    private fun getAppIconGroup(
        uiPreferences: UiPreferences,
    ): Preference.PreferenceGroup {
        val context = LocalContext.current
        val defaultLabel = stringResource(KMR.strings.pref_app_icon_default)
        val alt1Label = stringResource(KMR.strings.pref_app_icon_alt1)
        val entries = remember(defaultLabel, alt1Label) {
            mapOf(
                "default" to defaultLabel,
                "alt1" to alt1Label,
            ).toImmutableMap()
        }
        return Preference.PreferenceGroup(
            title = stringResource(KMR.strings.pref_app_icon),
            preferenceItems = persistentListOf(
                Preference.PreferenceItem.ListPreference(
                    preference = uiPreferences.appIcon(),
                    entries = entries,
                    title = stringResource(KMR.strings.pref_app_icon),
                    onValueChanged = { newIcon ->
                        AppIconManager.switchIcon(
                            context,
                            AppIconManager.AppIcon.fromId(newIcon),
                        )
                        true
                    },
                ),
            ),
        )
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom app logo / icon

1 participant