压缩安装包体积

This commit is contained in:
DelLevin-Home
2026-07-04 12:44:52 +08:00
parent f171b431d3
commit d7d2d236c3
10 changed files with 112 additions and 80 deletions

View File

@@ -32,6 +32,13 @@ android {
buildTypes { buildTypes {
release { release {
// 启用代码压缩和资源缩减
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// TODO: Add your own signing config for the release build. // TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works. // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")

33
android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,33 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# Flutter specific rules
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
# Keep classes used by reflection
-keep class * { @com.google.gson.annotations.SerializedName <fields>; }
# SQLite
-keep class net.sqlcipher.** { *; }
# Prevent obfuscation of model classes
-keep class top.iletter.mooknote.** { *; }
# Google Play Core - prevent R8 from removing these classes
-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication
-dontwarn com.google.android.play.core.splitinstall.SplitInstallException
-dontwarn com.google.android.play.core.splitinstall.SplitInstallManager
-dontwarn com.google.android.play.core.splitinstall.SplitInstallManagerFactory
-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest$Builder
-dontwarn com.google.android.play.core.splitinstall.SplitInstallRequest
-dontwarn com.google.android.play.core.splitinstall.SplitInstallSessionState
-dontwarn com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
-dontwarn com.google.android.play.core.tasks.OnFailureListener
-dontwarn com.google.android.play.core.tasks.OnSuccessListener
-dontwarn com.google.android.play.core.tasks.Task

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -5,10 +5,11 @@ import 'package:path/path.dart' as path;
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../utils/font_download_manager.dart'; import '../utils/font_download_manager.dart';
import '../utils/toast_util.dart'; import '../utils/toast_util.dart';
import '../utils/user_prefs.dart';
/// 字体选择页面 /// 字体选择页面
/// ///
/// 用户输入或选择字体目录,遍历目录下的字体文件,点击即可加载使用。 /// 用户选择字体目录,遍历目录下的字体文件,点击即可加载使用。
class FontPickerPage extends StatefulWidget { class FontPickerPage extends StatefulWidget {
final String? initialFamily; final String? initialFamily;
const FontPickerPage({super.key, this.initialFamily}); const FontPickerPage({super.key, this.initialFamily});
@@ -18,27 +19,24 @@ class FontPickerPage extends StatefulWidget {
} }
class _FontPickerPageState extends State<FontPickerPage> { class _FontPickerPageState extends State<FontPickerPage> {
final TextEditingController _pathController = TextEditingController();
final FontDownloadManager _fontManager = FontDownloadManager(); final FontDownloadManager _fontManager = FontDownloadManager();
List<FontFileInfo> _fonts = []; List<FontFileInfo> _fonts = [];
String? _loadingPath; String? _loadingPath;
String? _selectedFamily; String? _selectedFamily;
bool _isScanning = false; bool _isScanning = false;
String? _currentDirPath;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedFamily = widget.initialFamily; _selectedFamily = widget.initialFamily;
// 默认填入内置字体目录 // 恢复上次选择的目录
_pathController.text = '/sdcard/Documents/mooknote/fonts'; final savedDir = UserPrefs().lastFontDir;
_checkPermissionAndScan(); if (savedDir != null && savedDir.isNotEmpty) {
_currentDirPath = savedDir;
_scanDirectory(savedDir);
} }
@override
void dispose() {
_pathController.dispose();
super.dispose();
} }
/// 请求存储权限Android 11+ 需要 MANAGE_EXTERNAL_STORAGE /// 请求存储权限Android 11+ 需要 MANAGE_EXTERNAL_STORAGE
@@ -58,18 +56,6 @@ class _FontPickerPageState extends State<FontPickerPage> {
return status.isGranted; return status.isGranted;
} }
/// 检查权限并扫描
Future<void> _checkPermissionAndScan() async {
final hasPermission = await _requestStoragePermission();
if (!hasPermission) {
if (mounted) {
_showPermissionDialog();
}
return;
}
await _scanDirectory();
}
/// 显示权限提示弹窗 /// 显示权限提示弹窗
void _showPermissionDialog() { void _showPermissionDialog() {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
@@ -103,12 +89,8 @@ class _FontPickerPageState extends State<FontPickerPage> {
} }
/// 扫描目录字体 /// 扫描目录字体
Future<void> _scanDirectory() async { Future<void> _scanDirectory(String dirPath) async {
final dirPath = _pathController.text.trim(); if (dirPath.isEmpty) return;
if (dirPath.isEmpty) {
if (mounted) ToastUtil.show(context, '请输入目录路径');
return;
}
setState(() => _isScanning = true); setState(() => _isScanning = true);
try { try {
@@ -130,18 +112,30 @@ class _FontPickerPageState extends State<FontPickerPage> {
} }
} }
/// 使用 file_picker 选择目录 /// 扫描当前目录字体
Future<void> _pickDirectory() async { Future<void> _rescanDirectory() async {
if (_currentDirPath == null || _currentDirPath!.isEmpty) {
ToastUtil.show(context, '请先选择字体目录');
return;
}
await _scanDirectory(_currentDirPath!);
}
/// 点击选择目录按钮
Future<void> _onSelectDirectory() async {
final hasPermission = await _requestStoragePermission(); final hasPermission = await _requestStoragePermission();
if (!hasPermission) { if (!hasPermission) {
if (mounted) _showPermissionDialog(); if (mounted) _showPermissionDialog();
return; return;
} }
try { try {
final result = await FilePicker.platform.getDirectoryPath(); final result = await FilePicker.platform.getDirectoryPath();
if (result != null && result.isNotEmpty) { if (result != null && result.isNotEmpty) {
_pathController.text = result; _currentDirPath = result;
await _scanDirectory(); // 保存到 SharedPreferences
await UserPrefs().setLastFontDir(result);
await _scanDirectory(result);
} }
} catch (e) { } catch (e) {
if (mounted) ToastUtil.show(context, '选择目录失败: $e'); if (mounted) ToastUtil.show(context, '选择目录失败: $e');
@@ -195,7 +189,7 @@ class _FontPickerPageState extends State<FontPickerPage> {
), ),
body: Column( body: Column(
children: [ children: [
// 路径输入 // 选择目录按钮
Container( Container(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 12), padding: const EdgeInsets.fromLTRB(20, 16, 20, 12),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -219,40 +213,49 @@ class _FontPickerPageState extends State<FontPickerPage> {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: InkWell(
controller: _pathController, onTap: _onSelectDirectory,
style: TextStyle(fontSize: 13, color: colors.onSurface), borderRadius: BorderRadius.circular(8),
decoration: InputDecoration( child: Container(
hintText: '输入字体目录路径', padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
hintStyle: TextStyle( decoration: BoxDecoration(
fontSize: 13, color: colors.surfaceContainerHighest,
color: colors.onSurface.withValues(alpha: 0.3),
),
filled: true,
fillColor: colors.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
), ),
suffixIcon: IconButton( child: Row(
icon: Icon( children: [
Icon(
Icons.folder_open_outlined, Icons.folder_open_outlined,
size: 18, size: 18,
color: colors.onSurface.withValues(alpha: 0.5), color: colors.onSurface.withValues(alpha: 0.5),
), ),
onPressed: _pickDirectory, const SizedBox(width: 8),
Expanded(
child: Text(
_currentDirPath ?? '点击选择字体目录',
style: TextStyle(
fontSize: 13,
color: _currentDirPath != null
? colors.onSurface
: colors.onSurface.withValues(alpha: 0.3),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Icon(
Icons.chevron_right,
size: 18,
color: colors.onSurface.withValues(alpha: 0.3),
),
],
), ),
), ),
onSubmitted: (_) => _scanDirectory(),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: _scanDirectory, onPressed: _rescanDirectory,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: colors.primary, backgroundColor: colors.primary,
foregroundColor: colors.onPrimary, foregroundColor: colors.onPrimary,

View File

@@ -112,6 +112,8 @@ class _WebDAVSyncPageState extends State<WebDAVSyncPage> {
path: path, path: path,
); );
setState(() => _isConfigured = true); setState(() => _isConfigured = true);
// 保存成功后立即加载远程备份信息
_loadRemoteInfo();
ToastUtil.show(context, result['message'] ?? '连接成功,配置已保存'); ToastUtil.show(context, result['message'] ?? '连接成功,配置已保存');
} else { } else {
ToastUtil.show(context, result['message'] ?? '连接失败,请检查配置'); ToastUtil.show(context, result['message'] ?? '连接失败,请检查配置');

View File

@@ -267,4 +267,10 @@ class UserPrefs {
/// EPUB 句读列表视图模式: 0=瀑布流, 1=列表 /// EPUB 句读列表视图模式: 0=瀑布流, 1=列表
int get highlightsViewMode => prefs.getInt('highlightsViewMode') ?? 0; int get highlightsViewMode => prefs.getInt('highlightsViewMode') ?? 0;
Future<bool> setHighlightsViewMode(int value) => prefs.setInt('highlightsViewMode', value); Future<bool> setHighlightsViewMode(int value) => prefs.setInt('highlightsViewMode', value);
// ========== 字体选择器 ==========
/// 字体选择器上次使用的目录路径
String? get lastFontDir => prefs.getString('lastFontDir');
Future<bool> setLastFontDir(String value) => prefs.setString('lastFontDir', value);
} }

View File

@@ -1,14 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
android_intent_plus:
dependency: "direct main"
description:
name: android_intent_plus
sha256: "2329378af63f49b985cb2e110ac784d08374f1e2b1984be77ba9325b1c8cce11"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -271,7 +263,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_colorpicker: flutter_colorpicker:
dependency: "direct main" dependency: transitive
description: description:
name: flutter_colorpicker name: flutter_colorpicker
sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea"
@@ -453,14 +445,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
gbk_codec:
dependency: "direct main"
description:
name: gbk_codec
sha256: "3af5311fc9393115e3650ae6023862adf998051a804a08fb804f042724999f61"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:

View File

@@ -30,17 +30,14 @@ dependencies:
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
http: ^1.2.0 http: ^1.2.0
url_launcher: ^6.2.5 url_launcher: ^6.2.5
android_intent_plus: ^5.0.0
webview_flutter: ^4.8.0 webview_flutter: ^4.8.0
flutter_inappwebview: ^6.1.5 flutter_inappwebview: ^6.1.5
dynamic_color: ^1.8.1 dynamic_color: ^1.8.1
package_info_plus: ^8.0.0 package_info_plus: ^8.0.0
extended_text_field: ^16.0.2 extended_text_field: ^16.0.2
uuid: ^4.5.0 uuid: ^4.5.0
gbk_codec: ^0.4.0
expandable: ^5.0.1 expandable: ^5.0.1
flutter_quill: ^11.5.0 flutter_quill: ^11.5.0
flutter_colorpicker: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: