Skip to content

Commit

Permalink
BREAKING: add android support (#100)
Browse files Browse the repository at this point in the history
Co-authored-by: Riccardo Pizzoni <[email protected]>

BREAKING CHANGE
  • Loading branch information
pchalupa authored Oct 15, 2024
1 parent 18f1841 commit 61faf9a
Show file tree
Hide file tree
Showing 25 changed files with 818 additions and 224 deletions.
71 changes: 56 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Expo Alternate App Icons is a library that allows you to easily switch between d

| Android Device | Android Emulator | iOS Device | iOS Simulator | Web |
| -------------- | ---------------- | ---------- | ------------- | --- |
| | ||||
| | ||||

## Introduction

Expand All @@ -23,37 +23,69 @@ Customizing app icons can be a valuable way to provide users with a personalized
To get started, install the library using Expo CLI:

```sh
expo install expo-alternate-app-icons
npx expo install expo-alternate-app-icons
```

> Ensure your project is running Expo SDK 44+.
## How To Use

This package contains an Expo Plugin that copies your alternative icons to the Xcode project.
This package contains an Expo Plugin that copies your alternative icons to native projects.

1. Add `expo-alternate-app-icons` to the plugins array inside your [app.json](https://docs.expo.dev/versions/latest/config/app/).
2. The second item in the array accepts an array with paths to your alternate icons.
2. The second item in the array accepts an array with details about your alternate icons.
3. [Prebuild](https://docs.expo.dev/workflow/prebuild/) a project using `npx expo prebuild --clean` to apply the plugin changes.

```json5
```json
// app.json
{
// ...
plugins: [
"plugins": [
// ...
[
'expo-alternate-app-icons', // add "expo-alternate-app-icons" to the plugins array
['./assets/icon-a.png', './assets/icon-b.png', './assets/icon-c.png'], // array with paths to the icons
],
],
"expo-alternate-app-icons",
[
{
"name": "IconA", // The name of the alternate icon
"ios": "./assets/icon-a.png", // Path to the iOS app icon
"android": {
"foregroundImage": "./assets/icon-a-foreground.png", // Path to Android foreground image
"backgroundColor": "#001413" // Background color for Android adaptive icon
}
},
{
"name": "IconB",
"ios": "./assets/icon-b.png",
"android": {
"foregroundImage": "./assets/icon-b-foreground.png",
"backgroundColor": "#001413"
}
},
{
"name": "IconC",
"ios": "./assets/icon-c.png",
"android": {
"foregroundImage": "./assets/icon-c-foreground.png",
"backgroundColor": "#001413"
}
}
]
]
]
}
```

### Icons

Your icons should follow the same format as your [default app icon](https://docs.expo.dev/develop/user-interface/app-icons/#ios).
Your icons should follow the same format as your [default app icon](https://docs.expo.dev/develop/user-interface/splash-screen-and-app-icon/#export-the-icon-image-as-a-png).

- Use a **.png** file.
- Square format with resolution **1024x1024 px**.
- Without transparency layer.
- Use a **.png** file
- Square format with resolution **1024x1024 px**
- iOS
- Without transparency layer
- Android - Adaptive icon
- Foreground image
- Background fill color

### API Documentation

Expand All @@ -67,7 +99,7 @@ const supportsAlternateIcons: boolean;

#### Set Alternate App Icon

To set app icon to **icon-a.png**, use `setAlternateAppIcon("icon-a")`. This function takes exact icon name without suffix.
To set app icon to **IconA**, use `setAlternateAppIcon("IconA")`. This function takes icon name as argument.

To reset the app icon to the default pass `null` like `setAlternateAppIcon(null)`.

Expand All @@ -92,3 +124,12 @@ Reset app icon to the default one.
```ts
function resetAppIcon(): Promise<void>;
```

## Development

### Expo Config Plugin

```shell
npm run build plugin # Start build on save
cd example && npx expo prebuild # Execute the config plugin
```
92 changes: 92 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'

group = 'expo.modules.alternateappicons'
version = '0.1.0'

buildscript {
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
if (expoModulesCorePlugin.exists()) {
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
}

// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

// Ensures backward compatibility
ext.getKotlinVersion = {
if (ext.has("kotlinVersion")) {
ext.kotlinVersion()
} else {
ext.safeExtGet("kotlinVersion", "1.8.10")
}
}

repositories {
mavenCentral()
}

dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
}
}

afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
}
}
repositories {
maven {
url = mavenLocal().url
}
}
}
}

android {
compileSdkVersion safeExtGet("compileSdkVersion", 33)

def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.majorVersion
}
}

namespace "expo.modules.alternateappicons"
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
versionCode 1
versionName "0.1.0"
}
lintOptions {
abortOnError false
}
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}

repositories {
mavenCentral()
}

dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
}
2 changes: 2 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package expo.modules.alternateappicons

import android.content.ComponentName
import android.content.pm.PackageManager
import expo.modules.kotlin.Promise
import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

const val MAIN_ACTIVITY_NAME = ".MainActivity"

class ExpoAlternateAppIconsModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoAlternateAppIcons")

Constants(
"supportsAlternateIcons" to true
)

Function("getAppIconName", this@ExpoAlternateAppIconsModule::getAppIconName)
AsyncFunction("setAlternateAppIcon").Coroutine(this@ExpoAlternateAppIconsModule::setAlternateAppIcon)
}

private fun getAppIconName(): String? {
val activityName = appContext.activityProvider?.currentActivity?.componentName?.shortClassName

if(activityName !== null && !activityName.startsWith(MAIN_ACTIVITY_NAME) || activityName == MAIN_ACTIVITY_NAME) return null

return activityName?.substring(MAIN_ACTIVITY_NAME.length)
}

private suspend fun setAlternateAppIcon(icon: String?): String? = withContext(Dispatchers.Main) {
val currentActivityComponent = appContext.activityProvider?.currentActivity?.componentName

if (currentActivityComponent == null || !currentActivityComponent.shortClassName.startsWith(MAIN_ACTIVITY_NAME)) return@withContext null

val newActivityName = "$MAIN_ACTIVITY_NAME${icon ?: ""}"

if (currentActivityComponent.shortClassName == newActivityName) return@withContext icon

val packageName = currentActivityComponent.packageName;
val newActivityComponent = ComponentName(packageName, "$packageName$newActivityName")

appContext.reactContext?.packageManager?.run {
setComponentEnabledSetting(newActivityComponent, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
setComponentEnabledSetting(currentActivityComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
}

return@withContext icon
}
}
6 changes: 3 additions & 3 deletions example/App.preset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const icons = [
['icon-a', require('./assets/icon-a.png')],
['icon-b', require('./assets/icon-b.png')],
['icon-c', require('./assets/icon-c.png')],
['IconA', require('./assets/icon-a.png')],
['IconB', require('./assets/icon-b.png')],
['IconC', require('./assets/icon-c.png')],
];
34 changes: 31 additions & 3 deletions example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,44 @@
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
"foregroundImage": "./assets/icon-foreground.png",
"backgroundColor": "#001413"
},
"package": "expo.modules.alternateappicons.example"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
["../app.plugin.js", ["./assets/icon-a.png", "./assets/icon-b.png", "./assets/icon-c.png"]]
[
"../app.plugin.js",
[
{
"name": "IconA",
"ios": "./assets/icon-a.png",
"android": {
"foregroundImage": "./assets/icon-a-foreground.png",
"backgroundColor": "#001413"
}
},
{
"name": "IconB",
"ios": "./assets/icon-b.png",
"android": {
"foregroundImage": "./assets/icon-b-foreground.png",
"backgroundColor": "#001413"
}
},
{
"name": "IconC",
"ios": "./assets/icon-c.png",
"android": {
"foregroundImage": "./assets/icon-c-foreground.png",
"backgroundColor": "#001413"
}
}
]
]
]
}
}
Binary file added example/assets/icon-a-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon-b-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon-c-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/assets/icon-foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 61faf9a

Please sign in to comment.