fix: смена иконки через нативный MethodChannel — без плагина и без отключения MainActivity
This commit is contained in:
@@ -9,9 +9,6 @@
|
||||
android:label="Komet"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<service
|
||||
android:name="com.solusibejo.flutter_dynamic_icon_plus.FlutterDynamicIconPlusService"
|
||||
android:stopWithTask="false"/>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@@ -29,11 +26,19 @@
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
</activity>
|
||||
<activity-alias
|
||||
android:name=".DefaultIcon"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Komet"
|
||||
android:targetActivity=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</activity-alias>
|
||||
<activity-alias
|
||||
android:name=".MinimalIcon"
|
||||
android:enabled="false"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package ru.komet.app
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
@@ -20,11 +22,32 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
class MainActivity : FlutterActivity() {
|
||||
|
||||
private val channelName = "ru.komet.app/vpn_bypass"
|
||||
private val iconAliases = listOf("DefaultIcon", "MinimalIcon")
|
||||
|
||||
private companion object {
|
||||
const val LOG_TAG = "VpnBypass"
|
||||
}
|
||||
|
||||
private fun applyIcon(name: String) {
|
||||
val pm = packageManager
|
||||
for (alias in iconAliases) {
|
||||
val component = ComponentName(packageName, "$packageName.$alias")
|
||||
val state = if (alias == name) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
} else {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
}
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
state,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
finishAndRemoveTask()
|
||||
}, 250L)
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(
|
||||
@@ -39,6 +62,28 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
"ru.komet.app/app_icon",
|
||||
).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"setAppIcon" -> {
|
||||
val name = call.argument<String>("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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AppIcon> current = ValueNotifier(
|
||||
AppIcon.defaultIcon,
|
||||
);
|
||||
@@ -34,12 +36,12 @@ class AppIconConfig {
|
||||
static Future<void> 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<void>('setAppIcon', {
|
||||
'name': icon.platformName,
|
||||
});
|
||||
}
|
||||
|
||||
static AppIcon _parse(String? val) {
|
||||
|
||||
@@ -74,7 +74,7 @@ class _AppIconScreenState extends State<AppIconScreen> {
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
AppIconConfig.isSupported
|
||||
? 'Изменение применится сразу — приложение может ненадолго закрыться'
|
||||
? 'На Android приложение закроется — лаунчер подхватит новую иконку. На iOS — мгновенно с системным диалогом.'
|
||||
: 'Доступно только на Android и iOS',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user