diff --git a/lib/function/java/java_manager.dart b/lib/function/java/java_manager.dart index fe2668c..7ad0ce5 100644 --- a/lib/function/java/java_manager.dart +++ b/lib/function/java/java_manager.dart @@ -3,29 +3,55 @@ import 'dart:io'; import 'package:fml/function/log.dart'; import 'package:fml/function/java/models/java_info.dart'; import 'package:fml/function/java/models/java_runtime.dart'; +import 'package:path/path.dart' as path; class JavaManager { JavaManager._(); - // 寻找 Java 可执行文件 - static Future> searchPotentialJavaExecutables() async { + /// + /// Java 可执行文件名称 + /// + static String kJavaExecutableName = Platform.isWindows ? 'java.exe' : 'java'; + + static final RegExp _vendorVersionRegExp = RegExp( + r'(?:(OpenJDK|java|IBM|AdoptOpenJDK|Microsoft).*?)?version\s+"([^"]+)"', + caseSensitive: false, + ); + + static final RegExp _fallbackVersionRegExp = RegExp(r'"([0-9._-]+)"'); + + /// + /// 寻找 Java 可执行文件 + /// + static Future> searchPotentialJavaExecutables({ + int searchDepth = 4, + }) async { final Set found = {}; final List result = []; + // PATH - final pathEntries = Platform.environment['PATH']?.split(Platform.pathSeparator) ?? []; + final pathSeparator = Platform.isWindows ? ';' : ':'; + final pathEntries = + Platform.environment['PATH']?.split(pathSeparator) ?? []; + for (final entry in pathEntries) { if (entry.trim().isEmpty) continue; - final javaPath = _join(entry, _javaExecutableName()); + + final javaPath = _join(entry, kJavaExecutableName); + if (await File(javaPath).exists()) { found.add(await File(javaPath).resolveSymbolicLinks()); } } + // 常用系统目录 final List candidates = []; + if (Platform.isWindows) { final env = Platform.environment; final prog = env['ProgramFiles'] ?? 'C:\\Program Files'; final progx86 = env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)'; + candidates.add(Directory(prog)); candidates.add(Directory(progx86)); } else if (Platform.isLinux) { @@ -33,18 +59,30 @@ class JavaManager { candidates.add(Directory('/usr/lib/jvm')); candidates.add(Directory('/usr/lib32/jvm')); candidates.add(Directory('/usr/lib64/jvm')); + final home = Platform.environment['HOME']; - if (home != null) candidates.add(Directory('$home/.sdkman/candidates/java')); + + if (home != null) { + candidates.add(Directory('$home/.sdkman/candidates/java')); + } } else if (Platform.isMacOS) { candidates.add(Directory('/Library/Java/JavaVirtualMachines')); + final home = Platform.environment['HOME']; - if (home != null) candidates.add(Directory('$home/Library/Java/JavaVirtualMachines')); + + if (home != null) { + candidates.add(Directory('$home/Library/Java/JavaVirtualMachines')); + } } + // 用户jdks final home = Platform.environment['HOME']; + if (home != null) candidates.add(Directory('$home/.jdks')); + for (final dir in candidates) { if (!await dir.exists()) continue; + try { await for (final entry in dir.list(followLinks: false)) { if (entry is Directory) { @@ -61,10 +99,12 @@ class JavaManager { LogUtil.log('查找 Java 可执行文件时出错:$e', level: 'WARN'); } } + // 同时检查每个候选目录下常见的顶级 JDK 名称 for (final exe in found) { try { final info = await _probeJavaExecutable(exe); + if (info != null) { final isJdk = await _looksLikeJdk(exe); result.add(JavaRuntime(info: info, executable: exe, isJdk: isJdk)); @@ -74,92 +114,199 @@ class JavaManager { } } - // 通过可执行路径确保唯一性 - final unique = {}; - for (final r in result) { - unique[r.executable] = r; + final roots = []; + + if (Platform.isWindows) { + // Windows枚举 + for (int i = 0; i < 26; i++) { + // CharCode 68(排除A,B,C) + final drive = '${String.fromCharCode(68 + i)}:\\'; + final dir = Directory(drive); + + try { + if (dir.existsSync()) { + roots.add(dir); + } + } catch (_) { + // 忽略无法访问的驱动器 + } + } + + //final stopwatch = Stopwatch()..start(); + for (final rootDir in roots) { + final javaRuntimes = await _searchJavaInDirRecursive( + dir: rootDir, + searchDepth: searchDepth, + ); + + result.addAll(javaRuntimes); + } + //stopwatch.stop(); + + //print('timeee: ${stopwatch.elapsedMilliseconds} ms, $result'); } - return unique.values.toList(); - } - // 路径拼接 - static String _join(String a, String b) { - if (a.endsWith(Platform.pathSeparator)) return '$a$b'; - return a + Platform.pathSeparator + b; + // 去重返回(按 executable 路径去重) + final Map uniqueByExecutable = {}; + + for (final runtime in result) { + // 后出现的同一路径会覆盖先前的,确保最终列表中每个 executable 唯一 + uniqueByExecutable[runtime.executable] = runtime; + } + + return uniqueByExecutable.values.toList(); } - // Java 可执行文件名称 - static String _javaExecutableName() { - return Platform.isWindows ? 'java.exe' : 'java'; + /// + /// 递归搜索Java运行时 + /// + /// [dir] 要搜索的根目录 + /// [searchDepth] 最大允许的递归深度 + /// [currentDepth] 当前递归深度(内部调用使用) + /// + static Future> _searchJavaInDirRecursive({ + required Directory dir, + required int searchDepth, + int currentDepth = 0, + }) async { + List result = []; + + if (currentDepth > searchDepth) return result; + + try { + // 检查当前目录下的文件 + final dirs = dir.list(followLinks: false); + + await for (final entity in dirs) { + final name = path.basename(entity.path); + + // 排除 . 开头目录 + if (name.startsWith('.')) continue; + + if (entity is File) { + final lowerFileName = name.toLowerCase(); + + if (lowerFileName == kJavaExecutableName) { + // 创建JavaRuntime逻辑 + final exePath = entity.path; + final info = await _probeJavaExecutable(exePath); + + if (info != null) { + final isJdk = await _looksLikeJdk(exePath); + + result.add( + JavaRuntime(info: info, executable: exePath, isJdk: isJdk), + ); + } + } + } else if (entity is Directory) { + // 递归搜索子目录(深度+1) + result.addAll( + await _searchJavaInDirRecursive( + dir: entity, + currentDepth: currentDepth + 1, + searchDepth: searchDepth, + ), + ); + } + } + } catch (e) { + // 忽略权限错误或无法访问的目录 + } + + return result; } - // 可能的可执行文件路径 + /// + /// 可能的可执行文件路径 + /// static List _possibleExecutablePaths(String javaHome) { final List probes = []; + if (Platform.isMacOS) { - probes.add('$javaHome/jre.bundle/Contents/Home/bin/${_javaExecutableName()}'); - probes.add('$javaHome/Contents/Home/bin/${_javaExecutableName()}'); + probes.add('$javaHome/jre.bundle/Contents/Home/bin/$kJavaExecutableName'); + + probes.add('$javaHome/Contents/Home/bin/$kJavaExecutableName'); } - probes.add('$javaHome/bin/${_javaExecutableName()}'); - probes.add('$javaHome/jre/bin/${_javaExecutableName()}'); + + probes.add('$javaHome/bin/$kJavaExecutableName'); + probes.add('$javaHome/jre/bin/$kJavaExecutableName'); return probes; } - // 检查可执行文件是否看为 JDK(存在 javac) + /// + /// 检查可执行文件是否看为 JDK(存在 javac) + /// static Future _looksLikeJdk(String exe) async { try { final bin = File(exe).parent; - final javac = File('${bin.path}${Platform.pathSeparator}javac${Platform.isWindows ? '.exe' : ''}'); + final javac = File( + '${bin.path}${Platform.pathSeparator}javac${Platform.isWindows ? '.exe' : ''}', + ); + return await javac.exists(); } catch (_) { return false; } } - // Java 可执行文件信息 + /// + /// Java 可执行文件信息 + /// static Future _probeJavaExecutable(String exe) async { // 首先尝试“java -version” try { final proc = await Process.start(exe, ['-version']); final out = await proc.stderr.transform(SystemEncoding().decoder).join(); await proc.exitCode; - final parsed = _parseVersionOutput(out); + + final parsed = parseVersionOutput(out); + if (parsed != null) { return JavaInfo( version: parsed['version']!, vendor: parsed['vendor'], path: exe, - os: _currentOsName(), + os: Platform.operatingSystem, arch: Platform.version, ); } } catch (e) { LogUtil.log('执行 "$exe -version" 时出错:$e', level: 'WARN'); } + // 尝试读取父目录中的发布文件 try { final bin = File(exe).parent; final javaHome = bin.parent.path; final release = File('$javaHome${Platform.pathSeparator}release'); + if (await release.exists()) { final lines = await release.readAsLines(); final map = {}; - for (final l in lines) { - final idx = l.indexOf('='); - if (idx > 0) { - final k = l.substring(0, idx).trim(); - var v = l.substring(idx + 1).trim(); - if (v.startsWith('"') && v.endsWith('"')) v = v.substring(1, v.length - 1); - map[k] = v; + + for (final line in lines) { + final index = line.indexOf('='); + if (index > 0) { + final key = line.substring(0, index).trim(); + var value = line.substring(index + 1).trim(); + + if (value.startsWith('"') && value.endsWith('"')) { + value = value.substring(1, value.length - 1); + } + + map[key] = value; } } + final version = map['JAVA_VERSION'] ?? map['IMPLEMENTOR_VERSION'] ?? ''; + if (version.isNotEmpty) { return JavaInfo( version: version, vendor: map['IMPLEMENTOR'] ?? map['JAVA_VENDOR'], path: exe, - os: _currentOsName(), + os: Platform.operatingSystem, arch: Platform.version, ); } @@ -170,34 +317,47 @@ class JavaManager { return null; } - // 解析 "java -version" 输出 - static Map? _parseVersionOutput(String out) { - final lines = out.split('\n'); - for (final l in lines) { - final s = l.trim(); - if (s.isEmpty) continue; - final matches = RegExp(r'(?:(OpenJDK|java|IBM|AdoptOpenJDK|Microsoft).*?)?version\s+"([^"]+)"', caseSensitive: false).firstMatch(s); + /// + /// 解析 "java -version" 输出 + /// + static Map? parseVersionOutput(String output) { + // 分割每行 + final lines = output.split('\n'); + + for (final line in lines) { + final trimmedLine = line.trim(); + + if (trimmedLine.isEmpty) continue; + + final matches = _vendorVersionRegExp.firstMatch(trimmedLine); + if (matches != null) { String? vendor; + if (matches.group(1) == 'java') { vendor = 'Oracle'; } else { vendor = matches.group(1); } + final version = matches.group(2); return {'version': version ?? '', 'vendor': vendor}; } - final alt = RegExp(r'"([0-9._-]+)"').firstMatch(s); - if (alt != null) return {'version': alt.group(1) ?? '', 'vendor': null}; + + final fallbackMatch = _fallbackVersionRegExp.firstMatch(line); + + if (fallbackMatch != null) { + return {'version': fallbackMatch.group(1) ?? '', 'vendor': null}; + } } return null; } - // 当前操作系统名称 - static String _currentOsName() { - if (Platform.isWindows) return 'windows'; - if (Platform.isMacOS) return 'macos'; - if (Platform.isLinux) return 'linux'; - return 'unknown'; + /// + /// 路径拼接 + /// + static String _join(String a, String b) { + if (a.endsWith(Platform.pathSeparator)) return '$a$b'; + return a + Platform.pathSeparator + b; } } diff --git a/lib/main.dart b/lib/main.dart index 28855ee..9d1fe25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fml/function/slide_page_route.dart'; import 'package:fml/function/dio_client.dart'; +import 'package:lazy_load_indexed_stack/lazy_load_indexed_stack.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -425,7 +426,10 @@ class MainStartPageState extends State { ), // 显示当前页面 Expanded( - child: IndexedStack(index: _selectedIndex, children: _mainPages), + child: LazyLoadIndexedStack( + index: _selectedIndex, + children: _mainPages, + ), ), ], ), diff --git a/lib/pages/setting/about.dart b/lib/pages/setting/about.dart index 005cca6..eede41c 100644 --- a/lib/pages/setting/about.dart +++ b/lib/pages/setting/about.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart' hide LicensePage; import 'package:fml/constants.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; class AboutPage extends StatefulWidget { @@ -11,138 +10,113 @@ class AboutPage extends StatefulWidget { } class AboutPageState extends State { - String _appVersion = "unknown"; - - Future _loadAppVersion() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - setState(() { - _appVersion = prefs.getString('version') ?? "unknown"; - }); - } - - // 打开URL - Future _launchURL(String url) async { - try { - final Uri uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('无法打开链接: $url'))); - } - } catch (e) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('发生错误: $e'))); - } - } - - @override - void initState() { - super.initState(); - _loadAppVersion(); - } - @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('关于')), body: ListView( children: [ Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center( - child: Column( + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), + ), + + margin: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + + child: Column( + children: [ + Text( + '\n本项目使用GPL3.0协议开源,使用过程中请遵守GPL3.0协议\n', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - '\n本项目使用GPL3.0协议开源,使用过程中请遵守GPL3.0协议\n', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + Flexible( + child: Image.asset( + 'assets/img/icon/logo_transparent.png', + height: 150, ), - textAlign: TextAlign.center, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Image.asset( - 'assets/img/icon/logo_transparent.png', - height: 150, - ), - ), - const SizedBox(width: 70), - Flexible( - child: Image.asset( - 'assets/img/logo/flutter.png', - height: 150, - ), - ), - ], ), - const SizedBox(height: 16), - Text( - '$kAppName Version $_appVersion', - style: TextStyle( - fontSize: 17, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - Text( - 'Copyright © 2026 lxdklp. All rights reserved\n', - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, + + const SizedBox(width: 70), + + Flexible( + child: Image.asset( + 'assets/img/logo/flutter.png', + height: 150, ), - textAlign: TextAlign.center, ), ], ), - ), + + const SizedBox(height: kDefaultPadding), + + Text( + '$kAppName Version $gAppVersion', + style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + + Text( + 'Copyright © 2026 lxdklp. All rights reserved\n', + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ], ), ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: const Text('官网'), - subtitle: const Text(AppUrls.officialWebsite), - trailing: const Icon(Icons.open_in_new), - onTap: () => _launchURL(AppUrls.officialWebsite), - ), + + _buildCardWithListTile( + title: '官网', + subtitle: AppUrls.officialWebsite, + onTap: () => _launchURL(AppUrls.officialWebsite), ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: const Text('GitHub'), - subtitle: const Text(AppUrls.githubProject), - trailing: const Icon(Icons.open_in_new), - onTap: () => _launchURL(AppUrls.githubProject), - ), + + _buildCardWithListTile( + title: 'GitHub', + subtitle: AppUrls.githubProject, + onTap: () => _launchURL(AppUrls.githubProject), ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: const Text('BUG反馈与建议'), - subtitle: const Text('${AppUrls.githubProject}/issues'), - trailing: const Icon(Icons.open_in_new), - onTap: () => _launchURL('${AppUrls.githubProject}/issues'), - ), + + _buildCardWithListTile( + title: 'BUG反馈与建议', + subtitle: '${AppUrls.githubProject}/issues', + onTap: () => _launchURL('${AppUrls.githubProject}/issues'), ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: Text('许可'), - subtitle: Text('感谢各位依赖库的贡献者'), - trailing: const Icon(Icons.open_in_new), - onTap: () => showLicensePage(context: context), - ), + + _buildCardWithListTile( + title: '许可', + subtitle: '感谢各位依赖库的贡献者', + onTap: () => showLicensePage(context: context), ), + Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: const EdgeInsets.only( + left: kDefaultPadding, + right: kDefaultPadding, + bottom: kDefaultPadding, + ), + + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Column( children: [ const ListTile(title: Text('鸣谢'), subtitle: Text('排名不分先后顺序')), @@ -154,6 +128,7 @@ class AboutPageState extends State { trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://bmclapidoc.bangbang93.com'), ), + ListTile( title: const Text('MCIM'), subtitle: const Text( @@ -168,18 +143,21 @@ class AboutPageState extends State { trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://gh-proxy.com'), ), + ListTile( title: const Text('Modrinth'), subtitle: const Text('资源下载\nhttps://modrinth.com'), trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://modrinth.com'), ), + ListTile( title: const Text('CurseForge'), subtitle: const Text('资源下载\nhttps://www.curseforge.com'), trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://www.curseforge.com'), ), + ListTile( title: const Text('Sawaratsuki'), subtitle: const Text( @@ -189,6 +167,7 @@ class AboutPageState extends State { onTap: () => _launchURL('https://github.com/SAWARATSUKI/KawaiiLogos'), ), + ListTile( title: const Text('Noto CJK fonts'), subtitle: const Text( @@ -198,6 +177,7 @@ class AboutPageState extends State { onTap: () => _launchURL('https://github.com/notofonts/noto-cjk'), ), + ListTile( title: const Text('GNU General Public License Version 3'), subtitle: const Text( @@ -207,6 +187,7 @@ class AboutPageState extends State { onTap: () => _launchURL('https://www.gnu.org/licenses/gpl-3.0.html'), ), + ListTile( title: const Text('authlib-injector'), subtitle: const Text( @@ -217,12 +198,14 @@ class AboutPageState extends State { 'https://github.com/yushijinhun/authlib-injector', ), ), + ListTile( title: const Text('EasyTier'), subtitle: const Text('异地组网\nhttps://easytier.cn/'), trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://easytier.cn/'), ), + ListTile( title: const Text('Scaffolding-MC'), subtitle: const Text( @@ -233,6 +216,7 @@ class AboutPageState extends State { 'https://github.com/Scaffolding-MC/Scaffolding-MC', ), ), + ListTile( title: const Text('Terracotta'), subtitle: const Text( @@ -242,6 +226,7 @@ class AboutPageState extends State { onTap: () => _launchURL('https://github.com/burningtnt/Terracotta'), ), + ListTile( title: const Text('HMCL'), subtitle: const Text( @@ -250,6 +235,7 @@ class AboutPageState extends State { trailing: const Icon(Icons.open_in_new), onTap: () => _launchURL('https://github.com/HMCL-dev/HMCL'), ), + ListTile( title: const Text('futurw4v'), subtitle: const Text('贡献者\nhttps://github.com/futurw4v'), @@ -260,7 +246,8 @@ class AboutPageState extends State { title: const Text('图标画师'), subtitle: const Text('https://github.com/lxdklp/FML/pull/7'), trailing: const Icon(Icons.open_in_new), - onTap: () => _launchURL('https://github.com/lxdklp/FML/pull/7'), + onTap: () => + _launchURL('https://github.com/lxdklp/FML/pull/7'), ), const ListTile( title: Text('GitHub 上提出 Issue 等的各位'), @@ -273,4 +260,54 @@ class AboutPageState extends State { ), ); } + + Card _buildCardWithListTile({ + required String title, + required String subtitle, + required VoidCallback onTap, + }) { + return Card( + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant), + borderRadius: BorderRadius.circular(12), + ), + + margin: const EdgeInsets.symmetric( + horizontal: kDefaultPadding, + vertical: kDefaultPadding / 2, + ), + + child: ListTile( + title: Text(title), + subtitle: Text(subtitle), + trailing: const Icon(Icons.open_in_new), + onTap: onTap, + ), + ); + } + + /// + /// 打开URL + /// + Future _launchURL(String url) async { + try { + final Uri uri = Uri.parse(url); + + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('无法打开链接: $url'))); + } + } catch (e) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('发生错误: $e'))); + } + } } diff --git a/lib/pages/setting/java.dart b/lib/pages/setting/java.dart index cc2e661..989d621 100644 --- a/lib/pages/setting/java.dart +++ b/lib/pages/setting/java.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:fml/constants.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:fml/function/log.dart'; import 'package:fml/function/java/java_manager.dart'; @@ -14,77 +15,254 @@ class JavaPage extends StatefulWidget { } class JavaPageState extends State { - late Future> _future; - String? _currentJavaPath; + late Future> _javaRuntimesFuture; late Future _systemDefaultJavaInfo; + String? _currentJavaPath; + + // 每个设置间的间距 + static const _itemsPadding = Padding( + padding: EdgeInsets.symmetric(vertical: kDefaultPadding / 2), + ); + @override void initState() { super.initState(); - _getCurrentJavaPath(); - _systemDefaultJavaInfo = _getSystemDefaultJavaInfo(); + _getCurrentJavaPathFromPrefs(); _refresh(); } - // 当前选择 Java - Future _getCurrentJavaPath() async { + /// + /// 刷新 Java 列表与系统默认 Java + /// + Future _refresh() async { + setState(() { + _systemDefaultJavaInfo = _getSystemDefaultJavaInfo(); + _javaRuntimesFuture = JavaManager.searchPotentialJavaExecutables(); + }); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + // 大标题 + Padding( + padding: const EdgeInsets.only( + left: kDefaultPadding / 2, + top: kDefaultPadding, + bottom: kDefaultPadding, + ), + child: Text( + '设备上的Java列表', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + + _itemsPadding, + + // 确保FutureBuilder占满剩余空间 + Expanded( + child: FutureBuilder>( + future: Future.wait([ + _systemDefaultJavaInfo, + _javaRuntimesFuture, + ]), + + builder: (context, snapshot) { + // 加载中显示CircularProgressIndicator + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + // 加载失败显示错误信息 + // TODO: 包装一个表示错误的组件 + if (snapshot.hasError) { + return Center(child: Text('检测失败:${snapshot.error}')); + } + + // Index0: _systemDefaultJavaInfo 的结果(JavaInfo?) + // Index1: _javaRuntimesFuture 的结果(List) + final results = snapshot.data ?? []; + + // 提取系统默认 Java 信息 + final JavaInfo? systemJavaInfo = results.isNotEmpty + ? results[0] as JavaInfo? + : null; + + // 检测系统默认Java是否存在 + final systemJavaExists = systemJavaInfo != null; + + // 提取扫描到的Java运行时列表 + List javaRuntimes = []; + if (results.length > 1) { + javaRuntimes = (results[1] as List).cast(); + } + + // 如果系统默认存在且路径不为空,移除扫描列表中与系统默认路径相同的项 + if (systemJavaInfo != null && systemJavaInfo.path.isNotEmpty) { + javaRuntimes.removeWhere( + (runtime) => runtime.executable == systemJavaInfo.path, + ); + } + + final totalItems = systemJavaExists + ? javaRuntimes.length + 1 + : javaRuntimes.length; + + if (totalItems == 0) { + return const Center(child: Text('未检测到 Java')); + } + + return ListView.builder( + itemCount: totalItems, + + itemBuilder: (context, index) { + if (systemJavaExists && index == 0) { + final isCurrentJava = + _currentJavaPath == 'default' || + _currentJavaPath == null; + + // 构建系统默认Card + return _buildJavaCard( + javaInfo: systemJavaInfo, + + typeChipLabel: '系统默认', + + vendor: systemJavaInfo.vendor, + + isCurrent: isCurrentJava, + + onTap: _setSystemJava, + ); + } + + // 构建非系统默认的Java的卡片 + final realIndex = systemJavaExists ? index - 1 : index; + final javaRuntime = javaRuntimes[realIndex]; + + final isCurrentJava = + _currentJavaPath == javaRuntime.executable; + + return _buildJavaCard( + javaInfo: javaRuntime.info, + + typeChipLabel: javaRuntime.isJdk ? 'JDK' : 'JRE', + + vendor: javaRuntime.info.vendor, + + isCurrent: isCurrentJava, + + onTap: () => + _setCurrentJavaPathToPrefs(javaRuntime.executable), + ); + }, + ); + }, + ), + ), + ], + ), + ); + } + + /// + /// 从SharedPreferences读取选择的Java + /// + Future _getCurrentJavaPathFromPrefs() async { final prefs = await SharedPreferences.getInstance(); + setState(() { _currentJavaPath = prefs.getString('java'); }); } - // 设置当前 Java - Future _setCurrentJavaPath(String path) async { + /// + /// 写入当前 Java + /// + Future _setCurrentJavaPathToPrefs(String path) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('java', path); + setState(() { _currentJavaPath = path; }); } - // 设置为系统 Java + /// + /// 设置为系统 Java + /// Future _setSystemJava() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('java'); + setState(() { - _currentJavaPath = 'java'; + _currentJavaPath = 'default'; }); } + // // 获取系统默认 Java 信息 + // Future _getSystemDefaultJavaInfo() async { try { - final result = await Process.run('java', ['-version']); - if (result.exitCode != 0) { - LogUtil.log('获取系统默认 Java 信息失败,退出码:${result.exitCode}', level: 'WARN'); + final javaVersionProcess = await Process.run('java', ['-version']); + + if (javaVersionProcess.exitCode != 0) { + LogUtil.log( + '获取系统默认 Java 信息失败,退出码:${javaVersionProcess.exitCode}', + level: 'WARN', + ); } - final output = (result.stderr as String).isNotEmpty ? result.stderr as String : result.stdout as String; - final parsed = _parseVersionOutput(output); - if (parsed == null) { + + final versionOutput = (javaVersionProcess.stderr as String).isNotEmpty + ? javaVersionProcess.stderr as String + : javaVersionProcess.stdout as String; + + final parsedVersion = JavaManager.parseVersionOutput(versionOutput); + + if (parsedVersion == null) { LogUtil.log('无法解析系统默认 Java 版本信息', level: 'WARN'); return null; } - String path = ''; + + String executablePath = ''; + try { if (Platform.isWindows) { final where = await Process.run('where', ['java']); + if (where.exitCode == 0) { - path = (where.stdout as String).toString().split('\n').first.trim(); + executablePath = (where.stdout as String) + .toString() + .split('\n') + .first + .trim(); } } else { final which = await Process.run('which', ['java']); + if (which.exitCode == 0) { - path = (which.stdout as String).toString().split('\n').first.trim(); + executablePath = (which.stdout as String) + .toString() + .split('\n') + .first + .trim(); } } } catch (e) { LogUtil.log('获取系统默认 Java 路径时出错:$e', level: 'WARN'); } + return JavaInfo( - version: parsed['version'] ?? 'unknown', - vendor: parsed['vendor'], - path: path, + version: parsedVersion['version'] ?? 'unknown', + vendor: parsedVersion['vendor'], + path: executablePath, os: Platform.operatingSystem, arch: Platform.version, ); @@ -94,120 +272,52 @@ class JavaPageState extends State { } } - // 解析 "java -version" 输出 - static Map? _parseVersionOutput(String out) { - final lines = out.split('\n'); - for (final l in lines) { - final s = l.trim(); - if (s.isEmpty) continue; - final matches = RegExp(r'(?:(OpenJDK|java|IBM|AdoptOpenJDK|Microsoft).*?)?version\s+"([^"]+)"', caseSensitive: false).firstMatch(s); - if (matches != null) { - String? vendor; - if (matches.group(1) == 'java') { - vendor = 'Oracle'; - } else { - vendor = matches.group(1); - } - final version = matches.group(2); - return {'version': version ?? '', 'vendor': vendor}; - } - final alt = RegExp(r'"([0-9._-]+)"').firstMatch(s); - if (alt != null) return {'version': alt.group(1) ?? '', 'vendor': null}; - } - return null; - } + Widget _buildJavaCard({ + required JavaInfo javaInfo, + required String typeChipLabel, + String? vendor, + required bool isCurrent, + required VoidCallback onTap, + }) { + return Card( + // 裁剪掉ListTile超出圆角的部分 + clipBehavior: Clip.antiAlias, - // 刷新 Java 列表与系统默认 Java - Future _refresh() async { - setState(() { - _systemDefaultJavaInfo = _getSystemDefaultJavaInfo(); - _future = JavaManager.searchPotentialJavaExecutables(); - }); - } + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant), + borderRadius: BorderRadius.circular(12), + ), + + // 为当前Java时高亮 + color: isCurrent + ? Theme.of(context).colorScheme.secondaryContainer + : null, - // 构建 Java 条目 - Widget _buildJavaItem(JavaRuntime javaRuntime) { - return Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - color: javaRuntime.executable == _currentJavaPath ? Theme.of(context).colorScheme.primaryContainer : null, child: ListTile( - title: Text(javaRuntime.info.version), - subtitle: Text(javaRuntime.executable), - isThreeLine: true, + title: Text(javaInfo.version), + + subtitle: Text(javaInfo.path.isNotEmpty ? javaInfo.path : '路径未知'), + trailing: Row( mainAxisSize: MainAxisSize.min, + children: [ - Chip(label: Text(javaRuntime.isJdk ? 'JDK' : 'JRE')), - SizedBox(width: 8), - Chip(label: Text(javaRuntime.info.vendor ?? 'Unknown')), + if (isCurrent) ...[ + Chip(label: Text('当前')), + + SizedBox(width: kDefaultPadding / 2), + ], + Chip(label: Text(typeChipLabel)), + + SizedBox(width: kDefaultPadding / 2), + + Chip(label: Text(vendor ?? 'Unknown')), ], ), - onTap: () => _setCurrentJavaPath(javaRuntime.executable) - ) - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('设备上的 Java 列表'), - actions: [ - IconButton( - icon: const Icon(Icons.refresh), - tooltip: '刷新', - onPressed: _refresh, - ), - ], - ), - body: FutureBuilder>( - future: Future.wait([_systemDefaultJavaInfo, _future]), - builder: (context, snap) { - if (snap.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - if (snap.hasError) { - return Center(child: Text('检测失败:${snap.error}')); - } - final data = snap.data ?? []; - final JavaInfo? sys = data.isNotEmpty ? data[0] as JavaInfo? : null; - final List list = data.length > 1 ? (data[1] as List).cast() : []; - final int sysCount = sys != null ? 1 : 0; - final total = sysCount + list.length; - if (total == 0) { - return const Center(child: Text('未检测到 Java')); - } - return ListView.separated( - itemCount: total, - separatorBuilder: (_, _) => const Divider(height: 1), - itemBuilder: (context, index) { - if (sysCount == 1 && index == 0) { - final info = sys!; - return Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - color: _currentJavaPath == 'java' || _currentJavaPath == null ? Theme.of(context).colorScheme.primaryContainer : null, - child: ListTile( - title: Text(info.version), - subtitle: Text(info.path.isNotEmpty ? info.path : '路径未知'), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Chip(label: Text('系统默认')), - const SizedBox(width: 8), - Chip(label: Text(info.vendor ?? 'Unknown')), - ], - ), - onTap: () => _setSystemJava(), - ), - ); - } - final idx = index - sysCount; - final jt = list[idx]; - return _buildJavaItem(jt); - }, - ); - }, + onTap: onTap, ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/setting/log_viewer.dart b/lib/pages/setting/log_viewer.dart index 790e5c1..d64c71c 100644 --- a/lib/pages/setting/log_viewer.dart +++ b/lib/pages/setting/log_viewer.dart @@ -2,9 +2,11 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:fml/constants.dart'; import 'package:fml/function/log.dart'; import 'package:fml/function/slide_page_route.dart'; import 'package:fml/pages/setting/log_viewer/log_setting.dart'; +import 'package:intl/intl.dart'; class LogViewerPage extends StatefulWidget { const LogViewerPage({super.key}); @@ -14,32 +16,168 @@ class LogViewerPage extends StatefulWidget { } class LogViewerPageState extends State { - List> logs = []; - bool isLoading = true; + late Future>> _logsFuture; + String _dirPath = ''; + static final _kDateFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); + @override void initState() { super.initState(); - _loadLogs(); + _logsFuture = LogUtil.getLogs(); } - // 加载日志 - Future _loadLogs() async { - setState(() { - isLoading = true; - }); - final loadedLogs = await LogUtil.getLogs(); - setState(() { - logs = loadedLogs.reversed.toList(); - isLoading = false; - }); + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + child: Row( + children: [ + Expanded( + child: FutureBuilder( + future: _logsFuture, + + builder: (context, snapshot) { + // 加载时显示CircularProgressIndicator + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Center(child: Text('加载失败:${snapshot.error}')); + } + + final logs = (snapshot.data ?? []).reversed.toList(); + + // 使用变量来定义返回的内容 + late Widget content; + + if (logs.isEmpty) { + // 日志为空,将content定义为提示界面 + content = Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + + children: [ + Icon(Icons.inbox, size: 64, color: Colors.grey[400]), + + const SizedBox(height: kDefaultPadding), + + Text( + '暂无日志', + style: TextStyle( + fontSize: 16, + color: Colors.grey[600], + ), + ), + ], + ), + ); + } else { + // 否则将content定义日志ListView + content = ListView.builder( + itemCount: logs.length, + + itemBuilder: (context, index) { + final log = logs[index]; + final timestamp = log['timestamp'] as String; + final level = log['level'] as String; + final caller = log['caller'] as String; + final message = log['message'] as String; + + final dateTime = DateTime.parse(timestamp); + final formattedTime = _kDateFormat.format(dateTime); + + return Card( + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), + ), + + child: ListTile( + leading: Icon( + _getLevelIcon(level), + color: _getLevelColor(level), + ), + + title: Text( + message, + style: const TextStyle(fontSize: 14), + ), + + subtitle: Text( + '$caller\n$formattedTime', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + + trailing: Container( + padding: const EdgeInsets.all(kDefaultPadding / 4), + + decoration: BoxDecoration( + color: _getLevelColor( + level, + ).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + + child: Text( + level, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: _getLevelColor(level), + ), + ), + ), + + onLongPress: () => _copySingleLog(log), + ), + ); + }, + ); + } + + // 将标题按钮与要返回的内容统一一起返回 + return Column( + children: [ + _buildTitleAndButtons(logs), + + // 标题下的间距 + Padding( + padding: EdgeInsets.symmetric( + vertical: kDefaultPadding / 2, + ), + ), + + // 返回content + Expanded(child: content), + ], + ); + }, + ), + ), + ], + ), + ); } - // 清除所有日志 + /// + /// 清除所有日志 + /// Future _clearLogs() async { final confirmed = await showDialog( context: context, + builder: (context) => AlertDialog( title: const Text('确认清除'), content: const Text('确定要清除所有日志吗?'), @@ -48,6 +186,7 @@ class LogViewerPageState extends State { onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), + TextButton( onPressed: () => Navigator.pop(context, true), child: const Text('确定'), @@ -55,95 +194,177 @@ class LogViewerPageState extends State { ], ), ); + if (confirmed == true) { await LogUtil.clearLogs(); - _loadLogs(); - if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('日志已清除'))); - } + + if (!mounted) return; + + setState(() { + _logsFuture = LogUtil.getLogs(); + }); + + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('日志已清除'))); } } - // 文件夹选择器 + /// + /// 文件夹选择器 + /// Future _selectDirectory() async { final path = await FilePicker.platform.getDirectoryPath( dialogTitle: '选择版本路径', ); + if (!mounted) return; + if (path == null) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('未选择任何路径'))); return; } + setState(() { _dirPath = path; }); } - // 导出全部日志 + /// + /// 导出全部日志 + /// Future _exportAllLogs() async { await _selectDirectory(); + if (_dirPath.isEmpty) { return; } + try { final directory = Directory(_dirPath); if (!await directory.exists()) { await directory.create(recursive: true); } + final logs = await LogUtil.getLogs(); final timestamp = DateTime.now() .toString() .replaceAll(':', '-') .replaceAll(' ', '_') .split('.')[0]; + final logFileName = 'fml_$timestamp.log'; final logFile = File( '${directory.path}${Platform.pathSeparator}$logFileName', ); + final StringBuffer logContent = StringBuffer(); logContent.writeln('===== FML 日志 ====='); logContent.writeln('导出时间: ${DateTime.now()}'); logContent.writeln('====================\n'); + for (var log in logs) { final timestamp = log['timestamp'] as String; final level = log['level'] as String; final caller = log['caller'] as String; final message = log['message'] as String; final dateTime = DateTime.parse(timestamp); - final formattedTime = - '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; + final formattedTime = _kDateFormat.format(dateTime); logContent.writeln('[$formattedTime] [$level] [$caller] $message'); } + await logFile.writeAsString(logContent.toString()); + if (!mounted) return; + ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('日志已保存至: ${logFile.path}'))); + LogUtil.log('日志已导出到: ${logFile.path}', level: 'INFO'); } catch (e) { if (!mounted) return; + ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('日志保存失败: $e'))); + LogUtil.log('日志导出失败: $e', level: 'ERROR'); } } - // 复制单条日志到剪贴板 + /// + /// 构建带有标题和操作按钮的Row + /// + Widget _buildTitleAndButtons(List> logs) { + return Row( + children: [ + // 大标题 + Padding( + padding: const EdgeInsets.only( + left: kDefaultPadding / 2, + top: kDefaultPadding, + bottom: kDefaultPadding, + ), + child: Text('日志', style: Theme.of(context).textTheme.headlineMedium), + ), + + // 将按钮推到右边 + const Spacer(), + + Row( + // 使按钮组紧贴 + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () => Navigator.push( + context, + SlidePageRoute(page: const LogSettingPage()), + ), + ), + + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + if (!mounted) return; + setState(() { + _logsFuture = LogUtil.getLogs(); + }); + }, // _loadLogs, + tooltip: '刷新', + ), + + IconButton( + icon: const Icon(Icons.file_download), + onPressed: logs.isEmpty ? null : _exportAllLogs, + tooltip: '导出全部日志', + ), + + IconButton( + icon: const Icon(Icons.delete_sweep), + onPressed: logs.isEmpty ? null : _clearLogs, + tooltip: '清除日志', + ), + ], + ), + ], + ); + } + + /// + /// 复制单条日志到剪贴板 + /// Future _copySingleLog(Map log) async { final timestamp = log['timestamp'] as String; final level = log['level'] as String; final caller = log['caller'] as String; final message = log['message'] as String; final dateTime = DateTime.parse(timestamp); - final formattedTime = - '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; + final formattedTime = _kDateFormat.format(dateTime); + final logText = '[$formattedTime] [$level] [$caller] $message'; await Clipboard.setData(ClipboardData(text: logText)); if (mounted) { @@ -156,7 +377,9 @@ class LogViewerPageState extends State { } } - // 获取日志级别对应的颜色 + /// + /// 获取日志级别对应的颜色 + /// Color _getLevelColor(String level) { switch (level.toUpperCase()) { case 'ERROR': @@ -170,7 +393,9 @@ class LogViewerPageState extends State { } } - // 获取日志级别对应的图标 + /// + /// 获取日志级别对应的图标 + /// IconData _getLevelIcon(String level) { switch (level.toUpperCase()) { case 'ERROR': @@ -183,105 +408,4 @@ class LogViewerPageState extends State { return Icons.article; } } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('日志查看器'), - centerTitle: true, - actions: [ - IconButton( - icon: const Icon(Icons.settings), - onPressed: () => - Navigator.push(context, SlidePageRoute(page: const LogSettingPage())), - ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: _loadLogs, - tooltip: '刷新', - ), - IconButton( - icon: const Icon(Icons.file_download), - onPressed: logs.isEmpty ? null : _exportAllLogs, - tooltip: '导出全部日志', - ), - IconButton( - icon: const Icon(Icons.delete_sweep), - onPressed: logs.isEmpty ? null : _clearLogs, - tooltip: '清除日志', - ), - ], - ), - body: isLoading - ? const Center(child: CircularProgressIndicator()) - : logs.isEmpty - ? Center( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.inbox, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - '暂无日志', - style: TextStyle(fontSize: 16, color: Colors.grey[600]), - ), - ], - ), - ), - ) - : ListView.builder( - itemCount: logs.length, - itemBuilder: (context, index) { - final log = logs[index]; - final timestamp = log['timestamp'] as String; - final level = log['level'] as String; - final caller = log['caller'] as String; - final message = log['message'] as String; - final dateTime = DateTime.parse(timestamp); - final formattedTime = - '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; - return Card( - margin: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: ListTile( - leading: Icon( - _getLevelIcon(level), - color: _getLevelColor(level), - ), - title: Text(message, style: const TextStyle(fontSize: 14)), - subtitle: Text( - '$caller\n$formattedTime', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), - ), - trailing: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: _getLevelColor(level).withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - level, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: _getLevelColor(level), - ), - ), - ), - onLongPress: () => _copySingleLog(log), - ), - ); - }, - ), - ); - } } diff --git a/lib/pages/setting/log_viewer/log_setting.dart b/lib/pages/setting/log_viewer/log_setting.dart index d4fe2a4..e8205e8 100644 --- a/lib/pages/setting/log_viewer/log_setting.dart +++ b/lib/pages/setting/log_viewer/log_setting.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:fml/constants.dart'; import 'package:shared_preferences/shared_preferences.dart'; class LogSettingPage extends StatefulWidget { @@ -17,6 +18,7 @@ class LogSettingPageState extends State { final SharedPreferences prefs = await SharedPreferences.getInstance(); final int logLevel = prefs.getInt('logLevel') ?? 0; final bool autoClearLog = prefs.getBool('autoClearLog') ?? true; + setState(() { _logLevel = logLevel; _autoClearLog = autoClearLog; @@ -44,56 +46,90 @@ class LogSettingPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('日志设置'), - ), + appBar: AppBar(title: const Text('日志设置')), body: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(kDefaultPadding), child: Column( children: [ Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('日志等级'), - DropdownButton( - hint: const Text('日志等级'), - isExpanded: true, - value: _logLevel, - items: [ - DropdownMenuItem(value: 0, child: const Text('INFO')), - DropdownMenuItem(value: 1, child: const Text('WARNING')), - DropdownMenuItem(value: 2, child: const Text('ERROR')), - ], - onChanged: (int? value) { - setState(() { - _logLevel = value!; - }); - _saveLogLevel(); - } - ), - ] - ) - ) + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), ), - Card( - child: SwitchListTile( - title: const Text('打开 APP 时自动清理日志'), - subtitle: const Text('当遇到 APP 崩溃时,请关闭此选项尝试抓取日志'), - value: _autoClearLog, - onChanged: (bool value) { - setState(() { - _autoClearLog = value; - }); - _saveAutoClearLog(); - }, - ) - ) - ] - ) - ) + + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: kDefaultPadding, + vertical: kDefaultPadding / 2, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('日志等级', style: Theme.of(context).textTheme.bodyLarge), + + const Spacer(), + + DropdownButton( + hint: const Text('日志等级'), + value: _logLevel, + underline: SizedBox.shrink(), + items: [ + DropdownMenuItem(value: 0, child: const Text('INFO')), + + DropdownMenuItem( + value: 1, + child: const Text('WARNING'), + ), + + DropdownMenuItem(value: 2, child: const Text('ERROR')), + ], + + onChanged: (int? value) { + setState(() { + _logLevel = value!; + }); + + _saveLogLevel(); + }, + ), + ], + ), + ), + ), + + Card( + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), + ), + + child: SwitchListTile( + title: const Text('打开 APP 时自动清理日志'), + subtitle: const Text('当遇到 APP 崩溃时,请关闭此选项尝试抓取日志'), + value: _autoClearLog, + onChanged: (bool value) { + setState(() { + _autoClearLog = value; + }); + _saveAutoClearLog(); + }, + ), + ), + ], + ), + ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/setting/theme.dart b/lib/pages/setting/theme.dart index 4af565a..f11b3ec 100644 --- a/lib/pages/setting/theme.dart +++ b/lib/pages/setting/theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart' show BlockPicker; +import 'package:fml/constants.dart'; import 'package:fml/main.dart'; class ThemePage extends StatefulWidget { @@ -10,36 +11,151 @@ class ThemePage extends StatefulWidget { } class ThemePageState extends State { - bool _isDarkMode = false; - bool _followSystem = false; - Color _themeColor = Colors.blue; + // 每个设置间的间距 + static const _itemsPadding = Padding( + padding: EdgeInsets.symmetric(vertical: kDefaultPadding / 2), + ); @override - void initState() { - super.initState(); - _isDarkMode = FMLBaseApp.of(context).themeMode == ThemeMode.dark; - _followSystem = FMLBaseApp.of(context).themeMode == ThemeMode.system; - _themeColor = FMLBaseApp.of(context).themeColor; + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding), + + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + // 大标题 + Padding( + padding: const EdgeInsets.only( + left: kDefaultPadding / 2, + top: kDefaultPadding, + bottom: kDefaultPadding, + ), + child: Text( + '主题', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + + _itemsPadding, + + SizedBox( + width: double.infinity, + child: SegmentedButton( + segments: const [ + ButtonSegment( + value: ThemeMode.system, + icon: Icon(Icons.brightness_auto), + label: Text('跟随系统'), + ), + ButtonSegment( + value: ThemeMode.light, + icon: Icon(Icons.light_mode), + label: Text('浅色'), + ), + ButtonSegment( + value: ThemeMode.dark, + icon: Icon(Icons.dark_mode), + label: Text('深色'), + ), + ], + selected: {FMLBaseApp.of(context).themeMode}, + + onSelectionChanged: (Set newSelection) { + setState(() { + FMLBaseApp.of(context).changeTheme(newSelection.first); + }); + }, + ), + ), + + _itemsPadding, + + Card( + // 裁剪掉ListTile超出圆角的部分 + clipBehavior: Clip.antiAlias, + + elevation: 0, + + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: BorderRadius.circular(12), + ), + + child: ListTile( + leading: const Icon(Icons.color_lens_outlined), + title: const Text('主题色'), + subtitle: const Text('设置配色方案'), + trailing: CircleAvatar( + backgroundColor: FMLBaseApp.of(context).themeColor, + radius: 12, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + ), + ), + ), + + onTap: () => _selectColor(), + ), + ), + ], + ), + ); } Future _selectColor() async { showDialog( context: context, builder: (context) { + final themeColor = FMLBaseApp.of(context).themeColor; + return AlertDialog( title: const Text('选择主题色'), content: BlockPicker( - pickerColor: _themeColor, + // 覆写一个ItemBuilder,去除选色圈的阴影背景,更符合MD3 + itemBuilder: + ( + Color color, + bool isCurrentColor, + void Function() changeColor, + ) { + return GestureDetector( + onTap: changeColor, + child: Container( + margin: const EdgeInsets.all(kDefaultPadding / 4), + + // 添加选中时的框 + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + border: isCurrentColor + ? Border.all(color: Colors.black38, width: 2) + : null, + ), + width: 40, + height: 40, + ), + ); + }, + pickerColor: themeColor, + // 不使用自带的颜色 + availableColors: Colors.primaries, + onColorChanged: (Color color) { setState(() { - _themeColor = color; - FMLBaseApp.of(context).changeThemeColor(_themeColor); + FMLBaseApp.of(context).changeThemeColor(color); }); }, ), + actions: [ TextButton( - child: const Text('确定'), + child: const Text('关闭'), onPressed: () { Navigator.of(context).pop(); }, @@ -49,62 +165,4 @@ class ThemePageState extends State { }, ); } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('主题设置')), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: SwitchListTile( - title: const Text('暗色模式跟随系统'), - secondary: const Icon(Icons.brightness_6), - value: _followSystem, - onChanged: (bool value) { - setState(() { - _followSystem = value; - FMLBaseApp.of(context).changeTheme( - _followSystem - ? ThemeMode.system - : (_isDarkMode ? ThemeMode.dark : ThemeMode.light), - ); - }); - }, - ), - ), - if (!_followSystem) - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: SwitchListTile( - title: const Text('暗色模式'), - secondary: const Icon(Icons.dark_mode), - value: _isDarkMode, - onChanged: (bool value) { - setState(() { - _isDarkMode = value; - FMLBaseApp.of(context).changeTheme( - _isDarkMode ? ThemeMode.dark : ThemeMode.light, - ); - }); - }, - ), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: const Text('主题色'), - leading: const Icon(Icons.color_lens), - trailing: Container(width: 24, height: 24, color: _themeColor), - onTap: _selectColor, - ), - ), - ], - ), - ), - ); - } } diff --git a/lib/pages/setting_page.dart b/lib/pages/setting_page.dart index 876e325..658066e 100644 --- a/lib/pages/setting_page.dart +++ b/lib/pages/setting_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:fml/function/slide_page_route.dart'; +import 'package:fml/constants.dart'; +import 'package:fml/models/page/navigation_drawer_item.dart'; import 'package:fml/pages/setting/theme.dart'; import 'package:fml/pages/setting/log_viewer.dart'; import 'package:fml/pages/setting/about.dart'; @@ -13,67 +14,111 @@ class SettingPage extends StatefulWidget { } class SettingPageState extends State { + int _selectedIndex = 0; + + final List _settingPageItems = const [ + NavigationDrawerItem( + page: ThemePage(), + destination: NavigationDrawerDestination( + icon: Icon(Icons.imagesearch_roller), + label: Text('主题'), + ), + ), + NavigationDrawerItem( + page: JavaPage(), + destination: NavigationDrawerDestination( + icon: Icon(Icons.code), + label: Text('Java管理'), + ), + ), + NavigationDrawerItem( + page: LogViewerPage(), + destination: NavigationDrawerDestination( + icon: Icon(Icons.receipt_long), + label: Text('APP日志'), + ), + ), + NavigationDrawerItem( + page: AboutPage(), + destination: NavigationDrawerDestination( + icon: Icon(Icons.info), + label: Text('关于'), + ), + ), + ]; + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(), - body: Center( - child: ListView( - children: [ - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: Text('\n 主题设置 \n'), - leading: Icon(Icons.imagesearch_roller), - onTap: () { - Navigator.push( - context, - SlidePageRoute(page: const ThemePage()), - ); - }, - ), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: Text('\n Java 管理 \n'), - leading: Icon(Icons.code), - onTap: () { - Navigator.push( - context, - SlidePageRoute(page: const JavaPage()), - ); - }, - ), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: Text('\n APP日志 \n'), - leading: Icon(Icons.receipt_long), - onTap: () { - Navigator.push( - context, - SlidePageRoute(page: const LogViewerPage()), - ); - }, + ThemeData theme = Theme.of(context); + + return Material( + child: LayoutBuilder( + builder: (context, constraints) { + // 动态设置sidebar宽度 + // clamp限制最大最小值 + double sidebarWidth = (constraints.maxWidth * 0.25).clamp( + 150.0, + 320.0, + ); + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + + children: [ + Container( + decoration: BoxDecoration( + border: Border( + left: BorderSide(color: theme.dividerColor.withAlpha(100)), + ), + ), + + // 用SizedBox包裹NavigationDrawer避免宽度过大 + child: SizedBox( + width: sidebarWidth, + child: NavigationDrawer( + selectedIndex: _selectedIndex, + onDestinationSelected: (index) { + if (_selectedIndex == index) return; + // 移除当前上下文中的所有焦点,避免视觉残留 + FocusScope.of(context).unfocus(); + + setState(() { + _selectedIndex = index; + }); + }, + + children: [ + Padding( + // 将文字与Destination对齐 + padding: const EdgeInsets.fromLTRB( + kDefaultPadding * 1.5, + kDefaultPadding, + kDefaultPadding, + kDefaultPadding, + ), + child: Text( + '设置', + style: theme.textTheme.headlineMedium, + ), + ), + + // Destinations + for (var item in _settingPageItems) item.destination, + ], + ), + ), ), - ), - Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: ListTile( - title: Text('\n 关于 \n'), - leading: Icon(Icons.info), - onTap: () { - Navigator.push( - context, - SlidePageRoute(page: const AboutPage()), - ); - }, + + // 显示当前选择的页面 + Expanded( + child: IndexedStack( + index: _selectedIndex, + children: _settingPageItems.map((item) => item.page).toList(), + ), ), - ), - ], - ), + ], + ); + }, ), ); } diff --git a/pubspec.lock b/pubspec.lock index ae76b74..24fb789 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,16 +5,16 @@ packages: dependency: "direct main" description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.flutter-io.cn" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" asn1lib: @@ -22,7 +22,7 @@ packages: description: name: asn1lib sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.5" async: @@ -30,7 +30,7 @@ packages: description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.0" boolean_selector: @@ -38,7 +38,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" buffer: @@ -46,7 +46,7 @@ packages: description: name: buffer sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.3" characters: @@ -54,7 +54,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" checked_yaml: @@ -62,7 +62,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" cli_util: @@ -70,7 +70,7 @@ packages: description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.4.2" clock: @@ -78,7 +78,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" code_assets: @@ -86,7 +86,7 @@ packages: description: name: code_assets sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" collection: @@ -94,7 +94,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" convert: @@ -102,7 +102,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" cross_file: @@ -110,7 +110,7 @@ packages: description: name: cross_file sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.5+2" crypto: @@ -118,7 +118,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" csslib: @@ -126,7 +126,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" cupertino_icons: @@ -134,7 +134,7 @@ packages: description: name: cupertino_icons sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.8" dart_nbt: @@ -142,7 +142,7 @@ packages: description: name: dart_nbt sha256: "3225d709b15635749eed17bcd8ad24754262cf34df2fa0fa07b6278fdad82869" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.2" dbus: @@ -150,7 +150,7 @@ packages: description: name: dbus sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.12" desktop_drop: @@ -158,7 +158,7 @@ packages: description: name: desktop_drop sha256: e70b46b2d61f1af7a81a40d1f79b43c28a879e30a4ef31e87e9c27bea4d784e8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.0" device_info_plus: @@ -166,7 +166,7 @@ packages: description: name: device_info_plus sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "12.3.0" device_info_plus_platform_interface: @@ -174,31 +174,31 @@ packages: description: name: device_info_plus_platform_interface sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.3" dio: dependency: "direct main" description: name: dio - sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 - url: "https://pub.flutter-io.cn" + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" source: hosted - version: "5.9.1" + version: "5.9.2" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.flutter-io.cn" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" encrypt: dependency: "direct main" description: name: encrypt sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.3" fake_async: @@ -206,7 +206,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" ffi: @@ -214,7 +214,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" file: @@ -222,7 +222,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" file_picker: @@ -230,7 +230,7 @@ packages: description: name: file_picker sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.3.10" file_selector: @@ -238,7 +238,7 @@ packages: description: name: file_selector sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" file_selector_android: @@ -246,7 +246,7 @@ packages: description: name: file_selector_android sha256: "51e8fd0446de75e4b62c065b76db2210c704562d072339d333bd89c57a7f8a7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.2+4" file_selector_ios: @@ -254,7 +254,7 @@ packages: description: name: file_selector_ios sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.3+5" file_selector_linux: @@ -262,7 +262,7 @@ packages: description: name: file_selector_linux sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.4" file_selector_macos: @@ -270,7 +270,7 @@ packages: description: name: file_selector_macos sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.5" file_selector_platform_interface: @@ -278,7 +278,7 @@ packages: description: name: file_selector_platform_interface sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" file_selector_web: @@ -286,7 +286,7 @@ packages: description: name: file_selector_web sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.4+2" file_selector_windows: @@ -294,7 +294,7 @@ packages: description: name: file_selector_windows sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.9.3+5" fixnum: @@ -302,7 +302,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" flutter: @@ -315,7 +315,7 @@ packages: description: name: flutter_colorpicker sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" flutter_gbk2utf8: @@ -323,7 +323,7 @@ packages: description: name: flutter_gbk2utf8 sha256: c17323808d6ae7cfaf7676669e0130c33df6be322eb807cdd32face5824c1134 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" flutter_launcher_icons: @@ -331,7 +331,7 @@ packages: description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.14.4" flutter_lints: @@ -339,7 +339,7 @@ packages: description: name: flutter_lints sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.0" flutter_local_notifications: @@ -347,7 +347,7 @@ packages: description: name: flutter_local_notifications sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "20.1.0" flutter_local_notifications_linux: @@ -355,7 +355,7 @@ packages: description: name: flutter_local_notifications_linux sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.0" flutter_local_notifications_platform_interface: @@ -363,7 +363,7 @@ packages: description: name: flutter_local_notifications_platform_interface sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.0.0" flutter_local_notifications_windows: @@ -371,7 +371,7 @@ packages: description: name: flutter_local_notifications_windows sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_markdown_plus: @@ -379,7 +379,7 @@ packages: description: name: flutter_markdown_plus sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7" flutter_plugin_android_lifecycle: @@ -387,7 +387,7 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.33" flutter_test: @@ -405,7 +405,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.3" globbing: @@ -413,23 +413,23 @@ packages: description: name: globbing sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" hooks: dependency: transitive description: name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" - url: "https://pub.flutter-io.cn" + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" html: dependency: "direct main" description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.15.6" http: @@ -437,7 +437,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_parser: @@ -445,23 +445,23 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" image: dependency: transitive description: name: image - sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" - url: "https://pub.flutter-io.cn" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.8.0" intl: dependency: "direct main" description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.20.2" js: @@ -469,23 +469,31 @@ packages: description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.2" json_annotation: dependency: transitive description: name: json_annotation - sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" - url: "https://pub.flutter-io.cn" + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + lazy_load_indexed_stack: + dependency: "direct main" + description: + name: lazy_load_indexed_stack + sha256: "6026d0f1753ae0427587d6095f823143c1d3e4459c8640ebf0a808044ad8de1c" + url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "1.2.1" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -493,7 +501,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -501,7 +509,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -509,7 +517,7 @@ packages: description: name: lints sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.0" logging: @@ -517,7 +525,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.0" markdown: @@ -525,15 +533,15 @@ packages: description: name: markdown sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.flutter-io.cn" + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" source: hosted version: "0.12.19" material_color_utilities: @@ -541,7 +549,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.13.0" meta: @@ -549,7 +557,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.17.0" mime: @@ -557,23 +565,23 @@ packages: description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" native_toolchain_c: dependency: transitive description: name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" - url: "https://pub.flutter-io.cn" + sha256: "92b2ca62c8bd2b8d2f267cdfccf9bfbdb7322f778f8f91b3ce5b5cda23a3899f" + url: "https://pub.dev" source: hosted - version: "0.17.4" + version: "0.17.5" objective_c: dependency: transitive description: name: objective_c sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.3.0" package_info_plus: @@ -581,7 +589,7 @@ packages: description: name: package_info_plus sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.0.0" package_info_plus_platform_interface: @@ -589,7 +597,7 @@ packages: description: name: package_info_plus_platform_interface sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.1" path: @@ -597,7 +605,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_provider: @@ -605,7 +613,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" path_provider_android: @@ -613,7 +621,7 @@ packages: description: name: path_provider_android sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.22" path_provider_foundation: @@ -621,7 +629,7 @@ packages: description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.6.0" path_provider_linux: @@ -629,7 +637,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -637,7 +645,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" path_provider_windows: @@ -645,7 +653,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" petitparser: @@ -653,7 +661,7 @@ packages: description: name: petitparser sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.2" platform: @@ -661,7 +669,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -669,7 +677,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" pointycastle: @@ -677,23 +685,23 @@ packages: description: name: pointycastle sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.9.1" posix: dependency: transitive description: name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.flutter-io.cn" + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.5.0" pub_semver: dependency: transitive description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" screen_retriever: @@ -701,7 +709,7 @@ packages: description: name: screen_retriever sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_linux: @@ -709,7 +717,7 @@ packages: description: name: screen_retriever_linux sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_macos: @@ -717,7 +725,7 @@ packages: description: name: screen_retriever_macos sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_platform_interface: @@ -725,7 +733,7 @@ packages: description: name: screen_retriever_platform_interface sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" screen_retriever_windows: @@ -733,7 +741,7 @@ packages: description: name: screen_retriever_windows sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" share_plus: @@ -741,7 +749,7 @@ packages: description: name: share_plus sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "12.0.1" share_plus_platform_interface: @@ -749,7 +757,7 @@ packages: description: name: share_plus_platform_interface sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.0" shared_preferences: @@ -757,23 +765,23 @@ packages: description: name: shared_preferences sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f - url: "https://pub.flutter-io.cn" + sha256: "8374d6200ab33ac99031a852eba4c8eb2170c4bf20778b3e2c9eccb45384fb41" + url: "https://pub.dev" source: hosted - version: "2.4.20" + version: "2.4.21" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.6" shared_preferences_linux: @@ -781,7 +789,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -789,7 +797,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_web: @@ -797,7 +805,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -805,7 +813,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" sky_engine: @@ -818,7 +826,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.2" stack_trace: @@ -826,7 +834,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -834,7 +842,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -842,7 +850,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" synchronized: @@ -850,7 +858,7 @@ packages: description: name: synchronized sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.4.0" system_info2: @@ -858,7 +866,7 @@ packages: description: name: system_info2 sha256: b937736ecfa63c45b10dde1ceb6bb30e5c0c340e14c441df024150679d65ac43 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.0" term_glyph: @@ -866,15 +874,15 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.flutter-io.cn" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" source: hosted version: "0.7.10" timezone: @@ -882,7 +890,7 @@ packages: description: name: timezone sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.10.1" typed_data: @@ -890,7 +898,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" universal_platform: @@ -898,7 +906,7 @@ packages: description: name: universal_platform sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" url_launcher: @@ -906,7 +914,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.2" url_launcher_android: @@ -914,7 +922,7 @@ packages: description: name: url_launcher_android sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.3.28" url_launcher_ios: @@ -922,7 +930,7 @@ packages: description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.4.1" url_launcher_linux: @@ -930,7 +938,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" url_launcher_macos: @@ -938,7 +946,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -946,7 +954,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" url_launcher_web: @@ -954,7 +962,7 @@ packages: description: name: url_launcher_web sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.2" url_launcher_windows: @@ -962,23 +970,23 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.5" uuid: dependency: "direct main" description: name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 - url: "https://pub.flutter-io.cn" + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" vector_math: dependency: transitive description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" vm_service: @@ -986,7 +994,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" web: @@ -994,7 +1002,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" win32: @@ -1002,7 +1010,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.15.0" win32_registry: @@ -1010,7 +1018,7 @@ packages: description: name: win32_registry sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.0" window_manager: @@ -1018,7 +1026,7 @@ packages: description: name: window_manager sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.5.1" xdg_directories: @@ -1026,7 +1034,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" xml: @@ -1034,7 +1042,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.6.1" yaml: @@ -1042,7 +1050,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" sdks: diff --git a/pubspec.yaml b/pubspec.yaml index 2fb3e6b..753a66e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: dart_nbt: ^0.1.2 window_manager: ^0.5.1 intl: ^0.20.2 + lazy_load_indexed_stack: ^1.2.1 dev_dependencies: flutter_test: