diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 83e4929..2735665 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,9 +9,6 @@ android:label="Komet" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> - + + - + + when (call.method) { + "setAppIcon" -> { + val name = call.argument("name") + if (name == null || !iconAliases.contains(name)) { + result.error("INVALID_ICON", "Unknown icon: $name", null) + return@setMethodCallHandler + } + try { + applyIcon(name) + result.success(null) + } catch (e: Exception) { + result.error("APPLY_FAILED", e.message, null) + } + } + else -> result.notImplemented() + } + } + MethodChannel( flutterEngine.dartExecutor.binaryMessenger, "ru.komet.app/upload_service", diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..948ca04 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -8,6 +8,43 @@ import UIKit didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + + let controller = window?.rootViewController as? FlutterViewController + if let messenger = controller?.binaryMessenger { + let channel = FlutterMethodChannel( + name: "ru.komet.app/app_icon", + binaryMessenger: messenger + ) + channel.setMethodCallHandler { (call, result) in + guard call.method == "setAppIcon" else { + result(FlutterMethodNotImplemented) + return + } + let args = call.arguments as? [String: Any] + let name = args?["name"] as? String + let iconName: String? = (name == "DefaultIcon") ? nil : name + if !UIApplication.shared.supportsAlternateIcons { + result(FlutterError( + code: "UNSUPPORTED", + message: "Alternate icons are not supported", + details: nil + )) + return + } + UIApplication.shared.setAlternateIconName(iconName) { error in + if let error = error { + result(FlutterError( + code: "APPLY_FAILED", + message: error.localizedDescription, + details: nil + )) + } else { + result(nil) + } + } + } + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/lib/core/config/app_icon.dart b/lib/core/config/app_icon.dart index 9c3ef8d..96c662d 100644 --- a/lib/core/config/app_icon.dart +++ b/lib/core/config/app_icon.dart @@ -1,23 +1,25 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:flutter_dynamic_icon_plus/flutter_dynamic_icon_plus.dart'; +import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; enum AppIcon { - defaultIcon('default', 'Default', 'assets/komet_icon.png', null), + defaultIcon('default', 'Default', 'assets/komet_icon.png', 'DefaultIcon'), minimal('minimal', 'Minimal', 'assets/meteor_icon.png', 'MinimalIcon'); final String id; final String title; final String previewAsset; - final String? platformName; + final String platformName; const AppIcon(this.id, this.title, this.previewAsset, this.platformName); } class AppIconConfig { static const prefKey = 'app_icon'; + static const _channel = MethodChannel('ru.komet.app/app_icon'); + static final ValueNotifier current = ValueNotifier( AppIcon.defaultIcon, ); @@ -34,12 +36,12 @@ class AppIconConfig { static Future apply(AppIcon icon) async { if (!isSupported) return; if (current.value == icon) return; - await FlutterDynamicIconPlus.setAlternateIconName( - iconName: icon.platformName, - ); - current.value = icon; final prefs = await SharedPreferences.getInstance(); await prefs.setString(prefKey, icon.id); + current.value = icon; + await _channel.invokeMethod('setAppIcon', { + 'name': icon.platformName, + }); } static AppIcon _parse(String? val) { diff --git a/lib/frontend/screens/profile/app_icon_screen.dart b/lib/frontend/screens/profile/app_icon_screen.dart index 2f4e9d6..4534725 100644 --- a/lib/frontend/screens/profile/app_icon_screen.dart +++ b/lib/frontend/screens/profile/app_icon_screen.dart @@ -74,7 +74,7 @@ class _AppIconScreenState extends State { const SizedBox(height: 4), Text( AppIconConfig.isSupported - ? 'Изменение применится сразу — приложение может ненадолго закрыться' + ? 'На Android приложение закроется — лаунчер подхватит новую иконку. На iOS — мгновенно с системным диалогом.' : 'Доступно только на Android и iOS', style: TextStyle( color: cs.onSurfaceVariant, diff --git a/pubspec.lock b/pubspec.lock index 77ba49f..f44d5e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -318,14 +318,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" - flutter_dynamic_icon_plus: - dependency: "direct main" - description: - name: flutter_dynamic_icon_plus - sha256: "7721ef2666691064dece879d968fa7e1c4d64aa743ea998e6b1e65a1b482f212" - url: "https://pub.dev" - source: hosted - version: "1.4.0" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index e4bd3f8..ebb0e75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,7 +58,6 @@ dependencies: firebase_core: ^4.1.1 firebase_messaging: ^16.0.2 flutter_local_notifications: ^21.0.0 - flutter_dynamic_icon_plus: ^1.4.0 dev_dependencies: flutter_test: