This commit is contained in:
Alex
2026-07-15 12:15:42 -05:00
parent 2237078bd5
commit ba5651c1a2
61 changed files with 1429 additions and 41 deletions

View File

@@ -557,6 +557,18 @@
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_title": "Sign out",
"app_download_links": "App Download Links",
"app_icon_blossom": "Blossom",
"app_icon_change_failed": "Unable to change the app icon",
"app_icon_classic": "Classic",
"app_icon_forest": "Forest",
"app_icon_gold": "Gold",
"app_icon_ink": "Ink",
"app_icon_midnight": "Midnight",
"app_icon_neon": "Neon",
"app_icon_ocean": "Ocean",
"app_icon_subtitle": "Choose the icon shown on your home screen",
"app_icon_sunset": "Sunset",
"app_icon_title": "App Icon",
"app_settings": "App Settings",
"app_stores": "App Stores",
"app_update_available": "App update is available",

View File

@@ -56,11 +56,6 @@
Window background behind the Flutter UI. -->
<meta-data android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--TODO:
Add this filter if you want to handle shared images-->
@@ -137,6 +132,72 @@
</intent-filter>
</activity>
<!-- Launcher aliases for the selectable app icons (see AppIconApiImpl).
Exactly one is enabled at a time; the LAUNCHER intent-filter lives on
these instead of MainActivity so the icon can be swapped at runtime. -->
<activity-alias android:name=".AppIconClassic" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconMidnight" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_midnight" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconOcean" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_ocean" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconSunset" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_sunset" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconForest" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_forest" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconBlossom" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_blossom" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconInk" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_ink" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconNeon" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_neon" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias android:name=".AppIconGold" android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_gold" android:exported="true" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"

View File

@@ -4,6 +4,8 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.ext.SdkExtensions
import app.alextran.immich.appicon.AppIconApi
import app.alextran.immich.appicon.AppIconApiImpl
import app.alextran.immich.background.BackgroundEngineLock
import app.alextran.immich.background.BackgroundWorkerApiImpl
import app.alextran.immich.background.BackgroundWorkerFgHostApi
@@ -61,6 +63,7 @@ class MainActivity : FlutterFragmentActivity() {
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
AppIconApi.setUp(messenger, AppIconApiImpl(ctx))
flutterEngine.plugins.add(ViewIntentPlugin())
flutterEngine.plugins.add(backgroundEngineLockImpl)

View File

@@ -0,0 +1,110 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package app.alextran.immich.appicon
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object AppIconPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : RuntimeException()
private open class AppIconPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return super.readValueOfType(type, buffer)
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
super.writeValue(stream, value)
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface AppIconApi {
fun setAppIcon(iconId: String, callback: (Result<Unit>) -> Unit)
fun getAppIcon(): String
companion object {
/** The codec used by AppIconApi. */
val codec: MessageCodec<Any?> by lazy {
AppIconPigeonCodec()
}
/** Sets up an instance of `AppIconApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: AppIconApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.AppIconApi.setAppIcon$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val iconIdArg = args[0] as String
api.setAppIcon(iconIdArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(AppIconPigeonUtils.wrapError(error))
} else {
reply.reply(AppIconPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.AppIconApi.getAppIcon$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.getAppIcon())
} catch (exception: Throwable) {
AppIconPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
package app.alextran.immich.appicon
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
/**
* Switches the launcher icon by toggling the `.AppIcon*` activity-aliases
* declared in the AndroidManifest. Exactly one alias is enabled at a time;
* the aliases all target MainActivity.
*/
class AppIconApiImpl(context: Context) : AppIconApi {
private val ctx: Context = context.applicationContext
companion object {
private const val DEFAULT_ICON = "classic"
private val ICONS =
listOf("classic", "midnight", "ocean", "sunset", "forest", "blossom", "ink", "neon", "gold")
}
private fun componentFor(iconId: String): ComponentName {
val alias = "AppIcon" + iconId.replaceFirstChar { it.uppercase() }
return ComponentName(ctx.packageName, "app.alextran.immich.$alias")
}
override fun setAppIcon(iconId: String, callback: (Result<Unit>) -> Unit) {
if (iconId !in ICONS) {
callback(Result.failure(FlutterError("invalid_icon", "Unknown app icon: $iconId")))
return
}
val pm = ctx.packageManager
for (icon in ICONS) {
val state = if (icon == iconId) {
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
pm.setComponentEnabledSetting(componentFor(icon), state, PackageManager.DONT_KILL_APP)
}
callback(Result.success(Unit))
}
override fun getAppIcon(): String {
val pm = ctx.packageManager
for (icon in ICONS) {
if (pm.getComponentEnabledSetting(componentFor(icon)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
) {
return icon
}
}
return DEFAULT_ICON
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#E11D48"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#FB7185"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#F472B6"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#BE185D"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#F9A8D4"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#16A34A"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#84CC16"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#4ADE80"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#15803D"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#65A30D"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FBBF24"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#FDE68A"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#F59E0B"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#D97706"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#FCD34D"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#111827"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#6B7280"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#374151"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#9CA3AF"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#4B5563"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FA2921"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#ED79B5"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#FFB400"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#1E83F7"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#18C249"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF2D95"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#00E5FF"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#FFD600"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#7C4DFF"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#00FF9D"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#0284C7"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#38BDF8"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#1D4ED8"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#0891B2"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#14B8A6"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#F97316"
android:fillType="nonZero"
android:pathData="m52.25,43.55c3.4,3.08 6.15,6.39 7.91,9.5 3.03,-5.55 5.06,-12.14 5.08,-16.34 0,-0.03 0,-0.06 0,-0.08 0,-6.21 -6.06,-8.63 -11.28,-8.63 -5.22,0 -11.28,2.42 -11.28,8.63 0,0.08 0,0.2 0,0.34 2.91,1.32 6.36,3.69 9.56,6.59z" />
<path
android:fillColor="#7C3AED"
android:fillType="nonZero"
android:pathData="m33.68,60.5c2.13,-2.42 5.39,-5.05 9.08,-7.27 3.92,-2.36 7.84,-4.01 11.28,-4.76 -4.22,-4.67 -9.72,-8.67 -13.62,-10 -0.03,-0.01 -0.05,-0.02 -0.08,-0.03 -5.78,-1.92 -9.9,3.23 -11.51,8.31 -1.61,5.08 -1.24,11.72 4.54,13.64 0.08,0.03 0.18,0.06 0.31,0.1z" />
<path
android:fillColor="#FACC15"
android:fillType="nonZero"
android:pathData="M79.17,46.67C77.56,41.59 73.44,36.44 67.66,38.36c-0.08,0.03 -0.19,0.06 -0.31,0.1 -0.33,3.24 -1.46,7.33 -3.17,11.34 -1.81,4.27 -4.04,7.96 -6.39,10.64 6.1,1.24 12.85,1.17 16.76,-0.1 0.03,-0.01 0.05,-0.02 0.08,-0.03 5.78,-1.92 6.15,-8.56 4.54,-13.64z" />
<path
android:fillColor="#C026D3"
android:fillType="nonZero"
android:pathData="m48.82,65.46c-0.98,-4.54 -1.3,-8.87 -0.94,-12.45 -5.64,2.67 -11.07,6.78 -13.5,10.16 -0.02,0.02 -0.03,0.05 -0.05,0.07 -3.57,5.03 -0.06,10.63 4.16,13.77 4.22,3.14 10.51,4.83 14.09,-0.2 0.05,-0.07 0.11,-0.16 0.19,-0.27 -1.59,-2.82 -3.03,-6.81 -3.95,-11.08z" />
<path
android:fillColor="#F43F5E"
android:fillType="nonZero"
android:pathData="m73.58,62.92c-3.11,0.68 -7.26,0.84 -11.52,0.42 -4.53,-0.45 -8.64,-1.47 -11.86,-2.93 0.73,6.31 2.88,12.87 5.28,16.28 0.02,0.02 0.03,0.05 0.05,0.07 3.57,5.03 9.86,3.34 14.09,0.2 4.22,-3.14 7.74,-8.74 4.16,-13.77 -0.05,-0.07 -0.11,-0.16 -0.19,-0.27z" />
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_blossom"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_blossom"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_forest"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_forest"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_gold"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_gold"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_ink"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_ink"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_midnight"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_midnight"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_neon"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_neon"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_ocean"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_ocean"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_sunset"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_sunset"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background_midnight">#0F172A</color>
<color name="ic_launcher_background_ocean">#FFFFFF</color>
<color name="ic_launcher_background_sunset">#1E1B4B</color>
<color name="ic_launcher_background_forest">#FFFFFF</color>
<color name="ic_launcher_background_blossom">#FFF1F2</color>
<color name="ic_launcher_background_ink">#FFFFFF</color>
<color name="ic_launcher_background_neon">#0A0A12</color>
<color name="ic_launcher_background_gold">#1C1917</color>
</resources>

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#FFF1F2"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#E11D48"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#FB7185"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#F472B6"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#BE185D"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#F9A8D4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#FFFFFF"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#FA2921"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#ED79B5"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#FFB400"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#1E83F7"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#18C249"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#FFFFFF"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#16A34A"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#84CC16"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#4ADE80"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#15803D"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#65A30D"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#1C1917"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#FBBF24"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#FDE68A"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#F59E0B"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#D97706"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#FCD34D"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#FFFFFF"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#111827"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#6B7280"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#374151"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#9CA3AF"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#4B5563"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#0F172A"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#FA2921"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#ED79B5"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#FFB400"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#1E83F7"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#18C249"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#0A0A12"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#FF2D95"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#00E5FF"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#FFD600"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#7C4DFF"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#00FF9D"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#FFFFFF"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#0284C7"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#38BDF8"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#1D4ED8"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#0891B2"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#14B8A6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,10 @@
<svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="#1E1B4B"/>
<g transform="translate(21.12 21.12) scale(0.78)">
<path d="M90.0492 58.8333C102.552 69.9022 112.627 81.7638 119.111 92.9428C130.248 73.0245 137.69 49.3562 137.784 34.2816C137.784 34.1749 137.784 34.0778 137.784 33.9872C137.784 11.6811 115.533 3 96.3651 3C77.1975 3 54.9464 11.6811 54.9464 33.9872C54.9464 34.2913 54.9464 34.699 54.9464 35.1908C65.6303 39.9407 78.2943 48.4276 90.0492 58.8333Z" fill="#F97316"/>
<path d="M21.8495 119.662C29.6634 110.968 41.6513 101.546 55.1792 93.5802C69.5711 85.1095 83.9662 79.1916 96.6012 76.4834C81.0995 59.736 60.8901 45.3441 46.5791 40.5975C46.4788 40.5652 46.3849 40.5361 46.3008 40.5069C25.0851 33.6151 9.95223 52.0936 4.03111 70.3229C-1.89001 88.5522 -0.511647 112.398 20.7041 119.29C20.9921 119.384 21.3803 119.51 21.8495 119.662Z" fill="#7C3AED"/>
<path d="M188.925 70.0154C183.004 51.7861 167.871 33.3077 146.656 40.1995C146.365 40.2933 145.976 40.4195 145.51 40.5716C144.297 52.1971 140.139 66.8672 133.875 81.2623C127.213 96.5731 119.043 109.823 110.414 119.442C132.798 123.881 157.608 123.642 171.974 119.073C172.074 119.041 172.168 119.009 172.252 118.983C193.468 112.088 194.847 88.2415 188.925 70.0154Z" fill="#FACC15"/>
<path d="M77.4498 137.484C73.8421 121.18 72.6611 105.659 73.9877 92.8069C53.2703 102.375 33.3391 117.148 24.3992 129.291C24.3377 129.375 24.2794 129.456 24.2277 129.528C11.1171 147.576 24.0141 167.678 39.5223 178.941C55.0272 190.208 78.1325 196.265 91.2463 178.217C91.4275 177.971 91.667 177.641 91.9549 177.243C86.1018 167.125 80.8407 152.811 77.4498 137.484Z" fill="#C026D3"/>
<path d="M168.379 128.363C156.948 130.802 141.712 131.382 126.084 129.871C109.463 128.266 94.3395 124.59 82.5232 119.355C85.2184 142.014 93.1132 165.536 101.898 177.79C101.959 177.874 102.018 177.955 102.069 178.026C115.18 196.074 138.285 190.017 153.793 178.751C169.298 167.484 182.199 147.382 169.088 129.337C168.907 129.091 168.667 128.761 168.379 128.363Z" fill="#F43F5E"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,20 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/platform/app_icon_api.g.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('switches the launcher icon and reports it back', (tester) async {
final api = AppIconApi();
await api.setAppIcon('neon');
expect(await api.getAppIcon(), 'neon');
await api.setAppIcon('classic');
expect(await api.getAppIcon(), 'classic');
await expectLater(api.setAppIcon('not-an-icon'), throwsA(isA<PlatformException>()));
});
}

View File

@@ -16,8 +16,6 @@ PODS:
- Flutter
- native_video_player (1.0.0):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
- share_handler_ios (0.0.14):
- Flutter
- share_handler_ios/share_handler_ios_models (= 0.0.14)
@@ -36,7 +34,6 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- home_widget (from `.symlinks/plugins/home_widget/ios`)
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
@@ -57,8 +54,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/home_widget/ios"
native_video_player:
:path: ".symlinks/plugins/native_video_player/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_handler_ios:
:path: ".symlinks/plugins/share_handler_ios/ios"
share_handler_ios_models:
@@ -73,7 +68,6 @@ SPEC CHECKSUMS:
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -153,11 +153,15 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
B231F52D2E93A44A00BC45D1 /* Core */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Core;
sourceTree = "<group>";
};
B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Sync;
sourceTree = "<group>";
};
@@ -179,6 +183,8 @@
};
FEE084F22EC172080045228E /* Schemas */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
path = Schemas;
sourceTree = "<group>";
};
@@ -370,7 +376,6 @@
FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */,
2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -464,7 +469,7 @@
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */,
FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */,
);
@@ -511,27 +516,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2FA39DEC809D6D7C4A01EFCB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -556,14 +540,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -772,6 +752,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -917,6 +898,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -946,6 +928,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -1261,7 +1244,7 @@
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};

View File

@@ -31,6 +31,7 @@ import native_video_player
RemoteImageApiSetup.setUp(binaryMessenger: messenger, api: RemoteImageApiImpl())
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl())
ConnectivityApiSetup.setUp(binaryMessenger: messenger, api: ConnectivityApiImpl())
AppIconApiSetup.setUp(binaryMessenger: messenger, api: AppIconApiImpl())
NetworkApiSetup.setUp(binaryMessenger: messenger, api: NetworkApiImpl())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,14 @@
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
}
],
"info": {
"author": "xcode",
"version": 1
}
}

114
mobile/ios/Runner/Core/AppIcon.g.swift generated Normal file
View File

@@ -0,0 +1,114 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
import Foundation
#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif
private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}
private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(Swift.type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}
private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}
private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}
private class AppIconPigeonCodecReader: FlutterStandardReader {
}
private class AppIconPigeonCodecWriter: FlutterStandardWriter {
}
private class AppIconPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return AppIconPigeonCodecReader(data: data)
}
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return AppIconPigeonCodecWriter(data: data)
}
}
class AppIconPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = AppIconPigeonCodec(readerWriter: AppIconPigeonCodecReaderWriter())
}
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol AppIconApi {
func setAppIcon(iconId: String, completion: @escaping (Result<Void, Error>) -> Void)
func getAppIcon() throws -> String
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class AppIconApiSetup {
static var codec: FlutterStandardMessageCodec { AppIconPigeonCodec.shared }
/// Sets up an instance of `AppIconApi` to handle messages through the `binaryMessenger`.
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: AppIconApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let setAppIconChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.AppIconApi.setAppIcon\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setAppIconChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let iconIdArg = args[0] as! String
api.setAppIcon(iconId: iconIdArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
setAppIconChannel.setMessageHandler(nil)
}
let getAppIconChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.AppIconApi.getAppIcon\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAppIconChannel.setMessageHandler { _, reply in
do {
let result = try api.getAppIcon()
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
getAppIconChannel.setMessageHandler(nil)
}
}
}

View File

@@ -0,0 +1,45 @@
import UIKit
/// Switches between the alternate app icons bundled in the asset catalog
/// (`AppIconMidnight`, `AppIconOcean`, ...). The "classic" id maps to the
/// primary `AppIcon`.
class AppIconApiImpl: AppIconApi {
private static let defaultIcon = "classic"
private static let assetPrefix = "AppIcon"
private func assetName(for iconId: String) -> String? {
guard iconId != Self.defaultIcon else { return nil }
return Self.assetPrefix + iconId.prefix(1).uppercased() + String(iconId.dropFirst())
}
func setAppIcon(iconId: String, completion: @escaping (Result<Void, Error>) -> Void) {
let iconName = assetName(for: iconId)
DispatchQueue.main.async {
guard UIApplication.shared.supportsAlternateIcons else {
completion(
.failure(
PigeonError(
code: "unsupported",
message: "Alternate icons are not supported on this device",
details: nil
)))
return
}
UIApplication.shared.setAlternateIconName(iconName) { error in
if let error = error {
completion(
.failure(
PigeonError(code: "set_icon_failed", message: error.localizedDescription, details: nil)
))
} else {
completion(.success(()))
}
}
}
}
func getAppIcon() throws -> String {
guard let name = UIApplication.shared.alternateIconName else { return Self.defaultIcon }
return String(name.dropFirst(Self.assetPrefix.count)).lowercased()
}
}

View File

@@ -0,0 +1,21 @@
/// The selectable launcher icons. The [name] of each variant is the id shared
/// with the native implementations (Android activity-aliases and iOS
/// alternate icon asset names), so renaming a value is a breaking change.
enum AppIconVariant {
classic,
midnight,
ocean,
sunset,
forest,
blossom,
ink,
neon,
gold;
String get assetPath => 'assets/app-icons/app-icon-$name.svg';
String get translationKey => 'app_icon_$name';
static AppIconVariant fromId(String id) =>
AppIconVariant.values.firstWhere((variant) => variant.name == id, orElse: () => AppIconVariant.classic);
}

109
mobile/lib/platform/app_icon_api.g.dart generated Normal file
View File

@@ -0,0 +1,109 @@
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: unused_import, unused_shown_name
// ignore_for_file: type=lint
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List;
import 'package:flutter/services.dart';
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
Object? _extractReplyValueOrThrow(
List<Object?>? replyList,
String channelName, {
required bool isNullValid,
}) {
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
}
return replyList.firstOrNull;
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
default:
return super.readValueOfType(type, buffer);
}
}
}
class AppIconApi {
/// Constructor for [AppIconApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
AppIconApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<void> setAppIcon(String iconId) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.AppIconApi.setAppIcon$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[iconId]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
_extractReplyValueOrThrow(
pigeonVar_replyList,
pigeonVar_channelName,
isNullValid: true,
)
;
}
Future<String> getAppIcon() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.AppIconApi.getAppIcon$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
pigeonVar_replyList,
pigeonVar_channelName,
isNullValid: false,
)
;
return pigeonVar_replyValue! as String;
}
}

View File

@@ -1,5 +1,6 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/background_worker.service.dart';
import 'package:immich_mobile/platform/app_icon_api.g.dart';
import 'package:immich_mobile/platform/background_worker_api.g.dart';
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
import 'package:immich_mobile/platform/connectivity_api.g.dart';
@@ -21,6 +22,8 @@ final permissionApiProvider = Provider<PermissionApi>((_) => PermissionApi());
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
final appIconApiProvider = Provider<AppIconApi>((_) => AppIconApi());
final localImageApi = LocalImageApi();
final remoteImageApi = RemoteImageApi();

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/app_icons.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
class AppIconSetting extends HookConsumerWidget {
const AppIconSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appIconApi = ref.watch(appIconApiProvider);
final selectedIcon = useState(AppIconVariant.classic);
useEffect(() {
appIconApi
.getAppIcon()
.then((id) {
selectedIcon.value = AppIconVariant.fromId(id);
})
.catchError((_) {});
return null;
}, []);
Future<void> onIconSelected(AppIconVariant variant) async {
if (variant == selectedIcon.value) {
return;
}
try {
await appIconApi.setAppIcon(variant.name);
selectedIcon.value = variant;
} catch (_) {
if (context.mounted) {
ImmichToast.show(
context: context,
msg: 'app_icon_change_failed'.t(context: context),
toastType: ToastType.error,
);
}
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingGroupTitle(
title: "app_icon_title".t(context: context),
icon: Icons.apps_outlined,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text(
"app_icon_subtitle".t(context: context),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceVariant),
),
),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
children: [
for (final variant in AppIconVariant.values)
_AppIconTile(
variant: variant,
isSelected: variant == selectedIcon.value,
onTap: () => onIconSelected(variant),
),
],
),
],
);
}
}
class _AppIconTile extends StatelessWidget {
const _AppIconTile({required this.variant, required this.isSelected, required this.onTap});
final AppIconVariant variant;
final bool isSelected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
children: [
Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
border: Border.all(
color: isSelected ? context.colorScheme.primary : context.colorScheme.outlineVariant,
width: isSelected ? 3 : 1,
),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(13)),
child: SvgPicture.asset(variant.assetPath, width: 64, height: 64),
),
),
if (isSelected)
Positioned(
right: 2,
bottom: 2,
child: DecoratedBox(
decoration: BoxDecoration(color: context.colorScheme.primary, shape: BoxShape.circle),
child: Padding(
padding: const EdgeInsets.all(2),
child: Icon(Icons.check, size: 12, color: context.colorScheme.onPrimary),
),
),
),
],
),
const SizedBox(height: 6),
Text(
variant.translationKey.t(context: context),
style: context.textTheme.labelMedium?.copyWith(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? context.colorScheme.primary : null,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/app_icon_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/haptic_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/share_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/theme_setting.dart';
@@ -9,7 +10,7 @@ class PreferenceSetting extends StatelessWidget {
@override
Widget build(BuildContext context) {
const preferenceSettings = [ThemeSetting(), HapticSetting(), ShareSetting()];
const preferenceSettings = [ThemeSetting(), AppIconSetting(), HapticSetting(), ShareSetting()];
return const SettingsSubPageScaffold(settings: preferenceSettings, showDivider: true);
}

View File

@@ -0,0 +1,20 @@
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/platform/app_icon_api.g.dart',
swiftOut: 'ios/Runner/Core/AppIcon.g.swift',
swiftOptions: SwiftOptions(includeErrorClass: false),
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/appicon/AppIcon.g.kt',
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.appicon'),
dartOptions: DartOptions(),
dartPackageName: 'immich_mobile',
),
)
@HostApi()
abstract class AppIconApi {
@async
void setAppIcon(String iconId);
String getAppIcon();
}

View File

@@ -124,6 +124,7 @@ flutter:
uses-material-design: true
assets:
- assets/
- assets/app-icons/
- assets/feature_message/
fonts:
- family: GoogleSans

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""Generates the alternate app icon assets for Android and iOS.
Recolors the five petals of the Immich logo for each icon variant and emits:
- assets/app-icons/app-icon-<id>.svg (in-app previews)
- android res: drawable/ic_launcher_foreground_<id>.xml,
mipmap-anydpi-v26/ic_launcher_<id>.xml,
values/app_icon_colors.xml
- ios: Assets.xcassets/AppIcon<Name>.appiconset/{Contents.json,1024.png}
The iOS PNGs are rendered with macOS `qlmanage`, so this script must run on
macOS. Run it from anywhere: `python3 mobile/scripts/generate_app_icons.py`.
"""
import json
import re
import shutil
import struct
import subprocess
import sys
import tempfile
import zlib
from pathlib import Path
MOBILE = Path(__file__).resolve().parent.parent
# Original petal colors, in SVG document order:
# top (red), upper-left (pink), upper-right (yellow),
# lower-left (blue), lower-right (green)
BASE_PETALS = ["#FA2921", "#ED79B5", "#FFB400", "#1E83F7", "#18C249"]
BASE_BACKGROUND = "#FFFFFF"
# id -> (asset suffix, background, petal colors in BASE_PETALS order).
# "classic" is the default icon and only gets a preview SVG; its native
# assets are the stock ic_launcher / AppIcon.
VARIANTS = {
"classic": (BASE_BACKGROUND, BASE_PETALS),
"midnight": ("#0F172A", BASE_PETALS),
"ocean": ("#FFFFFF", ["#0284C7", "#38BDF8", "#1D4ED8", "#0891B2", "#14B8A6"]),
"sunset": ("#1E1B4B", ["#F97316", "#7C3AED", "#FACC15", "#C026D3", "#F43F5E"]),
"forest": ("#FFFFFF", ["#16A34A", "#84CC16", "#4ADE80", "#15803D", "#65A30D"]),
"blossom": ("#FFF1F2", ["#E11D48", "#FB7185", "#F472B6", "#BE185D", "#F9A8D4"]),
"ink": ("#FFFFFF", ["#111827", "#6B7280", "#374151", "#9CA3AF", "#4B5563"]),
"neon": ("#0A0A12", ["#FF2D95", "#00E5FF", "#FFD600", "#7C4DFF", "#00FF9D"]),
"gold": ("#1C1917", ["#FBBF24", "#FDE68A", "#F59E0B", "#D97706", "#FCD34D"]),
}
def recolor(text: str, petals: list[str]) -> str:
for base, new in zip(BASE_PETALS, petals):
pattern = re.compile(re.escape(base), re.IGNORECASE)
text = pattern.sub(new, text)
return text
def petal_paths() -> str:
svg = (MOBILE / "assets" / "immich-logo.svg").read_text()
return "\n".join(re.findall(r"<path[^>]*/>", svg))
def icon_svg(background: str, petals: list[str], size: int) -> str:
# The flower spans the full 192px viewBox; scale it down so the icon
# keeps the same padding as the original iOS app icon.
scale = 0.78
offset = 192 * (1 - scale) / 2
paths = recolor(petal_paths(), petals)
return f"""<svg width="{size}" height="{size}" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="192" height="192" fill="{background}"/>
<g transform="translate({offset:g} {offset:g}) scale({scale})">
{paths}
</g>
</svg>
"""
def write_previews() -> None:
out = MOBILE / "assets" / "app-icons"
out.mkdir(exist_ok=True)
for variant, (background, petals) in VARIANTS.items():
(out / f"app-icon-{variant}.svg").write_text(icon_svg(background, petals, 192))
print(f"previews: {out}")
def write_android() -> None:
res = MOBILE / "android" / "app" / "src" / "main" / "res"
foreground = (res / "drawable" / "ic_launcher_foreground.xml").read_text()
colors = ['<?xml version="1.0" encoding="utf-8"?>', "<resources>"]
for variant, (background, petals) in VARIANTS.items():
if variant == "classic":
continue
(res / "drawable" / f"ic_launcher_foreground_{variant}.xml").write_text(
recolor(foreground, petals)
)
colors.append(f' <color name="ic_launcher_background_{variant}">{background}</color>')
(res / "mipmap-anydpi-v26" / f"ic_launcher_{variant}.xml").write_text(
f"""<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_{variant}"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_{variant}"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>
"""
)
colors.append("</resources>")
(res / "values" / "app_icon_colors.xml").write_text("\n".join(colors) + "\n")
print(f"android res: {res}")
def strip_alpha(png: bytes) -> bytes:
"""Re-encodes an RGBA PNG as opaque RGB (required for app icons)."""
assert png[:8] == b"\x89PNG\r\n\x1a\n"
pos, chunks, idat = 8, [], b""
width = height = bit_depth = color_type = None
while pos < len(png):
(length,) = struct.unpack(">I", png[pos : pos + 4])
kind = png[pos + 4 : pos + 8]
data = png[pos + 8 : pos + 8 + length]
pos += 12 + length
if kind == b"IHDR":
width, height, bit_depth, color_type = struct.unpack(">IIBB", data[:10])
if kind == b"IDAT":
idat += data
else:
chunks.append((kind, data))
if color_type != 6 or bit_depth != 8:
return png # already opaque
raw = zlib.decompress(idat)
stride = width * 4
rgb = bytearray()
previous = bytearray(width * 4)
for y in range(height):
row_start = y * (stride + 1)
filter_type = raw[row_start]
row = bytearray(raw[row_start + 1 : row_start + 1 + stride])
for x in range(stride):
a = row[x - 4] if x >= 4 else 0
b = previous[x]
c = previous[x - 4] if x >= 4 else 0
if filter_type == 1:
row[x] = (row[x] + a) & 0xFF
elif filter_type == 2:
row[x] = (row[x] + b) & 0xFF
elif filter_type == 3:
row[x] = (row[x] + (a + b) // 2) & 0xFF
elif filter_type == 4:
p = a + b - c
pa, pb, pc = abs(p - a), abs(p - b), abs(p - c)
pred = a if pa <= pb and pa <= pc else b if pb <= pc else c
row[x] = (row[x] + pred) & 0xFF
previous = row
rgb.append(0)
for x in range(width):
rgb += row[x * 4 : x * 4 + 3]
out = bytearray(b"\x89PNG\r\n\x1a\n")
def chunk(kind: bytes, data: bytes) -> None:
out.extend(struct.pack(">I", len(data)))
out.extend(kind)
out.extend(data)
out.extend(struct.pack(">I", zlib.crc32(kind + data)))
chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
chunk(b"IDAT", zlib.compress(bytes(rgb), 9))
chunk(b"IEND", b"")
return bytes(out)
def write_ios() -> None:
xcassets = MOBILE / "ios" / "Runner" / "Assets.xcassets"
with tempfile.TemporaryDirectory() as tmp_dir:
tmp = Path(tmp_dir)
for variant, (background, petals) in VARIANTS.items():
if variant == "classic":
continue
svg = tmp / f"{variant}.svg"
svg.write_text(icon_svg(background, petals, 1024))
subprocess.run(
["qlmanage", "-t", "-s", "1024", "-o", tmp_dir, str(svg)],
check=True,
capture_output=True,
)
rendered = tmp / f"{variant}.svg.png"
if not rendered.exists():
sys.exit(f"qlmanage failed to render {svg}")
iconset = xcassets / f"AppIcon{variant.capitalize()}.appiconset"
shutil.rmtree(iconset, ignore_errors=True)
iconset.mkdir()
(iconset / "1024.png").write_bytes(strip_alpha(rendered.read_bytes()))
(iconset / "Contents.json").write_text(
json.dumps(
{
"images": [
{
"filename": "1024.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024",
}
],
"info": {"author": "xcode", "version": 1},
},
indent=2,
)
+ "\n"
)
print(f"ios appiconsets: {xcassets}")
if __name__ == "__main__":
write_previews()
write_android()
write_ios()