feat: смена иконки приложения в кастомизации (Default / Minimal)

This commit is contained in:
klockky
2026-05-28 04:39:38 +00:00
parent 757e649f94
commit b35626e36a
64 changed files with 503 additions and 196 deletions
+15
View File
@@ -9,6 +9,9 @@
android:label="Komet" android:label="Komet"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<service
android:name="com.solusibejo.flutter_dynamic_icon_plus.FlutterDynamicIconPlusService"
android:stopWithTask="false"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@@ -31,6 +34,18 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity-alias
android:name=".MinimalIcon"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_minimal"
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-alias>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<service <service
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,9 @@
<?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"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#000000</color>
</resources>
Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+10 -2
View File
@@ -15,6 +15,8 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */; };
B1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */; };
E835CC948F2F948DC3BBF405 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */; }; E835CC948F2F948DC3BBF405 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE8CB2654009FE276CACA4B /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -62,6 +64,8 @@
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MinimalIcon@2x.png"; sourceTree = "<group>"; };
A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "MinimalIcon@3x.png"; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; 9D7B0FFE63E0BD5D67D7758D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
@@ -143,6 +147,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */, 97C147021CF9000F007C117D /* Info.plist */,
A1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png */,
A1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -264,6 +270,8 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
B1B2C3D41234567890ABCDEF /* MinimalIcon@2x.png in Resources */,
B1B2C3D41234567890ABCDF0 /* MinimalIcon@3x.png in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -539,7 +547,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@@ -596,7 +604,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@@ -1,122 +1 @@
{ {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

+15
View File
@@ -14,6 +14,21 @@
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>Komet</string> <string>Komet</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>MinimalIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>MinimalIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+51
View File
@@ -0,0 +1,51 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_dynamic_icon_plus/flutter_dynamic_icon_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum AppIcon {
defaultIcon('default', 'Default', 'assets/komet_icon.png', null),
minimal('minimal', 'Minimal', 'assets/meteor_icon.png', 'MinimalIcon');
final String id;
final String title;
final String previewAsset;
final String? platformName;
const AppIcon(this.id, this.title, this.previewAsset, this.platformName);
}
class AppIconConfig {
static const prefKey = 'app_icon';
static final ValueNotifier<AppIcon> current = ValueNotifier(
AppIcon.defaultIcon,
);
static bool get isSupported => Platform.isAndroid || Platform.isIOS;
static Future<void> load() async {
if (!isSupported) return;
final prefs = await SharedPreferences.getInstance();
final id = prefs.getString(prefKey);
current.value = _parse(id);
}
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);
}
static AppIcon _parse(String? val) {
for (final icon in AppIcon.values) {
if (icon.id == val) return icon;
}
return AppIcon.defaultIcon;
}
}
@@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../core/config/app_icon.dart';
import '../../../core/utils/haptics.dart';
import '../../widgets/custom_notification.dart';
class AppIconScreen extends StatefulWidget {
const AppIconScreen({super.key});
@override
State<AppIconScreen> createState() => _AppIconScreenState();
}
class _AppIconScreenState extends State<AppIconScreen> {
@override
void initState() {
super.initState();
AppIconConfig.load();
}
Future<void> _select(AppIcon icon) async {
if (!AppIconConfig.isSupported) {
showCustomNotification(
context,
'Смена иконки доступна только на Android и iOS',
);
return;
}
if (AppIconConfig.current.value == icon) return;
Haptics.selection();
try {
await AppIconConfig.apply(icon);
if (!mounted) return;
showCustomNotification(context, 'Иконка изменена на «${icon.title}»');
} catch (e) {
if (!mounted) return;
showCustomNotification(context, 'Не удалось сменить иконку: $e');
}
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
appBar: AppBarM3E(
titleText: 'Иконка приложения',
backgroundColor: cs.surface,
),
body: SafeArea(
top: false,
child: ListView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 120),
children: [
Material(
color: cs.surfaceContainerHigh,
borderRadius: BorderRadius.circular(28),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 18, 20, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Внешний вид иконки',
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
AppIconConfig.isSupported
? 'Изменение применится сразу — приложение может ненадолго закрыться'
: 'Доступно только на Android и iOS',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 13,
height: 1.35,
),
),
const SizedBox(height: 12),
ValueListenableBuilder<AppIcon>(
valueListenable: AppIconConfig.current,
builder: (context, current, _) {
return Column(
children: [
for (final icon in AppIcon.values)
_IconTile(
icon: icon,
selected: current == icon,
onTap: () => _select(icon),
),
],
);
},
),
],
),
),
),
],
),
),
);
}
}
class _IconTile extends StatelessWidget {
final AppIcon icon;
final bool selected;
final VoidCallback onTap;
const _IconTile({
required this.icon,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.asset(
icon.previewAsset,
width: 56,
height: 56,
fit: BoxFit.cover,
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
icon.title,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
Icon(
selected
? Symbols.radio_button_checked
: Symbols.radio_button_unchecked,
color: selected ? cs.primary : cs.outline,
size: 22,
fill: selected ? 1 : 0,
),
],
),
),
),
);
}
}
@@ -3,6 +3,7 @@ import 'package:m3e_collection/m3e_collection.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import '../../../core/utils/haptics.dart'; import '../../../core/utils/haptics.dart';
import 'app_icon_screen.dart';
import 'appearance_screen.dart'; import 'appearance_screen.dart';
import 'font_settings_screen.dart'; import 'font_settings_screen.dart';
import 'message_actions_screen.dart'; import 'message_actions_screen.dart';
@@ -50,6 +51,12 @@ class CustomizationScreen extends StatelessWidget {
subtitle: 'Радиальное или список — для долгого нажатия на сообщение', subtitle: 'Радиальное или список — для долгого нажатия на сообщение',
builder: _buildMessageActions, builder: _buildMessageActions,
), ),
_CustomizationCategory(
icon: Symbols.apps,
title: 'Иконка приложения',
subtitle: 'Default или Minimal — иконка на главном экране',
builder: _buildAppIcon,
),
]; ];
static Widget _buildAppearance(BuildContext context) => static Widget _buildAppearance(BuildContext context) =>
@@ -64,6 +71,8 @@ class CustomizationScreen extends StatelessWidget {
static Widget _buildMessageActions(BuildContext context) => static Widget _buildMessageActions(BuildContext context) =>
const MessageActionsScreen(); const MessageActionsScreen();
static Widget _buildAppIcon(BuildContext context) => const AppIconScreen();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
@@ -1,68 +1,68 @@
{ {
"images" : [ "info": {
"version": 1,
"author": "xcode"
},
"images": [
{ {
"size" : "16x16", "size": "16x16",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_16.png", "filename": "app_icon_16.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "16x16", "size": "16x16",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_32.png", "filename": "app_icon_32.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "32x32", "size": "32x32",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_32.png", "filename": "app_icon_32.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "32x32", "size": "32x32",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_64.png", "filename": "app_icon_64.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "128x128", "size": "128x128",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_128.png", "filename": "app_icon_128.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "128x128", "size": "128x128",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_256.png", "filename": "app_icon_256.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "256x256", "size": "256x256",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_256.png", "filename": "app_icon_256.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "256x256", "size": "256x256",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_512.png", "filename": "app_icon_512.png",
"scale" : "2x" "scale": "2x"
}, },
{ {
"size" : "512x512", "size": "512x512",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_512.png", "filename": "app_icon_512.png",
"scale" : "1x" "scale": "1x"
}, },
{ {
"size" : "512x512", "size": "512x512",
"idiom" : "mac", "idiom": "mac",
"filename" : "app_icon_1024.png", "filename": "app_icon_1024.png",
"scale" : "2x" "scale": "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
} }
]
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

+70 -6
View File
@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +97,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -294,6 +318,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.1" 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:
name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
url: "https://pub.dev"
source: hosted
version: "0.14.4"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -413,6 +453,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.1" version: "0.2.1"
image:
dependency: transitive
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.8.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -421,6 +469,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.20.2"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -505,10 +561,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -537,10 +593,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
mobile_scanner: mobile_scanner:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -693,6 +749,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
posix:
dependency: transitive
description:
name: posix
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
progress_indicator_m3e: progress_indicator_m3e:
dependency: transitive dependency: transitive
description: description:
@@ -902,10 +966,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.11"
timezone: timezone:
dependency: "direct main" dependency: "direct main"
description: description:
+20
View File
@@ -58,6 +58,7 @@ dependencies:
firebase_core: ^4.1.1 firebase_core: ^4.1.1
firebase_messaging: ^16.0.2 firebase_messaging: ^16.0.2
flutter_local_notifications: ^21.0.0 flutter_local_notifications: ^21.0.0
flutter_dynamic_icon_plus: ^1.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -69,6 +70,23 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
flutter_launcher_icons: ^0.14.4
flutter_launcher_icons:
image_path: "assets/komet_icon.png"
android: "ic_launcher"
ios: true
remove_alpha_ios: true
background_color_ios: "#000000"
adaptive_icon_background: "#000000"
adaptive_icon_foreground: "assets/komet.png"
windows:
generate: true
image_path: "assets/komet_icon.png"
icon_size: 256
macos:
generate: true
image_path: "assets/komet_icon.png"
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@@ -83,6 +101,8 @@ flutter:
uses-material-design: true uses-material-design: true
assets: assets:
- assets/komet.png - assets/komet.png
- assets/komet_icon.png
- assets/meteor_icon.png
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: # assets:
+20
View File
@@ -0,0 +1,20 @@
import 'dart:io';
import 'package:image/image.dart' as img;
const _pairs = {
'assets/komet.png': 'assets/komet_icon.png',
'assets/meteor.png': 'assets/meteor_icon.png',
};
void main() {
for (final entry in _pairs.entries) {
final src = img.decodePng(File(entry.key).readAsBytesSync())!;
final size = src.width > src.height ? src.width : src.height;
final canvas = img.Image(width: size, height: size, numChannels: 4);
img.fill(canvas, color: img.ColorRgb8(0, 0, 0));
img.compositeImage(canvas, src,
dstX: (size - src.width) ~/ 2, dstY: (size - src.height) ~/ 2);
File(entry.value).writeAsBytesSync(img.encodePng(canvas));
stdout.writeln('wrote ${entry.value} (${size}x$size)');
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'dart:io';
import 'package:image/image.dart' as img;
const _densities = {
'mdpi': 48,
'hdpi': 72,
'xhdpi': 96,
'xxhdpi': 144,
'xxxhdpi': 192,
};
void main() {
final src = img.decodePng(File('assets/meteor_icon.png').readAsBytesSync())!;
for (final entry in _densities.entries) {
final resized = img.copyResize(src,
width: entry.value,
height: entry.value,
interpolation: img.Interpolation.average);
final dir =
Directory('android/app/src/main/res/mipmap-${entry.key}');
final file = File('${dir.path}/ic_launcher_minimal.png');
file.writeAsBytesSync(img.encodePng(resized));
stdout.writeln('wrote ${file.path} (${entry.value}x${entry.value})');
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'dart:io';
import 'package:image/image.dart' as img;
const _sizes = {
'ios/Runner/MinimalIcon@2x.png': 120,
'ios/Runner/MinimalIcon@3x.png': 180,
};
void main() {
final src = img.decodePng(File('assets/meteor_icon.png').readAsBytesSync())!;
for (final entry in _sizes.entries) {
final resized = img.copyResize(src,
width: entry.value,
height: entry.value,
interpolation: img.Interpolation.cubic);
File(entry.key).writeAsBytesSync(img.encodePng(resized));
stdout.writeln('wrote ${entry.key} (${entry.value}x${entry.value})');
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 16 KiB