From d7c17226e460672ba19d7b97d676cbd26eabf4e9 Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 16 May 2026 02:51:02 +0800 Subject: [PATCH 01/36] Enhance import preview with merge strategy, icon backup, and rich analysis --- .gitignore | 2 + l10n.yaml | 3 +- lib/Database/category_dao.dart | 11 +- .../token_category_payload.pb.dart | 38 ++ .../token_category_payload.proto | 4 + lib/Models/token_category.dart | 6 +- lib/Screens/Lock/pin_change_screen.dart | 3 + lib/Screens/Lock/pin_verify_screen.dart | 37 +- lib/Screens/Setting/backup_log_screen.dart | 11 +- lib/Screens/Setting/setting_safe_screen.dart | 17 + lib/Screens/Token/import_preview_screen.dart | 492 ++++++++++++++++ lib/Screens/main_screen.dart | 488 +++++++++++++++- lib/TokenUtils/ThirdParty/2fas_importer.dart | 13 +- lib/TokenUtils/ThirdParty/aegis_importer.dart | 2 +- .../ThirdParty/base_token_importer.dart | 32 +- lib/TokenUtils/import_token_util.dart | 301 ++++++++-- lib/TokenUtils/otp_token_parser.dart | 15 +- lib/TokenUtils/token_image_util.dart | 8 +- lib/Utils/hive_util.dart | 1 + lib/Utils/utils.dart | 14 +- lib/l10n/intl_en.arb | 93 +++- lib/l10n/intl_ja.arb | 93 +++- lib/l10n/intl_zh.arb | 93 +++- lib/l10n/intl_zh_TW.arb | 93 +++- lib/main.dart | 49 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- macos/Podfile.lock | 99 ++-- macos/Runner.xcodeproj/project.pbxproj | 11 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + macos/Runner/AppDelegate.swift | 14 + macos/Runner/DebugProfile.entitlements | 8 + macos/Runner/LocalNotifierOverride.swift | 125 +++++ macos/Runner/MainFlutterWindow.swift | 1 + macos/Runner/Release.entitlements | 10 + pubspec.lock | 97 ++-- pubspec.yaml | 2 +- third-party/awesome_cloud/pubspec.lock | 482 ++++++++++------ third-party/awesome_cloud/pubspec.yaml | 2 +- third-party/chewie/l10n.yaml | 3 +- .../lib/src/Resources/theme_color_data.dart | 2 +- .../lib/src/Widgets/Button/window_button.dart | 20 +- .../Module/Unlock/gesture_unlock_view.dart | 5 +- .../lib/src/Widgets/Scaffold/my_appbar.dart | 12 +- .../Scaffold/sliver_appbar_delegate.dart | 2 +- .../Selectable/my_selectable_region.dart | 49 ++ .../lib/src/Widgets/Tile/entry_item.dart | 23 +- third-party/chewie/pubspec.lock | 115 ++-- third-party/chewie/pubspec.yaml | 2 +- .../tool/biometric_storage/pubspec.lock | 250 +++++++++ .../tool/flutter_web_auth_2/pubspec.lock | 382 ++++++++----- .../pubspec.lock | 213 +++++++ .../tool/flutter_windowmanager/pubspec.lock | 189 +++++++ .../tool/image_gallery_saver/pubspec.lock | 189 +++++++ .../tool/move_to_background/pubspec.lock | 189 +++++++ .../screen_capturer/pubspec.lock | 273 +++++++++ .../screen_capturer_linux/pubspec.lock | 236 ++++++++ .../screen_capturer_macos/pubspec.lock | 236 ++++++++ .../pubspec.lock | 525 ++++++++++++++++++ .../screen_capturer_windows/pubspec.lock | 244 ++++++++ .../widget/desktop_multi_window/pubspec.lock | 142 ++--- third-party/widget/lucide_icons/pubspec.lock | 108 ++-- third-party/widget/pinput/pubspec.lock | 132 ++--- .../widget/smart_snackbars/pubspec.lock | 205 +++++++ 63 files changed, 5707 insertions(+), 812 deletions(-) create mode 100644 lib/Screens/Token/import_preview_screen.dart create mode 100644 macos/Runner/LocalNotifierOverride.swift create mode 100644 third-party/tool/biometric_storage/pubspec.lock create mode 100644 third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock create mode 100644 third-party/tool/flutter_windowmanager/pubspec.lock create mode 100644 third-party/tool/image_gallery_saver/pubspec.lock create mode 100644 third-party/tool/move_to_background/pubspec.lock create mode 100644 third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock create mode 100644 third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock create mode 100644 third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock create mode 100644 third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock create mode 100644 third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock create mode 100644 third-party/widget/smart_snackbars/pubspec.lock diff --git a/.gitignore b/.gitignore index 32e3845e..40731031 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/l10n.yaml b/l10n.yaml index bfb3f244..a4c7d648 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -2,5 +2,4 @@ arb-dir: lib/l10n template-arb-file: intl_en.arb output-localization-file: app_localizations.dart output-class: AppLocalizations -output-dir: lib/generated -synthetic-package: false \ No newline at end of file +output-dir: lib/generated \ No newline at end of file diff --git a/lib/Database/category_dao.dart b/lib/Database/category_dao.dart index eae432c8..e10c4bda 100644 --- a/lib/Database/category_dao.dart +++ b/lib/Database/category_dao.dart @@ -47,10 +47,15 @@ class CategoryDao { static Future insertCategories(List categories) async { if (categories.isEmpty) return 0; final db = await DatabaseManager.getDataBase(); + int maxSeq = await getMaxSeq(); + int maxId = await getMaxId(); Batch batch = db.batch(); - for (TokenCategory category in categories) { - category.seq = await getMaxSeq() + 1 + categories.indexOf(category); - category.id = await getMaxId() + 1 + categories.indexOf(category); + for (int i = 0; i < categories.length; i++) { + TokenCategory category = categories[i]; + if (category.seq <= 0) { + category.seq = maxSeq + 1 + i; + } + category.id = maxId + 1 + i; if (category.uid.isEmpty) category.uid = StringUtil.generateUid(); batch.insert( tableName, diff --git a/lib/Models/Proto/TokenCategory/token_category_payload.pb.dart b/lib/Models/Proto/TokenCategory/token_category_payload.pb.dart index d5eec248..0be61498 100644 --- a/lib/Models/Proto/TokenCategory/token_category_payload.pb.dart +++ b/lib/Models/Proto/TokenCategory/token_category_payload.pb.dart @@ -174,6 +174,8 @@ class TokenCategoryParameters extends $pb.GeneratedMessage { $core.String? remark, $core.String? uid, $core.String? bindings, + $core.bool? pinned, + $core.int? seq, }) { final $result = create(); if (secret != null) { @@ -197,6 +199,12 @@ class TokenCategoryParameters extends $pb.GeneratedMessage { if (bindings != null) { $result.bindings = bindings; } + if (pinned != null) { + $result.pinned = pinned; + } + if (seq != null) { + $result.seq = seq; + } return $result; } @@ -221,6 +229,8 @@ class TokenCategoryParameters extends $pb.GeneratedMessage { ..aOS(5, _omitFieldNames ? '' : 'remark') ..aOS(6, _omitFieldNames ? '' : 'uid') ..aOS(7, _omitFieldNames ? '' : 'bindings') + ..aOB(8, _omitFieldNames ? '' : 'pinned') + ..a<$core.int>(9, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.O3) ..hasRequiredFields = false; @$core.Deprecated('Using this can add significant overhead to your binary. ' @@ -349,6 +359,34 @@ class TokenCategoryParameters extends $pb.GeneratedMessage { @$pb.TagNumber(7) void clearBindings() => clearField(7); + + @$pb.TagNumber(8) + $core.bool get pinned => $_getBF(7); + + @$pb.TagNumber(8) + set pinned($core.bool v) { + $_setBool(7, v); + } + + @$pb.TagNumber(8) + $core.bool hasPinned() => $_has(7); + + @$pb.TagNumber(8) + void clearPinned() => clearField(8); + + @$pb.TagNumber(9) + $core.int get seq => $_getIZ(8); + + @$pb.TagNumber(9) + set seq($core.int v) { + $_setSignedInt32(8, v); + } + + @$pb.TagNumber(9) + $core.bool hasSeq() => $_has(8); + + @$pb.TagNumber(9) + void clearSeq() => clearField(9); } const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); diff --git a/lib/Models/Proto/TokenCategory/token_category_payload.proto b/lib/Models/Proto/TokenCategory/token_category_payload.proto index d9b9b615..195fd9e0 100644 --- a/lib/Models/Proto/TokenCategory/token_category_payload.proto +++ b/lib/Models/Proto/TokenCategory/token_category_payload.proto @@ -26,4 +26,8 @@ message TokenCategoryParameters { optional string uid = 6; optional string bindings = 7; + + optional bool pinned = 8; + + optional int32 seq = 9; } diff --git a/lib/Models/token_category.dart b/lib/Models/token_category.dart index 96630a6b..543d59a5 100644 --- a/lib/Models/token_category.dart +++ b/lib/Models/token_category.dart @@ -97,6 +97,8 @@ class TokenCategory { description: description, remark: jsonEncode(remark), bindings: (await BindingDao.getTokenUids(uid)).join(","), + pinned: pinned, + seq: seq, ); } @@ -112,13 +114,13 @@ class TokenCategory { } return TokenCategory( id: 0, - seq: 0, + seq: parameters.hasSeq() ? parameters.seq : 0, uid: parameters.uid, title: parameters.title, description: parameters.description, createTimeStamp: DateTime.now().millisecondsSinceEpoch, editTimeStamp: DateTime.now().millisecondsSinceEpoch, - pinned: false, + pinned: parameters.hasPinned() ? parameters.pinned : false, remark: jsonDecode(parameters.remark), bindings: parameters.bindings.split(","), ); diff --git a/lib/Screens/Lock/pin_change_screen.dart b/lib/Screens/Lock/pin_change_screen.dart index ed64d070..e0ef4c4c 100644 --- a/lib/Screens/Lock/pin_change_screen.dart +++ b/lib/Screens/Lock/pin_change_screen.dart @@ -42,6 +42,8 @@ class PinChangeScreenState extends BaseDynamicState { .isNotEmpty; late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); + final bool _hideGestureTrail = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); late final GestureNotifier _notifier = _isEditMode ? GestureNotifier( status: GestureStatus.verify, @@ -123,6 +125,7 @@ class PinChangeScreenState extends BaseDynamicState { solidRadiusRatio: 0.3, lineWidth: 2, touchRadiusRatio: 0.3, + showLine: !_hideGestureTrail, onCompleted: _gestureComplete, ), ), diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 71eefd03..9bc60c7a 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -55,6 +55,8 @@ class PinVerifyScreenState extends BaseWindowState ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey); late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); + final bool _hideGestureTrail = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); late final GestureNotifier _notifier = GestureNotifier( status: GestureStatus.verify, gestureText: appLocalizations.verifyGestureLock); @@ -136,31 +138,30 @@ class PinVerifyScreenState extends BaseWindowState child: PopScope( canPop: !widget.isModal, child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(height: 50), + const Spacer(), Text( _notifier.gestureText, style: ChewieTheme.titleMedium, ), const SizedBox(height: 30), - Flexible( - child: GestureUnlockView( - key: _gestureUnlockView, - size: min(MediaQuery.sizeOf(context).width, 400), - padding: 60, - roundSpace: 40, - defaultColor: Colors.grey.withOpacity(0.5), - selectedColor: ChewieTheme.primaryColor, - failedColor: Colors.redAccent, - disableColor: Colors.grey, - solidRadiusRatio: 0.3, - lineWidth: 2, - touchRadiusRatio: 0.3, - onCompleted: _gestureComplete, - ), + GestureUnlockView( + key: _gestureUnlockView, + size: min(MediaQuery.sizeOf(context).width, 400), + padding: 60, + roundSpace: 40, + defaultColor: Colors.grey.withOpacity(0.5), + selectedColor: ChewieTheme.primaryColor, + failedColor: Colors.redAccent, + disableColor: Colors.grey, + solidRadiusRatio: 0.3, + lineWidth: 2, + touchRadiusRatio: 0.3, + showLine: !_hideGestureTrail, + onCompleted: _gestureComplete, ), Visibility( visible: _biometricAvailable && _enableBiometric, diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 54916d82..60467b3d 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -18,6 +18,7 @@ import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Models/auto_backup_log.dart'; import 'package:cloudotp/Screens/Setting/setting_backup_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_navigation_screen.dart'; import 'package:cloudotp/Utils/app_provider.dart'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; @@ -165,21 +166,23 @@ class BackupLogScreenState extends BaseDynamicState { ), if (!canBackup) Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ + const SizedBox(height: 20), Text( appLocalizations.haveNotSetBackupPassword, style: ChewieTheme.bodyMedium, ), const SizedBox(height: 10), RoundIconTextButton( + height: 36, text: appLocalizations.goToSetBackupPassword, background: ChewieTheme.primaryColor, onPressed: () { if (widget.isOverlay) { - RouteUtil.pushDialogRoute( - context, - const BackupSettingScreen( - jumpToAutoBackupPassword: true)); + RouteUtil.pushDialogRoute(context, + const SettingNavigationScreen(initPageIndex: 3)); } else { RouteUtil.pushCupertinoRoute( context, diff --git a/lib/Screens/Setting/setting_safe_screen.dart b/lib/Screens/Setting/setting_safe_screen.dart index de4e1465..a50a00f7 100644 --- a/lib/Screens/Setting/setting_safe_screen.dart +++ b/lib/Screens/Setting/setting_safe_screen.dart @@ -55,6 +55,8 @@ class _SafeSettingScreenState extends BaseDynamicState bool _enableSafeMode = ChewieHiveUtil.getBool( CloudOTPHiveUtil.enableSafeModeKey, defaultValue: defaultEnableSafeMode); + bool _hideGestureTrail = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); bool _allowGuestureBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); bool _allowDatabaseBiometric = ChewieHiveUtil.getBool( @@ -148,6 +150,21 @@ class _SafeSettingScreenState extends BaseDynamicState onTap: onBiometricTapped, ), ), + Visibility( + visible: _gesturePasswdAvailableAndSet, + child: CheckboxItem( + value: _hideGestureTrail, + title: appLocalizations.hideGestureTrail, + description: appLocalizations.hideGestureTrailTip, + onTap: () { + setState(() { + _hideGestureTrail = !_hideGestureTrail; + ChewieHiveUtil.put( + CloudOTPHiveUtil.hideGestureTrailKey, _hideGestureTrail); + }); + }, + ), + ), ], ); } diff --git a/lib/Screens/Token/import_preview_screen.dart b/lib/Screens/Token/import_preview_screen.dart new file mode 100644 index 00000000..564c5768 --- /dev/null +++ b/lib/Screens/Token/import_preview_screen.dart @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Models/opt_token.dart'; +import 'package:cloudotp/Models/token_category.dart'; +import 'package:cloudotp/TokenUtils/ThirdParty/base_token_importer.dart'; +import 'package:cloudotp/TokenUtils/import_token_util.dart'; +import 'package:flutter/material.dart'; + +import '../../Widgets/cloudotp/cloudotp_item_builder.dart'; +import '../../l10n/l10n.dart'; + +class ImportPreviewScreen extends StatefulWidget { + final List tokens; + final List categories; + final List errors; + + const ImportPreviewScreen({ + super.key, + required this.tokens, + required this.categories, + this.errors = const [], + }); + + static void show({ + required List tokens, + required List categories, + List errors = const [], + }) { + if (tokens.isEmpty && errors.isEmpty && categories.isEmpty) { + IToast.showTop(appLocalizations.importNoTokens); + return; + } + RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + ImportPreviewScreen( + tokens: tokens, + categories: categories, + errors: errors, + ), + ); + } + + @override + State createState() => _ImportPreviewScreenState(); +} + +class _ImportPreviewScreenState extends BaseDynamicState { + List _tokenItems = []; + List _categoryItems = []; + bool _loading = true; + bool _overwriteExisting = false; + late SelectionItemModel _keepLocalOption; + late SelectionItemModel _overwriteLocalOption; + SelectionItemModel? _currentMergeOption; + + bool get _hasCategories => widget.categories.isNotEmpty; + + @override + void initState() { + super.initState(); + _keepLocalOption = + SelectionItemModel(appLocalizations.importKeepLocal, false); + _overwriteLocalOption = + SelectionItemModel(appLocalizations.importOverwriteLocal, true); + _currentMergeOption = _keepLocalOption; + _loadPreview(); + } + + Future _loadPreview() async { + final tokenItems = await ImportTokenUtil.previewImport( + widget.tokens, + errors: widget.errors, + ); + List categoryItems = []; + if (_hasCategories) { + categoryItems = + await ImportTokenUtil.previewCategories(widget.categories); + } + tokenItems.sort((a, b) => a.status.index.compareTo(b.status.index)); + setState(() { + _tokenItems = tokenItems; + _categoryItems = categoryItems; + _loading = false; + }); + } + + int get _selectedTokenCount => _tokenItems.where((e) => e.selected).length; + + int get _selectedCategoryCount => + _categoryItems.where((e) => e.selected).length; + + int get _totalSelectedCount => _selectedTokenCount + _selectedCategoryCount; + + List get _selectedTokens => + _tokenItems.where((e) => e.selected).map((e) => e.token).toList(); + + List get _selectedCategories => + _categoryItems.where((e) => e.selected).map((e) => e.category).toList(); + + bool get _allTokensSelectableSelected => _tokenItems + .where((e) => e.status != ImportTokenStatus.error) + .every((e) => e.selected); + + bool get _allCategoriesSelected => + _categoryItems.isEmpty || _categoryItems.every((e) => e.selected); + + bool get _allSelected => + _allTokensSelectableSelected && _allCategoriesSelected; + + String get _buttonText { + if (_selectedTokenCount > 0 && _selectedCategoryCount > 0) { + return appLocalizations.importSelectedBothCount( + _selectedTokenCount, _selectedCategoryCount); + } else if (_selectedCategoryCount > 0) { + return appLocalizations + .importSelectedCategoryCount(_selectedCategoryCount); + } else { + return appLocalizations.importSelectedCount(_selectedTokenCount); + } + } + + void _toggleSelectAll() { + setState(() { + bool selectAll = !_allSelected; + for (var item in _tokenItems) { + if (item.status != ImportTokenStatus.error) { + item.selected = selectAll; + } + } + for (var item in _categoryItems) { + item.selected = selectAll; + } + }); + } + + void _setOverwrite(bool overwrite) { + setState(() { + _overwriteExisting = overwrite; + _currentMergeOption = + overwrite ? _overwriteLocalOption : _keepLocalOption; + for (var item in _tokenItems) { + if (item.status == ImportTokenStatus.duplicate) { + item.selected = _overwriteExisting; + } + } + for (var item in _categoryItems) { + if (!item.isNew) { + item.selected = _overwriteExisting; + } + } + }); + } + + Future _confirmImport() async { + if (_totalSelectedCount == 0) return; + CustomLoadingDialog.showLoading(title: appLocalizations.importing); + try { + final selectedCategories = _selectedCategories; + final selectedTokenUids = _selectedTokens.map((t) => t.uid).toSet(); + for (var cat in selectedCategories) { + cat.bindings = cat.bindings + .where((uid) => selectedTokenUids.contains(uid)) + .toList(); + } + ImportAnalysis analysis = await ImportTokenUtil.confirmImport( + _selectedTokens, + selectedCategories, + overwriteExisting: _overwriteExisting, + tokenItems: _tokenItems, + categoryItems: _categoryItems, + ); + CustomLoadingDialog.dismissLoading(); + analysis.showToast(appLocalizations.importNoTokens); + if (mounted) Navigator.of(context).pop(); + } catch (e, t) { + CustomLoadingDialog.dismissLoading(); + ILogger.error("Failed to confirm import", e, t); + IToast.showTop(appLocalizations.importFailed); + } + } + + String _buildCategoryBindingText(ImportCategoryItem item) { + final bindings = item.category.bindings; + if (bindings.isEmpty) return ""; + List names = []; + for (String uid in bindings) { + final match = _tokenItems.where((t) => t.token.uid == uid); + if (match.isNotEmpty) { + final token = match.first.token; + names.add(token.issuer.isNotEmpty ? token.issuer : token.account); + } + } + if (names.isEmpty) return ""; + final count = names.length; + if (count <= 3) { + return appLocalizations.importCategoryContains(names.join(", "), count); + } else { + final displayNames = names.take(3).join(", "); + return appLocalizations.importCategoryContainsMore(displayNames, count); + } + } + + @override + Widget build(BuildContext context) { + return MyScaffold( + appBar: ResponsiveAppBar( + title: appLocalizations.importPreview, + showBack: ResponsiveUtil.isLandscapeLayout() ? false : true, + titleLeftMargin: ResponsiveUtil.isLandscapeLayout() ? 15 : 5, + actions: [ + if (!_loading) + TextButton( + onPressed: _toggleSelectAll, + child: Text( + _allSelected + ? appLocalizations.importDeselectAll + : appLocalizations.importSelectAll, + style: TextStyle(color: ChewieTheme.primaryColor), + ), + ), + const SizedBox(width: 5), + ], + ), + body: _loading + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 10), + children: [ + const SizedBox(height: 10), + InlineSelectionItem>( + title: appLocalizations.importMergeStrategy, + selections: [ + _keepLocalOption, + _overwriteLocalOption, + ], + hint: appLocalizations.importMergeStrategy, + selected: _currentMergeOption, + onChanged: (item) { + if (item != null) { + _setOverwrite(item.value); + } + }, + ), + CaptionItem( + title: + "${appLocalizations.tokenCount} (${_tokenItems.length})", + children: _tokenItems + .map((item) => _buildTokenItem(item)) + .toList(), + ), + if (_hasCategories) + CaptionItem( + title: + "${appLocalizations.categoryCount} (${_categoryItems.length})", + children: _categoryItems + .map((item) => _buildCategoryItem(item)) + .toList(), + ), + ], + ), + ), + _buildBottomBar(), + ], + ), + ); + } + + Widget _buildTokenItem(ImportTokenItem item) { + final isError = item.status == ImportTokenStatus.error; + return Container( + margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 8), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(8), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: isError + ? null + : () { + setState(() { + item.selected = !item.selected; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + Checkbox( + value: item.selected, + onChanged: isError + ? null + : (value) { + setState(() { + item.selected = value ?? false; + }); + }, + activeColor: ChewieTheme.primaryColor, + ), + const SizedBox(width: 4), + CloudOTPItemBuilder.buildTokenImage(item.token, size: 24), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.token.issuer.isNotEmpty + ? item.token.issuer + : item.token.account, + style: ChewieTheme.titleSmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (item.token.account.isNotEmpty && + item.token.account != item.token.issuer) + Text( + item.token.account, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (isError && item.errorReason != null) + Text( + item.errorReason!, + style: + ChewieTheme.bodySmall.copyWith(color: Colors.red), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 8), + _buildTokenStatusText(item), + ], + ), + ), + ), + ), + ); + } + + Widget _buildTokenStatusText(ImportTokenItem item) { + String label; + Color? color; + switch (item.status) { + case ImportTokenStatus.ready: + label = appLocalizations.importReady; + case ImportTokenStatus.duplicate: + label = _overwriteExisting + ? appLocalizations.importOverwrite + : appLocalizations.importDuplicate; + case ImportTokenStatus.error: + label = appLocalizations.importError; + color = Colors.red; + } + return Text( + label, + style: ChewieTheme.bodySmall.copyWith( + color: color ?? ChewieTheme.bodySmall.color, + ), + ); + } + + Widget _buildCategoryItem(ImportCategoryItem item) { + final bindingText = _buildCategoryBindingText(item); + return Container( + margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 8), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(8), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + setState(() { + item.selected = !item.selected; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + Checkbox( + value: item.selected, + onChanged: (value) { + setState(() { + item.selected = value ?? false; + }); + }, + activeColor: ChewieTheme.primaryColor, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.category.title, + style: ChewieTheme.titleSmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (bindingText.isNotEmpty) + Text( + bindingText, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 8), + Text( + item.isNew + ? appLocalizations.importCategoryNew + : (_overwriteExisting + ? appLocalizations.importOverwrite + : appLocalizations.importCategoryExisting), + style: ChewieTheme.bodySmall, + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBottomBar() { + return Container( + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 12, + bottom: 12 + MediaQuery.of(context).padding.bottom, + ), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + border: Border( + top: BorderSide( + color: Theme.of(context).dividerColor.withValues(alpha: 0.3), + ), + ), + ), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _totalSelectedCount > 0 ? _confirmImport : null, + style: ElevatedButton.styleFrom( + backgroundColor: ChewieTheme.primaryColor, + disabledBackgroundColor: Colors.grey.withValues(alpha: 0.3), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + _buttonText, + style: const TextStyle(fontSize: 16), + ), + ), + ), + ); + } +} diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 71c62c78..bae38377 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -34,8 +34,15 @@ import 'package:screen_capturer/screen_capturer.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; +import '../Database/category_dao.dart'; +import '../Database/token_category_binding_dao.dart'; +import '../Database/token_dao.dart'; +import '../Models/opt_token.dart'; +import '../Models/token_category.dart'; +import '../TokenUtils/code_generator.dart'; import '../TokenUtils/import_token_util.dart'; import '../Utils/app_provider.dart'; +import '../Utils/constant.dart'; import '../Utils/hive_util.dart'; import '../Utils/lottie_util.dart'; import '../Utils/utils.dart'; @@ -70,6 +77,8 @@ class MainScreenState extends BaseWindowState AutomaticKeepAliveClientMixin { Timer? _timer; TextEditingController searchController = TextEditingController(); + List _menuTokens = []; + List _menuCategories = []; @override void onWindowMinimize() { @@ -129,6 +138,10 @@ class MainScreenState extends BaseWindowState appProvider.canShowCloudBackupButton = value; }); fetchReleases(); + if (ResponsiveUtil.isMacOS()) { + _checkNotificationPermission(); + _loadMenuTokenData(); + } WidgetsBinding.instance.addPostFrameCallback((_) async { if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoFocusSearchBarKey, defaultValue: false)) { @@ -156,6 +169,64 @@ class MainScreenState extends BaseWindowState }); } + static const _notifierChannel = MethodChannel('local_notifier'); + + Future _checkNotificationPermission() async { + await Future.delayed(const Duration(seconds: 2)); + if (!mounted) return; + try { + final status = + await _notifierChannel.invokeMethod('checkPermission'); + if (status == 'denied' || status == 'notDetermined') { + if (!mounted) return; + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.setting, + message: status == 'denied' + ? appLocalizations.notificationPermissionDenied + : appLocalizations.notificationPermissionRequest, + confirmButtonText: appLocalizations.goToSettings, + cancelButtonText: appLocalizations.cancel, + onTapConfirm: () async { + await _notifierChannel.invokeMethod('openNotificationSettings'); + }, + ); + } + } catch (e) { + ILogger.error("Failed to check notification permission", e); + } + } + + Future _loadMenuTokenData() async { + if (!DatabaseManager.initialized) return; + _menuTokens = await TokenDao.listTokens(); + _menuTokens.sort((a, b) => a.issuer.compareTo(b.issuer)); + List cats = await CategoryDao.listCategories(); + for (var cat in cats) { + cat.tokens = await BindingDao.getTokens(cat.uid); + cat.tokens.sort((a, b) => a.issuer.compareTo(b.issuer)); + } + _menuCategories = cats.where((e) => e.tokens.isNotEmpty).toList(); + if (mounted) setState(() {}); + } + + Future _copyTokenCode(OtpToken token) async { + double currentProgress = token.period == 0 + ? 0 + : (token.period * 1000 - + (DateTime.now().millisecondsSinceEpoch % + (token.period * 1000))) / + (token.period * 1000); + if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoCopyNextCodeKey) && + currentProgress < autoCopyNextCodeProgressThrehold) { + ChewieUtils.copy(context, CodeGenerator.getNextCode(token), + toastText: appLocalizations.alreadyCopiedNextCode); + } else { + ChewieUtils.copy(context, CodeGenerator.getCurrentCode(token)); + } + TokenDao.incTokenCopyTimes(token); + } + initGlobalConfig() { if (ResponsiveUtil.isDesktop()) { windowManager @@ -209,11 +280,410 @@ class MainScreenState extends BaseWindowState } _buildBodyByPlatform() { - return ResponsiveUtil.selectByResponsive( + Widget body = ResponsiveUtil.selectByResponsive( desktop: _buildDesktopBody(), landscape: SafeArea(child: _buildDesktopBody()), portrait: HomeScreen(key: chewieProvider.panelScreenKey), ); + if (ResponsiveUtil.isMacOS()) { + return PlatformMenuBar( + menus: _buildMacMenuBar(), + child: body, + ); + } + return body; + } + + String _checked(String label, bool isChecked) => + isChecked ? '✓ $label' : ' $label'; + + List _buildMacMenuBar() { + return [ + // App menu + PlatformMenu( + label: ResponsiveUtil.appName, + menus: [ + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.about, + onSelected: () => ShortcutsUtil.jumpToAbout(context), + ), + PlatformMenuItem( + label: appLocalizations.checkUpdates, + onSelected: () => ChewieUtils.getReleases( + context: context, + showLoading: true, + showUpdateDialog: true, + showFailedToast: true, + showLatestToast: true, + ), + ), + ]), + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.setting, + shortcut: const SingleActivator( + LogicalKeyboardKey.comma, + meta: true, + ), + onSelected: () => ShortcutsUtil.jumpToSetting(context), + ), + ]), + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: '${appLocalizations.quit} ${ResponsiveUtil.appName}', + shortcut: const SingleActivator( + LogicalKeyboardKey.keyQ, + meta: true, + ), + onSelected: () => windowManager.close(), + ), + ]), + ], + ), + // File menu + PlatformMenu( + label: appLocalizations.menuFile, + menus: [ + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.addToken, + shortcut: const SingleActivator( + LogicalKeyboardKey.keyA, + control: true, + alt: true, + ), + onSelected: () => RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + const AddTokenScreen(), + ), + ), + PlatformMenuItem( + label: appLocalizations.category, + shortcut: const SingleActivator( + LogicalKeyboardKey.keyC, + control: true, + alt: true, + ), + onSelected: () => RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + const CategoryScreen(), + ), + ), + PlatformMenu( + label: appLocalizations.scanToken, + menus: [ + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.scanFromImageFile, + onSelected: () async { + FilePickerResult? result = await FileUtil.pickFiles( + type: FileType.image, + lockParentWindow: true, + ); + if (result == null) return; + if (!mounted) return; + await ImportTokenUtil.analyzeImageFile( + result.files.single.path!, + context: context, + ); + }, + ), + PlatformMenuItem( + label: appLocalizations.scanFromClipboard, + onSelected: () { + ScreenCapturerPlatform.instance + .readImageFromClipboard() + .then((value) { + if (value != null) { + ImportTokenUtil.analyzeImage(value, context: context); + } else { + IToast.showTop(appLocalizations.clipboardNoImage); + } + }); + }, + ), + ]), + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.scanFromRegionCapture, + onSelected: () => capture(CaptureMode.region), + ), + PlatformMenuItem( + label: appLocalizations.scanFromWindowCapture, + onSelected: () => capture(CaptureMode.window), + ), + PlatformMenuItem( + label: appLocalizations.scanFromScreenCapture, + onSelected: () => capture(CaptureMode.screen), + ), + ]), + ], + ), + ]), + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.exportImport, + shortcut: const SingleActivator( + LogicalKeyboardKey.keyI, + control: true, + alt: true, + ), + onSelected: () => RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + const ImportExportTokenScreen(), + ), + ), + PlatformMenuItem( + label: appLocalizations.importFromThirdParty, + onSelected: () => RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + const ImportFromThirdPartyBottomSheet(), + ), + ), + ]), + PlatformMenuItemGroup(members: [ + PlatformMenuItem( + label: appLocalizations.cloudBackupServiceSetting, + onSelected: () => RouteUtil.pushDialogRoute( + chewieProvider.rootContext, + const CloudServiceScreen(showBack: false), + ), + ), + ]), + ], + ), + // View menu + PlatformMenu( + label: appLocalizations.menuView, + menus: [ + PlatformMenu( + label: appLocalizations.changeLayoutType, + menus: [ + PlatformMenuItem( + label: _checked(appLocalizations.simpleLayoutType, + homeScreenState?.layoutType == LayoutType.Simple), + onSelected: () => + homeScreenState?.changeLayoutType(LayoutType.Simple), + ), + PlatformMenuItem( + label: _checked(appLocalizations.compactLayoutType, + homeScreenState?.layoutType == LayoutType.Compact), + onSelected: () => + homeScreenState?.changeLayoutType(LayoutType.Compact), + ), + PlatformMenuItem( + label: _checked(appLocalizations.listLayoutType, + homeScreenState?.layoutType == LayoutType.List), + onSelected: () => + homeScreenState?.changeLayoutType(LayoutType.List), + ), + PlatformMenuItem( + label: _checked(appLocalizations.spotlightLayoutType, + homeScreenState?.layoutType == LayoutType.Spotlight), + onSelected: () => + homeScreenState?.changeLayoutType(LayoutType.Spotlight), + ), + ], + ), + PlatformMenu( + label: appLocalizations.menuSortOrder, + menus: [ + PlatformMenuItem( + label: _checked(appLocalizations.defaultOrder, + homeScreenState?.orderType == OrderType.Default), + onSelected: () => + homeScreenState?.changeOrderType(type: OrderType.Default), + ), + PlatformMenuItem( + label: _checked(appLocalizations.alphabeticalASCOrder, + homeScreenState?.orderType == OrderType.AlphabeticalASC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.AlphabeticalASC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.alphabeticalDESCOrder, + homeScreenState?.orderType == OrderType.AlphabeticalDESC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.AlphabeticalDESC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.copyTimesDESCOrder, + homeScreenState?.orderType == OrderType.CopyTimesDESC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.CopyTimesDESC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.copyTimesASCOrder, + homeScreenState?.orderType == OrderType.CopyTimesASC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.CopyTimesASC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.lastCopyTimeDESCOrder, + homeScreenState?.orderType == OrderType.LastCopyTimeDESC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.LastCopyTimeDESC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.lastCopyTimeASCOrder, + homeScreenState?.orderType == OrderType.LastCopyTimeASC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.LastCopyTimeASC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.createTimeDESCOrder, + homeScreenState?.orderType == OrderType.CreateTimeDESC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.CreateTimeDESC), + ), + PlatformMenuItem( + label: _checked(appLocalizations.createTimeASCOrder, + homeScreenState?.orderType == OrderType.CreateTimeASC), + onSelected: () => homeScreenState?.changeOrderType( + type: OrderType.CreateTimeASC), + ), + ], + ), + PlatformMenuItemGroup(members: [ + PlatformMenu( + label: appLocalizations.themeMode, + menus: [ + PlatformMenuItem( + label: _checked(appLocalizations.followSystem, + appProvider.themeMode == ActiveThemeMode.system), + onSelected: () { + appProvider.themeMode = ActiveThemeMode.system; + setState(() {}); + }, + ), + PlatformMenuItem( + label: _checked(appLocalizations.lightTheme, + appProvider.themeMode == ActiveThemeMode.light), + onSelected: () { + appProvider.themeMode = ActiveThemeMode.light; + setState(() {}); + }, + ), + PlatformMenuItem( + label: _checked(appLocalizations.darkTheme, + appProvider.themeMode == ActiveThemeMode.dark), + onSelected: () { + appProvider.themeMode = ActiveThemeMode.dark; + setState(() {}); + }, + ), + ], + ), + PlatformMenu( + label: appLocalizations.language, + menus: LocaleUtil.localeLabels + .map((tuple) => PlatformMenuItem( + label: _checked( + tuple.item1, + appProvider.locale?.toString() == + tuple.item2?.toString()), + onSelected: () { + appProvider.locale = tuple.item2; + setState(() {}); + }, + )) + .toList(), + ), + PlatformMenu( + label: appLocalizations.fontFamily, + menus: CustomFont.getAllFonts() + .map((font) => PlatformMenuItem( + label: _checked( + font.intlFontName, appProvider.currentFont == font), + onSelected: () { + CustomFont.loadFont(context, font, + autoRestartApp: true); + }, + )) + .toList(), + ), + ]), + ], + ), + // Tokens menu + PlatformMenu( + label: appLocalizations.menuTokens, + menus: [ + if (_menuTokens.isNotEmpty) + PlatformMenu( + label: appLocalizations.allTokens, + menus: _menuTokens + .map((token) => PlatformMenuItem( + label: token.issuer, + onSelected: () => _copyTokenCode(token), + )) + .toList(), + ), + ..._menuCategories.map( + (category) => PlatformMenu( + label: category.title, + menus: category.tokens + .map((token) => PlatformMenuItem( + label: token.issuer, + onSelected: () => _copyTokenCode(token), + )) + .toList(), + ), + ), + ], + ), + // Window menu + // PlatformMenu( + // label: appLocalizations.menuWindow, + // menus: [ + // PlatformMenuItemGroup(members: [ + // PlatformMenuItem( + // label: appLocalizations.minimize, + // shortcut: const SingleActivator( + // LogicalKeyboardKey.keyM, + // meta: true, + // ), + // onSelected: () => windowManager.minimize(), + // ), + // PlatformMenuItem( + // label: appLocalizations.zoom, + // onSelected: () => ResponsiveUtil.maximizeOrRestore(), + // ), + // ]), + // PlatformMenuItemGroup(members: [ + // PlatformMenuItem( + // label: appLocalizations.lock, + // shortcut: const SingleActivator( + // LogicalKeyboardKey.keyL, + // control: true, + // alt: true, + // ), + // onSelected: () => ShortcutsUtil.lock(context), + // ), + // ]), + // ], + // ), + // Help menu + PlatformMenu( + label: appLocalizations.menuHelp, + menus: [ + PlatformMenuItem( + label: appLocalizations.shortcutHelp, + shortcut: const SingleActivator(LogicalKeyboardKey.f1), + onSelected: () => ShortcutsUtil.showShortcutHelp(context), + ), + PlatformMenuItem( + label: 'GitHub', + onSelected: () => UriUtil.launchUrlUri(context, repoUrl), + ), + PlatformMenuItem( + label: appLocalizations.officialWebsite, + onSelected: () => UriUtil.launchUrlUri(context, officialWebsite), + ), + ], + ), + ]; } _buildDesktopBody() { @@ -249,6 +719,9 @@ class MainScreenState extends BaseWindowState } refresh() { + if (ResponsiveUtil.isMacOS()) { + _loadMenuTokenData(); + } setState(() {}); } @@ -504,11 +977,14 @@ class MainScreenState extends BaseWindowState mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ResponsiveUtil.selectByResponsive( - desktop: const SizedBox(height: 8), - landscape: const SizedBox(height: 12), - portrait: const SizedBox(height: 8), - ), + SizedBox( + height: ResponsiveUtil.isMacOS() + ? 38 + : ResponsiveUtil.isDesktop() + ? 8 + : ResponsiveUtil.isLandscapeLayout() + ? 12 + : 8), _buildLogo(), const SizedBox(height: 8), ToolButton( diff --git a/lib/TokenUtils/ThirdParty/2fas_importer.dart b/lib/TokenUtils/ThirdParty/2fas_importer.dart index bb3a5e21..0014fd84 100644 --- a/lib/TokenUtils/ThirdParty/2fas_importer.dart +++ b/lib/TokenUtils/ThirdParty/2fas_importer.dart @@ -92,13 +92,9 @@ class TwoFASToken { OtpToken toOtpToken() { OtpToken token = OtpToken.init(); - token.uid = serviceTypeID; token.issuer = otp.issuer; token.account = otp.account; token.secret = secret; - // token.counterString = otp.digits > 0 - // ? otp.digits.toString() - // : token.tokenType.defaultDigits.toString(); token.digits = otp.digits > 0 ? OtpDigits.fromString(otp.digits.toString()) : token.tokenType.defaultDigits; @@ -110,10 +106,10 @@ class TwoFASToken { return token; } - List getBindings() { + List getBindings(String tokenUid) { return [ TokenCategoryBinding( - tokenUid: serviceTypeID, + tokenUid: tokenUid, categoryUid: groupId, ), ]; @@ -219,7 +215,10 @@ class TwoFASTokenImporter implements BaseTokenImporter { } categories = twoFASGroups.map((e) => e.toTokenCategory()).toList(); tokens = twoFASTokens.map((e) => e.toOtpToken()).toList(); - bindings = twoFASTokens.expand((e) => e.getBindings()).toList(); + bindings = []; + for (int i = 0; i < twoFASTokens.length; i++) { + bindings.addAll(twoFASTokens[i].getBindings(tokens[i].uid)); + } await BaseTokenImporter.importResult( ImporterResult(tokens, categories, bindings)); } diff --git a/lib/TokenUtils/ThirdParty/aegis_importer.dart b/lib/TokenUtils/ThirdParty/aegis_importer.dart index c3848743..760cbbd9 100644 --- a/lib/TokenUtils/ThirdParty/aegis_importer.dart +++ b/lib/TokenUtils/ThirdParty/aegis_importer.dart @@ -394,7 +394,7 @@ class AegisTokenImporter implements BaseTokenImporter { try { return decryptSlot(slot, passwordBytes); } catch (e) { - return null; + continue; } } return null; diff --git a/lib/TokenUtils/ThirdParty/base_token_importer.dart b/lib/TokenUtils/ThirdParty/base_token_importer.dart index 7fda039d..e30a9ba7 100644 --- a/lib/TokenUtils/ThirdParty/base_token_importer.dart +++ b/lib/TokenUtils/ThirdParty/base_token_importer.dart @@ -17,8 +17,7 @@ import 'package:cloudotp/Models/token_category_binding.dart'; import '../../Models/opt_token.dart'; import '../../Models/token_category.dart'; -import '../../l10n/l10n.dart'; -import '../import_token_util.dart'; +import '../../Screens/Token/import_preview_screen.dart'; enum DecryptResult { success, @@ -33,9 +32,6 @@ abstract class BaseTokenImporter { }); static importResult(ImporterResult res) async { - ImportAnalysis analysis = ImportAnalysis(); - analysis.parseSuccess = res.tokens.length; - analysis.parseCategorySuccess = res.categories.length; for (TokenCategoryBinding binding in res.bindings) { res.categories .where((element) => element.uid == binding.categoryUid) @@ -43,13 +39,11 @@ abstract class BaseTokenImporter { element.bindings.add(binding.tokenUid); }); } - ImportAnalysis tmpAnalysis = await ImportTokenUtil.mergeTokensAndCategories( - res.tokens, - res.categories, + ImportPreviewScreen.show( + tokens: res.tokens, + categories: res.categories, + errors: res.errors, ); - analysis.importSuccess = tmpAnalysis.importSuccess; - analysis.importCategorySuccess = tmpAnalysis.importCategorySuccess; - analysis.showToast(appLocalizations.fileDoesNotContainToken); } } @@ -57,6 +51,20 @@ class ImporterResult { final List tokens; final List categories; final List bindings; + final List errors; - ImporterResult(this.tokens, this.categories, this.bindings); + ImporterResult(this.tokens, this.categories, this.bindings, + [this.errors = const []]); +} + +class ImportTokenError { + final String issuer; + final String account; + final String reason; + + ImportTokenError({ + required this.issuer, + required this.account, + required this.reason, + }); } diff --git a/lib/TokenUtils/import_token_util.dart b/lib/TokenUtils/import_token_util.dart index 2dc53420..312f6743 100644 --- a/lib/TokenUtils/import_token_util.dart +++ b/lib/TokenUtils/import_token_util.dart @@ -30,7 +30,9 @@ import 'package:zxing2/qrcode.dart'; import '../Database/category_dao.dart'; import '../Database/config_dao.dart'; +import '../Database/token_category_binding_dao.dart'; import '../Models/token_category.dart'; +import '../Screens/Token/import_preview_screen.dart'; import '../Utils/constant.dart'; import '../Utils/hive_util.dart'; import '../Widgets/BottomSheet/token_option_bottom_sheet.dart'; @@ -38,6 +40,7 @@ import '../l10n/l10n.dart'; import 'Backup/backup.dart'; import 'Backup/backup_encrypt_interface.dart'; import 'Backup/backup_encrypt_v1.dart'; +import 'ThirdParty/base_token_importer.dart'; extension TrimPadding on String { String trimPadding() { @@ -46,47 +49,47 @@ extension TrimPadding on String { } class ImportAnalysis { - int parseFailed; - - int parseSuccess; - - int importSuccess; - + int parseTokenSuccess; + int parseTokenFailed; + int importTokenSuccess; int parseCategorySuccess; - int importCategorySuccess; - int importConfigSuccess; - - int importCloudServiceConfigSuccess; - ImportAnalysis({ - this.parseFailed = 0, - this.parseSuccess = 0, - this.importSuccess = 0, + this.parseTokenSuccess = 0, + this.parseTokenFailed = 0, + this.importTokenSuccess = 0, this.parseCategorySuccess = 0, this.importCategorySuccess = 0, - this.importConfigSuccess = 0, - this.importCloudServiceConfigSuccess = 0, }); showToast([String noTokenToast = ""]) { - String tokenToast = - appLocalizations.importResultTip(importSuccess, parseSuccess); - String categoryToast = appLocalizations.importCategoryResultTip( - importCategorySuccess, parseCategorySuccess); - if (parseSuccess > 0) { - if (parseCategorySuccess > 0) { - IToast.showTop("$tokenToast; $categoryToast"); + ILogger.info(toString()); + List parts = []; + if (parseTokenSuccess > 0 || parseTokenFailed > 0) { + if (parseTokenFailed > 0) { + parts.add(appLocalizations.importTokenResultWithError( + parseTokenSuccess, parseTokenFailed, importTokenSuccess)); } else { - IToast.showTop(tokenToast); + parts.add(appLocalizations.importTokenResult( + parseTokenSuccess, importTokenSuccess)); } - } else if (parseCategorySuccess > 0) { - IToast.showTop(categoryToast); + } + if (parseCategorySuccess > 0) { + parts.add(appLocalizations.importCategoryResult( + parseCategorySuccess, importCategorySuccess)); + } + if (parts.isNotEmpty) { + IToast.showTop(parts.join("; ")); } else { IToast.showTop(noTokenToast); } } + + @override + String toString() { + return "ImportAnalysis(parseTokenSuccess: $parseTokenSuccess, parseTokenFailed: $parseTokenFailed, importTokenSuccess: $importTokenSuccess, parseCategorySuccess: $parseCategorySuccess, importCategorySuccess: $importCategorySuccess)"; + } } class ImportTokenUtil { @@ -298,13 +301,13 @@ class ImportTokenUtil { IToast.showTop(appLocalizations.importFailed); return true; } - ImportAnalysis analysis = ImportAnalysis(); - analysis.parseSuccess = tokens.length; - analysis.importSuccess = await mergeTokens(tokens); if (showLoading) { CustomLoadingDialog.dismissLoading(); } - analysis.showToast(appLocalizations.fileDoesNotContainToken); + ImportPreviewScreen.show( + tokens: tokens, + categories: [], + ); return true; } } catch (e, t) { @@ -458,16 +461,10 @@ class ImportTokenUtil { Backup backup = await compute((_) async { return await BackupEncryptionV1().decrypt(content, tmpPassword); }, null); - ImportAnalysis analysis = ImportAnalysis(); - analysis.parseSuccess = backup.tokens.length; - analysis.parseCategorySuccess = backup.categories.length; - ImportAnalysis tmpAnalysis = await mergeTokensAndCategories( - backup.tokens, - backup.categories, + ImportPreviewScreen.show( + tokens: backup.tokens, + categories: backup.categories, ); - analysis.importSuccess = tmpAnalysis.importSuccess; - analysis.importCategorySuccess = tmpAnalysis.importCategorySuccess; - analysis.showToast(appLocalizations.fileDoesNotContainToken); return true; } @@ -485,7 +482,6 @@ class ImportTokenUtil { if (showLoading) { CustomLoadingDialog.showLoading(title: appLocalizations.importing); } - ImportAnalysis analysis = ImportAnalysis(); List lines = content.split("\n"); List tokens = []; for (String line in lines) { @@ -493,16 +489,19 @@ class ImportTokenUtil { List parsedTokens = OtpTokenParser.parseUri(line); if (parsedTokens.isNotEmpty) { tokens.addAll(parsedTokens); - analysis.parseSuccess += parsedTokens.length; - } else { - analysis.parseFailed++; } } - analysis.importSuccess = await mergeTokens(tokens); if (showLoading) { CustomLoadingDialog.dismissLoading(); } - if (showToast) analysis.showToast(noTokenToast); + if (tokens.isEmpty) { + if (showToast && noTokenToast.isNotEmpty) IToast.showTop(noTokenToast); + return []; + } + ImportPreviewScreen.show( + tokens: tokens, + categories: [], + ); return tokens; } @@ -620,7 +619,7 @@ class ImportTokenUtil { return null; } - static bool checkCategoryExist( + static TokenCategory? findExistingCategory( TokenCategory category, List categoryList, ) { @@ -630,10 +629,10 @@ class ImportTokenUtil { category.uid = StringUtil.generateUid(); } if (tokenCategory.title == category.title) { - return true; + return tokenCategory; } } - return false; + return null; } static Future mergeTokensAndCategories( @@ -642,7 +641,9 @@ class ImportTokenUtil { bool performInsert = true, }) async { ImportAnalysis analysis = ImportAnalysis(); - analysis.importSuccess = await mergeTokens(tokenList); + analysis.parseTokenSuccess = tokenList.length; + analysis.parseCategorySuccess = categoryList.length; + analysis.importTokenSuccess = await mergeTokens(tokenList); Map uidMap = await getAlreadyExistUid(tokenList); for (TokenCategory category in categoryList) { category.bindings = category.bindings.map((e) => uidMap[e] ?? e).toList(); @@ -661,8 +662,10 @@ class ImportTokenUtil { if (toMergeToken.issuer.isEmpty) { toMergeToken.issuer = toMergeToken.account; } - toMergeToken.imagePath = - TokenImageUtil.matchBrandLogo(toMergeToken) ?? ""; + if (toMergeToken.imagePath.isEmpty) { + toMergeToken.imagePath = + TokenImageUtil.matchBrandLogo(toMergeToken) ?? ""; + } OtpToken? alreadyToken = checkTokenExist(toMergeToken, already); if (alreadyToken == null && checkTokenExist(toMergeToken, finalMergeTokenList) == null) { @@ -695,16 +698,208 @@ class ImportTokenUtil { } List already = await CategoryDao.listCategories(); List newCategoryList = []; + List updatedCategoryList = []; for (TokenCategory category in categoryList) { - if (!checkCategoryExist(category, already) && - !checkCategoryExist(category, newCategoryList)) { + TokenCategory? existingInDb = findExistingCategory(category, already); + TokenCategory? existingInNew = + findExistingCategory(category, newCategoryList); + if (existingInDb != null) { + bool needUpdate = false; + if (category.pinned && !existingInDb.pinned) { + existingInDb.pinned = true; + needUpdate = true; + } + if (category.bindings.isNotEmpty) { + for (String binding in category.bindings) { + if (!existingInDb.bindings.contains(binding)) { + existingInDb.bindings.add(binding); + needUpdate = true; + } + } + } + if (needUpdate) { + updatedCategoryList.add(existingInDb); + } + } else if (existingInNew == null) { newCategoryList.add(category); } } if (performInsert) { await CategoryDao.insertCategories(newCategoryList); + if (updatedCategoryList.isNotEmpty) { + await CategoryDao.updateCategories(updatedCategoryList); + for (TokenCategory cat in updatedCategoryList) { + if (cat.bindings.isNotEmpty) { + await BindingDao.bingdingsForCategory(cat.uid, cat.bindings); + } + } + } homeScreenState?.refresh(); } return newCategoryList.length; } + + static Future> previewImport( + List tokens, { + List errors = const [], + }) async { + List already = await TokenDao.listTokens(); + List items = []; + for (OtpToken token in tokens) { + if (token.issuer.isEmpty) { + token.issuer = token.account; + } + if (token.imagePath.isEmpty) { + token.imagePath = TokenImageUtil.matchBrandLogo(token) ?? ""; + } + OtpToken? existing = checkTokenExist(token, already); + if (existing != null) { + items.add(ImportTokenItem( + token: token, + existingToken: existing, + status: ImportTokenStatus.duplicate, + selected: false, + )); + } else { + items.add(ImportTokenItem( + token: token, + status: ImportTokenStatus.ready, + selected: true, + )); + } + } + for (ImportTokenError error in errors) { + OtpToken placeholder = OtpToken.init(); + placeholder.issuer = error.issuer; + placeholder.account = error.account; + items.add(ImportTokenItem( + token: placeholder, + status: ImportTokenStatus.error, + errorReason: error.reason, + selected: false, + )); + } + return items; + } + + static Future> previewCategories( + List categories, + ) async { + List already = await CategoryDao.listCategories(); + List items = []; + for (TokenCategory category in categories) { + TokenCategory? existingCat = + already.where((e) => e.title == category.title).firstOrNull; + items.add(ImportCategoryItem( + category: category, + existingCategory: existingCat, + isNew: existingCat == null, + selected: existingCat == null, + )); + } + return items; + } + + static Future confirmImport( + List selectedTokens, + List categories, { + bool overwriteExisting = false, + List tokenItems = const [], + List categoryItems = const [], + }) async { + ImportAnalysis analysis = ImportAnalysis(); + analysis.parseTokenSuccess = + tokenItems.where((e) => e.status != ImportTokenStatus.error).length; + analysis.parseTokenFailed = + tokenItems.where((e) => e.status == ImportTokenStatus.error).length; + analysis.parseCategorySuccess = categoryItems.length; + if (!overwriteExisting) { + var result = + await mergeTokensAndCategories(selectedTokens, categories); + analysis.importTokenSuccess = result.importTokenSuccess; + analysis.importCategorySuccess = result.importCategorySuccess; + return analysis; + } + Set selectedUids = selectedTokens.map((t) => t.uid).toSet(); + List newTokens = []; + List overwriteTokens = []; + for (var item in tokenItems) { + if (!selectedUids.contains(item.token.uid)) continue; + if (item.status == ImportTokenStatus.duplicate && + item.existingToken != null) { + OtpToken existing = item.existingToken!; + existing.pinned = item.token.pinned; + existing.imagePath = item.token.imagePath; + existing.description = item.token.description; + overwriteTokens.add(existing); + } else if (item.status == ImportTokenStatus.ready) { + newTokens.add(item.token); + } + } + analysis.importTokenSuccess = await mergeTokens(newTokens); + if (overwriteTokens.isNotEmpty) { + await TokenDao.updateTokens(overwriteTokens); + analysis.importTokenSuccess += overwriteTokens.length; + } + Map uidMap = + await getAlreadyExistUid([...newTokens, ...selectedTokens]); + List newCategories = []; + for (var catItem in categoryItems) { + if (!catItem.selected) continue; + var cat = catItem.category; + cat.bindings = cat.bindings.map((e) => uidMap[e] ?? e).toList(); + if (!catItem.isNew && catItem.existingCategory != null) { + TokenCategory existing = catItem.existingCategory!; + existing.pinned = cat.pinned; + existing.description = cat.description; + existing.bindings = cat.bindings; + await CategoryDao.updateCategories([existing]); + await BindingDao.bingdingsForCategory(existing.uid, existing.bindings); + analysis.importCategorySuccess++; + } else if (catItem.isNew) { + newCategories.add(cat); + } + } + if (newCategories.isNotEmpty) { + analysis.importCategorySuccess += await mergeCategories(newCategories); + } + homeScreenState?.refresh(); + return analysis; + } +} + +enum ImportTokenStatus { + ready, + duplicate, + error, +} + +class ImportTokenItem { + final OtpToken token; + final OtpToken? existingToken; + final ImportTokenStatus status; + final String? errorReason; + bool selected; + + ImportTokenItem({ + required this.token, + this.existingToken, + required this.status, + this.errorReason, + required this.selected, + }); +} + +class ImportCategoryItem { + final TokenCategory category; + final TokenCategory? existingCategory; + final bool isNew; + bool selected; + + ImportCategoryItem({ + required this.category, + this.existingCategory, + required this.isNew, + required this.selected, + }); } diff --git a/lib/TokenUtils/otp_token_parser.dart b/lib/TokenUtils/otp_token_parser.dart index 4ce1a495..3b583975 100644 --- a/lib/TokenUtils/otp_token_parser.dart +++ b/lib/TokenUtils/otp_token_parser.dart @@ -51,6 +51,9 @@ class OtpTokenParser { default: break; } + if (token.imagePath.isNotEmpty) { + uriText += "&image=${token.imagePath}"; + } return Uri.parse(Uri.encodeFull(uriText)); } @@ -171,7 +174,13 @@ class OtpTokenParser { ); } } - token.imagePath = TokenImageUtil.matchBrandLogo(token) ?? ""; + if (queryParameters.containsKey("image") && + queryParameters["image"].notNullOrEmpty) { + token.imagePath = queryParameters["image"]!; + } + if (token.imagePath.isEmpty) { + token.imagePath = TokenImageUtil.matchBrandLogo(token) ?? ""; + } return token; } @@ -213,7 +222,9 @@ class OtpTokenParser { token.secret = queryParameters["secret"]!; if (!CheckTokenUtil.isSecretBase32(token.secret)) return null; } - token.imagePath = TokenImageUtil.matchBrandLogo(token) ?? ""; + if (token.imagePath.isEmpty) { + token.imagePath = TokenImageUtil.matchBrandLogo(token) ?? ""; + } return token; } diff --git a/lib/TokenUtils/token_image_util.dart b/lib/TokenUtils/token_image_util.dart index dd3d9fea..6d8e481c 100644 --- a/lib/TokenUtils/token_image_util.dart +++ b/lib/TokenUtils/token_image_util.dart @@ -13,8 +13,6 @@ * If not, see . */ -import 'dart:convert'; - import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Models/opt_token.dart'; import 'package:flutter/services.dart'; @@ -26,9 +24,9 @@ class TokenImageUtil { static final Map> _matchCache = {}; static loadBrandLogos() async { - final manifestContent = await rootBundle.loadString('AssetManifest.json'); - final Map manifestMap = json.decode(manifestContent); - final brandFiles = manifestMap.keys + final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle); + final brandFiles = assetManifest + .listAssets() .where((String key) => key.startsWith('assets/brand/') && key.endsWith('.png')) .toList(); diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 97eaf09b..81a52a8a 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -81,6 +81,7 @@ class CloudOTPHiveUtil { static const String autoLockKey = "autoLock"; static const String autoLockTimeKey = "autoLockTime"; static const String enableSafeModeKey = "enableSafeMode"; + static const String hideGestureTrailKey = "hideGestureTrail"; //System static const String oldVersionKey = "oldVersion"; diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index 6010f396..0fe2fe74 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -155,7 +155,12 @@ class Utils { await trayManager.setIcon('assets/logo-transparent.png'); } - bool lauchAtStartup = await LaunchAtStartup.instance.isEnabled(); + bool lauchAtStartup = false; + try { + lauchAtStartup = await LaunchAtStartup.instance.isEnabled(); + } catch (e, t) { + ILogger.error("Failed to check LaunchAtStartup in tray", e, t); + } if (!ResponsiveUtil.isLinux()) { ILogger.debug( "Setting tray tooltip to app name ${ResponsiveUtil.appName}"); @@ -241,7 +246,12 @@ class Utils { await trayManager.setIcon('assets/logo-transparent.png'); } - bool lauchAtStartup = await LaunchAtStartup.instance.isEnabled(); + bool lauchAtStartup = false; + try { + lauchAtStartup = await LaunchAtStartup.instance.isEnabled(); + } catch (e, t) { + ILogger.error("Failed to check LaunchAtStartup in initSimpleTray", e, t); + } if (!ResponsiveUtil.isLinux()) { await trayManager.setToolTip(ResponsiveUtil.appName); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 22ecfa26..f93ec681 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -589,8 +589,28 @@ "clipboardNoImage": "No image in clipboard", "importSuccess": "Import successful", "copySuccess": "Copied to clipboard", - "importResultTip": "{parseSuccess} tokens parsed successfully, {importSuccess} tokens imported successfully", - "importCategoryResultTip": "{parseSuccess} categories found, {importSuccess} categories imported successfully", + "importTokenResult": "Parsed {parseSuccess} tokens, imported {importSuccess}", + "@importTokenResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importTokenResultWithError": "Parsed {parseSuccess} tokens ({parseFailed} failed), imported {importSuccess}", + "@importTokenResultWithError": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "parseFailed": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importCategoryResult": "Parsed {parseSuccess} categories, imported {importSuccess}", + "@importCategoryResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, "importFailed": "Import failed", "searchIconName": "Search icon name", "setIconForToken": "Choose token icon", @@ -704,8 +724,75 @@ "cloudBackupSettings": "Cloud Backup", "databaseEncryptionSettings": "Database Encryption", "gestureLockSettings": "Gesture Lock", + "hideGestureTrail": "Hide Gesture Trail", + "hideGestureTrailTip": "Hide the drawn pattern when unlocking", "autoLockSettings": "Auto Lock", "projectRepoAbout": "Project Repository", "appAbout": "App", - "contactAbout": "Contact" + "contactAbout": "Contact", + "menuFile": "File", + "menuEdit": "Edit", + "menuView": "View", + "menuWindow": "Window", + "menuHelp": "Help", + "quit": "Quit", + "minimize": "Minimize", + "zoom": "Zoom", + "notificationPermissionDenied": "Notification permission has been denied. Please enable it in System Settings to receive desktop notifications.", + "notificationPermissionRequest": "Desktop notification permission is required. Go to System Settings to enable it?", + "goToSettings": "Go to Settings", + "menuTokens": "Tokens", + "menuSortOrder": "Sort Order", + "importPreview": "Import Preview", + "importReady": "Ready", + "importDuplicate": "Duplicate", + "importError": "Error", + "importSelectedCount": "Import {count} tokens", + "@importSelectedCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "importSelectAll": "Select All", + "importDeselectAll": "Deselect All", + "importInvalidData": "Invalid token data", + "importNoTokens": "No tokens to import", + "importCategoryNew": "New", + "importCategoryExisting": "Existing", + "importCategoryContains": "Contains: {names} ({count} tokens)", + "@importCategoryContains": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importCategoryContainsMore": "Contains: {names}... ({count} tokens)", + "@importCategoryContainsMore": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importSelectedCategoryCount": "Import {count} categories", + "@importSelectedCategoryCount": { + "placeholders": { + "count": {"type": "int"} + } + }, + "importSelectedBothCount": "Import {tokenCount} tokens and {categoryCount} categories", + "@importSelectedBothCount": { + "placeholders": { + "tokenCount": {"type": "int"}, + "categoryCount": {"type": "int"} + } + }, + "importOverwriteExisting": "Overwrite", + "importOverwrite": "Overwrite", + "importMergeStrategy": "Merge Strategy", + "importKeepLocal": "Keep Local", + "importOverwriteLocal": "Overwrite Local", + "tokenCount": "Tokens", + "categoryCount": "Categories" } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 45157f93..6159ba94 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -589,8 +589,28 @@ "clipboardNoImage": "クリップボードに画像がありません", "importSuccess": "インポート成功", "copySuccess": "クリップボードにコピーしました", - "importResultTip": "解析成功トークン数: {parseSuccess}、インポート成功トークン数: {importSuccess}", - "importCategoryResultTip": "カテゴリの合計数: {parseSuccess}、インポート成功カテゴリ数: {importSuccess}", + "importTokenResult": "解析 {parseSuccess} 個のトークン、インポート {importSuccess} 個", + "@importTokenResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importTokenResultWithError": "解析 {parseSuccess} 個のトークン(失敗 {parseFailed} 個)、インポート {importSuccess} 個", + "@importTokenResultWithError": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "parseFailed": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importCategoryResult": "解析 {parseSuccess} 個のカテゴリ、インポート {importSuccess} 個", + "@importCategoryResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, "importFailed": "インポート失敗", "searchIconName": "アイコン名を検索", "setIconForToken": "トークンのアイコンを選択", @@ -704,8 +724,75 @@ "cloudBackupSettings": "クラウドバックアップ", "databaseEncryptionSettings": "データベース暗号化", "gestureLockSettings": "ジェスチャーロック", + "hideGestureTrail": "ジェスチャー軌跡を隠す", + "hideGestureTrailTip": "ロック解除時にパターンを非表示にする", "autoLockSettings": "自動ロック", "projectRepoAbout": "プロジェクトリポジトリ", "appAbout": "APP", - "contactAbout": "お問い合わせ" + "contactAbout": "お問い合わせ", + "menuFile": "ファイル", + "menuEdit": "編集", + "menuView": "表示", + "menuWindow": "ウインドウ", + "menuHelp": "ヘルプ", + "quit": "終了", + "minimize": "最小化", + "zoom": "拡大/縮小", + "notificationPermissionDenied": "通知の権限が拒否されています。デスクトップ通知を受信するには、システム設定で有効にしてください。", + "notificationPermissionRequest": "デスクトップ通知の権限が必要です。システム設定で有効にしますか?", + "goToSettings": "設定を開く", + "menuTokens": "トークン", + "menuSortOrder": "並べ替え", + "importPreview": "インポートプレビュー", + "importReady": "インポート可能", + "importDuplicate": "重複", + "importError": "エラー", + "importSelectedCount": "{count} 個のトークンをインポート", + "@importSelectedCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "importSelectAll": "すべて選択", + "importDeselectAll": "すべて解除", + "importInvalidData": "無効なトークンデータ", + "importNoTokens": "インポートするトークンがありません", + "importCategoryNew": "新規", + "importCategoryExisting": "既存", + "importCategoryContains": "含む: {names} ({count}個のトークン)", + "@importCategoryContains": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importCategoryContainsMore": "含む: {names}… ({count}個のトークン)", + "@importCategoryContainsMore": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importSelectedCategoryCount": "{count} 個のカテゴリをインポート", + "@importSelectedCategoryCount": { + "placeholders": { + "count": {"type": "int"} + } + }, + "importSelectedBothCount": "{tokenCount} 個のトークンと{categoryCount} 個のカテゴリをインポート", + "@importSelectedBothCount": { + "placeholders": { + "tokenCount": {"type": "int"}, + "categoryCount": {"type": "int"} + } + }, + "importOverwriteExisting": "上書き", + "importOverwrite": "上書き", + "importMergeStrategy": "マージ方法", + "importKeepLocal": "ローカルを保持", + "importOverwriteLocal": "ローカルを上書き", + "tokenCount": "トークン", + "categoryCount": "カテゴリ" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 263c26b7..dc508a52 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -589,8 +589,28 @@ "clipboardNoImage": "剪贴板中无图片", "importSuccess": "导入成功", "copySuccess": "已复制到剪贴板", - "importResultTip": "解析成功{parseSuccess}个令牌,导入成功{importSuccess}个令牌", - "importCategoryResultTip": "共包含{parseSuccess}个分类,导入成功{importSuccess}个分类", + "importTokenResult": "解析 {parseSuccess} 个令牌,导入 {importSuccess} 个", + "@importTokenResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importTokenResultWithError": "解析 {parseSuccess} 个令牌(失败 {parseFailed} 个),导入 {importSuccess} 个", + "@importTokenResultWithError": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "parseFailed": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importCategoryResult": "解析 {parseSuccess} 个分类,导入 {importSuccess} 个", + "@importCategoryResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, "importFailed": "导入失败", "searchIconName": "搜索图标名称", "setIconForToken": "选择令牌图标", @@ -704,8 +724,75 @@ "cloudBackupSettings": "云备份", "databaseEncryptionSettings": "数据库加密", "gestureLockSettings": "手势锁", + "hideGestureTrail": "隐藏手势轨迹", + "hideGestureTrailTip": "解锁时隐藏绘制的图案", "autoLockSettings": "自动锁定", "projectRepoAbout": "项目仓库", "appAbout": "APP", - "contactAbout": "联系我们" + "contactAbout": "联系我们", + "menuFile": "文件", + "menuEdit": "编辑", + "menuView": "视图", + "menuWindow": "窗口", + "menuHelp": "帮助", + "quit": "退出", + "minimize": "最小化", + "zoom": "缩放", + "notificationPermissionDenied": "通知权限已被拒绝,无法显示桌面通知。请在系统设置中开启通知权限。", + "notificationPermissionRequest": "需要通知权限以显示桌面通知。是否前往系统设置开启?", + "goToSettings": "前往设置", + "menuTokens": "令牌", + "menuSortOrder": "排序方式", + "importPreview": "导入预览", + "importReady": "可导入", + "importDuplicate": "已存在", + "importError": "错误", + "importSelectedCount": "导入 {count} 个令牌", + "@importSelectedCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "importSelectAll": "全选", + "importDeselectAll": "取消全选", + "importInvalidData": "无效的令牌数据", + "importNoTokens": "没有可导入的令牌", + "importCategoryNew": "新增", + "importCategoryExisting": "已存在", + "importCategoryContains": "包含: {names} ({count}个令牌)", + "@importCategoryContains": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importCategoryContainsMore": "包含: {names}…等({count}个令牌)", + "@importCategoryContainsMore": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importSelectedCategoryCount": "导入 {count} 个分类", + "@importSelectedCategoryCount": { + "placeholders": { + "count": {"type": "int"} + } + }, + "importSelectedBothCount": "导入 {tokenCount} 个令牌和 {categoryCount} 个分类", + "@importSelectedBothCount": { + "placeholders": { + "tokenCount": {"type": "int"}, + "categoryCount": {"type": "int"} + } + }, + "importOverwriteExisting": "覆盖已存在", + "importOverwrite": "覆盖", + "importMergeStrategy": "合并策略", + "importKeepLocal": "保留本地", + "importOverwriteLocal": "覆盖本地", + "tokenCount": "令牌", + "categoryCount": "分类" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index b8b61c07..c0b531af 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -589,8 +589,28 @@ "clipboardNoImage": "剪貼簿中無圖片", "importSuccess": "導入成功", "copySuccess": "已複製到剪貼簿", - "importResultTip": "解析成功{parseSuccess}個令牌,導入成功{importSuccess}個令牌", - "importCategoryResultTip": "共包含{parseSuccess}個分類,導入成功{importSuccess}個分類", + "importTokenResult": "解析 {parseSuccess} 個令牌,導入 {importSuccess} 個", + "@importTokenResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importTokenResultWithError": "解析 {parseSuccess} 個令牌(失敗 {parseFailed} 個),導入 {importSuccess} 個", + "@importTokenResultWithError": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "parseFailed": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, + "importCategoryResult": "解析 {parseSuccess} 個分類,導入 {importSuccess} 個", + "@importCategoryResult": { + "placeholders": { + "parseSuccess": {"type": "int"}, + "importSuccess": {"type": "int"} + } + }, "importFailed": "導入失敗", "searchIconName": "搜尋圖示名稱", "setIconForToken": "選擇令牌圖示", @@ -704,8 +724,75 @@ "cloudBackupSettings": "雲端備份", "databaseEncryptionSettings": "資料庫加密", "gestureLockSettings": "手勢鎖", + "hideGestureTrail": "隱藏手勢軌跡", + "hideGestureTrailTip": "解鎖時隱藏繪製的圖案", "autoLockSettings": "自動鎖定", "projectRepoAbout": "專案倉庫", "appAbout": "APP", - "contactAbout": "聯絡我們" + "contactAbout": "聯絡我們", + "menuFile": "檔案", + "menuEdit": "編輯", + "menuView": "顯示方式", + "menuWindow": "視窗", + "menuHelp": "說明", + "quit": "結束", + "minimize": "最小化", + "zoom": "縮放", + "notificationPermissionDenied": "通知權限已被拒絕,無法顯示桌面通知。請在系統設定中開啟通知權限。", + "notificationPermissionRequest": "需要通知權限以顯示桌面通知。是否前往系統設定開啟?", + "goToSettings": "前往設定", + "menuTokens": "令牌", + "menuSortOrder": "排序方式", + "importPreview": "匯入預覽", + "importReady": "可匯入", + "importDuplicate": "已存在", + "importError": "錯誤", + "importSelectedCount": "匯入 {count} 個令牌", + "@importSelectedCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "importSelectAll": "全選", + "importDeselectAll": "取消全選", + "importInvalidData": "無效的令牌資料", + "importNoTokens": "沒有可匯入的令牌", + "importCategoryNew": "新增", + "importCategoryExisting": "已存在", + "importCategoryContains": "包含: {names} ({count}個令牌)", + "@importCategoryContains": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importCategoryContainsMore": "包含: {names}…等({count}個令牌)", + "@importCategoryContainsMore": { + "placeholders": { + "names": {"type": "String"}, + "count": {"type": "int"} + } + }, + "importSelectedCategoryCount": "匯入 {count} 個分類", + "@importSelectedCategoryCount": { + "placeholders": { + "count": {"type": "int"} + } + }, + "importSelectedBothCount": "匯入 {tokenCount} 個令牌和 {categoryCount} 個分類", + "@importSelectedBothCount": { + "placeholders": { + "tokenCount": {"type": "int"}, + "categoryCount": {"type": "int"} + } + }, + "importOverwriteExisting": "覆蓋已存在", + "importOverwrite": "覆蓋", + "importMergeStrategy": "合併策略", + "importKeepLocal": "保留本地", + "importOverwriteLocal": "覆蓋本地", + "tokenCount": "令牌", + "categoryCount": "分類" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8b123f77..6a80d7d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -143,22 +143,41 @@ Future initAndroid() async { Future initDesktop() async { await initWindow(); - LaunchAtStartup.instance.setup( - appName: ResponsiveUtil.appName, - appPath: Platform.resolvedExecutable, - ); - await LocalNotifier.instance.setup( - appName: ResponsiveUtil.appName, - shortcutPolicy: ShortcutPolicy.requireCreate, - ); - ILogger.debug( - "LaunchAtStartup: ${await LaunchAtStartup.instance.isEnabled()}"); - ChewieHiveUtil.put(ChewieHiveUtil.launchAtStartupKey, - await LaunchAtStartup.instance.isEnabled()); - for (String scheme in kWindowsSchemes) { - await protocolHandler.register(scheme); + try { + LaunchAtStartup.instance.setup( + appName: ResponsiveUtil.appName, + appPath: Platform.resolvedExecutable, + ); + } catch (e, t) { + ILogger.error("Failed to setup LaunchAtStartup", e, t); + } + try { + await LocalNotifier.instance.setup( + appName: ResponsiveUtil.appName, + shortcutPolicy: ShortcutPolicy.requireCreate, + ); + } catch (e, t) { + ILogger.error("Failed to setup LocalNotifier", e, t); + } + try { + bool isEnabled = await LaunchAtStartup.instance.isEnabled(); + ILogger.debug("LaunchAtStartup: $isEnabled"); + ChewieHiveUtil.put(ChewieHiveUtil.launchAtStartupKey, isEnabled); + } catch (e, t) { + ILogger.error("Failed to check LaunchAtStartup status", e, t); + } + try { + for (String scheme in kWindowsSchemes) { + await protocolHandler.register(scheme); + } + } catch (e, t) { + ILogger.error("Failed to register protocol handler", e, t); + } + try { + await HotKeyManager.instance.unregisterAll(); + } catch (e, t) { + ILogger.error("Failed to unregister hotkeys", e, t); } - await HotKeyManager.instance.unregisterAll(); ILogger.debug( "http proxy: ${Platform.environment['http_proxy']}, https proxy: ${Platform.environment['https_proxy']}"); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 21104d93..e8aa6937 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -48,7 +48,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) - FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) + LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 903a2ed7..0de9b4f1 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,15 +1,17 @@ PODS: + - app_links (1.0.0): + - FlutterMacOS - audio_session (0.0.1): - FlutterMacOS - biometric_storage (0.0.1): - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS - - flutter_inappwebview_macos (0.0.1): + - file_picker (0.0.1): - FlutterMacOS - - OrderedSet (~> 5.0) - - flutter_local_notifications (0.0.1): + - flutter_inappwebview_macos (0.0.1): - FlutterMacOS + - OrderedSet (~> 6.0.3) - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - flutter_web_auth_2 (3.0.0): @@ -17,7 +19,7 @@ PODS: - FlutterMacOS (1.0.0) - FMDB/SQLCipher (2.7.11): - SQLCipher (~> 4.0) - - HotKey (0.2.0) + - HotKey (0.2.1) - hotkey_manager_macos (0.0.1): - FlutterMacOS - HotKey @@ -32,7 +34,7 @@ PODS: - FlutterMacOS - mobile_scanner (5.2.3): - FlutterMacOS - - OrderedSet (5.0.0) + - OrderedSet (6.0.3) - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): @@ -42,7 +44,7 @@ PODS: - FlutterMacOS - screen_capturer_macos (0.0.1): - FlutterMacOS - - screen_retriever (0.0.1): + - screen_retriever_macos (0.0.1): - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS @@ -78,11 +80,12 @@ PODS: - FlutterMacOS DEPENDENCIES: + - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - biometric_storage (from `Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - flutter_web_auth_2 (from `Flutter/ephemeral/.symlinks/plugins/flutter_web_auth_2/macos`) - FlutterMacOS (from `Flutter/ephemeral`) @@ -96,11 +99,12 @@ DEPENDENCIES: - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`) - screen_capturer_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_capturer_macos/macos`) - - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - sodium_libs (from `Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) - sqflite_sqlcipher (from `Flutter/ephemeral/.symlinks/plugins/sqflite_sqlcipher/macos`) + - SQLCipher (~> 4.5) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) @@ -117,16 +121,18 @@ SPEC REPOS: - SQLCipher EXTERNAL SOURCES: + app_links: + :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos audio_session: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos biometric_storage: :path: Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos flutter_inappwebview_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos - flutter_local_notifications: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos flutter_secure_storage_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos flutter_web_auth_2: @@ -153,8 +159,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos screen_capturer_macos: :path: Flutter/ephemeral/.symlinks/plugins/screen_capturer_macos/macos - screen_retriever: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos sodium_libs: @@ -179,41 +185,42 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_to_front/macos SPEC CHECKSUMS: - audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72 - biometric_storage: 43caa6e7ef00e8e19c074216e7e1786dacda9e76 - device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 - flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d - flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 - flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 - flutter_web_auth_2: 2e1dc2d2139973e4723c5286ce247dd590390d70 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d + audio_session: 48ab6500f7a5e7c64363e206565a5dfe5a0c1441 + biometric_storage: 9de0cb4e591e52329ca0da7df42e964db6c526cf + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 + file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a + flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d + flutter_secure_storage_macos: b2d62a774c23b060f0b99d0173b0b36abb4a8632 + flutter_web_auth_2: 62b08da29f15a20fa63f144234622a1488d45b65 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 FMDB: 57486c1117fd8e0e6b947b2f54c3f42bf8e57a4e - HotKey: e96d8a2ddbf4591131e2bb3f54e69554d90cdca6 - hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160 - isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a - just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489 - local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff - mobile_scanner: 0a05256215b047af27b9495db3b77640055e8824 - OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa - screen_capturer_macos: e91a66e1eeefd5e4fe068bb1bffc053cc34b63b8 - screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf - sodium_libs: d39bd76697736cb11ce4a0be73b9b4bc64466d6f - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - sqflite_sqlcipher: 87b476a4b1dbaa86041d5be30112d33ba7b48d07 + HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 + hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe + isar_flutter_libs: a65381780401f81ad6bf3f2e7cd0de5698fb98c4 + just_audio: eb8b016ac4493159ab24db4f7215e55303b39a84 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e + mobile_scanner: bd1e7cd9b67b442f4d903747f4778e040513f860 + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + protocol_handler_macos: f9cd7b13bcaf6b0425f7410cbe52376cb843a936 + screen_capturer_macos: 229306903c56767a7c7d3a48167ba303e95c6d29 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + sodium_libs: b9459e5bfc1185349f43472e79fc5d8e526b2bda + sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3 + sqflite_sqlcipher: d60db72e70c8a75053810dbb988f66458043793c SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 - tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 - wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 - webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 - window_to_front: 4cdc24ddd8461ad1a55fa06286d6a79d8b29e8d8 + tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c + window_to_front: 9e76fd432e36700a197dac86a0011e49c89abe0a -PODFILE CHECKSUM: b8db6f1aee8a2a93682b04d5d0af948b996b6a1d +PODFILE CHECKSUM: a93941be6bc34b39b845d80e274606784deb57a4 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 4efe9deb..a2a33da1 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + AA0001012044BFA00003C045 /* LocalNotifierOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */; }; 7A7A95CE29141F3546CCC15E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8FA190C15AB53FB43E71E4E /* Pods_RunnerTests.framework */; }; F84DEA6E45FA7511E0E158EE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C3F37A8706029947F81B05D /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -73,6 +74,7 @@ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotifierOverride.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; @@ -177,6 +179,7 @@ children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, @@ -195,7 +198,6 @@ 31C8C8D8E119FCDDAC6236F6 /* Pods-RunnerTests.release.xcconfig */, FC85FC4B6AD978221380EB5C /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -440,6 +442,7 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + AA0001012044BFA00003C045 /* LocalNotifierOverride.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -572,8 +575,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -704,8 +709,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -724,8 +731,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7a49a087..15435c1d 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 8e02df28..5482ca0d 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,9 +1,23 @@ import Cocoa import FlutterMacOS +import UserNotifications @main class AppDelegate: FlutterAppDelegate { + override func applicationDidFinishLaunching(_ notification: Notification) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + if let error = error { + NSLog("Notification permission error: \(error.localizedDescription)") + } + } + super.applicationDidFinishLaunching(notification) + } + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a30..fd6760c4 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,13 @@ com.apple.security.network.server + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.downloads.read-write + + keychain-access-groups + diff --git a/macos/Runner/LocalNotifierOverride.swift b/macos/Runner/LocalNotifierOverride.swift new file mode 100644 index 00000000..ae56caed --- /dev/null +++ b/macos/Runner/LocalNotifierOverride.swift @@ -0,0 +1,125 @@ +import Cocoa +import FlutterMacOS +import UserNotifications + +public class LocalNotifierOverride: NSObject, FlutterPlugin, UNUserNotificationCenterDelegate { + var channel: FlutterMethodChannel! + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "local_notifier", binaryMessenger: registrar.messenger) + let instance = LocalNotifierOverride() + instance.channel = channel + registrar.addMethodCallDelegate(instance, channel: channel) + UNUserNotificationCenter.current().delegate = instance + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "notify": + notify(call, result: result) + case "close": + close(call, result: result) + case "checkPermission": + checkPermission(result: result) + case "openNotificationSettings": + openNotificationSettings(result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + public func checkPermission(result: @escaping FlutterResult) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + DispatchQueue.main.async { + switch settings.authorizationStatus { + case .authorized: + result("authorized") + case .denied: + result("denied") + case .notDetermined: + result("notDetermined") + case .provisional: + result("provisional") + case .ephemeral: + result("ephemeral") + @unknown default: + result("unknown") + } + } + } + } + + public func openNotificationSettings(result: @escaping FlutterResult) { + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.notifications") { + NSWorkspace.shared.open(url) + } + result(true) + } + + public func notify(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let identifier = args["identifier"] as? String else { + result(FlutterError(code: "INVALID_ARGS", message: "Missing identifier", details: nil)) + return + } + + let title = args["title"] as? String ?? "" + let subtitle = args["subtitle"] as? String ?? "" + let body = args["body"] as? String ?? "" + + let content = UNMutableNotificationContent() + content.title = title + content.subtitle = subtitle + content.body = body + content.sound = .default + + let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + result(FlutterError(code: "NOTIFY_ERROR", message: error.localizedDescription, details: nil)) + } else { + DispatchQueue.main.async { + self.channel.invokeMethod("onLocalNotificationShow", arguments: ["notificationId": identifier]) + } + result(true) + } + } + } + + public func close(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let identifier = args["identifier"] as? String else { + result(FlutterError(code: "INVALID_ARGS", message: "Missing identifier", details: nil)) + return + } + + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) + channel.invokeMethod("onLocalNotificationClose", arguments: ["notificationId": identifier]) + result(true) + } + + // Show notifications even when app is in foreground + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + if #available(macOS 11.0, *) { + completionHandler([.banner, .sound]) + } else { + completionHandler([.alert, .sound]) + } + } + + // Handle notification click + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let identifier = response.notification.request.identifier + channel.invokeMethod("onLocalNotificationClick", arguments: ["notificationId": identifier]) + completionHandler() + } +} diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 3cc05eb2..5be98f29 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -9,6 +9,7 @@ class MainFlutterWindow: NSWindow { self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) + LocalNotifierOverride.register(with: flutterViewController.registrar(forPlugin: "LocalNotifierOverride")) super.awakeFromNib() } diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a4..69b7d913 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,5 +4,15 @@ com.apple.security.app-sandbox + com.apple.security.network.server + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.downloads.read-write + + keychain-access-groups + diff --git a/pubspec.lock b/pubspec.lock index 1dc74e7d..29269453 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,23 +13,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.flutter-io.cn" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.flutter-io.cn" source: hosted - version: "6.11.0" + version: "6.4.1" animations: dependency: "direct main" description: @@ -255,10 +250,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -295,10 +290,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -319,10 +314,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.flutter-io.cn" source: hosted - version: "1.19.0" + version: "1.19.1" console: dependency: transitive description: @@ -383,10 +378,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.7" + version: "2.3.6" dbus: dependency: transitive description: @@ -471,10 +466,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -1015,10 +1010,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.flutter-io.cn" source: hosted - version: "0.19.0" + version: "0.20.2" intl_translation: dependency: "direct dev" description: @@ -1111,26 +1106,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1159,10 +1154,10 @@ packages: dependency: transitive description: name: local_auth_darwin - sha256: "7ba5738c874ca2b910d72385d00d2bebad9d4e807612936cf5e32bc01a048c71" + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.6.1" local_auth_platform_interface: dependency: transitive description: @@ -1218,14 +1213,6 @@ packages: relative: true source: path version: "0.257.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.3-main.0" markdown: dependency: transitive description: @@ -1245,18 +1232,18 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.flutter-io.cn" source: hosted - version: "0.11.1" + version: "0.13.0" menu_base: dependency: transitive description: @@ -1269,10 +1256,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.flutter-io.cn" source: hosted - version: "1.15.0" + version: "1.17.0" metadata_fetch: dependency: transitive description: @@ -1380,10 +1367,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.9.1" path_drawing: dependency: "direct main" description: @@ -1947,18 +1934,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.flutter-io.cn" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -2003,10 +1990,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.3" + version: "0.7.10" timing: dependency: transitive description: @@ -2155,10 +2142,10 @@ packages: dependency: "direct main" description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: transitive description: @@ -2360,5 +2347,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index b762dab9..e0c339c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: awesome_cloud: path: third-party/awesome_cloud # 工具 - intl: ^0.19.0 + intl: ^0.20.2 html: ^0.15.0 uuid: ^4.4.2 # UUID logger: ^2.4.0 # 日志 diff --git a/third-party/awesome_cloud/pubspec.lock b/third-party/awesome_cloud/pubspec.lock index 41675a1b..6ad5e33f 100644 --- a/third-party/awesome_cloud/pubspec.lock +++ b/third-party/awesome_cloud/pubspec.lock @@ -6,65 +6,113 @@ packages: description: name: _discoveryapis_commons sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" + buffer: + dependency: "direct main" + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: "direct main" + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "3.1.2" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" - url: "https://pub.flutter-io.cn" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -75,7 +123,7 @@ packages: description: name: flutter_lints sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" flutter_secure_storage: @@ -83,31 +131,31 @@ packages: description: name: flutter_secure_storage sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.1.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" - url: "https://pub.flutter-io.cn" + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.3" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" - url: "https://pub.flutter-io.cn" + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" flutter_secure_storage_web: @@ -115,7 +163,7 @@ packages: description: name: flutter_secure_storage_web sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" flutter_secure_storage_windows: @@ -123,7 +171,7 @@ packages: description: name: flutter_secure_storage_windows sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" flutter_test: @@ -134,14 +182,14 @@ packages: flutter_web_auth_2: dependency: "direct main" description: - path: "../flutter_web_auth_2" + path: "../tool/flutter_web_auth_2" relative: true source: path version: "4.0.0-alpha.8" flutter_web_auth_2_platform_interface: dependency: transitive description: - path: "../flutter_web_auth_2_platform_interface" + path: "../tool/flutter_web_auth_2_platform_interface" relative: true source: path version: "4.0.0-alpha.1" @@ -150,148 +198,228 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" googleapis: dependency: "direct main" description: name: googleapis sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "13.2.0" hashlib: dependency: "direct main" description: name: hashlib - sha256: d41795742c10947930630118c6836608deeb9047cd05aee32d2baeb697afd66a - url: "https://pub.flutter-io.cn" + sha256: f547a6273fa2bba211e903f32b234a6a7097aca1d218d2391f40021d25e7f200 + url: "https://pub.dev" source: hosted - version: "1.19.2" + version: "1.24.0" hashlib_codecs: dependency: transitive description: name: hashlib_codecs sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.6.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + url: "https://pub.dev" + source: hosted + version: "1.0.3" http: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 - url: "https://pub.flutter-io.cn" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.6.0" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.flutter-io.cn" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" js: dependency: transitive description: name: js sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.6.7" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" - url: "https://pub.flutter-io.cn" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" - url: "https://pub.flutter-io.cn" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: - dependency: transitive + dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.15.0" - path: + version: "1.17.0" + mime: + dependency: "direct main" + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + objective_c: dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 - url: "https://pub.flutter-io.cn" + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" - url: "https://pub.flutter-io.cn" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 - url: "https://pub.flutter-io.cn" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.6.0" path_provider_linux: dependency: transitive 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: @@ -299,7 +427,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: @@ -307,23 +435,31 @@ 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: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" - url: "https://pub.flutter-io.cn" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" protocol_handler: @@ -331,7 +467,7 @@ packages: description: name: protocol_handler sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_android: @@ -339,7 +475,7 @@ packages: description: name: protocol_handler_android sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_ios: @@ -347,7 +483,7 @@ packages: description: name: protocol_handler_ios sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_macos: @@ -355,7 +491,7 @@ packages: description: name: protocol_handler_macos sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_platform_interface: @@ -363,7 +499,7 @@ packages: description: name: protocol_handler_platform_interface sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_windows: @@ -371,9 +507,25 @@ packages: description: name: protocol_handler_windows sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" sky_engine: dependency: transitive description: flutter @@ -383,178 +535,194 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.10" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.flutter-io.cn" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" url_launcher: dependency: transitive description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" - url: "https://pub.flutter-io.cn" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 - url: "https://pub.flutter-io.cn" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.29" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e - url: "https://pub.flutter-io.cn" + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.4.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af - url: "https://pub.flutter-io.cn" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" - url: "https://pub.flutter-io.cn" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive 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: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" - url: "https://pub.flutter-io.cn" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" - url: "https://pub.flutter-io.cn" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "15.2.0" web: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 - url: "https://pub.flutter-io.cn" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.1" win32: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" - url: "https://pub.flutter-io.cn" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.15.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" - url: "https://pub.flutter-io.cn" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" window_to_front: dependency: transitive description: name: window_to_front sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.3" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.flutter-io.cn" + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: "direct main" + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.1.3" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/third-party/awesome_cloud/pubspec.yaml b/third-party/awesome_cloud/pubspec.yaml index b2de37fc..9be3ecc7 100644 --- a/third-party/awesome_cloud/pubspec.yaml +++ b/third-party/awesome_cloud/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: convert: ^3.1.1 xml: ^6.1.0 buffer: ^1.1.1 - intl: ^0.19.0 + intl: ^0.20.2 mime: ^1.0.2 dev_dependencies: diff --git a/third-party/chewie/l10n.yaml b/third-party/chewie/l10n.yaml index 24627ac2..d8fc8a5c 100644 --- a/third-party/chewie/l10n.yaml +++ b/third-party/chewie/l10n.yaml @@ -2,5 +2,4 @@ arb-dir: lib/src/l10n template-arb-file: intl_en.arb output-localization-file: chewie_localizations.dart output-class: ChewieLocalizations -output-dir: lib/src/generated -synthetic-package: false \ No newline at end of file +output-dir: lib/src/generated \ No newline at end of file diff --git a/third-party/chewie/lib/src/Resources/theme_color_data.dart b/third-party/chewie/lib/src/Resources/theme_color_data.dart index e5f40a0b..05ab0f40 100644 --- a/third-party/chewie/lib/src/Resources/theme_color_data.dart +++ b/third-party/chewie/lib/src/Resources/theme_color_data.dart @@ -594,7 +594,7 @@ class ChewieThemeColorData { backgroundColor: appBarBackgroundColor, surfaceTintColor: appBarSurfaceTintColor, ), - tabBarTheme: const TabBarTheme( + tabBarTheme: const TabBarThemeData( splashFactory: NoSplash.splashFactory, ), buttonTheme: ButtonThemeData( diff --git a/third-party/chewie/lib/src/Widgets/Button/window_button.dart b/third-party/chewie/lib/src/Widgets/Button/window_button.dart index 2c342acd..59dbf427 100644 --- a/third-party/chewie/lib/src/Widgets/Button/window_button.dart +++ b/third-party/chewie/lib/src/Widgets/Button/window_button.dart @@ -164,12 +164,12 @@ class WindowButton extends StatelessWidget { return selectedAnimated; } + bool get _isWindowChromeButton => false; + @override Widget build(BuildContext context) { - if (kIsWeb) { - return emptyWidget; - } else { - if (Platform.isMacOS) { + if (_isWindowChromeButton) { + if (kIsWeb || Platform.isMacOS) { return emptyWidget; } } @@ -315,6 +315,9 @@ class StayOnTopWindowButton extends WindowButton { } class MinimizeWindowButton extends WindowButton { + @override + bool get _isWindowChromeButton => true; + MinimizeWindowButton({ super.key, super.colors, @@ -330,6 +333,9 @@ class MinimizeWindowButton extends WindowButton { } class MaximizeWindowButton extends WindowButton { + @override + bool get _isWindowChromeButton => true; + MaximizeWindowButton({ super.key, super.colors, @@ -347,6 +353,9 @@ class MaximizeWindowButton extends WindowButton { } class RestoreWindowButton extends WindowButton { + @override + bool get _isWindowChromeButton => true; + RestoreWindowButton({ super.key, super.colors, @@ -368,6 +377,9 @@ final _defaultCloseButtonColors = WindowButtonColors( iconMouseOver: const Color(0xFFFFFFFF)); class CloseWindowButton extends WindowButton { + @override + bool get _isWindowChromeButton => true; + CloseWindowButton({ super.key, WindowButtonColors? colors, diff --git a/third-party/chewie/lib/src/Widgets/Module/Unlock/gesture_unlock_view.dart b/third-party/chewie/lib/src/Widgets/Module/Unlock/gesture_unlock_view.dart index 50225fa5..151682ab 100644 --- a/third-party/chewie/lib/src/Widgets/Module/Unlock/gesture_unlock_view.dart +++ b/third-party/chewie/lib/src/Widgets/Module/Unlock/gesture_unlock_view.dart @@ -37,6 +37,8 @@ class GestureUnlockView extends StatefulWidget { final int delayTime; + final bool showLine; + final Function(List, UnlockStatus) onCompleted; const GestureUnlockView({ @@ -54,6 +56,7 @@ class GestureUnlockView extends StatefulWidget { this.solidRadiusRatio = 0.4, this.touchRadiusRatio = 0.6, this.delayTime = 500, + this.showLine = true, required this.onCompleted, }); @@ -149,7 +152,7 @@ class GestureState extends State { child: CustomPaint( size: Size(widget.size, widget.size), painter: UnlockLinePainter( - pathPoints: pathPoints, + pathPoints: widget.showLine ? pathPoints : [], status: _status, selectColor: widget.selectedColor, failedColor: widget.failedColor, diff --git a/third-party/chewie/lib/src/Widgets/Scaffold/my_appbar.dart b/third-party/chewie/lib/src/Widgets/Scaffold/my_appbar.dart index b41963ae..e26bedaa 100644 --- a/third-party/chewie/lib/src/Widgets/Scaffold/my_appbar.dart +++ b/third-party/chewie/lib/src/Widgets/Scaffold/my_appbar.dart @@ -813,8 +813,8 @@ class _MyAppBarState extends State { assert(debugCheckHasMaterialLocalizations(context)); final ThemeData theme = Theme.of(context); final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context); - final AppBarTheme appBarTheme = AppBarTheme.of(context); - final AppBarTheme defaults = theme.useMaterial3 + final AppBarThemeData appBarTheme = AppBarTheme.of(context); + final AppBarThemeData defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context); final ScaffoldState? scaffold = Scaffold.maybeOf(context); @@ -1210,7 +1210,7 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox { } // Hand coded defaults based on Material Design 2. -class _AppBarDefaultsM2 extends AppBarTheme { +class _AppBarDefaultsM2 extends AppBarThemeData { _AppBarDefaultsM2(this.context) : super( elevation: 4.0, @@ -1249,7 +1249,7 @@ class _AppBarDefaultsM2 extends AppBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -class _AppBarDefaultsM3 extends AppBarTheme { +class _AppBarDefaultsM3 extends AppBarThemeData { _AppBarDefaultsM3(this.context) : super( elevation: 0.0, @@ -2485,8 +2485,8 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget { @override Widget build(BuildContext context) { - late final AppBarTheme appBarTheme = AppBarTheme.of(context); - late final AppBarTheme defaults = Theme.of(context).useMaterial3 + late final AppBarThemeData appBarTheme = AppBarTheme.of(context); + late final AppBarThemeData defaults = Theme.of(context).useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context); final FlexibleSpaceBarSettings settings = diff --git a/third-party/chewie/lib/src/Widgets/Scaffold/sliver_appbar_delegate.dart b/third-party/chewie/lib/src/Widgets/Scaffold/sliver_appbar_delegate.dart index 02b18f3c..d7c4b09b 100644 --- a/third-party/chewie/lib/src/Widgets/Scaffold/sliver_appbar_delegate.dart +++ b/third-party/chewie/lib/src/Widgets/Scaffold/sliver_appbar_delegate.dart @@ -32,7 +32,7 @@ class SliverAppBarDelegate extends SliverPersistentHeaderDelegate { data: ThemeData( splashColor: Colors.transparent, highlightColor: Colors.transparent, - tabBarTheme: const TabBarTheme( + tabBarTheme: const TabBarThemeData( splashFactory: NoSplash.splashFactory, ), ), diff --git a/third-party/chewie/lib/src/Widgets/Selectable/my_selectable_region.dart b/third-party/chewie/lib/src/Widgets/Selectable/my_selectable_region.dart index af96892a..6bf59e23 100644 --- a/third-party/chewie/lib/src/Widgets/Selectable/my_selectable_region.dart +++ b/third-party/chewie/lib/src/Widgets/Selectable/my_selectable_region.dart @@ -1664,6 +1664,55 @@ class _SelectableRegionContainerDelegate Offset? _lastStartEdgeUpdateGlobalPosition; Offset? _lastEndEdgeUpdateGlobalPosition; + @override + SelectedContentRange? getSelection() { + if (currentSelectionStartIndex == -1 || currentSelectionEndIndex == -1) { + return null; + } + var startOffset = 0; + var endOffset = 0; + var foundStart = false; + bool forwardSelection = currentSelectionEndIndex >= currentSelectionStartIndex; + if (currentSelectionEndIndex == currentSelectionStartIndex) { + final range = selectables[currentSelectionStartIndex].getSelection()!; + forwardSelection = range.endOffset >= range.startOffset; + } + for (var index = 0; index < selectables.length; index++) { + final selectable = selectables[index]; + final range = selectable.getSelection(); + if (range == null) { + if (foundStart) { + return SelectedContentRange( + startOffset: forwardSelection ? startOffset : endOffset, + endOffset: forwardSelection ? endOffset : startOffset, + ); + } + startOffset += selectable.contentLength; + endOffset = startOffset; + continue; + } + final selectionStartNormalized = min(range.startOffset, range.endOffset); + final selectionEndNormalized = max(range.startOffset, range.endOffset); + if (!foundStart) { + startOffset += selectionStartNormalized; + endOffset = startOffset + (selectionEndNormalized - selectionStartNormalized).abs(); + foundStart = true; + } else { + endOffset += (selectionEndNormalized - selectionStartNormalized).abs(); + } + } + return foundStart + ? SelectedContentRange( + startOffset: forwardSelection ? startOffset : endOffset, + endOffset: forwardSelection ? endOffset : startOffset, + ) + : null; + } + + @override + int get contentLength => + selectables.fold(0, (int sum, Selectable selectable) => sum + selectable.contentLength); + @override void remove(Selectable selectable) { _hasReceivedStartEvent.remove(selectable); diff --git a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart index 0aa7f967..6fed2376 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart @@ -111,14 +111,21 @@ class EntryItemState extends SearchableState { // onTap: widget.onTap, child: Column( children: [ - Container( - padding: EdgeInsets.symmetric( - vertical: _paddingVertical, - horizontal: _paddingHorizontal, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildRowChildren(), + GestureDetector( + onTap: widget.onTap, + child: Container( + decoration: BoxDecoration( + color: widget.backgroundColor ?? Colors.transparent, + borderRadius: _borderRadius, + ), + padding: EdgeInsets.symmetric( + vertical: _paddingVertical, + horizontal: _paddingHorizontal, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildRowChildren(), + ), ), ), // _buildBottomDivider(), diff --git a/third-party/chewie/pubspec.lock b/third-party/chewie/pubspec.lock index c092b3b9..d645ef7c 100644 --- a/third-party/chewie/pubspec.lock +++ b/third-party/chewie/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.flutter-io.cn" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.flutter-io.cn" source: hosted - version: "6.11.0" + version: "6.4.1" app_links: dependency: "direct main" description: @@ -114,10 +109,10 @@ packages: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.4.1" build_config: dependency: transitive description: @@ -138,26 +133,26 @@ packages: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.4" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.15" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.flutter-io.cn" source: hosted - version: "8.0.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -202,10 +197,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -226,10 +221,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -250,10 +245,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.flutter-io.cn" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -306,10 +301,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.8" + version: "2.3.6" dbus: dependency: transitive description: @@ -378,10 +373,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -780,10 +775,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.flutter-io.cn" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -828,10 +823,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.flutter-io.cn" source: hosted - version: "6.9.0" + version: "6.8.0" just_audio: dependency: transitive description: @@ -860,26 +855,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -967,14 +962,6 @@ packages: relative: true source: path version: "0.257.0" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.3-main.0" markdown: dependency: "direct main" description: @@ -994,26 +981,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.flutter-io.cn" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.flutter-io.cn" source: hosted - version: "1.15.0" + version: "1.17.0" metadata_fetch: dependency: "direct main" description: @@ -1090,10 +1077,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.flutter-io.cn" source: hosted - version: "1.9.0" + version: "1.9.1" path_drawing: dependency: "direct main" description: @@ -1410,10 +1397,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "2.0.1" shell_executor: dependency: "direct main" description: @@ -1510,18 +1497,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.flutter-io.cn" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1566,10 +1553,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.3" + version: "0.7.10" timing: dependency: transitive description: @@ -1710,10 +1697,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: transitive description: @@ -1899,5 +1886,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/third-party/chewie/pubspec.yaml b/third-party/chewie/pubspec.yaml index f115f929..73deb280 100644 --- a/third-party/chewie/pubspec.yaml +++ b/third-party/chewie/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: hotkey_manager: ^0.2.3 flutter_inappwebview: 6.0.0 # 工具 - intl: ^0.19.0 + intl: ^0.20.2 html: ^0.15.5+1 uuid: ^4.4.2 tuple: ^2.0.0 diff --git a/third-party/tool/biometric_storage/pubspec.lock b/third-party/tool/biometric_storage/pubspec.lock new file mode 100644 index 00000000..7ed3d84f --- /dev/null +++ b/third-party/tool/biometric_storage/pubspec.lock @@ -0,0 +1,250 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: "direct main" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + web: + dependency: "direct main" + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: "direct main" + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/flutter_web_auth_2/pubspec.lock b/third-party/tool/flutter_web_auth_2/pubspec.lock index c9455e58..67ee680d 100644 --- a/third-party/tool/flutter_web_auth_2/pubspec.lock +++ b/third-party/tool/flutter_web_auth_2/pubspec.lock @@ -1,62 +1,94 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "3.0.7" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" - url: "https://pub.flutter-io.cn" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -67,7 +99,7 @@ packages: description: name: flutter_lints sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" flutter_test: @@ -87,100 +119,164 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" - url: "https://pub.flutter-io.cn" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" - url: "https://pub.flutter-io.cn" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 - url: "https://pub.flutter-io.cn" + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" - url: "https://pub.flutter-io.cn" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 - url: "https://pub.flutter-io.cn" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.6.0" path_provider_linux: dependency: transitive 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: @@ -188,7 +284,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: @@ -196,23 +292,23 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" - url: "https://pub.flutter-io.cn" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" protocol_handler: @@ -220,7 +316,7 @@ packages: description: name: protocol_handler sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_android: @@ -228,7 +324,7 @@ packages: description: name: protocol_handler_android sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_ios: @@ -236,7 +332,7 @@ packages: description: name: protocol_handler_ios sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_macos: @@ -244,7 +340,7 @@ packages: description: name: protocol_handler_macos sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_platform_interface: @@ -252,7 +348,7 @@ packages: description: name: protocol_handler_platform_interface sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" protocol_handler_windows: @@ -260,9 +356,25 @@ packages: description: name: protocol_handler_windows sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" sky_engine: dependency: transitive description: flutter @@ -272,170 +384,186 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "1.4.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" - url: "https://pub.flutter-io.cn" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 - url: "https://pub.flutter-io.cn" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.29" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e - url: "https://pub.flutter-io.cn" + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.4.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af - url: "https://pub.flutter-io.cn" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" - url: "https://pub.flutter-io.cn" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive 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: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" - url: "https://pub.flutter-io.cn" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" - url: "https://pub.flutter-io.cn" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.5" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "15.2.0" web: dependency: "direct main" description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 - url: "https://pub.flutter-io.cn" + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.1" win32: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" - url: "https://pub.flutter-io.cn" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.15.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" - url: "https://pub.flutter-io.cn" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" window_to_front: dependency: "direct main" description: name: window_to_front sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.3" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.flutter-io.cn" + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.1.3" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock b/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock new file mode 100644 index 00000000..477abfc3 --- /dev/null +++ b/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock @@ -0,0 +1,213 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: "direct main" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/flutter_windowmanager/pubspec.lock b/third-party/tool/flutter_windowmanager/pubspec.lock new file mode 100644 index 00000000..9fe59906 --- /dev/null +++ b/third-party/tool/flutter_windowmanager/pubspec.lock @@ -0,0 +1,189 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/image_gallery_saver/pubspec.lock b/third-party/tool/image_gallery_saver/pubspec.lock new file mode 100644 index 00000000..9fe59906 --- /dev/null +++ b/third-party/tool/image_gallery_saver/pubspec.lock @@ -0,0 +1,189 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/move_to_background/pubspec.lock b/third-party/tool/move_to_background/pubspec.lock new file mode 100644 index 00000000..9fe59906 --- /dev/null +++ b/third-party/tool/move_to_background/pubspec.lock @@ -0,0 +1,189 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock new file mode 100644 index 00000000..7b5e7d02 --- /dev/null +++ b/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock @@ -0,0 +1,273 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mostly_reasonable_lints: + dependency: "direct dev" + description: + name: mostly_reasonable_lints + sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: "direct dev" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + screen_capturer_linux: + dependency: "direct main" + description: + path: "../screen_capturer_linux" + relative: true + source: path + version: "0.2.2" + screen_capturer_macos: + dependency: "direct main" + description: + path: "../screen_capturer_macos" + relative: true + source: path + version: "0.2.2" + screen_capturer_platform_interface: + dependency: "direct main" + description: + path: "../screen_capturer_platform_interface" + relative: true + source: path + version: "0.2.2" + screen_capturer_windows: + dependency: "direct main" + description: + path: "../screen_capturer_windows" + relative: true + source: path + version: "0.2.2" + shell_executor: + dependency: transitive + description: + name: shell_executor + sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock new file mode 100644 index 00000000..f1346be1 --- /dev/null +++ b/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock @@ -0,0 +1,236 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mostly_reasonable_lints: + dependency: "direct dev" + description: + name: mostly_reasonable_lints + sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + screen_capturer_platform_interface: + dependency: "direct main" + description: + path: "../screen_capturer_platform_interface" + relative: true + source: path + version: "0.2.2" + shell_executor: + dependency: "direct main" + description: + name: shell_executor + sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock new file mode 100644 index 00000000..f1346be1 --- /dev/null +++ b/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock @@ -0,0 +1,236 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mostly_reasonable_lints: + dependency: "direct dev" + description: + name: mostly_reasonable_lints + sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + screen_capturer_platform_interface: + dependency: "direct main" + description: + path: "../screen_capturer_platform_interface" + relative: true + source: path + version: "0.2.2" + shell_executor: + dependency: "direct main" + description: + name: shell_executor + sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock new file mode 100644 index 00000000..dcca0999 --- /dev/null +++ b/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock @@ -0,0 +1,525 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + url: "https://pub.dev" + source: hosted + version: "93.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + url: "https://pub.dev" + source: hosted + version: "10.0.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.dev" + source: hosted + version: "4.0.6" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + url: "https://pub.dev" + source: hosted + version: "3.1.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "2c15e78e1cc6e62aadecf59f81566fd56829713d96a8c4177699e2b2e17f20db" + url: "https://pub.dev" + source: hosted + version: "6.13.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mostly_reasonable_lints: + dependency: "direct dev" + description: + name: mostly_reasonable_lints + sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: "direct main" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" + url: "https://pub.dev" + source: hosted + version: "1.3.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock new file mode 100644 index 00000000..571e55d6 --- /dev/null +++ b/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock @@ -0,0 +1,244 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mostly_reasonable_lints: + dependency: "direct dev" + description: + name: mostly_reasonable_lints + sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + screen_capturer_platform_interface: + dependency: "direct main" + description: + path: "../screen_capturer_platform_interface" + relative: true + source: path + version: "0.2.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + win32: + dependency: "direct main" + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/widget/desktop_multi_window/pubspec.lock b/third-party/widget/desktop_multi_window/pubspec.lock index 91bbac3c..c01ef112 100644 --- a/third-party/widget/desktop_multi_window/pubspec.lock +++ b/third-party/widget/desktop_multi_window/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -58,10 +58,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c - url: "https://pub.flutter-io.cn" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -71,135 +71,135 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" - url: "https://pub.flutter-io.cn" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" - url: "https://pub.flutter-io.cn" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" - url: "https://pub.flutter-io.cn" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.10" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "15.2.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/widget/lucide_icons/pubspec.lock b/third-party/widget/lucide_icons/pubspec.lock index 21d783fd..857bff92 100644 --- a/third-party/widget/lucide_icons/pubspec.lock +++ b/third-party/widget/lucide_icons/pubspec.lock @@ -5,32 +5,32 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" clock: dependency: transitive description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" csslib: @@ -46,17 +46,17 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -71,72 +71,72 @@ packages: dependency: "direct dev" description: name: html - sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8" - url: "https://pub.flutter-io.cn" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" source: hosted - version: "0.15.5+1" + version: "0.15.6" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec - url: "https://pub.flutter-io.cn" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.flutter-io.cn" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lint: dependency: "direct dev" description: name: lint sha256: "4a539aa34ec5721a2c7574ae2ca0336738ea4adc2a34887d54b7596310b33c85" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" path: dependency: transitive description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" recase: @@ -144,7 +144,7 @@ packages: description: name: recase sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.0" sky_engine: @@ -156,16 +156,16 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -173,7 +173,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: @@ -181,7 +181,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" term_glyph: @@ -189,33 +189,33 @@ 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: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.10" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.2.0" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/widget/pinput/pubspec.lock b/third-party/widget/pinput/pubspec.lock index fa87fc76..6d421cd6 100644 --- a/third-party/widget/pinput/pubspec.lock +++ b/third-party/widget/pinput/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.flutter-io.cn" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.flutter-io.cn" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -63,135 +63,135 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" - url: "https://pub.flutter-io.cn" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" - url: "https://pub.flutter-io.cn" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.flutter-io.cn" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.flutter-io.cn" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.flutter-io.cn" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.flutter-io.cn" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.flutter-io.cn" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.10" universal_platform: dependency: "direct main" description: name: universal_platform sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc - url: "https://pub.flutter-io.cn" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "15.2.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/third-party/widget/smart_snackbars/pubspec.lock b/third-party/widget/smart_snackbars/pubspec.lock new file mode 100644 index 00000000..c01ef112 --- /dev/null +++ b/third-party/widget/smart_snackbars/pubspec.lock @@ -0,0 +1,205 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" From 2bc553bcf61abfcec81a5d4eba2fda5958eaa53f Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 16 May 2026 21:31:07 +0800 Subject: [PATCH 02/36] Fix several security bugs and update flutter version --- .github/workflows/release.yml | 4 +- lib/Database/database_manager.dart | 10 ++- lib/Database/token_dao.dart | 10 ++- lib/Screens/Lock/pin_change_screen.dart | 11 +-- lib/Screens/Lock/pin_verify_screen.dart | 76 ++++++++++++++++--- lib/Screens/Setting/select_theme_screen.dart | 2 +- lib/Screens/Token/token_layout.dart | 25 +++--- lib/TokenUtils/Backup/backup_encrypt_old.dart | 12 ++- lib/TokenUtils/Backup/backup_encrypt_v1.dart | 10 ++- lib/Utils/app_provider.dart | 25 ++++++ lib/Utils/hive_util.dart | 28 +++++++ .../import_from_third_party_bottom_sheet.dart | 2 +- lib/main.dart | 15 +++- .../lib/src/Providers/chewie_provider.dart | 6 +- .../chewie/lib/src/Resources/fonts.dart | 15 ++-- .../lib/src/Utils/System/hive_util.dart | 9 ++- .../lib/src/Utils/System/locale_util.dart | 1 + 17 files changed, 200 insertions(+), 61 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3acca990..0e487e2f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,7 @@ jobs: arch: ${{ steps.get_version.outputs.arch}} runs-on: ${{ matrix.os }} env: - FLUTTER_VERSION: 3.27.2 + FLUTTER_VERSION: 3.41.9 steps: # Checkout branch - name: Checkout @@ -81,7 +81,7 @@ jobs: uses: subosito/flutter-action@v2.21.0 with: flutter-version: ${{ env.FLUTTER_VERSION }} - channel: 'master' + channel: 'stable' cache: true # Setup JDK diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index 5f1ae129..b9f2872d 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -107,11 +107,14 @@ class DatabaseManager { await ConfigDao.initConfig(); } + static String _escapeSql(String value) => value.replaceAll("'", "''"); + static Future changePassword(String password) async { if (_database != null) { + final escaped = _escapeSql(password); if (isDatabaseEncrypted) { List> res = - await _database!.rawQuery("PRAGMA rekey='$password'"); + await _database!.rawQuery("PRAGMA rekey='$escaped'"); ILogger.info("Change database password result is $res"); if (res.isNotEmpty) { appProvider.currentDatabasePassword = password; @@ -120,7 +123,7 @@ class DatabaseManager { } else { try { await _database!.rawQuery( - "ATTACH DATABASE 'encrypted.db' AS tmp KEY '$password'"); + "ATTACH DATABASE 'encrypted.db' AS tmp KEY '$escaped'"); await _database!.rawQuery("SELECT sqlcipher_export('tmp')"); await _database!.rawQuery("DETACH DATABASE tmp"); return true; @@ -136,8 +139,9 @@ class DatabaseManager { static Future _onConfigure(Database db, String password) async { if (isDatabaseEncrypted) { + final escaped = _escapeSql(password); List> res = - await db.rawQuery("PRAGMA KEY='$password'"); + await db.rawQuery("PRAGMA KEY='$escaped'"); if (res.isNotEmpty) { ILogger.info( "Configure database with cipher successfully. Result is $res"); diff --git a/lib/Database/token_dao.dart b/lib/Database/token_dao.dart index 9b510d5c..a6e2f28f 100644 --- a/lib/Database/token_dao.dart +++ b/lib/Database/token_dao.dart @@ -44,13 +44,15 @@ class TokenDao { static Future insertTokens(List tokens) async { if (tokens.isEmpty) return 0; final db = await DatabaseManager.getDataBase(); + int maxSeq = await getMaxSeq(); + int maxId = await getMaxId(); Batch batch = db.batch(); - for (OtpToken token in tokens) { - token.seq = await getMaxSeq() + 1 + tokens.indexOf(token); - token.id = await getMaxId() + 1 + tokens.indexOf(token); + for (int i = 0; i < tokens.length; i++) { + tokens[i].seq = maxSeq + 1 + i; + tokens[i].id = maxId + 1 + i; batch.insert( tableName, - token.toMap(), + tokens[i].toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } diff --git a/lib/Screens/Lock/pin_change_screen.dart b/lib/Screens/Lock/pin_change_screen.dart index e0ef4c4c..34ecdfa4 100644 --- a/lib/Screens/Lock/pin_change_screen.dart +++ b/lib/Screens/Lock/pin_change_screen.dart @@ -34,12 +34,9 @@ class PinChangeScreen extends StatefulWidget { class PinChangeScreenState extends BaseDynamicState { String _gesturePassword = ""; - final String? _oldPassword = - ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey); bool _isEditMode = - ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey) != null && - ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey)! - .isNotEmpty; + ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey) + .notNullOrEmpty; late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); final bool _hideGestureTrail = @@ -183,7 +180,7 @@ class PinChangeScreenState extends BaseDynamicState { ); Navigator.pop(context); }); - ChewieHiveUtil.put(CloudOTPHiveUtil.guesturePasswdKey, + CloudOTPHiveUtil.setGesturePassword( GestureUnlockView.selectedToString(selected)); } else { setState(() { @@ -196,7 +193,7 @@ class PinChangeScreenState extends BaseDynamicState { } } else { String password = GestureUnlockView.selectedToString(selected); - if (_oldPassword == password) { + if (CloudOTPHiveUtil.verifyGesturePassword(password)) { setState(() { _notifier.setStatus( status: GestureStatus.create, diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 9bc60c7a..d2f3de39 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -13,6 +13,7 @@ * If not, see . */ +import 'dart:async'; import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -51,8 +52,11 @@ class PinVerifyScreen extends StatefulWidget { class PinVerifyScreenState extends BaseWindowState with TrayListener { - final String? _password = - ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey); + static const int _maxFailedAttempts = 5; + static const int _lockoutDurationSeconds = 30; + static const int _extendedLockoutDurationSeconds = 300; + static const int _extendedLockoutThreshold = 10; + late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); final bool _hideGestureTrail = @@ -63,11 +67,16 @@ class PinVerifyScreenState extends BaseWindowState final GlobalKey _gestureUnlockView = GlobalKey(); String? canAuthenticateResponseString; CanAuthenticateResponse? canAuthenticateResponse; + int _failedAttempts = 0; + bool _isLockedOut = false; + Timer? _lockoutTimer; + int _lockoutRemaining = 0; bool get _biometricAvailable => canAuthenticateResponse?.isSuccess ?? false; @override void dispose() { + _lockoutTimer?.cancel(); super.dispose(); trayManager.removeListener(this); windowManager.removeListener(this); @@ -148,7 +157,9 @@ class PinVerifyScreenState extends BaseWindowState style: ChewieTheme.titleMedium, ), const SizedBox(height: 30), - GestureUnlockView( + Semantics( + label: appLocalizations.verifyGestureLock, + child: GestureUnlockView( key: _gestureUnlockView, size: min(MediaQuery.sizeOf(context).width, 400), padding: 60, @@ -163,6 +174,7 @@ class PinVerifyScreenState extends BaseWindowState showLine: !_hideGestureTrail, onCompleted: _gestureComplete, ), + ), Visibility( visible: _biometricAvailable && _enableBiometric, child: RoundIconTextButton( @@ -201,7 +213,43 @@ class PinVerifyScreenState extends BaseWindowState ); } + void _startLockout() { + final duration = _failedAttempts >= _extendedLockoutThreshold + ? _extendedLockoutDurationSeconds + : _lockoutDurationSeconds; + _isLockedOut = true; + _lockoutRemaining = duration; + _lockoutTimer?.cancel(); + _lockoutTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + setState(() { + _lockoutRemaining--; + if (_lockoutRemaining <= 0) { + _isLockedOut = false; + timer.cancel(); + _notifier.setStatus( + status: GestureStatus.verify, + gestureText: appLocalizations.verifyGestureLock, + ); + } else { + _notifier.setStatus( + status: GestureStatus.verifyFailedCountOverflow, + gestureText: + '${appLocalizations.gestureLockWrong} (${_lockoutRemaining}s)', + ); + } + }); + }); + setState(() { + _notifier.setStatus( + status: GestureStatus.verifyFailedCountOverflow, + gestureText: + '${appLocalizations.gestureLockWrong} (${_lockoutRemaining}s)', + ); + }); + } + success() { + _failedAttempts = 0; if (widget.onSuccess != null) widget.onSuccess!(); if (widget.jumpToMain) { ShortcutsUtil.jumpToMain(); @@ -212,19 +260,27 @@ class PinVerifyScreenState extends BaseWindowState } void _gestureComplete(List selected, UnlockStatus status) async { + if (_isLockedOut) return; switch (_notifier.status) { case GestureStatus.verify: case GestureStatus.verifyFailed: String password = GestureUnlockView.selectedToString(selected); - if (_password == password) { + if (CloudOTPHiveUtil.verifyGesturePassword(password)) { success(); } else { - setState(() { - _notifier.setStatus( - status: GestureStatus.verifyFailed, - gestureText: appLocalizations.gestureLockWrong, - ); - }); + _failedAttempts++; + if (_failedAttempts >= _maxFailedAttempts) { + _startLockout(); + } else { + final remaining = _maxFailedAttempts - _failedAttempts; + setState(() { + _notifier.setStatus( + status: GestureStatus.verifyFailed, + gestureText: + '${appLocalizations.gestureLockWrong} ($remaining)', + ); + }); + } _gestureUnlockView.currentState?.updateStatus(UnlockStatus.failed); } break; diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index cb924281..98027964 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -100,7 +100,7 @@ class _SelectThemeScreenState extends BaseDynamicState }, ), ); - // list.add(ItemBuilder.buildEmptyThemeItem(context: context, onTap: null)); + // TODO: Enable custom theme creation when theme editor UI is implemented return list; } diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 06c43ccb..419ad6fd 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -88,7 +88,7 @@ class TokenLayoutNotifier extends ChangeNotifier { class TokenLayoutState extends BaseDynamicState with TickerProviderStateMixin { - Timer? _timer; + StreamSubscription? _tickerSubscription; TokenLayoutNotifier tokenLayoutNotifier = TokenLayoutNotifier(); @@ -110,7 +110,7 @@ class TokenLayoutState extends BaseDynamicState @override void dispose() { - _timer?.cancel(); + _tickerSubscription?.cancel(); tokenLayoutNotifier.dispose(); super.dispose(); } @@ -135,7 +135,9 @@ class TokenLayoutState extends BaseDynamicState resetTimer() { tokenLayoutNotifier.haveToResetHOTP = false; - _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { + _tickerSubscription?.cancel(); + globalTokenTicker.start(); + _tickerSubscription = globalTokenTicker.stream.listen((_) { if (mounted) { progressNotifier.value = currentProgress; if (remainingMilliseconds <= 180 && appProvider.autoHideCode) { @@ -238,12 +240,16 @@ class TokenLayoutState extends BaseDynamicState } _buildContextMenuRegion() { - return ContextMenuRegion( - key: ValueKey("contextMenuRegion${widget.token.keyString}"), - enable: ResponsiveUtil.isDesktop(), - enableOnLongPress: false, - contextMenu: _buildContextMenuButtons(), - child: Selector( + return Semantics( + label: '${widget.token.issuer} ${widget.token.account}', + hint: appLocalizations.copyTokenCode, + button: true, + child: ContextMenuRegion( + key: ValueKey("contextMenuRegion${widget.token.keyString}"), + enable: ResponsiveUtil.isDesktop(), + enableOnLongPress: false, + contextMenu: _buildContextMenuButtons(), + child: Selector( selector: (context, provider) => provider.dragToReorder, builder: (context, dragToReorder, child) => Selector( @@ -265,6 +271,7 @@ class TokenLayoutState extends BaseDynamicState } ), ), + ), ); } diff --git a/lib/TokenUtils/Backup/backup_encrypt_old.dart b/lib/TokenUtils/Backup/backup_encrypt_old.dart index 4623b5ac..cdc4e752 100644 --- a/lib/TokenUtils/Backup/backup_encrypt_old.dart +++ b/lib/TokenUtils/Backup/backup_encrypt_old.dart @@ -14,6 +14,7 @@ */ import 'dart:convert'; +import 'dart:math'; import 'dart:typed_data'; import 'package:pointycastle/export.dart' as pointycastle; @@ -23,7 +24,10 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'backup.dart'; import 'backup_encrypt_interface.dart'; +@Deprecated('Legacy format — only used for decrypting old backups. ' + 'New backups must use BackupEncryptionV1.') class BackupEncryptionOld implements BackupEncryptInterface { + @Deprecated('Do not encrypt with this format — uses password as salt.') Future getEncryptedData( String password, List otpTokens) async { final json = jsonEncode(otpTokens.map((token) => token.toJson()).toList()); @@ -33,6 +37,7 @@ class BackupEncryptionOld implements BackupEncryptInterface { return encryptedData; } + @Deprecated('Do not encrypt with this format — uses password as salt.') @override Future encrypt(Backup backup, String password) async { final key = @@ -83,10 +88,9 @@ class AESStringCipher { } static Uint8List _generateIv(int length) { - final random = pointycastle.SecureRandom("Fortuna") - ..seed(pointycastle.KeyParameter( - Uint8List.fromList(List.generate(32, (_) => 1)))); - return random.nextBytes(length); + final secureRandom = Random.secure(); + return Uint8List.fromList( + List.generate(length, (_) => secureRandom.nextInt(256))); } static String encrypt(String plaintext, Uint8List key) { diff --git a/lib/TokenUtils/Backup/backup_encrypt_v1.dart b/lib/TokenUtils/Backup/backup_encrypt_v1.dart index c222100e..ba7411a6 100644 --- a/lib/TokenUtils/Backup/backup_encrypt_v1.dart +++ b/lib/TokenUtils/Backup/backup_encrypt_v1.dart @@ -15,6 +15,7 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:math'; import 'dart:typed_data'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -42,10 +43,11 @@ class BackupEncryptionV1 implements BackupEncryptInterface { throw EncryptEmptyPasswordException(); } - final random = SecureRandom("Fortuna") - ..seed(KeyParameter(Uint8List.fromList(List.generate(32, (_) => 1)))); - final salt = random.nextBytes(saltLength); - final iv = random.nextBytes(ivLength); + final secureRandom = Random.secure(); + final salt = Uint8List.fromList( + List.generate(saltLength, (_) => secureRandom.nextInt(256))); + final iv = Uint8List.fromList( + List.generate(ivLength, (_) => secureRandom.nextInt(256))); final key = deriveKey(password, salt); final parameters = diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index 133419a7..fbc448e5 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -13,6 +13,8 @@ * If not, see . */ +import 'dart:async'; + import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Models/auto_backup_log.dart'; import 'package:cloudotp/Screens/Setting/setting_general_screen.dart'; @@ -46,6 +48,29 @@ HomeScreenState? get homeScreenState => Queue autoBackupQueue = Queue(); +class GlobalTokenTicker { + static final GlobalTokenTicker _instance = GlobalTokenTicker._(); + factory GlobalTokenTicker() => _instance; + GlobalTokenTicker._(); + + Timer? _timer; + final StreamController _controller = StreamController.broadcast(); + + Stream get stream => _controller.stream; + + void start() { + _timer ??= Timer.periodic( + const Duration(milliseconds: 100), (_) => _controller.add(null)); + } + + void stop() { + _timer?.cancel(); + _timer = null; + } +} + +final globalTokenTicker = GlobalTokenTicker(); + AppProvider appProvider = AppProvider(); enum AutoLockTime { diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 81a52a8a..ad4e7c69 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -13,10 +13,13 @@ * If not, see . */ +import 'dart:convert'; + import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/config_dao.dart'; import 'package:cloudotp/Screens/home_screen.dart'; import 'package:cloudotp/Utils/app_provider.dart'; +import 'package:hashlib/hashlib.dart'; import '../Database/database_manager.dart'; import 'constant.dart'; @@ -82,6 +85,7 @@ class CloudOTPHiveUtil { static const String autoLockTimeKey = "autoLockTime"; static const String enableSafeModeKey = "enableSafeMode"; static const String hideGestureTrailKey = "hideGestureTrail"; + static const String followSystemTextScaleKey = "followSystemTextScale"; //System static const String oldVersionKey = "oldVersion"; @@ -113,6 +117,30 @@ class CloudOTPHiveUtil { getEncryptDatabaseStatus() == EncryptDatabaseStatus.customPassword && DatabaseManager.isDatabaseEncrypted; + static String _hashGesturePassword(String password) => + sha256.convert(utf8.encode(password)).hex(); + + static bool _isHashed(String value) => + value.length == 64 && RegExp(r'^[0-9a-f]+$').hasMatch(value); + + static void setGesturePassword(String password) { + ChewieHiveUtil.put(guesturePasswdKey, _hashGesturePassword(password)); + } + + static bool verifyGesturePassword(String input) { + final stored = ChewieHiveUtil.getString(guesturePasswdKey); + if (stored == null || stored.isEmpty) return false; + if (_isHashed(stored)) { + return stored == _hashGesturePassword(input); + } + // Legacy plaintext — migrate to hash on successful match + if (stored == input) { + setGesturePassword(input); + return true; + } + return false; + } + static Future showCloudEntry() async { String autoBackupPassword = (await ConfigDao.getConfig()).backupPassword; bool enableCloudBackup = diff --git a/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart b/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart index 9d0f172b..8a3ac726 100644 --- a/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart @@ -210,7 +210,7 @@ class ImportFromThirdPartyBottomSheetState title: appLocalizations.importFromWinauth, dialogTitle: appLocalizations.importFromWinauthTitle, description: appLocalizations.importFromWinauthTip, - allowedExtensions: ['zip', 'txt'], + allowedExtensions: ['txt'], onImport: (path) { WinauthTokenImporter().importFromPath(path); }, diff --git a/lib/main.dart b/lib/main.dart index 6a80d7d5..5cf434be 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -93,8 +93,11 @@ Future initApp(WidgetsBinding widgetsBinding) async { await ResponsiveUtil.init(); await FileUtil.migrationDataToSupportDirectory(); FlutterError.onError = onError; - imageCache.maximumSizeBytes = 1024 * 1024 * 1024 * 2; - PaintingBinding.instance.imageCache.maximumSizeBytes = 1024 * 1024 * 1024 * 2; + final cacheSize = ResponsiveUtil.isMobile() + ? 256 * 1024 * 1024 // 256MB for mobile + : 1024 * 1024 * 1024; // 1GB for desktop + imageCache.maximumSizeBytes = cacheSize; + PaintingBinding.instance.imageCache.maximumSizeBytes = cacheSize; FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); await initHive(); await initCryptoUtil(); @@ -316,8 +319,12 @@ class MyApp extends StatelessWidget { if (widget != null) ...[ OverlayEntry( builder: (context) => MediaQuery( - data: MediaQuery.of(context) - .copyWith(textScaler: TextScaler.noScaling), + data: ChewieHiveUtil.getBool( + CloudOTPHiveUtil.followSystemTextScaleKey, + defaultValue: false) + ? MediaQuery.of(context) + : MediaQuery.of(context) + .copyWith(textScaler: TextScaler.noScaling), child: Listener( onPointerDown: (_) { if (!ResponsiveUtil.isDesktop() && diff --git a/third-party/chewie/lib/src/Providers/chewie_provider.dart b/third-party/chewie/lib/src/Providers/chewie_provider.dart index 53da21fe..103fe490 100644 --- a/third-party/chewie/lib/src/Providers/chewie_provider.dart +++ b/third-party/chewie/lib/src/Providers/chewie_provider.dart @@ -117,11 +117,11 @@ class ChewieProvider with ChangeNotifier { ]; } - int? _fontSize = ChewieHiveUtil.getFontSize(); + int _fontSize = ChewieHiveUtil.getFontSize(); - int? get fontSize => _fontSize; + int get fontSize => _fontSize; - set fontSize(int? value) { + set fontSize(int value) { if (value != _fontSize) { _fontSize = value; notifyListeners(); diff --git a/third-party/chewie/lib/src/Resources/fonts.dart b/third-party/chewie/lib/src/Resources/fonts.dart index 91584881..fb454ea7 100644 --- a/third-party/chewie/lib/src/Resources/fonts.dart +++ b/third-party/chewie/lib/src/Resources/fonts.dart @@ -206,12 +206,17 @@ class CustomFont { await downloadFont( context: context, showToast: false, - onFinished: (value) { + onFinished: (success) { dialog.dismiss(); - chewieProvider.darkTheme = chewieProvider.darkTheme; - chewieProvider.lightTheme = chewieProvider.lightTheme; - if (autoRestartApp) { - ResponsiveUtil.restartApp(context); + if (success) { + chewieProvider.darkTheme = chewieProvider.darkTheme; + chewieProvider.lightTheme = chewieProvider.lightTheme; + if (autoRestartApp) { + ResponsiveUtil.restartApp(context); + } + } else { + IToast.showTop(chewieLocalizations.fontFamlyLoadFailed); + ChewieHiveUtil.put(ChewieHiveUtil.fontFamilyKey, Default.fontFamily); } }, onReceiveProgress: (progress) { diff --git a/third-party/chewie/lib/src/Utils/System/hive_util.dart b/third-party/chewie/lib/src/Utils/System/hive_util.dart index 0a33b247..81de330c 100644 --- a/third-party/chewie/lib/src/Utils/System/hive_util.dart +++ b/third-party/chewie/lib/src/Utils/System/hive_util.dart @@ -24,6 +24,7 @@ class ChewieHiveUtil { //Appearance static const String enableLandscapeInTabletKey = "enableLandscapeInTablet"; static const String fontFamilyKey = "fontFamily"; + static const String fontSizeKey = "fontSize"; static const String customFontsKey = "customFonts"; static const String lightThemeIndexKey = "lightThemeIndex"; static const String darkThemeIndexKey = "darkThemeIndex"; @@ -162,12 +163,12 @@ class ChewieHiveUtil { } } - static int? getFontSize() { - return 2; + static int getFontSize() { + return ChewieHiveUtil.getInt(ChewieHiveUtil.fontSizeKey, defaultValue: 2); } - static void setFontSize(int? fontSize) { - ChewieHiveUtil.put(ChewieHiveUtil.fontFamilyKey, fontSize); + static void setFontSize(int fontSize) { + ChewieHiveUtil.put(ChewieHiveUtil.fontSizeKey, fontSize); } static ActiveThemeMode getThemeMode() { diff --git a/third-party/chewie/lib/src/Utils/System/locale_util.dart b/third-party/chewie/lib/src/Utils/System/locale_util.dart index 32f10ac7..36fe9370 100644 --- a/third-party/chewie/lib/src/Utils/System/locale_util.dart +++ b/third-party/chewie/lib/src/Utils/System/locale_util.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:tuple/tuple.dart'; class LocaleUtil with ChangeNotifier { + // Full label list for shared library; apps filter via supportedLocales static List> localeLabels = >[ Tuple2(chewieLocalizations.followSystem, null), const Tuple2("Deutsch", Locale("de")), From bdbd111a233764a6dead32c85305ec3c2b1b1562 Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 16 May 2026 23:19:55 +0800 Subject: [PATCH 03/36] Support custom theme --- lib/Screens/Setting/select_theme_screen.dart | 643 +++++++++++++++++- lib/Screens/Setting/theme_editor_screen.dart | 423 ++++++++++++ lib/Screens/Token/token_layout.dart | 39 +- lib/Utils/app_provider.dart | 100 +++ lib/Utils/theme_util.dart | 90 +++ .../color_picker_bottom_sheet.dart | 171 +++++ lib/l10n/intl_en.arb | 31 +- lib/l10n/intl_ja.arb | 31 +- lib/l10n/intl_zh.arb | 31 +- lib/l10n/intl_zh_TW.arb | 31 +- pubspec.lock | 18 +- pubspec.yaml | 1 + .../chewie/lib/src/Resources/colors.dart | 8 +- .../lib/src/Resources/theme_color_data.dart | 100 +++ .../lib/src/Utils/System/hive_util.dart | 133 +++- .../core/utils/helpers.dart | 4 +- .../lib/src/Widgets/Tile/entry_item.dart | 5 +- .../lib/src/Widgets/Tile/theme_item.dart | 151 ++-- 18 files changed, 1864 insertions(+), 146 deletions(-) create mode 100644 lib/Screens/Setting/theme_editor_screen.dart create mode 100644 lib/Utils/theme_util.dart create mode 100644 lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index 98027964..de4e0238 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -15,8 +15,12 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Screens/Setting/base_setting_screen.dart'; +import 'package:cloudotp/Screens/Setting/theme_editor_screen.dart'; import 'package:cloudotp/Utils/app_provider.dart'; +import 'package:cloudotp/Utils/theme_util.dart'; +import 'package:cloudotp/Widgets/BottomSheet/color_picker_bottom_sheet.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../l10n/l10n.dart'; @@ -33,6 +37,22 @@ class _SelectThemeScreenState extends BaseDynamicState with TickerProviderStateMixin { int _selectedLightIndex = ChewieHiveUtil.getLightThemeIndex(); int _selectedDarkIndex = ChewieHiveUtil.getDarkThemeIndex(); + int _lightPrimaryColorIndex = ChewieHiveUtil.getLightThemePrimaryColorIndex(); + int _darkPrimaryColorIndex = ChewieHiveUtil.getDarkThemePrimaryColorIndex(); + + static const List _presetAccentColors = [ + Color(0xFF11b566), + Color(0xFF2196F3), + Color(0xFF009688), + Color(0xFFE91E63), + Color(0xFF9C27B0), + Color(0xFFFF5722), + Color(0xFF795548), + Color(0xFF607D8B), + Color(0xFFFF9800), + Color(0xFF4CAF50), + Color(0xFF3F51B5), + ]; @override Widget build(BuildContext context) { @@ -49,33 +69,43 @@ class _SelectThemeScreenState extends BaseDynamicState CaptionItem( title: appLocalizations.lightTheme, children: [ - Container( - margin: const EdgeInsets.only(top: 10), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: IntrinsicHeight( - child: Row( - children: _buildLightThemeList(), + Align( + alignment: Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(top: 10), + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: IntrinsicHeight( + child: Row( + children: _buildLightThemeList(), + ), ), ), ), ), + _buildAccentColorPalette(isDark: false), ], ), CaptionItem( title: appLocalizations.darkTheme, children: [ - Container( - margin: const EdgeInsets.only(top: 10), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: IntrinsicHeight( - child: Row( - children: _buildDarkThemeList(), + Align( + alignment: Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(top: 10), + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: IntrinsicHeight( + child: Row( + children: _buildDarkThemeList(), + ), ), ), ), ), + _buildAccentColorPalette(isDark: true), ], ), const SizedBox(height: 10), @@ -84,43 +114,600 @@ class _SelectThemeScreenState extends BaseDynamicState } List _buildLightThemeList() { + final builtInCount = ChewieThemeColorData.defaultLightThemes.length; + final customThemes = appProvider.customLightThemes; var list = List.generate( - ChewieThemeColorData.defaultLightThemes.length, + builtInCount, (index) => ThemeItem( index: index, groupIndex: _selectedLightIndex, themeColorData: ChewieThemeColorData.defaultLightThemes[index], onChanged: (index) { - setState( - () { - _selectedLightIndex = index ?? 0; - appProvider.setLightTheme(index ?? 0); - }, - ); + setState(() { + _selectedLightIndex = index ?? 0; + appProvider.setLightTheme(index ?? 0); + }); }, ), ); - // TODO: Enable custom theme creation when theme editor UI is implemented + if (customThemes.isNotEmpty) { + list.add(_buildVerticalDivider()); + } + for (int i = 0; i < customThemes.length; i++) { + final globalIndex = builtInCount + i; + list.add(_wrapWithContextMenu( + isDark: false, + customIndex: i, + child: ThemeItem( + index: globalIndex, + groupIndex: _selectedLightIndex, + themeColorData: customThemes[i], + onChanged: (index) { + setState(() { + _selectedLightIndex = index ?? 0; + appProvider.setLightTheme(index ?? 0); + }); + }, + onLongPress: ResponsiveUtil.isDesktop() + ? null + : () => _showCustomThemeOptions(false, i), + ), + )); + } + list.add(_buildVerticalDivider()); + list.add(EmptyThemeItem(onTap: () => _createNewTheme(false))); return list; } List _buildDarkThemeList() { + final builtInCount = ChewieThemeColorData.defaultDarkThemes.length; + final customThemes = appProvider.customDarkThemes; var list = List.generate( - ChewieThemeColorData.defaultDarkThemes.length, + builtInCount, (index) => ThemeItem( index: index, groupIndex: _selectedDarkIndex, themeColorData: ChewieThemeColorData.defaultDarkThemes[index], onChanged: (index) { - setState( - () { + setState(() { + _selectedDarkIndex = index ?? 0; + appProvider.setDarkTheme(index ?? 0); + }); + }, + ), + ); + if (customThemes.isNotEmpty) { + list.add(_buildVerticalDivider()); + } + for (int i = 0; i < customThemes.length; i++) { + final globalIndex = builtInCount + i; + list.add(_wrapWithContextMenu( + isDark: true, + customIndex: i, + child: ThemeItem( + index: globalIndex, + groupIndex: _selectedDarkIndex, + themeColorData: customThemes[i], + onChanged: (index) { + setState(() { _selectedDarkIndex = index ?? 0; appProvider.setDarkTheme(index ?? 0); + }); + }, + onLongPress: ResponsiveUtil.isDesktop() + ? null + : () => _showCustomThemeOptions(true, i), + ), + )); + } + list.add(_buildVerticalDivider()); + list.add(EmptyThemeItem(onTap: () => _createNewTheme(true))); + return list; + } + + Widget _buildVerticalDivider() { + return Container( + width: 1, + margin: const EdgeInsets.only( + left: 0, + right: 10, + top: 10, + bottom: 40, + ), + color: ChewieTheme.dividerColor, + ); + } + + Widget _wrapWithContextMenu({ + required bool isDark, + required int customIndex, + required Widget child, + }) { + return ContextMenuRegion( + enable: ResponsiveUtil.isDesktop(), + enableOnLongPress: false, + contextMenu: _buildCustomThemeContextMenu(isDark, customIndex), + child: child, + ); + } + + FlutterContextMenu _buildCustomThemeContextMenu( + bool isDark, int customIndex) { + final themes = + isDark ? appProvider.customDarkThemes : appProvider.customLightThemes; + final theme = themes[customIndex]; + return FlutterContextMenu( + entries: [ + FlutterContextMenuItem( + appLocalizations.editTheme, + iconData: LucideIcons.pencilLine, + onPressed: () { + _navigateToEditor(theme, isDark, editIndex: customIndex); + }, + ), + FlutterContextMenuItem( + appLocalizations.duplicateTheme, + iconData: LucideIcons.copy, + onPressed: () { + final copy = theme.copyWith( + id: ChewieThemeColorData.generateId(), + name: '${theme.name} (copy)', + ); + if (isDark) { + appProvider.addCustomDarkTheme(copy); + } else { + appProvider.addCustomLightTheme(copy); + } + setState(() {}); + }, + ), + FlutterContextMenuItem.divider(), + FlutterContextMenuItem( + appLocalizations.exportToClipboard, + iconData: LucideIcons.clipboardCopy, + onPressed: () { + ThemeUtil.exportToClipboard(context, theme); + IToast.showTop(appLocalizations.themeExportSuccess); + }, + ), + FlutterContextMenuItem( + appLocalizations.exportToFile, + iconData: LucideIcons.save, + onPressed: () async { + final ok = await ThemeUtil.exportToFile(theme); + if (ok) IToast.showTop(appLocalizations.themeExportSuccess); + }, + ), + FlutterContextMenuItem.divider(), + FlutterContextMenuItem( + appLocalizations.deleteTheme, + iconData: LucideIcons.trash2, + status: MenuItemStatus.error, + onPressed: () { + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.deleteTheme, + message: appLocalizations.deleteThemeConfirm, + onTapConfirm: () { + if (isDark) { + appProvider.deleteCustomDarkTheme(customIndex); + } else { + appProvider.deleteCustomLightTheme(customIndex); + } + setState(() { + _selectedLightIndex = ChewieHiveUtil.getLightThemeIndex(); + _selectedDarkIndex = ChewieHiveUtil.getDarkThemeIndex(); + }); + }, + ); + }, + ), + ], + ); + } + + void _createNewTheme(bool isDark) { + final allThemes = isDark + ? ChewieThemeColorData.defaultDarkThemes + : ChewieThemeColorData.defaultLightThemes; + final customThemes = + isDark ? appProvider.customDarkThemes : appProvider.customLightThemes; + + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (ctx) => _MenuListSheet( + title: appLocalizations.chooseBaseTheme, + items: [ + ...allThemes.map((theme) => _MenuAction( + label: theme.i18nName, + onTap: () { + Navigator.of(ctx).pop(); + _navigateToEditor(theme, isDark); + }, + )), + ...customThemes.map((theme) => _MenuAction( + label: theme.name, + onTap: () { + Navigator.of(ctx).pop(); + _navigateToEditor(theme, isDark); + }, + )), + _MenuAction(isDivider: true), + _MenuAction( + label: appLocalizations.importFromClipboard, + icon: Icons.content_paste_rounded, + onTap: () { + Navigator.of(ctx).pop(); + _importFromClipboard(isDark); }, - ); - }, + ), + _MenuAction( + label: appLocalizations.importFromFile, + icon: Icons.file_open_outlined, + onTap: () { + Navigator.of(ctx).pop(); + _importFromFile(isDark); + }, + ), + ], + ), + ); + } + + void _navigateToEditor(ChewieThemeColorData baseTheme, bool isDark, + {int? editIndex}) { + RouteUtil.pushDialogRoute( + context, + ThemeEditorScreen( + baseTheme: baseTheme, + isDarkMode: isDark, + editIndex: editIndex, + ), + onThen: (_) { + setState(() { + _selectedLightIndex = ChewieHiveUtil.getLightThemeIndex(); + _selectedDarkIndex = ChewieHiveUtil.getDarkThemeIndex(); + }); + }, + ); + } + + void _showCustomThemeOptions(bool isDark, int customIndex) { + final themes = + isDark ? appProvider.customDarkThemes : appProvider.customLightThemes; + final theme = themes[customIndex]; + + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (ctx) => _MenuListSheet( + title: theme.name, + items: [ + _MenuAction( + label: appLocalizations.editTheme, + icon: Icons.edit_outlined, + onTap: () { + Navigator.of(ctx).pop(); + _navigateToEditor(theme, isDark, editIndex: customIndex); + }, + ), + _MenuAction( + label: appLocalizations.duplicateTheme, + icon: Icons.copy_outlined, + onTap: () { + Navigator.of(ctx).pop(); + final copy = theme.copyWith( + id: ChewieThemeColorData.generateId(), + name: '${theme.name} (copy)', + ); + if (isDark) { + appProvider.addCustomDarkTheme(copy); + } else { + appProvider.addCustomLightTheme(copy); + } + setState(() {}); + }, + ), + _MenuAction(isDivider: true), + _MenuAction( + label: appLocalizations.exportToClipboard, + icon: Icons.content_copy_outlined, + onTap: () { + Navigator.of(ctx).pop(); + ThemeUtil.exportToClipboard(context, theme); + IToast.showTop(appLocalizations.themeExportSuccess); + }, + ), + _MenuAction( + label: appLocalizations.exportToFile, + icon: Icons.save_outlined, + onTap: () async { + Navigator.of(ctx).pop(); + final ok = await ThemeUtil.exportToFile(theme); + if (ok) IToast.showTop(appLocalizations.themeExportSuccess); + }, + ), + _MenuAction(isDivider: true), + _MenuAction( + label: appLocalizations.deleteTheme, + icon: Icons.delete_outline, + isDestructive: true, + onTap: () { + Navigator.of(ctx).pop(); + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.deleteTheme, + message: appLocalizations.deleteThemeConfirm, + onTapConfirm: () { + if (isDark) { + appProvider.deleteCustomDarkTheme(customIndex); + } else { + appProvider.deleteCustomLightTheme(customIndex); + } + setState(() { + _selectedLightIndex = ChewieHiveUtil.getLightThemeIndex(); + _selectedDarkIndex = ChewieHiveUtil.getDarkThemeIndex(); + }); + }, + ); + }, + ), + ], + ), + ); + } + + Future _importFromClipboard(bool isDark) async { + final theme = await ThemeUtil.importFromClipboard(); + if (theme == null) { + IToast.showTop(appLocalizations.themeImportFailed); + return; + } + _addImportedTheme(theme, isDark); + } + + Future _importFromFile(bool isDark) async { + final theme = await ThemeUtil.importFromFile(); + if (theme == null) { + IToast.showTop(appLocalizations.themeImportFailed); + return; + } + _addImportedTheme(theme, isDark); + } + + void _addImportedTheme(ChewieThemeColorData theme, bool isDark) { + final imported = theme.copyWith( + isDarkMode: isDark, + id: ChewieThemeColorData.generateId(), + ); + if (isDark) { + appProvider.addCustomDarkTheme(imported); + } else { + appProvider.addCustomLightTheme(imported); + } + IToast.showTop(appLocalizations.themeImportSuccess); + setState(() {}); + } + + Widget _buildAccentColorPalette({required bool isDark}) { + final selectedIndex = + isDark ? _darkPrimaryColorIndex : _lightPrimaryColorIndex; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Wrap( + spacing: 10, + runSpacing: 10, + children: [ + _buildAccentCircle( + color: null, + label: appLocalizations.resetToDefault, + isSelected: selectedIndex == 0, + onTap: () { + setState(() { + if (isDark) { + _darkPrimaryColorIndex = 0; + appProvider.setDarkPrimaryColorOverride(null, 0); + } else { + _lightPrimaryColorIndex = 0; + appProvider.setLightPrimaryColorOverride(null, 0); + } + }); + }, + ), + for (int i = 0; i < _presetAccentColors.length; i++) + _buildAccentCircle( + color: _presetAccentColors[i], + isSelected: selectedIndex == i + 1, + onTap: () { + setState(() { + final paletteIndex = i + 1; + if (isDark) { + _darkPrimaryColorIndex = paletteIndex; + appProvider.setDarkPrimaryColorOverride( + _presetAccentColors[i], paletteIndex); + } else { + _lightPrimaryColorIndex = paletteIndex; + appProvider.setLightPrimaryColorOverride( + _presetAccentColors[i], paletteIndex); + } + }); + }, + ), + _buildAccentCircle( + color: null, + icon: Icons.colorize, + label: appLocalizations.customColor, + isSelected: selectedIndex == _presetAccentColors.length + 1, + onTap: () { + final currentColor = isDark + ? (ChewieHiveUtil.getCustomDarkPrimaryColor() ?? + ChewieTheme.primaryColor) + : (ChewieHiveUtil.getCustomLightPrimaryColor() ?? + ChewieTheme.primaryColor); + ColorPickerBottomSheet.show( + context, + initialColor: currentColor, + title: appLocalizations.customColor, + onColorChanged: (color) { + setState(() { + final paletteIndex = _presetAccentColors.length + 1; + if (isDark) { + _darkPrimaryColorIndex = paletteIndex; + appProvider.setDarkPrimaryColorOverride( + color, paletteIndex); + } else { + _lightPrimaryColorIndex = paletteIndex; + appProvider.setLightPrimaryColorOverride( + color, paletteIndex); + } + }); + }, + ); + }, + ), + ], + ), + ); + } + + Widget _buildAccentCircle({ + Color? color, + IconData? icon, + String? label, + required bool isSelected, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Tooltip( + message: label ?? '', + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? ChewieTheme.primaryColor + : ChewieTheme.dividerColor, + width: isSelected ? 2.5 : 1, + ), + ), + child: icon != null + ? Icon(icon, size: 18, color: ChewieTheme.iconColor) + : (color == null + ? Icon(Icons.format_color_reset, + size: 18, color: ChewieTheme.iconColor) + : (isSelected + ? const Icon(Icons.check, size: 18, color: Colors.white) + : null)), + ), + ), + ); + } +} + +class _MenuAction { + final String label; + final IconData? icon; + final VoidCallback? onTap; + final bool isDivider; + final bool isDestructive; + + _MenuAction({ + this.label = '', + this.icon, + this.onTap, + this.isDivider = false, + this.isDestructive = false, + }); +} + +class _MenuListSheet extends StatelessWidget { + final String title; + final List<_MenuAction> items; + + const _MenuListSheet({required this.title, required this.items}); + + static const Radius _radius = ChewieDimens.defaultRadius; + + @override + Widget build(BuildContext context) { + return AnimatedPadding( + padding: MediaQuery.of(context).viewInsets, + duration: const Duration(milliseconds: 100), + child: Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: _radius, + bottom: + ResponsiveUtil.isWideDevice() ? _radius : Radius.zero), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 12), + alignment: Alignment.center, + child: Text(title, style: ChewieTheme.titleLarge), + ), + ...items.map((item) { + if (item.isDivider) { + return const MyDivider( + vertical: 8, + horizontal: 4, + width: 1, + ); + } + return InkWell( + onTap: item.onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 12), + child: Row( + children: [ + if (item.icon != null) ...[ + Icon(item.icon, + size: 22, + color: item.isDestructive + ? ChewieTheme.errorColor + : ChewieTheme.iconColor), + const SizedBox(width: 12), + ], + Expanded( + child: Text( + item.label, + style: ChewieTheme.bodyLarge.copyWith( + color: item.isDestructive + ? ChewieTheme.errorColor + : null, + ), + ), + ), + ], + ), + ), + ); + }), + const SizedBox(height: 8), + ], + ), + ), + ), + ], ), ); - return list; } } diff --git a/lib/Screens/Setting/theme_editor_screen.dart b/lib/Screens/Setting/theme_editor_screen.dart new file mode 100644 index 00000000..da9a1850 --- /dev/null +++ b/lib/Screens/Setting/theme_editor_screen.dart @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Screens/Setting/base_setting_screen.dart'; +import 'package:cloudotp/Utils/app_provider.dart'; +import 'package:cloudotp/Widgets/BottomSheet/color_picker_bottom_sheet.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../../l10n/l10n.dart'; + +class ThemeEditorScreen extends BaseSettingScreen { + final ChewieThemeColorData baseTheme; + final bool isDarkMode; + final int? editIndex; + + const ThemeEditorScreen({ + super.key, + required this.baseTheme, + required this.isDarkMode, + this.editIndex, + }); + + @override + State createState() => _ThemeEditorScreenState(); +} + +class _ThemeEditorScreenState extends BaseDynamicState + with TickerProviderStateMixin { + late ChewieThemeColorData _theme; + late TextEditingController _nameController; + bool get _isEditing => widget.editIndex != null; + + @override + void initState() { + super.initState(); + _theme = widget.baseTheme.copyWith( + isDarkMode: widget.isDarkMode, + ); + _nameController = TextEditingController( + text: _isEditing ? _theme.name : '', + ); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + void _save() { + final name = _nameController.text.trim(); + if (name.isEmpty) { + IToast.showTop(appLocalizations.themeNameEmpty); + return; + } + _theme.name = name; + if (!_isEditing) { + _theme.id = ChewieThemeColorData.generateId(); + } + if (widget.isDarkMode) { + if (_isEditing) { + appProvider.updateCustomDarkTheme(widget.editIndex!, _theme); + } else { + appProvider.addCustomDarkTheme(_theme); + final newIndex = ChewieThemeColorData.defaultDarkThemes.length + + appProvider.customDarkThemes.length - + 1; + appProvider.setDarkTheme(newIndex); + } + } else { + if (_isEditing) { + appProvider.updateCustomLightTheme(widget.editIndex!, _theme); + } else { + appProvider.addCustomLightTheme(_theme); + final newIndex = ChewieThemeColorData.defaultLightThemes.length + + appProvider.customLightThemes.length - + 1; + appProvider.setLightTheme(newIndex); + } + } + DialogNavigatorHelper.responsivePopPage(); + } + + void _updateColor(String field, Color color) { + setState(() { + switch (field) { + case 'scaffoldBackgroundColor': + _theme = _theme.copyWith(scaffoldBackgroundColor: color); + case 'canvasColor': + _theme = _theme.copyWith(canvasColor: color); + case 'cardColor': + _theme = _theme.copyWith(cardColor: color); + case 'textColor': + _theme = _theme.copyWith(textColor: color); + case 'textLightGreyColor': + _theme = _theme.copyWith(textLightGreyColor: color); + case 'textDarkGreyColor': + _theme = _theme.copyWith(textDarkGreyColor: color); + case 'hintColor': + _theme = _theme.copyWith(hintColor: color); + case 'primaryColor': + _theme = _theme.copyWith(primaryColor: color); + case 'indicatorColor': + _theme = _theme.copyWith(indicatorColor: color); + case 'cursorColor': + _theme = _theme.copyWith(cursorColor: color); + case 'textSelectionColor': + _theme = _theme.copyWith(textSelectionColor: color); + case 'textSelectionHandleColor': + _theme = _theme.copyWith(textSelectionHandleColor: color); + case 'buttonPrimaryColor': + _theme = _theme.copyWith(buttonPrimaryColor: color); + case 'buttonSecondaryColor': + _theme = _theme.copyWith(buttonSecondaryColor: color); + case 'buttonHoverColor': + _theme = _theme.copyWith(buttonHoverColor: color); + case 'buttonLightHoverColor': + _theme = _theme.copyWith(buttonLightHoverColor: color); + case 'buttonDisabledColor': + _theme = _theme.copyWith(buttonDisabledColor: color); + case 'appBarBackgroundColor': + _theme = _theme.copyWith(appBarBackgroundColor: color); + case 'appBarSurfaceTintColor': + _theme = _theme.copyWith(appBarSurfaceTintColor: color); + case 'appBarShadowColor': + _theme = _theme.copyWith(appBarShadowColor: color); + case 'hoverColor': + _theme = _theme.copyWith(hoverColor: color); + case 'splashColor': + _theme = _theme.copyWith(splashColor: color); + case 'highlightColor': + _theme = _theme.copyWith(highlightColor: color); + case 'shadowColor': + _theme = _theme.copyWith(shadowColor: color); + case 'dividerColor': + _theme = _theme.copyWith(dividerColor: color); + case 'borderColor': + _theme = _theme.copyWith(borderColor: color); + case 'iconColor': + _theme = _theme.copyWith(iconColor: color); + case 'scrollBarThumbColor': + _theme = _theme.copyWith(scrollBarThumbColor: color); + case 'scrollBarThumbHoverColor': + _theme = _theme.copyWith(scrollBarThumbHoverColor: color); + case 'scrollBarTrackColor': + _theme = _theme.copyWith(scrollBarTrackColor: color); + case 'scrollBarTrackHoverColor': + _theme = _theme.copyWith(scrollBarTrackHoverColor: color); + case 'successColor': + _theme = _theme.copyWith(successColor: color); + case 'warningColor': + _theme = _theme.copyWith(warningColor: color); + case 'errorColor': + _theme = _theme.copyWith(errorColor: color); + } + }); + } + + Color _getColor(String field) { + switch (field) { + case 'scaffoldBackgroundColor': + return _theme.scaffoldBackgroundColor; + case 'canvasColor': + return _theme.canvasColor; + case 'cardColor': + return _theme.cardColor; + case 'textColor': + return _theme.textColor; + case 'textLightGreyColor': + return _theme.textLightGreyColor; + case 'textDarkGreyColor': + return _theme.textDarkGreyColor; + case 'hintColor': + return _theme.hintColor; + case 'primaryColor': + return _theme.primaryColor; + case 'indicatorColor': + return _theme.indicatorColor; + case 'cursorColor': + return _theme.cursorColor; + case 'textSelectionColor': + return _theme.textSelectionColor; + case 'textSelectionHandleColor': + return _theme.textSelectionHandleColor; + case 'buttonPrimaryColor': + return _theme.buttonPrimaryColor; + case 'buttonSecondaryColor': + return _theme.buttonSecondaryColor; + case 'buttonHoverColor': + return _theme.buttonHoverColor; + case 'buttonLightHoverColor': + return _theme.buttonLightHoverColor; + case 'buttonDisabledColor': + return _theme.buttonDisabledColor; + case 'appBarBackgroundColor': + return _theme.appBarBackgroundColor; + case 'appBarSurfaceTintColor': + return _theme.appBarSurfaceTintColor; + case 'appBarShadowColor': + return _theme.appBarShadowColor; + case 'hoverColor': + return _theme.hoverColor; + case 'splashColor': + return _theme.splashColor; + case 'highlightColor': + return _theme.highlightColor; + case 'shadowColor': + return _theme.shadowColor; + case 'dividerColor': + return _theme.dividerColor; + case 'borderColor': + return _theme.borderColor; + case 'iconColor': + return _theme.iconColor; + case 'scrollBarThumbColor': + return _theme.scrollBarThumbColor; + case 'scrollBarThumbHoverColor': + return _theme.scrollBarThumbHoverColor; + case 'scrollBarTrackColor': + return _theme.scrollBarTrackColor; + case 'scrollBarTrackHoverColor': + return _theme.scrollBarTrackHoverColor; + case 'successColor': + return _theme.successColor; + case 'warningColor': + return _theme.warningColor; + case 'errorColor': + return _theme.errorColor; + default: + return Colors.white; + } + } + + Widget _buildColorRow(String field, String label) { + final color = _getColor(field); + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + ColorPickerBottomSheet.show( + context, + initialColor: color, + title: label, + onColorChanged: (newColor) => _updateColor(field, newColor), + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: ChewieTheme.dividerColor, width: 1), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text(label, style: ChewieTheme.bodyMedium), + ), + Text( + '#${color.toHex().substring(3)}', + style: ChewieTheme.bodySmall, + ), + const SizedBox(width: 4), + Icon(Icons.chevron_right, size: 18, color: ChewieTheme.iconColor), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ItemBuilder.buildSettingScreen( + context: context, + title: appLocalizations.themeEditor, + showTitleBar: widget.showTitleBar, + showBack: true, + padding: widget.padding, + onTapBack: () => DialogNavigatorHelper.responsivePopPage(), + actions: [ + TextButton( + onPressed: _save, + child: Text( + appLocalizations.save, + style: TextStyle(color: ChewieTheme.primaryColor), + ), + ), + ], + desktopActions: [ + ToolButton( + context: context, + icon: LucideIcons.check, + buttonSize: const Size(32, 32), + onPressed: _save, + ), + ], + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: InputItem( + controller: _nameController, + hint: appLocalizations.themeNameHint, + textInputAction: TextInputAction.done, + ), + ), + Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: ThemeItem( + themeColorData: _theme, + index: 0, + groupIndex: 0, + onChanged: null, + ), + ), + ), + CaptionItem( + title: appLocalizations.colorGroupBackground, + children: [ + _buildColorRow('scaffoldBackgroundColor', 'Scaffold Background'), + _buildColorRow('canvasColor', 'Canvas'), + _buildColorRow('cardColor', 'Card'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupText, + initiallyExpanded: false, + children: [ + _buildColorRow('textColor', 'Text'), + _buildColorRow('textLightGreyColor', 'Text Light Grey'), + _buildColorRow('textDarkGreyColor', 'Text Dark Grey'), + _buildColorRow('hintColor', 'Hint'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupAccent, + initiallyExpanded: false, + children: [ + _buildColorRow('primaryColor', 'Primary'), + _buildColorRow('indicatorColor', 'Indicator'), + _buildColorRow('cursorColor', 'Cursor'), + _buildColorRow('textSelectionColor', 'Text Selection'), + _buildColorRow('textSelectionHandleColor', 'Selection Handle'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupButtons, + initiallyExpanded: false, + children: [ + _buildColorRow('buttonPrimaryColor', 'Primary Button'), + _buildColorRow('buttonSecondaryColor', 'Secondary Button'), + _buildColorRow('buttonHoverColor', 'Button Hover'), + _buildColorRow('buttonLightHoverColor', 'Button Light Hover'), + _buildColorRow('buttonDisabledColor', 'Button Disabled'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupAppBar, + initiallyExpanded: false, + children: [ + _buildColorRow('appBarBackgroundColor', 'AppBar Background'), + _buildColorRow('appBarSurfaceTintColor', 'AppBar Surface Tint'), + _buildColorRow('appBarShadowColor', 'AppBar Shadow'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupSurfaces, + initiallyExpanded: false, + children: [ + _buildColorRow('hoverColor', 'Hover'), + _buildColorRow('splashColor', 'Splash'), + _buildColorRow('highlightColor', 'Highlight'), + _buildColorRow('shadowColor', 'Shadow'), + _buildColorRow('dividerColor', 'Divider'), + _buildColorRow('borderColor', 'Border'), + _buildColorRow('iconColor', 'Icon'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupScrollbar, + initiallyExpanded: false, + children: [ + _buildColorRow('scrollBarThumbColor', 'Thumb'), + _buildColorRow('scrollBarThumbHoverColor', 'Thumb Hover'), + _buildColorRow('scrollBarTrackColor', 'Track'), + _buildColorRow('scrollBarTrackHoverColor', 'Track Hover'), + ], + ), + CaptionItem( + title: appLocalizations.colorGroupStatus, + initiallyExpanded: false, + children: [ + _buildColorRow('successColor', 'Success'), + _buildColorRow('warningColor', 'Warning'), + _buildColorRow('errorColor', 'Error'), + ], + ), + const SizedBox(height: 30), + ], + ); + } +} diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 419ad6fd..47a4fc8f 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -250,28 +250,29 @@ class TokenLayoutState extends BaseDynamicState enableOnLongPress: false, contextMenu: _buildContextMenuButtons(), child: Selector( - selector: (context, provider) => provider.dragToReorder, - builder: (context, dragToReorder, child) => - Selector( - selector: (context, provider) => provider.issuerAndAccountShowOption, - builder: (context, issuerAndAccountShowOption, child) { - return GestureDetector( - onLongPress: dragToReorder && !ResponsiveUtil.isDesktop() - ? () { - showContextMenu(); - HapticFeedback.lightImpact(); - } - : null, - child: PressableAnimation( - child: _buildBody( - issuerAndAccountShowOption: issuerAndAccountShowOption, + selector: (context, provider) => provider.dragToReorder, + builder: (context, dragToReorder, child) => + Selector( + selector: (context, provider) => + provider.issuerAndAccountShowOption, + builder: (context, issuerAndAccountShowOption, child) { + return GestureDetector( + onLongPress: dragToReorder && !ResponsiveUtil.isDesktop() + ? () { + showContextMenu(); + HapticFeedback.lightImpact(); + } + : null, + child: PressableAnimation( + child: _buildBody( + issuerAndAccountShowOption: issuerAndAccountShowOption, + ), ), - ), - ); - } + ); + }, + ), ), ), - ), ); } diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index fbc448e5..23d1d284 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -496,6 +496,106 @@ class AppProvider with ChangeNotifier { notifyListeners(); } + final List _customLightThemes = + ChewieHiveUtil.getCustomLightThemes(); + + List get customLightThemes => _customLightThemes; + + final List _customDarkThemes = + ChewieHiveUtil.getCustomDarkThemes(); + + List get customDarkThemes => _customDarkThemes; + + void addCustomLightTheme(ChewieThemeColorData theme) { + _customLightThemes.add(theme); + ChewieHiveUtil.setCustomLightThemes(_customLightThemes); + notifyListeners(); + } + + void updateCustomLightTheme(int index, ChewieThemeColorData theme) { + if (index >= 0 && index < _customLightThemes.length) { + _customLightThemes[index] = theme; + ChewieHiveUtil.setCustomLightThemes(_customLightThemes); + int activeIndex = ChewieHiveUtil.getLightThemeIndex(); + int builtInCount = ChewieThemeColorData.defaultLightThemes.length; + if (activeIndex == builtInCount + index) { + _lightTheme = ChewieHiveUtil.getLightTheme(); + chewieProvider.lightTheme = _lightTheme; + } + notifyListeners(); + } + } + + void deleteCustomLightTheme(int index) { + if (index < 0 || index >= _customLightThemes.length) return; + _customLightThemes.removeAt(index); + ChewieHiveUtil.setCustomLightThemes(_customLightThemes); + int activeIndex = ChewieHiveUtil.getLightThemeIndex(); + int builtInCount = ChewieThemeColorData.defaultLightThemes.length; + int deletedGlobalIndex = builtInCount + index; + if (activeIndex == deletedGlobalIndex) { + setLightTheme(0); + } else if (activeIndex > deletedGlobalIndex) { + ChewieHiveUtil.setLightTheme(activeIndex - 1); + _lightTheme = ChewieHiveUtil.getLightTheme(); + chewieProvider.lightTheme = _lightTheme; + } + notifyListeners(); + } + + void addCustomDarkTheme(ChewieThemeColorData theme) { + _customDarkThemes.add(theme); + ChewieHiveUtil.setCustomDarkThemes(_customDarkThemes); + notifyListeners(); + } + + void updateCustomDarkTheme(int index, ChewieThemeColorData theme) { + if (index >= 0 && index < _customDarkThemes.length) { + _customDarkThemes[index] = theme; + ChewieHiveUtil.setCustomDarkThemes(_customDarkThemes); + int activeIndex = ChewieHiveUtil.getDarkThemeIndex(); + int builtInCount = ChewieThemeColorData.defaultDarkThemes.length; + if (activeIndex == builtInCount + index) { + _darkTheme = ChewieHiveUtil.getDarkTheme(); + chewieProvider.darkTheme = _darkTheme; + } + notifyListeners(); + } + } + + void deleteCustomDarkTheme(int index) { + if (index < 0 || index >= _customDarkThemes.length) return; + _customDarkThemes.removeAt(index); + ChewieHiveUtil.setCustomDarkThemes(_customDarkThemes); + int activeIndex = ChewieHiveUtil.getDarkThemeIndex(); + int builtInCount = ChewieThemeColorData.defaultDarkThemes.length; + int deletedGlobalIndex = builtInCount + index; + if (activeIndex == deletedGlobalIndex) { + setDarkTheme(0); + } else if (activeIndex > deletedGlobalIndex) { + ChewieHiveUtil.setDarkTheme(activeIndex - 1); + _darkTheme = ChewieHiveUtil.getDarkTheme(); + chewieProvider.darkTheme = _darkTheme; + } + notifyListeners(); + } + + void setLightPrimaryColorOverride(Color? color, int paletteIndex) { + ChewieHiveUtil.setCustomLightPrimaryColor(color); + ChewieHiveUtil.setLightThemePrimaryColorIndex(paletteIndex); + _lightTheme = ChewieHiveUtil.getLightTheme(); + chewieProvider.lightTheme = _lightTheme; + notifyListeners(); + } + + void setDarkPrimaryColorOverride(Color? color, int paletteIndex) { + ChewieHiveUtil.setCustomDarkPrimaryColor(color); + ChewieHiveUtil.setDarkThemePrimaryColorIndex(paletteIndex); + _darkTheme = ChewieHiveUtil.getDarkTheme(); + chewieProvider.darkTheme = _darkTheme; + notifyListeners(); + } + Locale? _locale = ChewieHiveUtil.getLocale(); Locale? get locale => _locale; diff --git a/lib/Utils/theme_util.dart b/lib/Utils/theme_util.dart new file mode 100644 index 00000000..6a3c1f54 --- /dev/null +++ b/lib/Utils/theme_util.dart @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'dart:convert'; +import 'dart:io'; + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; + +import '../l10n/l10n.dart'; + +class ThemeUtil { + static const int _version = 1; + + static String exportThemeToJson(ChewieThemeColorData theme) { + return jsonEncode({ + 'cloudotp_theme_version': _version, + 'theme': theme.toJson(), + }); + } + + static ChewieThemeColorData? importThemeFromJson(String jsonString) { + try { + final map = jsonDecode(jsonString) as Map; + if (!map.containsKey('cloudotp_theme_version') || + !map.containsKey('theme')) { + return null; + } + final version = map['cloudotp_theme_version'] as int?; + if (version == null || version > _version) return null; + return ChewieThemeColorData.fromJson( + map['theme'] as Map); + } catch (_) { + return null; + } + } + + static void exportToClipboard( + BuildContext context, ChewieThemeColorData theme) { + ChewieUtils.copy(context, exportThemeToJson(theme)); + } + + static Future exportToFile(ChewieThemeColorData theme) async { + final json = exportThemeToJson(theme); + final fileName = '${theme.name.replaceAll(RegExp(r'[^\w一-鿿-]'), '_')}.json'; + String? filePath = await FileUtil.saveFile( + dialogTitle: appLocalizations.exportTheme, + fileName: fileName, + type: FileType.custom, + allowedExtensions: ['json'], + ); + if (filePath == null) return false; + final file = File(filePath); + await file.writeAsString(json); + return true; + } + + static Future importFromClipboard() async { + final data = await ChewieUtils.getClipboardData(); + if (data == null || data.isEmpty) return null; + return importThemeFromJson(data); + } + + static Future importFromFile() async { + FilePickerResult? result = await FileUtil.pickFiles( + dialogTitle: appLocalizations.importTheme, + type: FileType.custom, + allowedExtensions: ['json'], + ); + if (result == null || result.files.isEmpty) return null; + final path = result.files.single.path; + if (path == null) return null; + final file = File(path); + final content = await file.readAsString(); + return importThemeFromJson(content); + } +} diff --git a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart new file mode 100644 index 00000000..dd1dec10 --- /dev/null +++ b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flex_color_picker/flex_color_picker.dart'; +import 'package:flutter/material.dart'; + +import '../../l10n/l10n.dart'; + +class ColorPickerBottomSheet extends StatefulWidget { + final Color initialColor; + final String title; + final ValueChanged onColorChanged; + + const ColorPickerBottomSheet({ + super.key, + required this.initialColor, + required this.title, + required this.onColorChanged, + }); + + static Future show( + BuildContext context, { + required Color initialColor, + required String title, + required ValueChanged onColorChanged, + }) { + return BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => ColorPickerBottomSheet( + initialColor: initialColor, + title: title, + onColorChanged: onColorChanged, + ), + ); + } + + @override + State createState() => _ColorPickerBottomSheetState(); +} + +class _ColorPickerBottomSheetState extends State { + late Color _currentColor; + + @override + void initState() { + super.initState(); + _currentColor = widget.initialColor; + } + + Radius radius = ChewieDimens.defaultRadius; + + @override + Widget build(BuildContext context) { + return AnimatedPadding( + padding: MediaQuery.of(context).viewInsets, + duration: const Duration(milliseconds: 100), + child: Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: radius, + bottom: + ResponsiveUtil.isWideDevice() ? radius : Radius.zero), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 12), + alignment: Alignment.center, + child: Text(widget.title, style: ChewieTheme.titleLarge), + ), + ColorPicker( + color: _currentColor, + onColorChanged: (Color color) { + setState(() => _currentColor = color); + }, + pickersEnabled: const { + ColorPickerType.wheel: true, + ColorPickerType.primary: false, + ColorPickerType.accent: false, + }, + enableShadesSelection: true, + enableOpacity: true, + showColorCode: true, + colorCodeHasColor: true, + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyFormat: ColorPickerCopyFormat.hexRRGGBB, + ), + actionButtons: const ColorPickerActionButtons( + okButton: false, + closeButton: false, + dialogActionButtons: false, + ), + width: 44, + height: 44, + borderRadius: 22, + heading: null, + subheading: null, + wheelWidth: 20, + wheelDiameter: 220, + ), + Container( + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), + fontSizeDelta: 2, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: appLocalizations.confirm, + onPressed: () { + widget.onColorChanged(_currentColor); + Navigator.of(context).pop(); + }, + fontSizeDelta: 2, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index f93ec681..a600dac2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -794,5 +794,34 @@ "importKeepLocal": "Keep Local", "importOverwriteLocal": "Overwrite Local", "tokenCount": "Tokens", - "categoryCount": "Categories" + "categoryCount": "Categories", + "themeEditor": "Theme Editor", + "themeName": "Theme Name", + "themeNameHint": "Enter theme name", + "themeNameEmpty": "Theme name cannot be empty", + "chooseBaseTheme": "Choose Base Theme", + "editTheme": "Edit Theme", + "deleteTheme": "Delete Theme", + "deleteThemeConfirm": "Are you sure to delete this theme?", + "duplicateTheme": "Duplicate Theme", + "exportTheme": "Export Theme", + "importTheme": "Import Theme", + "importFromClipboard": "Import from Clipboard", + "importFromFile": "Import from File", + "exportToClipboard": "Export to Clipboard", + "exportToFile": "Export to File", + "themeImportSuccess": "Theme imported successfully", + "themeImportFailed": "Failed to import theme", + "themeExportSuccess": "Theme exported successfully", + "colorGroupBackground": "Background", + "colorGroupText": "Text", + "colorGroupAccent": "Accent", + "colorGroupButtons": "Buttons", + "colorGroupAppBar": "App Bar", + "colorGroupSurfaces": "Surfaces", + "colorGroupScrollbar": "Scrollbar", + "colorGroupStatus": "Status", + "accentColor": "Accent Color", + "resetToDefault": "Default", + "customColor": "Custom" } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 6159ba94..a28090e5 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -794,5 +794,34 @@ "importKeepLocal": "ローカルを保持", "importOverwriteLocal": "ローカルを上書き", "tokenCount": "トークン", - "categoryCount": "カテゴリ" + "categoryCount": "カテゴリ", + "themeEditor": "テーマエディタ", + "themeName": "テーマ名", + "themeNameHint": "テーマ名を入力", + "themeNameEmpty": "テーマ名を空にできません", + "chooseBaseTheme": "ベーステーマを選択", + "editTheme": "テーマを編集", + "deleteTheme": "テーマを削除", + "deleteThemeConfirm": "このテーマを削除してもよろしいですか?", + "duplicateTheme": "テーマを複製", + "exportTheme": "テーマをエクスポート", + "importTheme": "テーマをインポート", + "importFromClipboard": "クリップボードからインポート", + "importFromFile": "ファイルからインポート", + "exportToClipboard": "クリップボードにエクスポート", + "exportToFile": "ファイルにエクスポート", + "themeImportSuccess": "テーマのインポートに成功しました", + "themeImportFailed": "テーマのインポートに失敗しました", + "themeExportSuccess": "テーマのエクスポートに成功しました", + "colorGroupBackground": "背景", + "colorGroupText": "テキスト", + "colorGroupAccent": "アクセント", + "colorGroupButtons": "ボタン", + "colorGroupAppBar": "アプリバー", + "colorGroupSurfaces": "サーフェス", + "colorGroupScrollbar": "スクロールバー", + "colorGroupStatus": "ステータス", + "accentColor": "アクセントカラー", + "resetToDefault": "デフォルト", + "customColor": "カスタム" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index dc508a52..5f48eaef 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -794,5 +794,34 @@ "importKeepLocal": "保留本地", "importOverwriteLocal": "覆盖本地", "tokenCount": "令牌", - "categoryCount": "分类" + "categoryCount": "分类", + "themeEditor": "主题编辑器", + "themeName": "主题名称", + "themeNameHint": "输入主题名称", + "themeNameEmpty": "主题名称不能为空", + "chooseBaseTheme": "选择基础主题", + "editTheme": "编辑主题", + "deleteTheme": "删除主题", + "deleteThemeConfirm": "确定要删除该主题吗?", + "duplicateTheme": "复制主题", + "exportTheme": "导出主题", + "importTheme": "导入主题", + "importFromClipboard": "从剪贴板导入", + "importFromFile": "从文件导入", + "exportToClipboard": "导出到剪贴板", + "exportToFile": "导出到文件", + "themeImportSuccess": "主题导入成功", + "themeImportFailed": "主题导入失败", + "themeExportSuccess": "主题导出成功", + "colorGroupBackground": "背景", + "colorGroupText": "文字", + "colorGroupAccent": "强调色", + "colorGroupButtons": "按钮", + "colorGroupAppBar": "导航栏", + "colorGroupSurfaces": "表面", + "colorGroupScrollbar": "滚动条", + "colorGroupStatus": "状态", + "accentColor": "强调色", + "resetToDefault": "默认", + "customColor": "自定义" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index c0b531af..40f94278 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -794,5 +794,34 @@ "importKeepLocal": "保留本地", "importOverwriteLocal": "覆蓋本地", "tokenCount": "令牌", - "categoryCount": "分類" + "categoryCount": "分類", + "themeEditor": "主題編輯器", + "themeName": "主題名稱", + "themeNameHint": "輸入主題名稱", + "themeNameEmpty": "主題名稱不能為空", + "chooseBaseTheme": "選擇基礎主題", + "editTheme": "編輯主題", + "deleteTheme": "刪除主題", + "deleteThemeConfirm": "確定要刪除該主題嗎?", + "duplicateTheme": "複製主題", + "exportTheme": "匯出主題", + "importTheme": "匯入主題", + "importFromClipboard": "從剪貼簿匯入", + "importFromFile": "從檔案匯入", + "exportToClipboard": "匯出到剪貼簿", + "exportToFile": "匯出到檔案", + "themeImportSuccess": "主題匯入成功", + "themeImportFailed": "主題匯入失敗", + "themeExportSuccess": "主題匯出成功", + "colorGroupBackground": "背景", + "colorGroupText": "文字", + "colorGroupAccent": "強調色", + "colorGroupButtons": "按鈕", + "colorGroupAppBar": "導覽列", + "colorGroupSurfaces": "表面", + "colorGroupScrollbar": "捲軸", + "colorGroupStatus": "狀態", + "accentColor": "強調色", + "resetToDefault": "預設", + "customColor": "自訂" } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 29269453..bda5414c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -502,6 +502,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" + flex_color_picker: + dependency: "direct main" + description: + name: flex_color_picker + sha256: a0979dd61f21b634717b98eb4ceaed2bfe009fe020ce8597aaf164b9eeb57aaa + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.8.0" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.1" flutter: dependency: "direct main" description: flutter @@ -2348,4 +2364,4 @@ packages: version: "0.2.3" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index e0c339c4..4b9c38cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: vector_math: ^2.1.4 # 矢量数学 path_drawing: ^1.0.1 # 路径绘制 auto_size_text: ^3.0.0 # 自适应文本 + flex_color_picker: ^3.6.0 # 颜色选择器 flutter_inappwebview: ^6.0.0 # Webview flutter_widget_from_html: ^0.15.2 # 将HTML渲染成组件 lucide_icons: diff --git a/third-party/chewie/lib/src/Resources/colors.dart b/third-party/chewie/lib/src/Resources/colors.dart index 19578a71..b7d99651 100644 --- a/third-party/chewie/lib/src/Resources/colors.dart +++ b/third-party/chewie/lib/src/Resources/colors.dart @@ -146,10 +146,10 @@ extension HexColor on Color { } String toHex({bool leadingHashSign = true}) => '${leadingHashSign ? '#' : ''}' - '${a.toInt().toRadixString(16).padLeft(2, '0')}' - '${r.toInt().toRadixString(16).padLeft(2, '0')}' - '${g.toInt().toRadixString(16).padLeft(2, '0')}' - '${b.toInt().toRadixString(16).padLeft(2, '0')}'; + '${(a * 255).round().toRadixString(16).padLeft(2, '0')}' + '${(r * 255).round().toRadixString(16).padLeft(2, '0')}' + '${(g * 255).round().toRadixString(16).padLeft(2, '0')}' + '${(b * 255).round().toRadixString(16).padLeft(2, '0')}'; } extension HexColorString on String { diff --git a/third-party/chewie/lib/src/Resources/theme_color_data.dart b/third-party/chewie/lib/src/Resources/theme_color_data.dart index 05ab0f40..5cb824b2 100644 --- a/third-party/chewie/lib/src/Resources/theme_color_data.dart +++ b/third-party/chewie/lib/src/Resources/theme_color_data.dart @@ -278,6 +278,7 @@ class ChewieThemeColorData { static List defaultDarkThemes = [ ChewieThemeColorData( + isDarkMode: true, id: "PureBlack", name: "极简黑", canvasColor: const Color(0xFF212121), @@ -316,6 +317,7 @@ class ChewieThemeColorData { errorColor: const Color(0xFFCF6679), ), ChewieThemeColorData( + isDarkMode: true, id: "BlueIron", name: "蓝铁", scaffoldBackgroundColor: const Color(0xFF1D2733), @@ -354,6 +356,7 @@ class ChewieThemeColorData { errorColor: const Color(0xFFCF6679), ), ChewieThemeColorData( + isDarkMode: true, id: "GithubDark", name: "Github深色", canvasColor: const Color(0xFF0d1117), @@ -679,6 +682,103 @@ class ChewieThemeColorData { 'errorColor': errorColor.toHex(), }; + static String generateId() => + 'custom_${DateTime.now().millisecondsSinceEpoch}'; + + ChewieThemeColorData copyWith({ + bool? isDarkMode, + String? id, + String? name, + String? description, + Color? primaryColor, + Color? canvasColor, + Color? scaffoldBackgroundColor, + Color? cardColor, + Color? hintColor, + Color? indicatorColor, + Color? hoverColor, + Color? splashColor, + Color? highlightColor, + Color? shadowColor, + Color? iconColor, + Color? appBarBackgroundColor, + Color? appBarSurfaceTintColor, + Color? appBarShadowColor, + double? appBarElevation, + double? appBarScrollUnderElevation, + Color? textColor, + Color? textLightGreyColor, + Color? textDarkGreyColor, + Color? buttonPrimaryColor, + Color? buttonSecondaryColor, + Color? buttonDisabledColor, + Color? buttonHoverColor, + Color? buttonLightHoverColor, + Color? textSelectionColor, + Color? textSelectionHandleColor, + Color? cursorColor, + Color? dividerColor, + Color? borderColor, + Color? scrollBarThumbColor, + Color? scrollBarThumbHoverColor, + Color? scrollBarTrackColor, + Color? scrollBarTrackHoverColor, + Color? successColor, + Color? warningColor, + Color? errorColor, + }) { + return ChewieThemeColorData( + isDarkMode: isDarkMode ?? this.isDarkMode, + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + primaryColor: primaryColor ?? this.primaryColor, + canvasColor: canvasColor ?? this.canvasColor, + scaffoldBackgroundColor: + scaffoldBackgroundColor ?? this.scaffoldBackgroundColor, + cardColor: cardColor ?? this.cardColor, + hintColor: hintColor ?? this.hintColor, + indicatorColor: indicatorColor ?? this.indicatorColor, + hoverColor: hoverColor ?? this.hoverColor, + splashColor: splashColor ?? this.splashColor, + highlightColor: highlightColor ?? this.highlightColor, + shadowColor: shadowColor ?? this.shadowColor, + iconColor: iconColor ?? this.iconColor, + appBarBackgroundColor: + appBarBackgroundColor ?? this.appBarBackgroundColor, + appBarSurfaceTintColor: + appBarSurfaceTintColor ?? this.appBarSurfaceTintColor, + appBarShadowColor: appBarShadowColor ?? this.appBarShadowColor, + appBarElevation: appBarElevation ?? this.appBarElevation, + appBarScrollUnderElevation: + appBarScrollUnderElevation ?? this.appBarScrollUnderElevation, + textColor: textColor ?? this.textColor, + textLightGreyColor: textLightGreyColor ?? this.textLightGreyColor, + textDarkGreyColor: textDarkGreyColor ?? this.textDarkGreyColor, + buttonPrimaryColor: buttonPrimaryColor ?? this.buttonPrimaryColor, + buttonSecondaryColor: buttonSecondaryColor ?? this.buttonSecondaryColor, + buttonDisabledColor: buttonDisabledColor ?? this.buttonDisabledColor, + buttonHoverColor: buttonHoverColor ?? this.buttonHoverColor, + buttonLightHoverColor: + buttonLightHoverColor ?? this.buttonLightHoverColor, + textSelectionColor: textSelectionColor ?? this.textSelectionColor, + textSelectionHandleColor: + textSelectionHandleColor ?? this.textSelectionHandleColor, + cursorColor: cursorColor ?? this.cursorColor, + dividerColor: dividerColor ?? this.dividerColor, + borderColor: borderColor ?? this.borderColor, + scrollBarThumbColor: scrollBarThumbColor ?? this.scrollBarThumbColor, + scrollBarThumbHoverColor: + scrollBarThumbHoverColor ?? this.scrollBarThumbHoverColor, + scrollBarTrackColor: scrollBarTrackColor ?? this.scrollBarTrackColor, + scrollBarTrackHoverColor: + scrollBarTrackHoverColor ?? this.scrollBarTrackHoverColor, + successColor: successColor ?? this.successColor, + warningColor: warningColor ?? this.warningColor, + errorColor: errorColor ?? this.errorColor, + ); + } + factory ChewieThemeColorData.fromJson(Map map) { return ChewieThemeColorData( id: map['id'] ?? "", diff --git a/third-party/chewie/lib/src/Utils/System/hive_util.dart b/third-party/chewie/lib/src/Utils/System/hive_util.dart index 81de330c..e5192ea0 100644 --- a/third-party/chewie/lib/src/Utils/System/hive_util.dart +++ b/third-party/chewie/lib/src/Utils/System/hive_util.dart @@ -183,7 +183,7 @@ class ChewieHiveUtil { static int getLightThemeIndex() { int index = ChewieHiveUtil.getInt(ChewieHiveUtil.lightThemeIndexKey, defaultValue: 0); - if (index > ChewieThemeColorData.defaultLightThemes.length) { + if (index >= ChewieThemeColorData.defaultLightThemes.length) { String? json = ChewieHiveUtil.getString(ChewieHiveUtil.customLightThemeListKey); if (json == null || json.isEmpty) { @@ -191,7 +191,7 @@ class ChewieHiveUtil { return 0; } else { List list = jsonDecode(json); - if (index > + if (index >= ChewieThemeColorData.defaultLightThemes.length + list.length) { setLightTheme(0); return 0; @@ -208,7 +208,7 @@ class ChewieHiveUtil { static int getDarkThemeIndex() { int index = ChewieHiveUtil.getInt(ChewieHiveUtil.darkThemeIndexKey, defaultValue: 0); - if (index > ChewieThemeColorData.defaultDarkThemes.length) { + if (index >= ChewieThemeColorData.defaultDarkThemes.length) { String? json = ChewieHiveUtil.getString(ChewieHiveUtil.customDarkThemeListKey); if (json == null || json.isEmpty) { @@ -216,7 +216,7 @@ class ChewieHiveUtil { return 0; } else { List list = jsonDecode(json); - if (index > + if (index >= ChewieThemeColorData.defaultDarkThemes.length + list.length) { setDarkTheme(0); return 0; @@ -230,56 +230,77 @@ class ChewieHiveUtil { } } + static ChewieThemeColorData _applyPrimaryColorOverride( + ChewieThemeColorData theme, bool isDark) { + final key = + isDark ? customDarkThemePrimaryColorKey : customLightThemePrimaryColorKey; + String? hex = ChewieHiveUtil.getString(key, autoCreate: false); + if (hex == null || hex.isEmpty) return theme; + final color = HexColor.fromHex(hex); + return theme.copyWith( + primaryColor: color, + indicatorColor: color, + cursorColor: color, + textSelectionColor: color.withAlpha(70), + textSelectionHandleColor: color, + buttonPrimaryColor: color, + ); + } + static ChewieThemeColorData getLightTheme() { int index = ChewieHiveUtil.getInt(ChewieHiveUtil.lightThemeIndexKey, defaultValue: 0); - if (index > ChewieThemeColorData.defaultLightThemes.length) { + ChewieThemeColorData theme; + if (index >= ChewieThemeColorData.defaultLightThemes.length) { String? json = ChewieHiveUtil.getString(ChewieHiveUtil.customLightThemeListKey); if (json == null || json.isEmpty) { setLightTheme(0); - return ChewieThemeColorData.defaultLightThemes[0]; + theme = ChewieThemeColorData.defaultLightThemes[0]; } else { List list = jsonDecode(json); - if (index > + if (index >= ChewieThemeColorData.defaultLightThemes.length + list.length) { setLightTheme(0); - return ChewieThemeColorData.defaultLightThemes[0]; + theme = ChewieThemeColorData.defaultLightThemes[0]; } else { - return ChewieThemeColorData.fromJson( + theme = ChewieThemeColorData.fromJson( list[index - ChewieThemeColorData.defaultLightThemes.length]); } } } else { - return ChewieThemeColorData.defaultLightThemes[ChewieUtils.patchEnum( + theme = ChewieThemeColorData.defaultLightThemes[ChewieUtils.patchEnum( index, ChewieThemeColorData.defaultLightThemes.length)]; } + return _applyPrimaryColorOverride(theme, false); } static ChewieThemeColorData getDarkTheme() { int index = ChewieHiveUtil.getInt(ChewieHiveUtil.darkThemeIndexKey, defaultValue: 0); - if (index > ChewieThemeColorData.defaultDarkThemes.length) { + ChewieThemeColorData theme; + if (index >= ChewieThemeColorData.defaultDarkThemes.length) { String? json = ChewieHiveUtil.getString(ChewieHiveUtil.customDarkThemeListKey); if (json == null || json.isEmpty) { setDarkTheme(0); - return ChewieThemeColorData.defaultDarkThemes[0]; + theme = ChewieThemeColorData.defaultDarkThemes[0]; } else { List list = jsonDecode(json); - if (index > + if (index >= ChewieThemeColorData.defaultDarkThemes.length + list.length) { setDarkTheme(0); - return ChewieThemeColorData.defaultDarkThemes[0]; + theme = ChewieThemeColorData.defaultDarkThemes[0]; } else { - return ChewieThemeColorData.fromJson( + theme = ChewieThemeColorData.fromJson( list[index - ChewieThemeColorData.defaultDarkThemes.length]); } } } else { - return ChewieThemeColorData.defaultDarkThemes[ChewieUtils.patchEnum( + theme = ChewieThemeColorData.defaultDarkThemes[ChewieUtils.patchEnum( index, ChewieThemeColorData.defaultDarkThemes.length)]; } + return _applyPrimaryColorOverride(theme, true); } static void setLightTheme(int index) => @@ -288,6 +309,86 @@ class ChewieHiveUtil { static void setDarkTheme(int index) => ChewieHiveUtil.put(ChewieHiveUtil.darkThemeIndexKey, index); + static List getCustomLightThemes() { + String? json = + ChewieHiveUtil.getString(ChewieHiveUtil.customLightThemeListKey); + if (json == null || json.isEmpty) return []; + List list = jsonDecode(json); + return list.map((e) => ChewieThemeColorData.fromJson(e)).toList(); + } + + static List getCustomDarkThemes() { + String? json = + ChewieHiveUtil.getString(ChewieHiveUtil.customDarkThemeListKey); + if (json == null || json.isEmpty) return []; + List list = jsonDecode(json); + return list.map((e) => ChewieThemeColorData.fromJson(e)).toList(); + } + + static void setCustomLightThemes(List themes) { + ChewieHiveUtil.put(ChewieHiveUtil.customLightThemeListKey, + jsonEncode(themes.map((e) => e.toJson()).toList())); + } + + static void setCustomDarkThemes(List themes) { + ChewieHiveUtil.put(ChewieHiveUtil.customDarkThemeListKey, + jsonEncode(themes.map((e) => e.toJson()).toList())); + } + + static Color? getCustomLightPrimaryColor() { + String? hex = ChewieHiveUtil.getString( + ChewieHiveUtil.customLightThemePrimaryColorKey, + autoCreate: false); + if (hex == null || hex.isEmpty) return null; + return HexColor.fromHex(hex); + } + + static void setCustomLightPrimaryColor(Color? color) { + if (color == null) { + ChewieHiveUtil.delete(ChewieHiveUtil.customLightThemePrimaryColorKey); + } else { + ChewieHiveUtil.put( + ChewieHiveUtil.customLightThemePrimaryColorKey, color.toHex()); + } + } + + static Color? getCustomDarkPrimaryColor() { + String? hex = ChewieHiveUtil.getString( + ChewieHiveUtil.customDarkThemePrimaryColorKey, + autoCreate: false); + if (hex == null || hex.isEmpty) return null; + return HexColor.fromHex(hex); + } + + static void setCustomDarkPrimaryColor(Color? color) { + if (color == null) { + ChewieHiveUtil.delete(ChewieHiveUtil.customDarkThemePrimaryColorKey); + } else { + ChewieHiveUtil.put( + ChewieHiveUtil.customDarkThemePrimaryColorKey, color.toHex()); + } + } + + static int getLightThemePrimaryColorIndex() { + return ChewieHiveUtil.getInt( + ChewieHiveUtil.lightThemePrimaryColorIndexKey, + defaultValue: 0); + } + + static void setLightThemePrimaryColorIndex(int index) { + ChewieHiveUtil.put(ChewieHiveUtil.lightThemePrimaryColorIndexKey, index); + } + + static int getDarkThemePrimaryColorIndex() { + return ChewieHiveUtil.getInt( + ChewieHiveUtil.darkThemePrimaryColorIndexKey, + defaultValue: 0); + } + + static void setDarkThemePrimaryColorIndex(int index) { + ChewieHiveUtil.put(ChewieHiveUtil.darkThemePrimaryColorIndexKey, index); + } + static List getSortableItems( String key, List defaultValue, diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/core/utils/helpers.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/core/utils/helpers.dart index e87b4df4..7d36e791 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/core/utils/helpers.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/core/utils/helpers.dart @@ -19,10 +19,10 @@ Future showContextMenu( bool allowSnapshotting = true, bool maintainState = false, FocusNode? focusNode, + bool useRootNavigator = true, }) async { final menuState = ContextMenuState(menu: contextMenu); - return await Navigator.push( - context, + return await Navigator.of(context, rootNavigator: useRootNavigator).push( PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) { return Stack( diff --git a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart index 6fed2376..1dd812cd 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart @@ -512,7 +512,10 @@ class CaptionItemState extends BaseDynamicState child: SizeTransition( sizeFactor: _sizeAnimation, axisAlignment: -1.0, - child: Column(children: widget.children), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.children, + ), ), ), ], diff --git a/third-party/chewie/lib/src/Widgets/Tile/theme_item.dart b/third-party/chewie/lib/src/Widgets/Tile/theme_item.dart index 2c1bb5a6..e80604c4 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/theme_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/theme_item.dart @@ -6,6 +6,7 @@ class ThemeItem extends StatefulWidget { final int index; final int groupIndex; final Function(int?)? onChanged; + final VoidCallback? onLongPress; const ThemeItem({ Key? key, @@ -13,6 +14,7 @@ class ThemeItem extends StatefulWidget { required this.index, required this.groupIndex, required this.onChanged, + this.onLongPress, }) : super(key: key); @override @@ -22,48 +24,56 @@ class ThemeItem extends StatefulWidget { class _ThemeItemState extends State { @override Widget build(BuildContext context) { - return Container( - width: 107.3, - height: 166.4, - margin: EdgeInsets.only(left: widget.index == 0 ? 10 : 0, right: 10), - child: Column( - children: [ - Container( - padding: - const EdgeInsets.only(top: 10, bottom: 0, left: 8, right: 8), - decoration: BoxDecoration( - color: widget.themeColorData.scaffoldBackgroundColor, - borderRadius: const BorderRadius.all(Radius.circular(10)), - border: ChewieTheme.border, - ), - child: Column( - children: [ - _buildCardRow(widget.themeColorData), - const SizedBox(height: 5), - _buildCardRow(widget.themeColorData), - const SizedBox(height: 15), - Radio( - value: widget.index, - groupValue: widget.groupIndex, - onChanged: widget.onChanged, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - fillColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.selected)) { - return widget.themeColorData.primaryColor; - } else { - return widget.themeColorData.textLightGreyColor; - } - }), + return GestureDetector( + onLongPress: widget.onLongPress, + onTap: () => widget.onChanged?.call(widget.index), + child: Container( + width: 107.3, + height: 166.4, + margin: EdgeInsets.only(left: widget.index == 0 ? 10 : 0, right: 10), + child: Column( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.only( + top: 10, bottom: 0, left: 8, right: 8), + decoration: BoxDecoration( + color: widget.themeColorData.scaffoldBackgroundColor, + borderRadius: BorderRadius.circular(10), + border: ChewieTheme.border, ), - ], + child: Column( + children: [ + _buildCardRow(widget.themeColorData), + const SizedBox(height: 5), + _buildCardRow(widget.themeColorData), + const SizedBox(height: 15), + Radio( + value: widget.index, + groupValue: widget.groupIndex, + onChanged: widget.onChanged, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return widget.themeColorData.primaryColor; + } else { + return widget.themeColorData.textLightGreyColor; + } + }), + ), + ], + ), + ), ), - ), - const SizedBox(height: 8), - Text( - widget.themeColorData.i18nName, - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(height: 8), + Text( + widget.themeColorData.i18nName, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), ); } @@ -75,7 +85,7 @@ class _ThemeItemState extends State { padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: themeColorData.canvasColor, - borderRadius: const BorderRadius.all(Radius.circular(5)), + borderRadius: BorderRadius.circular(5), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, @@ -139,40 +149,39 @@ class EmptyThemeItem extends StatefulWidget { class _EmptyThemeItemState extends State { @override Widget build(BuildContext context) { - return Container( - width: 107.3, - height: 166.4, - margin: const EdgeInsets.only(right: 10), - child: Column( - children: [ - Container( - width: 107.3, - height: 141.7, - padding: const EdgeInsets.only(left: 8, right: 8), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(10)), - border: ChewieTheme.border, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( + return GestureDetector( + onTap: widget.onTap, + child: Container( + width: 107.3, + height: 166.4, + margin: const EdgeInsets.only(right: 10), + child: Column( + children: [ + Expanded( + child: Container( + width: 107.3, + padding: const EdgeInsets.only( + top: 10, bottom: 0, left: 8, right: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: ChewieTheme.border, + ), + child: Icon( Icons.add_rounded, size: 30, - color: ChewieTheme.titleSmall.color, + color: ChewieTheme.bodySmall.color, ), - const SizedBox(height: 6), - Text(chewieLocalizations.newTheme, - style: ChewieTheme.titleSmall), - ], + ), ), - ), - const SizedBox(height: 6), - Text( - "", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(height: 8), + Text( + chewieLocalizations.newTheme, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), ); } From 8728ebdb80aa0833824ba6fbcc90e91c36c4fd54 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 01:34:15 +0800 Subject: [PATCH 04/36] Support ios --- .github/workflows/release.yml | 2 +- ios/Flutter/AppFrameworkInfo.plist | 2 - ios/Podfile | 4 +- ios/Podfile.lock | 193 +++------- ios/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + ios/Runner/AppDelegate.swift | 7 +- ios/Runner/Info.plist | 53 ++- lib/Screens/Lock/pin_change_screen.dart | 5 +- lib/Screens/Lock/pin_verify_screen.dart | 33 +- lib/Screens/Setting/about_setting_screen.dart | 2 +- lib/Screens/Setting/select_theme_screen.dart | 171 ++++----- lib/Screens/Setting/setting_safe_screen.dart | 5 +- lib/Screens/Setting/theme_editor_screen.dart | 347 +++++++++++++++--- lib/Screens/Token/import_preview_screen.dart | 31 +- lib/Screens/main_screen.dart | 3 +- lib/Utils/constant.dart | 13 +- lib/Utils/hive_util.dart | 4 +- lib/Utils/theme_util.dart | 11 +- lib/Widgets/BottomSheet/add_bottom_sheet.dart | 10 +- .../color_picker_bottom_sheet.dart | 129 +++---- macos/Podfile.lock | 9 +- pubspec.lock | 8 +- pubspec.yaml | 4 +- .../lib/src/Resources/theme_color_data.dart | 10 +- .../lib/src/Utils/System/file_util.dart | 6 +- .../chewie/lib/src/Utils/System/uri_util.dart | 15 +- third-party/chewie/lib/src/Utils/utils.dart | 27 +- .../lib/src/Widgets/Tile/entry_item.dart | 7 +- 29 files changed, 658 insertions(+), 462 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e487e2f..83979732 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: # Setup Flutter - name: Setup Flutter - uses: subosito/flutter-action@v2.21.0 + uses: subosito/flutter-action@v2.23.0 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: 'stable' diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 7c569640..391a902b 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/ios/Podfile b/ios/Podfile index 2f6ae3bd..c32d94fd 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -107,4 +107,4 @@ post_install do |installer| end end -end \ No newline at end of file +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7a780080..bede7a18 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - app_links (0.0.2): + - Flutter - audio_session (0.0.1): - Flutter - biometric_storage (0.0.1): @@ -43,12 +45,10 @@ PODS: - flutter_inappwebview_ios (0.0.1): - Flutter - flutter_inappwebview_ios/Core (= 0.0.1) - - OrderedSet (~> 5.0) + - OrderedSet (~> 6.0.3) - flutter_inappwebview_ios/Core (0.0.1): - Flutter - - OrderedSet (~> 5.0) - - flutter_local_notifications (0.0.1): - - Flutter + - OrderedSet (~> 6.0.3) - flutter_native_splash (0.0.1): - Flutter - flutter_secure_storage (6.0.0): @@ -57,37 +57,9 @@ PODS: - Flutter - fluttertoast (0.0.2): - Flutter - - Toast - FMDB/SQLCipher (2.7.11): - SQLCipher (~> 4.0) - - GoogleDataTransport (9.4.1): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30911.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - - GoogleMLKit/BarcodeScanning (6.0.0): - - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 5.0.0) - - GoogleMLKit/MLKitCore (6.0.0): - - MLKitCommon (~> 11.0.0) - - GoogleToolboxForMac/Defines (4.2.1) - - GoogleToolboxForMac/Logger (4.2.1): - - GoogleToolboxForMac/Defines (= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (4.2.1)": - - GoogleToolboxForMac/Defines (= 4.2.1) - - GoogleUtilities/Environment (7.13.3): - - GoogleUtilities/Privacy - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.13.3): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.3) - - GoogleUtilities/UserDefaults (7.13.3): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GoogleUtilitiesComponents (1.1.0): - - GoogleUtilities/Logger - - GTMSessionFetcher/Core (3.5.0) - - install_plugin (2.0.0): + - image_gallery_saver (2.0.2): - Flutter - isar_flutter_libs (1.0.0): - Flutter @@ -96,52 +68,29 @@ PODS: - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS - - MLImage (1.0.0-beta5) - - MLKitBarcodeScanning (5.0.0): - - MLKitCommon (~> 11.0) - - MLKitVision (~> 7.0) - - MLKitCommon (11.0.0): - - GoogleDataTransport (< 10.0, >= 9.4.1) - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0) - - GoogleUtilitiesComponents (~> 1.0) - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLKitVision (7.0.0): - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLImage (= 1.0.0-beta5) - - MLKitCommon (~> 11.0) - - mobile_scanner (5.2.3): - - Flutter - - GoogleMLKit/BarcodeScanning (~> 6.0.0) + - mobile_scanner (7.0.0): + - Flutter + - FlutterMacOS - move_to_background (0.0.1): - Flutter - - nanopb (2.30910.0): - - nanopb/decode (= 2.30910.0) - - nanopb/encode (= 2.30910.0) - - nanopb/decode (2.30910.0) - - nanopb/encode (2.30910.0) - native_device_orientation (0.0.1): - Flutter - - OrderedSet (5.0.0) + - OrderedSet (6.0.3) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.3.0): - Flutter - - PromisesObjC (2.4.0) - protocol_handler_ios (0.0.1): - Flutter - restart_app (0.0.1): - Flutter - - screen_protector (1.2.1): + - screen_protector (1.5.1): - Flutter - - ScreenProtectorKit (~> 1.3.1) - - ScreenProtectorKit (1.3.1) + - ScreenProtectorKit (= 1.5.1) + - ScreenProtectorKit (1.5.1) - SDWebImage (5.19.7): - SDWebImage/Core (= 5.19.7) - SDWebImage/Core (5.19.7) @@ -162,7 +111,6 @@ PODS: - SQLCipher/standard (4.5.7): - SQLCipher/common - SwiftyGif (5.4.5) - - Toast (4.1.1) - url_launcher_ios (0.0.1): - Flutter - video_player_avfoundation (0.0.1): @@ -175,22 +123,22 @@ PODS: - FlutterMacOS DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - biometric_storage (from `.symlinks/plugins/biometric_storage/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - install_plugin (from `.symlinks/plugins/install_plugin/ios`) + - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - move_to_background (from `.symlinks/plugins/move_to_background/ios`) - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) @@ -203,6 +151,7 @@ DEPENDENCIES: - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) - sqflite_sqlcipher (from `.symlinks/plugins/sqflite_sqlcipher/ios`) + - SQLCipher (~> 4.5) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) @@ -213,26 +162,15 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - FMDB - - GoogleDataTransport - - GoogleMLKit - - GoogleToolboxForMac - - GoogleUtilities - - GoogleUtilitiesComponents - - GTMSessionFetcher - - MLImage - - MLKitBarcodeScanning - - MLKitCommon - - MLKitVision - - nanopb - OrderedSet - - PromisesObjC - ScreenProtectorKit - SDWebImage - SQLCipher - SwiftyGif - - Toast EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" audio_session: :path: ".symlinks/plugins/audio_session/ios" biometric_storage: @@ -245,8 +183,6 @@ EXTERNAL SOURCES: :path: Flutter flutter_inappwebview_ios: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage: @@ -255,8 +191,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_web_auth_2/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - install_plugin: - :path: ".symlinks/plugins/install_plugin/ios" + image_gallery_saver: + :path: ".symlinks/plugins/image_gallery_saver/ios" isar_flutter_libs: :path: ".symlinks/plugins/isar_flutter_libs/ios" just_audio: @@ -264,7 +200,7 @@ EXTERNAL SOURCES: local_auth_darwin: :path: ".symlinks/plugins/local_auth_darwin/darwin" mobile_scanner: - :path: ".symlinks/plugins/mobile_scanner/ios" + :path: ".symlinks/plugins/mobile_scanner/darwin" move_to_background: :path: ".symlinks/plugins/move_to_background/ios" native_device_orientation: @@ -299,60 +235,47 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 - biometric_storage: 1400f1382af3a4cc2bf05340e13c3d8de873ceb9 - device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d + app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 + audio_session: f08db0697111ac84ba46191b55488c0563bb29c6 + biometric_storage: 662167ef947fba48891850f0b3042f892a839e9a + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 - flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 - flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be - flutter_web_auth_2: 051cf9f5dc366f31b5dcc4e2952c2b954767be8a - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0 + flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 + flutter_web_auth_2: 3464a7c16dc6480b6194fc89913bae6e82f28405 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 FMDB: 57486c1117fd8e0e6b947b2f54c3f42bf8e57a4e - GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065 - GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 - GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 - GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe - GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - install_plugin: b52f81470a1f37b006607bdf7ffc6031df43fe76 - isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 - just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 - MLImage: 1824212150da33ef225fbd3dc49f184cf611046c - MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b - MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 - MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 - mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740 - move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d - nanopb: 438bc412db1928dac798aa6fd75726007be04262 - native_device_orientation: 348b10c346a60ebbc62fb235a4fdb5d1b61a8f55 - OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990 - restart_app: 806659942bf932f6ce51c5372f91ce5e81c8c14a - screen_protector: 6f92086bd2f2f4b54f54913289b9d1310610140b - ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4 + image_gallery_saver: 14711d79da40581063e8842a11acf1969d781ed7 + isar_flutter_libs: 9fc2cfb928c539e1b76c481ba5d143d556d94920 + just_audio: 6c031bb61297cf218b4462be616638e81c058e97 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710 + native_device_orientation: e3580675687d5034770da198f6839ebf2122ef94 + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + protocol_handler_ios: 59f23ee71f3ec602d67902ca7f669a80957888d5 + restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf + screen_protector: 18c6aca2dc5d2a832f6787a5318f97f03e9d3150 + ScreenProtectorKit: 6ceb3e0808341a9bc15d175bff40dfdd4b32da71 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad - sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - sqflite_sqlcipher: 6f70d131e6b340a17d4bb6708a023e5c07ca24d3 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + sodium_libs: 6c6d0e83f4ee427c6464caa1f1bdc2abf3ca0b7f + sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3 + sqflite_sqlcipher: 253ead9c2add962dbd31b1fd8cd5c0c6d3c736d1 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 - wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 - webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + wakelock_plus: fd58c82b1388f4afe3fe8aa2c856503a262a5b03 + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c -PODFILE CHECKSUM: 44086395d77aeeb5c32c934343e42735d44366be +PODFILE CHECKSUM: 59067792e03d7a5801ba684f46eb50a563a19de4 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 677f7372..78882539 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -473,7 +473,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -602,7 +602,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -653,7 +653,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5df..e3773d42 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 62666446..c30b367e 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,12 +2,15 @@ import Flutter import UIKit @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 3fe8c6ce..cad82ea6 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -20,14 +22,6 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main CFBundleURLTypes @@ -41,17 +35,50 @@ + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) LSApplicationQueriesSchemes sms tel - NSFaceIDUsageDescription - Authenticate + LSRequiresIPhoneOS + NSCameraUsageDescription Scan qrcodes to analyze tokens. + NSFaceIDUsageDescription + Authenticate NSPhotoLibraryUsageDescription Select photos to analyze tokens from qrcodes. + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -65,11 +92,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - UIStatusBarHidden - diff --git a/lib/Screens/Lock/pin_change_screen.dart b/lib/Screens/Lock/pin_change_screen.dart index 34ecdfa4..872e06fb 100644 --- a/lib/Screens/Lock/pin_change_screen.dart +++ b/lib/Screens/Lock/pin_change_screen.dart @@ -39,8 +39,9 @@ class PinChangeScreenState extends BaseDynamicState { .notNullOrEmpty; late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); - final bool _hideGestureTrail = - ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); + final bool _hideGestureTrail = ChewieHiveUtil.getBool( + CloudOTPHiveUtil.hideGestureTrailKey, + defaultValue: false); late final GestureNotifier _notifier = _isEditMode ? GestureNotifier( status: GestureStatus.verify, diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index d2f3de39..68a9e1df 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -59,8 +59,9 @@ class PinVerifyScreenState extends BaseWindowState late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); - final bool _hideGestureTrail = - ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); + final bool _hideGestureTrail = ChewieHiveUtil.getBool( + CloudOTPHiveUtil.hideGestureTrailKey, + defaultValue: false); late final GestureNotifier _notifier = GestureNotifier( status: GestureStatus.verify, gestureText: appLocalizations.verifyGestureLock); @@ -160,20 +161,20 @@ class PinVerifyScreenState extends BaseWindowState Semantics( label: appLocalizations.verifyGestureLock, child: GestureUnlockView( - key: _gestureUnlockView, - size: min(MediaQuery.sizeOf(context).width, 400), - padding: 60, - roundSpace: 40, - defaultColor: Colors.grey.withOpacity(0.5), - selectedColor: ChewieTheme.primaryColor, - failedColor: Colors.redAccent, - disableColor: Colors.grey, - solidRadiusRatio: 0.3, - lineWidth: 2, - touchRadiusRatio: 0.3, - showLine: !_hideGestureTrail, - onCompleted: _gestureComplete, - ), + key: _gestureUnlockView, + size: min(MediaQuery.sizeOf(context).width, 400), + padding: 60, + roundSpace: 40, + defaultColor: Colors.grey.withOpacity(0.5), + selectedColor: ChewieTheme.primaryColor, + failedColor: Colors.redAccent, + disableColor: Colors.grey, + solidRadiusRatio: 0.3, + lineWidth: 2, + touchRadiusRatio: 0.3, + showLine: !_hideGestureTrail, + onCompleted: _gestureComplete, + ), ), Visibility( visible: _biometricAvailable && _enableBiometric, diff --git a/lib/Screens/Setting/about_setting_screen.dart b/lib/Screens/Setting/about_setting_screen.dart index b693a5c7..dbe7455e 100644 --- a/lib/Screens/Setting/about_setting_screen.dart +++ b/lib/Screens/Setting/about_setting_screen.dart @@ -268,7 +268,7 @@ class _AboutSettingScreenState extends BaseDynamicState EntryItem( title: appLocalizations.officialWebsite, onTap: () { - UriUtil.launchUrlUri(context, officialWebsite); + UriUtil.launchUrlUri(context, cloudotpOfficialWebsite); }, showLeading: true, leading: LucideIcons.house, diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index de4e0238..d5e2f90d 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -318,33 +318,43 @@ class _SelectThemeScreenState extends BaseDynamicState responsive: true, (ctx) => _MenuListSheet( title: appLocalizations.chooseBaseTheme, - items: [ - ...allThemes.map((theme) => _MenuAction( - label: theme.i18nName, + children: [ + ...allThemes.map((theme) => EntryItem( + paddingHorizontal: 20, + title: theme.i18nName, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); _navigateToEditor(theme, isDark); }, )), - ...customThemes.map((theme) => _MenuAction( - label: theme.name, + ...customThemes.map((theme) => EntryItem( + paddingHorizontal: 20, + title: theme.name, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); _navigateToEditor(theme, isDark); }, )), - _MenuAction(isDivider: true), - _MenuAction( - label: appLocalizations.importFromClipboard, - icon: Icons.content_paste_rounded, + const MyDivider(horizontal: 10, vertical: 5, width: 1), + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.importFromClipboard, + showLeading: true, + leading: LucideIcons.clipboardPaste, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); _importFromClipboard(isDark); }, ), - _MenuAction( - label: appLocalizations.importFromFile, - icon: Icons.file_open_outlined, + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.importFromFile, + showLeading: true, + leading: LucideIcons.fileInput, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); _importFromFile(isDark); @@ -383,18 +393,24 @@ class _SelectThemeScreenState extends BaseDynamicState responsive: true, (ctx) => _MenuListSheet( title: theme.name, - items: [ - _MenuAction( - label: appLocalizations.editTheme, - icon: Icons.edit_outlined, + children: [ + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.editTheme, + showLeading: true, + leading: LucideIcons.pencil, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); _navigateToEditor(theme, isDark, editIndex: customIndex); }, ), - _MenuAction( - label: appLocalizations.duplicateTheme, - icon: Icons.copy_outlined, + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.duplicateTheme, + showLeading: true, + leading: LucideIcons.copy, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); final copy = theme.copyWith( @@ -409,30 +425,38 @@ class _SelectThemeScreenState extends BaseDynamicState setState(() {}); }, ), - _MenuAction(isDivider: true), - _MenuAction( - label: appLocalizations.exportToClipboard, - icon: Icons.content_copy_outlined, + const MyDivider(horizontal: 10, vertical: 5, width: 1), + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.exportToClipboard, + showLeading: true, + leading: LucideIcons.clipboardCopy, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); ThemeUtil.exportToClipboard(context, theme); - IToast.showTop(appLocalizations.themeExportSuccess); }, ), - _MenuAction( - label: appLocalizations.exportToFile, - icon: Icons.save_outlined, + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.exportToFile, + showLeading: true, + leading: LucideIcons.fileOutput, + showTrailing: false, onTap: () async { Navigator.of(ctx).pop(); final ok = await ThemeUtil.exportToFile(theme); if (ok) IToast.showTop(appLocalizations.themeExportSuccess); }, ), - _MenuAction(isDivider: true), - _MenuAction( - label: appLocalizations.deleteTheme, - icon: Icons.delete_outline, - isDestructive: true, + const MyDivider(horizontal: 10, vertical: 5, width: 1), + EntryItem( + paddingHorizontal: 20, + title: appLocalizations.deleteTheme, + showLeading: true, + leading: LucideIcons.trash2, + titleColor: ChewieTheme.errorColor, + showTrailing: false, onTap: () { Navigator.of(ctx).pop(); DialogBuilder.showConfirmDialog( @@ -609,27 +633,11 @@ class _SelectThemeScreenState extends BaseDynamicState } } -class _MenuAction { - final String label; - final IconData? icon; - final VoidCallback? onTap; - final bool isDivider; - final bool isDestructive; - - _MenuAction({ - this.label = '', - this.icon, - this.onTap, - this.isDivider = false, - this.isDestructive = false, - }); -} - class _MenuListSheet extends StatelessWidget { final String title; - final List<_MenuAction> items; + final List children; - const _MenuListSheet({required this.title, required this.items}); + const _MenuListSheet({required this.title, required this.children}); static const Radius _radius = ChewieDimens.defaultRadius; @@ -642,7 +650,6 @@ class _MenuListSheet extends StatelessWidget { runAlignment: WrapAlignment.center, children: [ Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.vertical( top: _radius, @@ -652,58 +659,16 @@ class _MenuListSheet extends StatelessWidget { border: ChewieTheme.border, boxShadow: ChewieTheme.defaultBoxShadow, ), - child: Material( - type: MaterialType.transparency, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 12), - alignment: Alignment.center, - child: Text(title, style: ChewieTheme.titleLarge), - ), - ...items.map((item) { - if (item.isDivider) { - return const MyDivider( - vertical: 8, - horizontal: 4, - width: 1, - ); - } - return InkWell( - onTap: item.onTap, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 12), - child: Row( - children: [ - if (item.icon != null) ...[ - Icon(item.icon, - size: 22, - color: item.isDestructive - ? ChewieTheme.errorColor - : ChewieTheme.iconColor), - const SizedBox(width: 12), - ], - Expanded( - child: Text( - item.label, - style: ChewieTheme.bodyLarge.copyWith( - color: item.isDestructive - ? ChewieTheme.errorColor - : null, - ), - ), - ), - ], - ), - ), - ); - }), - const SizedBox(height: 8), - ], - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 20), + alignment: Alignment.center, + child: Text(title, style: ChewieTheme.titleLarge), + ), + Column(children: children), + ], ), ), ], diff --git a/lib/Screens/Setting/setting_safe_screen.dart b/lib/Screens/Setting/setting_safe_screen.dart index a50a00f7..b696657d 100644 --- a/lib/Screens/Setting/setting_safe_screen.dart +++ b/lib/Screens/Setting/setting_safe_screen.dart @@ -55,8 +55,9 @@ class _SafeSettingScreenState extends BaseDynamicState bool _enableSafeMode = ChewieHiveUtil.getBool( CloudOTPHiveUtil.enableSafeModeKey, defaultValue: defaultEnableSafeMode); - bool _hideGestureTrail = - ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideGestureTrailKey); + bool _hideGestureTrail = ChewieHiveUtil.getBool( + CloudOTPHiveUtil.hideGestureTrailKey, + defaultValue: false); bool _allowGuestureBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); bool _allowDatabaseBiometric = ChewieHiveUtil.getBool( diff --git a/lib/Screens/Setting/theme_editor_screen.dart b/lib/Screens/Setting/theme_editor_screen.dart index da9a1850..73d399ee 100644 --- a/lib/Screens/Setting/theme_editor_screen.dart +++ b/lib/Screens/Setting/theme_editor_screen.dart @@ -42,6 +42,7 @@ class _ThemeEditorScreenState extends BaseDynamicState with TickerProviderStateMixin { late ChewieThemeColorData _theme; late TextEditingController _nameController; + final FocusNode _nameFocusNode = FocusNode(); bool get _isEditing => widget.editIndex != null; @override @@ -53,11 +54,15 @@ class _ThemeEditorScreenState extends BaseDynamicState _nameController = TextEditingController( text: _isEditing ? _theme.name : '', ); + WidgetsBinding.instance.addPostFrameCallback((_) { + _nameFocusNode.requestFocus(); + }); } @override void dispose() { _nameController.dispose(); + _nameFocusNode.dispose(); super.dispose(); } @@ -245,6 +250,28 @@ class _ThemeEditorScreenState extends BaseDynamicState } } + Widget _buildColorGroup( + String title, + List children, { + bool initiallyExpanded = false, + }) { + return CaptionItem( + title: title, + initiallyExpanded: initiallyExpanded, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 8, + right: 8, + top: 3, + bottom: 3, + ), + child: Column(children: children), + ), + ], + ); + } + Widget _buildColorRow(String field, String label) { final color = _getColor(field); return InkWell( @@ -261,7 +288,7 @@ class _ThemeEditorScreenState extends BaseDynamicState decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), ), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Row( children: [ Container( @@ -278,11 +305,9 @@ class _ThemeEditorScreenState extends BaseDynamicState child: Text(label, style: ChewieTheme.bodyMedium), ), Text( - '#${color.toHex().substring(3)}', + '#${color.toHex().substring(3).toUpperCase()}', style: ChewieTheme.bodySmall, ), - const SizedBox(width: 4), - Icon(Icons.chevron_right, size: 18, color: ChewieTheme.iconColor), ], ), ), @@ -299,12 +324,12 @@ class _ThemeEditorScreenState extends BaseDynamicState padding: widget.padding, onTapBack: () => DialogNavigatorHelper.responsivePopPage(), actions: [ - TextButton( - onPressed: _save, - child: Text( - appLocalizations.save, - style: TextStyle(color: ChewieTheme.primaryColor), + CircleIconButton( + icon: Icon( + LucideIcons.check, + color: ChewieTheme.iconColor, ), + onTap: _save, ), ], desktopActions: [ @@ -317,46 +342,40 @@ class _ThemeEditorScreenState extends BaseDynamicState ], children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: InputItem( controller: _nameController, + focusNode: _nameFocusNode, + title: appLocalizations.themeName, hint: appLocalizations.themeNameHint, textInputAction: TextInputAction.done, ), ), - Center( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: ThemeItem( - themeColorData: _theme, - index: 0, - groupIndex: 0, - onChanged: null, - ), - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: _ThemePreview(theme: _theme), ), - CaptionItem( - title: appLocalizations.colorGroupBackground, - children: [ + _buildColorGroup( + appLocalizations.colorGroupBackground, + [ _buildColorRow('scaffoldBackgroundColor', 'Scaffold Background'), _buildColorRow('canvasColor', 'Canvas'), _buildColorRow('cardColor', 'Card'), ], + initiallyExpanded: true, ), - CaptionItem( - title: appLocalizations.colorGroupText, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupText, + [ _buildColorRow('textColor', 'Text'), _buildColorRow('textLightGreyColor', 'Text Light Grey'), _buildColorRow('textDarkGreyColor', 'Text Dark Grey'), _buildColorRow('hintColor', 'Hint'), ], ), - CaptionItem( - title: appLocalizations.colorGroupAccent, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupAccent, + [ _buildColorRow('primaryColor', 'Primary'), _buildColorRow('indicatorColor', 'Indicator'), _buildColorRow('cursorColor', 'Cursor'), @@ -364,10 +383,9 @@ class _ThemeEditorScreenState extends BaseDynamicState _buildColorRow('textSelectionHandleColor', 'Selection Handle'), ], ), - CaptionItem( - title: appLocalizations.colorGroupButtons, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupButtons, + [ _buildColorRow('buttonPrimaryColor', 'Primary Button'), _buildColorRow('buttonSecondaryColor', 'Secondary Button'), _buildColorRow('buttonHoverColor', 'Button Hover'), @@ -375,19 +393,17 @@ class _ThemeEditorScreenState extends BaseDynamicState _buildColorRow('buttonDisabledColor', 'Button Disabled'), ], ), - CaptionItem( - title: appLocalizations.colorGroupAppBar, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupAppBar, + [ _buildColorRow('appBarBackgroundColor', 'AppBar Background'), _buildColorRow('appBarSurfaceTintColor', 'AppBar Surface Tint'), _buildColorRow('appBarShadowColor', 'AppBar Shadow'), ], ), - CaptionItem( - title: appLocalizations.colorGroupSurfaces, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupSurfaces, + [ _buildColorRow('hoverColor', 'Hover'), _buildColorRow('splashColor', 'Splash'), _buildColorRow('highlightColor', 'Highlight'), @@ -397,20 +413,18 @@ class _ThemeEditorScreenState extends BaseDynamicState _buildColorRow('iconColor', 'Icon'), ], ), - CaptionItem( - title: appLocalizations.colorGroupScrollbar, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupScrollbar, + [ _buildColorRow('scrollBarThumbColor', 'Thumb'), _buildColorRow('scrollBarThumbHoverColor', 'Thumb Hover'), _buildColorRow('scrollBarTrackColor', 'Track'), _buildColorRow('scrollBarTrackHoverColor', 'Track Hover'), ], ), - CaptionItem( - title: appLocalizations.colorGroupStatus, - initiallyExpanded: false, - children: [ + _buildColorGroup( + appLocalizations.colorGroupStatus, + [ _buildColorRow('successColor', 'Success'), _buildColorRow('warningColor', 'Warning'), _buildColorRow('errorColor', 'Error'), @@ -421,3 +435,234 @@ class _ThemeEditorScreenState extends BaseDynamicState ); } } + +class _ThemePreview extends StatelessWidget { + final ChewieThemeColorData theme; + + const _ThemePreview({required this.theme}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: theme.borderColor, width: 1), + boxShadow: [ + BoxShadow( + color: theme.shadowColor.withValues(alpha: 0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildAppBar(), + Container( + color: theme.scaffoldBackgroundColor, + padding: const EdgeInsets.all(12), + child: Column( + children: [ + _buildListCard(), + const SizedBox(height: 10), + _buildButtonRow(), + const SizedBox(height: 10), + _buildStatusRow(), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildAppBar() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + color: theme.appBarBackgroundColor, + child: Row( + children: [ + Icon(LucideIcons.chevronLeft, size: 18, color: theme.iconColor), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Preview', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: theme.textColor, + ), + ), + ), + Icon(LucideIcons.ellipsis, size: 18, color: theme.iconColor), + ], + ), + ); + } + + Widget _buildListCard() { + return Container( + decoration: BoxDecoration( + color: theme.canvasColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.borderColor, width: 0.5), + ), + child: Column( + children: [ + _buildListTile(LucideIcons.user, 'Title Text', 'Description text'), + Divider(height: 1, color: theme.dividerColor, indent: 42), + _buildListTile(LucideIcons.settings, 'Second Item', 'Detail info'), + ], + ), + ); + } + + Widget _buildListTile(IconData icon, String title, String subtitle) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: theme.splashColor, + borderRadius: BorderRadius.circular(6), + ), + child: Icon(icon, size: 15, color: theme.primaryColor), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: theme.textColor, + ), + ), + const SizedBox(height: 1), + Text( + subtitle, + style: TextStyle( + fontSize: 11, + color: theme.textLightGreyColor, + ), + ), + ], + ), + ), + Icon(LucideIcons.chevronRight, + size: 14, color: theme.textDarkGreyColor), + ], + ), + ); + } + + Widget _buildButtonRow() { + return Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: theme.buttonPrimaryColor, + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Text( + 'Primary', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: theme.canvasColor, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: theme.buttonSecondaryColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.borderColor, width: 0.5), + ), + alignment: Alignment.center, + child: Text( + 'Secondary', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: theme.textColor, + ), + ), + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 14), + decoration: BoxDecoration( + color: theme.buttonDisabledColor, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Disabled', + style: TextStyle( + fontSize: 12, + color: theme.hintColor, + ), + ), + ), + ], + ); + } + + Widget _buildStatusRow() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: theme.cardColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.borderColor, width: 0.5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildStatusDot(theme.successColor, 'Success'), + _buildStatusDot(theme.warningColor, 'Warning'), + _buildStatusDot(theme.errorColor, 'Error'), + _buildStatusDot(theme.primaryColor, 'Primary'), + ], + ), + ); + } + + Widget _buildStatusDot(Color color, String label) { + return Column( + children: [ + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle(fontSize: 10, color: theme.textLightGreyColor), + ), + ], + ); + } +} diff --git a/lib/Screens/Token/import_preview_screen.dart b/lib/Screens/Token/import_preview_screen.dart index 564c5768..652a22b7 100644 --- a/lib/Screens/Token/import_preview_screen.dart +++ b/lib/Screens/Token/import_preview_screen.dart @@ -19,6 +19,7 @@ import 'package:cloudotp/Models/token_category.dart'; import 'package:cloudotp/TokenUtils/ThirdParty/base_token_importer.dart'; import 'package:cloudotp/TokenUtils/import_token_util.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../Widgets/cloudotp/cloudotp_item_builder.dart'; import '../../l10n/l10n.dart'; @@ -223,17 +224,24 @@ class _ImportPreviewScreenState extends BaseDynamicState { titleLeftMargin: ResponsiveUtil.isLandscapeLayout() ? 15 : 5, actions: [ if (!_loading) - TextButton( - onPressed: _toggleSelectAll, - child: Text( - _allSelected - ? appLocalizations.importDeselectAll - : appLocalizations.importSelectAll, - style: TextStyle(color: ChewieTheme.primaryColor), + CircleIconButton( + icon: Icon( + _allSelected ? LucideIcons.listX : LucideIcons.listChecks, + color: ChewieTheme.iconColor, ), + onTap: _toggleSelectAll, ), const SizedBox(width: 5), ], + desktopActions: [ + if (!_loading) + ToolButton( + context: context, + icon: _allSelected ? LucideIcons.listX : LucideIcons.listChecks, + buttonSize: const Size(32, 32), + onPressed: _toggleSelectAll, + ), + ], ), body: _loading ? const Center(child: CircularProgressIndicator()) @@ -241,7 +249,8 @@ class _ImportPreviewScreenState extends BaseDynamicState { children: [ Expanded( child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: + const EdgeInsets.only(left: 6, right: 6, bottom: 10), children: [ const SizedBox(height: 10), InlineSelectionItem>( @@ -303,7 +312,7 @@ class _ImportPreviewScreenState extends BaseDynamicState { }); }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.all(8), child: Row( children: [ Checkbox( @@ -454,11 +463,11 @@ class _ImportPreviewScreenState extends BaseDynamicState { Widget _buildBottomBar() { return Container( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: 16, right: 16, top: 12, - bottom: 12 + MediaQuery.of(context).padding.bottom, + bottom: 12, ), decoration: BoxDecoration( color: ChewieTheme.scaffoldBackgroundColor, diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index bae38377..09297024 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -679,7 +679,8 @@ class MainScreenState extends BaseWindowState ), PlatformMenuItem( label: appLocalizations.officialWebsite, - onSelected: () => UriUtil.launchUrlUri(context, officialWebsite), + onSelected: () => + UriUtil.launchUrlUri(context, cloudotpOfficialWebsite), ), ], ), diff --git a/lib/Utils/constant.dart b/lib/Utils/constant.dart index 8eb981f5..8dfd76b5 100644 --- a/lib/Utils/constant.dart +++ b/lib/Utils/constant.dart @@ -13,7 +13,6 @@ * If not, see . */ -import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flutter/cupertino.dart'; import '../l10n/l10n.dart'; @@ -31,22 +30,20 @@ const String hotpPlaceholderText = "*"; const appLicense = "GPL-3.0"; -String shareAppText = appLocalizations.shareAppText(officialWebsite); +String shareAppText = appLocalizations.shareAppText(cloudotpOfficialWebsite); const String feedbackEmail = "2014027378@qq.com"; String feedbackSubject = appLocalizations.feedbackSubject; const String feedbackBody = ""; const List websiteSupportLocales = [Locale("en"), Locale("zh", "CN")]; +String cloudotpOfficialWebsite = "https://otp.cloudchewie.com"; const String defaultDownloadsWebsite = "https://apps.cloudchewie.com/cloudotp/downloads"; const String downloadsWebsite = "https://apps.cloudchewie.com/{locale}/cloudotp/downloads"; -const String sqlcipherLearnMore = - "https://apps.cloudchewie.com/cloudotp/sqlcipher/"; +const String sqlcipherLearnMore = "https://otp.cloudchewie.com/docs/sqlcipher"; const String telegramLink = "https://t.me/CloudOTP_official"; -const String privacyPolicyWebsite = - "https://apps.cloudchewie.com/cloudotp/privacy/"; -const String serviceTermWebsite = - "https://apps.cloudchewie.com/cloudotp/service/"; +const String privacyPolicyWebsite = "https://otp.cloudchewie.com/privacy"; +const String serviceTermWebsite = "https://otp.cloudchewie.com/service-term"; RegExp otpauthMigrationReg = RegExp(r"^otpauth-migration://offline\?data=(.*)$"); diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index ad4e7c69..7b974746 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -51,7 +51,8 @@ class CloudOTPHiveUtil { static const String autoHideCodeKey = "autoHideCode"; static const String defaultHideCodeKey = "defaultHideCode"; static const String showEyeKey = "showEye"; - static const String issuerAndAccountShowOptionKey = "issuerAndAccountShowOption"; + static const String issuerAndAccountShowOptionKey = + "issuerAndAccountShowOption"; //Appearance static const String showCloudBackupButtonKey = "showCloudBackupButton"; @@ -102,6 +103,7 @@ class CloudOTPHiveUtil { CloudOTPHiveUtil.dragToReorderKey, !ResponsiveUtil.isMobile()); await ChewieHiveUtil.put( CloudOTPHiveUtil.autoMinimizeAfterClickToCopyKey, false); + await ChewieHiveUtil.put(CloudOTPHiveUtil.hideGestureTrailKey, false); } static bool canLock() => canGuestureLock() || canDatabaseLock(); diff --git a/lib/Utils/theme_util.dart b/lib/Utils/theme_util.dart index 6a3c1f54..d63d00e1 100644 --- a/lib/Utils/theme_util.dart +++ b/lib/Utils/theme_util.dart @@ -18,6 +18,7 @@ import 'dart:io'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../l10n/l10n.dart'; @@ -50,22 +51,22 @@ class ThemeUtil { static void exportToClipboard( BuildContext context, ChewieThemeColorData theme) { - ChewieUtils.copy(context, exportThemeToJson(theme)); + ChewieUtils.copy(context, exportThemeToJson(theme), + toastText: appLocalizations.themeExportSuccess); } static Future exportToFile(ChewieThemeColorData theme) async { final json = exportThemeToJson(theme); + final bytes = Uint8List.fromList(utf8.encode(json)); final fileName = '${theme.name.replaceAll(RegExp(r'[^\w一-鿿-]'), '_')}.json'; String? filePath = await FileUtil.saveFile( dialogTitle: appLocalizations.exportTheme, fileName: fileName, type: FileType.custom, allowedExtensions: ['json'], + bytes: bytes, ); - if (filePath == null) return false; - final file = File(filePath); - await file.writeAsString(json); - return true; + return filePath != null; } static Future importFromClipboard() async { diff --git a/lib/Widgets/BottomSheet/add_bottom_sheet.dart b/lib/Widgets/BottomSheet/add_bottom_sheet.dart index 362abbbc..7dad1b81 100644 --- a/lib/Widgets/BottomSheet/add_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/add_bottom_sheet.dart @@ -46,8 +46,8 @@ class AddBottomSheet extends StatefulWidget { class AddBottomSheetState extends BaseDynamicState with WidgetsBindingObserver { final MobileScannerController scannerController = - MobileScannerController(useNewCameraSelector: true); - StreamSubscription? _subscription; + MobileScannerController(); + StreamSubscription? _subscription; static const double _defaultZoomFactor = 0.43; double _zoomFactor = _defaultZoomFactor; final double _scaleSensitivity = 0.005; @@ -248,7 +248,7 @@ class AddBottomSheetState extends BaseDynamicState child: MobileScanner( key: scannerKey, controller: scannerController, - placeholderBuilder: (context, child) { + placeholderBuilder: (context) { return RotatedBox( quarterTurns: 4 - turns, child: ColoredBox( @@ -278,7 +278,7 @@ class AddBottomSheetState extends BaseDynamicState ), ); }, - errorBuilder: (context, error, child) { + errorBuilder: (context, error) { return RotatedBox( quarterTurns: 4 - turns, child: ScannerErrorWidget(error: error), @@ -398,6 +398,8 @@ class SwitchCameraButton extends StatelessWidget { icon = const Icon(Icons.camera_front_rounded, color: Colors.white, size: 32); case CameraFacing.back: + case CameraFacing.external: + case CameraFacing.unknown: icon = const Icon(Icons.camera_rear_rounded, color: Colors.white, size: 32); } diff --git a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart index dd1dec10..1d1010dd 100644 --- a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart @@ -16,6 +16,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../l10n/l10n.dart'; @@ -76,8 +77,7 @@ class _ColorPickerBottomSheetState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.vertical( top: radius, - bottom: - ResponsiveUtil.isWideDevice() ? radius : Radius.zero), + bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, border: ChewieTheme.border, boxShadow: ChewieTheme.defaultBoxShadow, @@ -94,73 +94,76 @@ class _ColorPickerBottomSheetState extends State { child: Text(widget.title, style: ChewieTheme.titleLarge), ), ColorPicker( - color: _currentColor, - onColorChanged: (Color color) { - setState(() => _currentColor = color); - }, - pickersEnabled: const { - ColorPickerType.wheel: true, - ColorPickerType.primary: false, - ColorPickerType.accent: false, - }, - enableShadesSelection: true, - enableOpacity: true, - showColorCode: true, - colorCodeHasColor: true, - copyPasteBehavior: const ColorPickerCopyPasteBehavior( - copyFormat: ColorPickerCopyFormat.hexRRGGBB, + color: _currentColor, + onColorChanged: (Color color) { + setState(() => _currentColor = color); + }, + pickersEnabled: const { + ColorPickerType.wheel: true, + ColorPickerType.primary: false, + ColorPickerType.accent: false, + }, + enableShadesSelection: true, + enableOpacity: true, + showColorCode: true, + colorCodeHasColor: true, + copyPasteBehavior: const ColorPickerCopyPasteBehavior( + copyFormat: ColorPickerCopyFormat.hexRRGGBB, + copyIcon: LucideIcons.copy, + pasteButton: true, + pasteIcon: LucideIcons.clipboardPaste, + ), + actionButtons: const ColorPickerActionButtons( + okButton: false, + closeButton: false, + dialogActionButtons: false, + ), + width: 44, + height: 44, + borderRadius: 22, + heading: null, + subheading: null, + wheelWidth: 20, + wheelDiameter: 220, ), - actionButtons: const ColorPickerActionButtons( - okButton: false, - closeButton: false, - dialogActionButtons: false, - ), - width: 44, - height: 44, - borderRadius: 22, - heading: null, - subheading: null, - wheelWidth: 20, - wheelDiameter: 220, - ), - Container( - padding: - const EdgeInsets.symmetric(vertical: 16, horizontal: 0), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: SizedBox( - height: 45, - child: RoundIconTextButton( - text: appLocalizations.cancel, - onPressed: () => Navigator.of(context).pop(), - fontSizeDelta: 2, + Container( + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), + fontSizeDelta: 2, + ), ), ), - ), - const SizedBox(width: 10), - Expanded( - child: SizedBox( - height: 45, - child: RoundIconTextButton( - background: ChewieTheme.primaryColor, - color: Colors.white, - text: appLocalizations.confirm, - onPressed: () { - widget.onColorChanged(_currentColor); - Navigator.of(context).pop(); - }, - fontSizeDelta: 2, + const SizedBox(width: 10), + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: appLocalizations.confirm, + onPressed: () { + widget.onColorChanged(_currentColor); + Navigator.of(context).pop(); + }, + fontSizeDelta: 2, + ), ), ), - ), - ], + ], + ), ), - ), - ], + ], ), ), ), diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 0de9b4f1..75c78569 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -32,7 +32,8 @@ PODS: - FlutterMacOS - local_notifier (0.1.0): - FlutterMacOS - - mobile_scanner (5.2.3): + - mobile_scanner (7.0.0): + - Flutter - FlutterMacOS - OrderedSet (6.0.3) - package_info_plus (0.0.1): @@ -94,7 +95,7 @@ DEPENDENCIES: - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) - - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) + - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - protocol_handler_macos (from `Flutter/ephemeral/.symlinks/plugins/protocol_handler_macos/macos`) @@ -150,7 +151,7 @@ EXTERNAL SOURCES: local_notifier: :path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos mobile_scanner: - :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos + :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: @@ -201,7 +202,7 @@ SPEC CHECKSUMS: just_audio: eb8b016ac4493159ab24db4f7215e55303b39a84 local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e - mobile_scanner: bd1e7cd9b67b442f4d903747f4778e040513f860 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: f0052d280d17aa382b932f399edf32507174e870 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/pubspec.lock b/pubspec.lock index bda5414c..6ecb68b0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1296,10 +1296,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 + sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce url: "https://pub.flutter-io.cn" source: hosted - version: "5.2.3" + version: "7.2.0" modal_bottom_sheet: dependency: "direct main" description: @@ -1746,10 +1746,10 @@ packages: dependency: "direct main" description: name: screen_protector - sha256: "305fd157f6f0b210afe216e790022bfe469c3b1d1f2e0d3dcc5906cc9c49c3e9" + sha256: "7f53d7a0c238a62dc970841fd13053f2b79f14b7f8d2c82a11fef65b066f42cd" url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.2+1" + version: "1.5.1" screen_retriever: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4b9c38cf..61a1b359 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: # 二维码 image: ^4.2.0 # 图片 zxing2: ^0.2.3 # 二维码 - mobile_scanner: ^5.2.2 # 扫码 + mobile_scanner: ^7.2.0 # 扫码 pretty_qr_code: ^3.3.0 # 二维码 screen_capturer: path: third-party/tool/screen_capturer_lib/screen_capturer @@ -83,7 +83,7 @@ dependencies: responsive_builder: ^0.7.1 # 响应式布局 flutter_displaymode: ^0.6.0 # 设置刷新率 flutter_resizable_container: ^2.0.0 # 可调整大小的容器 - screen_protector: ^1.4.2+1 + screen_protector: ^1.5.1 flutter_windowmanager: path: third-party/tool/flutter_windowmanager move_to_background: diff --git a/third-party/chewie/lib/src/Resources/theme_color_data.dart b/third-party/chewie/lib/src/Resources/theme_color_data.dart index 5cb824b2..f8adb148 100644 --- a/third-party/chewie/lib/src/Resources/theme_color_data.dart +++ b/third-party/chewie/lib/src/Resources/theme_color_data.dart @@ -189,7 +189,7 @@ class ChewieThemeColorData { buttonSecondaryColor: const Color(0xFFF2F2F2), buttonHoverColor: const Color(0xFFF2F2F2), buttonLightHoverColor: const Color(0xFFECECED), - buttonDisabledColor: const Color(0xFF96BBFA), + buttonDisabledColor: const Color(0xFFD4D4D5), scrollBarThumbColor: const Color(0xFFC4C4C4), scrollBarThumbHoverColor: const Color(0xFFB5B5B5), scrollBarTrackColor: const Color(0xFFF0F0F0), @@ -223,11 +223,11 @@ class ChewieThemeColorData { textColor: const Color(0xFF2D2D2D), textLightGreyColor: const Color(0xFF9E9E9E), textDarkGreyColor: const Color(0xFF636363), - buttonPrimaryColor: const Color(0xFF1976D2), + buttonPrimaryColor: const Color(0xFF3A3A3A), buttonSecondaryColor: const Color(0xFFE0E0E0), - buttonHoverColor: const Color(0xFF2196F3), - buttonLightHoverColor: const Color(0xFF90CAF9), - buttonDisabledColor: const Color(0xFFB0BEC5), + buttonHoverColor: const Color(0xFF5D5D5D), + buttonLightHoverColor: const Color(0xFF979797), + buttonDisabledColor: const Color(0xFFDADADA), scrollBarThumbColor: const Color(0xFFC1C1C1), scrollBarThumbHoverColor: const Color(0xFF9E9E9E), scrollBarTrackColor: const Color(0xFFF5F5F5), diff --git a/third-party/chewie/lib/src/Utils/System/file_util.dart b/third-party/chewie/lib/src/Utils/System/file_util.dart index 259e34c1..ce87334b 100644 --- a/third-party/chewie/lib/src/Utils/System/file_util.dart +++ b/third-party/chewie/lib/src/Utils/System/file_util.dart @@ -426,7 +426,11 @@ class FileUtil { image.imageUrl, headers: headers, ); - final result = await Share.shareXFiles([XFile(file.path)], text: message); + final result = await Share.shareXFiles( + [XFile(file.path)], + text: message, + sharePositionOrigin: UriUtil.shareOriginRect(), + ); if (result.status == ShareResultStatus.success) { IToast.showTop("分享成功"); } else if (result.status == ShareResultStatus.dismissed) { diff --git a/third-party/chewie/lib/src/Utils/System/uri_util.dart b/third-party/chewie/lib/src/Utils/System/uri_util.dart index e8f1020e..3899101b 100644 --- a/third-party/chewie/lib/src/Utils/System/uri_util.dart +++ b/third-party/chewie/lib/src/Utils/System/uri_util.dart @@ -125,8 +125,19 @@ class UriUtil { return true; } + static Rect shareOriginRect() { + final view = WidgetsBinding.instance.platformDispatcher.views.first; + final size = view.physicalSize / view.devicePixelRatio; + return Rect.fromCenter( + center: Offset(size.width / 2, size.height / 2), + width: 1, + height: 1, + ); + } + static share(String str) { - Share.share(str).then((shareResult) { + Share.share(str, sharePositionOrigin: shareOriginRect()) + .then((shareResult) { if (shareResult.status == ShareResultStatus.success) { IToast.showTop(chewieLocalizations.shareSuccess); } else if (shareResult.status == ShareResultStatus.dismissed) { @@ -222,7 +233,7 @@ class UriUtil { } catch (e, t) { ILogger.error("Failed to resolve url $url", e, t); if (!quiet) await CustomLoadingDialog.dismissLoading(); - if (!quiet) Share.share(url); + if (!quiet) Share.share(url, sharePositionOrigin: shareOriginRect()); return false; } } diff --git a/third-party/chewie/lib/src/Utils/utils.dart b/third-party/chewie/lib/src/Utils/utils.dart index 62c3978f..a2b94933 100644 --- a/third-party/chewie/lib/src/Utils/utils.dart +++ b/third-party/chewie/lib/src/Utils/utils.dart @@ -23,25 +23,24 @@ class ChewieUtils { } static Future enableSafeMode() async { - await ScreenProtector.preventScreenshotOn(); - await ScreenProtector.protectDataLeakageOn(); - await ScreenProtector.protectDataLeakageWithBlur(); - await ScreenProtector.protectDataLeakageWithColor( - ChewieTheme.scaffoldBackgroundColor); - if (ResponsiveUtil.isAndroid()) { - SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle(statusBarColor: Colors.white)); + if (ResponsiveUtil.isIOS()) { + await ScreenProtector.preventScreenshotOn(); + await ScreenProtector.protectDataLeakageWithBlur(); + await ScreenProtector.protectDataLeakageWithColor( + ChewieTheme.scaffoldBackgroundColor); + } else if (ResponsiveUtil.isAndroid()) { + await ScreenProtector.protectDataLeakageOn(); FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE); - FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_BLUR_BEHIND); } } static Future disableSafeMode() async { - await ScreenProtector.preventScreenshotOff(); - await ScreenProtector.protectDataLeakageOff(); - await ScreenProtector.protectDataLeakageWithBlurOff(); - await ScreenProtector.protectDataLeakageWithColorOff(); - if (ResponsiveUtil.isAndroid()) { + if (ResponsiveUtil.isIOS()) { + await ScreenProtector.preventScreenshotOff(); + await ScreenProtector.protectDataLeakageWithBlurOff(); + await ScreenProtector.protectDataLeakageWithColorOff(); + } else if (ResponsiveUtil.isAndroid()) { + await ScreenProtector.protectDataLeakageOff(); FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE); } } diff --git a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart index 1dd812cd..7994ef21 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart @@ -136,7 +136,12 @@ class EntryItemState extends SearchableState { List _buildRowChildren() { return [ - if (widget.showLeading) Icon(widget.leading, size: 20), + if (widget.showLeading) + Icon( + widget.leading, + size: 20, + color: widget.titleColor ?? ChewieTheme.iconColor, + ), SizedBox(width: widget.showLeading ? 10 : 5), Expanded(child: _buildTextContent()), const SizedBox(width: 50), From df075fc7e7e4032429969e6a202aecc09d152477 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 02:56:56 +0800 Subject: [PATCH 05/36] Fix macos bugs --- .../main/res/xml/network_security_config.xml | 3 +- lib/Models/opt_token.dart | 2 +- lib/Screens/Backup/webdav_service_screen.dart | 3 + lib/Screens/Lock/database_decrypt_screen.dart | 2 +- lib/Screens/Lock/pin_change_screen.dart | 69 ++++++++++++-- lib/Screens/Lock/pin_verify_screen.dart | 51 ++++++++++- lib/Screens/Setting/setting_safe_screen.dart | 2 - lib/Screens/Token/token_layout.dart | 4 +- lib/Screens/home_screen.dart | 38 ++++---- lib/Screens/main_screen.dart | 89 ++++++++++++++++--- .../ThirdParty/freeotp_importer.dart | 7 +- lib/TokenUtils/check_token_util.dart | 2 +- lib/TokenUtils/code_generator.dart | 4 +- lib/TokenUtils/otp_token_parser.dart | 2 +- lib/Utils/app_provider.dart | 15 +++- lib/Utils/hive_util.dart | 64 +++++++++++-- lib/Utils/utils.dart | 9 +- .../token_option_bottom_sheet.dart | 4 +- lib/l10n/intl_en.arb | 3 +- lib/l10n/intl_ja.arb | 3 +- lib/l10n/intl_zh.arb | 3 +- lib/l10n/intl_zh_TW.arb | 3 +- lib/main.dart | 3 +- macos/Runner/MainFlutterWindow.swift | 24 +++++ pubspec.lock | 2 +- pubspec.yaml | 1 + .../awesome_cloud/lib/utils/cloud_logger.dart | 4 +- .../lib/webdav/src/webdav_dio.dart | 17 ++-- third-party/chewie/lib/src/Utils/utils.dart | 10 +++ .../Widgets/General/responsive_app_bar.dart | 3 +- 30 files changed, 360 insertions(+), 86 deletions(-) diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml index bb6ab93d..d7b4192e 100644 --- a/android/app/src/main/res/xml/network_security_config.xml +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -3,7 +3,6 @@ - - \ No newline at end of file + diff --git a/lib/Models/opt_token.dart b/lib/Models/opt_token.dart index 5f57b10f..210f0f68 100644 --- a/lib/Models/opt_token.dart +++ b/lib/Models/opt_token.dart @@ -462,7 +462,7 @@ class OtpToken { @override String toString() { - return "OtpToken($id, $uid, $seq, $issuer, $secret, $account, $imagePath, $tokenType, $algorithm, $digits, $counterString, $periodString, $pinned, $createTimeStamp, $editTimeStamp, $remark, $copyTimes, $lastCopyTimeStamp, $pin, $description)"; + return "OtpToken($id, $uid, $seq, $issuer, [REDACTED], $account, $imagePath, $tokenType, $algorithm, $digits, $counterString, $periodString, $pinned, $createTimeStamp, $editTimeStamp, $remark, $copyTimes, $lastCopyTimeStamp, [REDACTED], $description)"; } OtpToken({ diff --git a/lib/Screens/Backup/webdav_service_screen.dart b/lib/Screens/Backup/webdav_service_screen.dart index 4bda7e1a..ad9aa220 100644 --- a/lib/Screens/Backup/webdav_service_screen.dart +++ b/lib/Screens/Backup/webdav_service_screen.dart @@ -203,6 +203,9 @@ class _WebDavServiceScreenState extends BaseDynamicState if (!RegexUtil.isUrlOrIp(text)) { return appLocalizations.webDavServerInvalid; } + if (text.startsWith('http://')) { + return appLocalizations.webDavHttpWarning; + } return null; }, hint: appLocalizations.webDavServerHint, diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index 6350a1da..c3cce293 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -161,7 +161,7 @@ class DatabaseDecryptScreenState extends BaseWindowState ? ResponsiveAppBar( title: appLocalizations.appName, showBack: false, - titleLeftMargin: 15, + titleLeftMargin: ResponsiveUtil.isMacOS() ? 78 : 15, actions: const [ BlankIconButton(), ], diff --git a/lib/Screens/Lock/pin_change_screen.dart b/lib/Screens/Lock/pin_change_screen.dart index 872e06fb..a13be5a1 100644 --- a/lib/Screens/Lock/pin_change_screen.dart +++ b/lib/Screens/Lock/pin_change_screen.dart @@ -13,6 +13,7 @@ * If not, see . */ +import 'dart:async'; import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -33,6 +34,13 @@ class PinChangeScreen extends StatefulWidget { } class PinChangeScreenState extends BaseDynamicState { + static const int _maxVerifyAttempts = 5; + static const int _verifyLockoutSeconds = 60; + int _verifyFailedAttempts = 0; + bool _verifyLockedOut = false; + Timer? _verifyLockoutTimer; + int _verifyLockoutRemaining = 0; + String _gesturePassword = ""; bool _isEditMode = ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey) @@ -57,6 +65,46 @@ class PinChangeScreenState extends BaseDynamicState { bool get _biometricAvailable => canAuthenticateResponse?.isSuccess ?? false; + @override + void dispose() { + _verifyLockoutTimer?.cancel(); + super.dispose(); + } + + void _startVerifyLockout() { + _verifyLockedOut = true; + _verifyLockoutRemaining = _verifyLockoutSeconds; + _verifyLockoutTimer?.cancel(); + _verifyLockoutTimer = + Timer.periodic(const Duration(seconds: 1), (timer) { + setState(() { + _verifyLockoutRemaining--; + if (_verifyLockoutRemaining <= 0) { + _verifyLockedOut = false; + _verifyFailedAttempts = 0; + timer.cancel(); + _notifier.setStatus( + status: GestureStatus.verify, + gestureText: appLocalizations.drawOldGestureLock, + ); + } else { + _notifier.setStatus( + status: GestureStatus.verifyFailedCountOverflow, + gestureText: + '${appLocalizations.gestureLockWrong} (${_verifyLockoutRemaining}s)', + ); + } + }); + }); + setState(() { + _notifier.setStatus( + status: GestureStatus.verifyFailedCountOverflow, + gestureText: + '${appLocalizations.gestureLockWrong} (${_verifyLockoutRemaining}s)', + ); + }); + } + @override void initState() { super.initState(); @@ -193,8 +241,10 @@ class PinChangeScreenState extends BaseDynamicState { _gestureUnlockView.currentState?.updateStatus(UnlockStatus.failed); } } else { + if (_verifyLockedOut) return; String password = GestureUnlockView.selectedToString(selected); if (CloudOTPHiveUtil.verifyGesturePassword(password)) { + _verifyFailedAttempts = 0; setState(() { _notifier.setStatus( status: GestureStatus.create, @@ -204,12 +254,19 @@ class PinChangeScreenState extends BaseDynamicState { }); _gestureUnlockView.currentState?.updateStatus(UnlockStatus.normal); } else { - setState(() { - _notifier.setStatus( - status: GestureStatus.verifyFailed, - gestureText: appLocalizations.gestureLockWrong, - ); - }); + _verifyFailedAttempts++; + if (_verifyFailedAttempts >= _maxVerifyAttempts) { + _startVerifyLockout(); + } else { + final remaining = _maxVerifyAttempts - _verifyFailedAttempts; + setState(() { + _notifier.setStatus( + status: GestureStatus.verifyFailed, + gestureText: + '${appLocalizations.gestureLockWrong} ($remaining)', + ); + }); + } _gestureUnlockView.currentState?.updateStatus(UnlockStatus.failed); } } diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 68a9e1df..219f2fb5 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -73,6 +73,46 @@ class PinVerifyScreenState extends BaseWindowState Timer? _lockoutTimer; int _lockoutRemaining = 0; + void _restoreLockoutState() { + _failedAttempts = ChewieHiveUtil.getInt( + CloudOTPHiveUtil.gestureFailedAttemptsKey, + defaultValue: 0); + final lockoutEnd = ChewieHiveUtil.getInt( + CloudOTPHiveUtil.gestureLockoutEndKey, + defaultValue: 0); + if (lockoutEnd > 0) { + final remaining = + (lockoutEnd - DateTime.now().millisecondsSinceEpoch) ~/ 1000; + if (remaining > 0) { + _lockoutRemaining = remaining; + _isLockedOut = true; + _lockoutTimer?.cancel(); + _lockoutTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + setState(() { + _lockoutRemaining--; + if (_lockoutRemaining <= 0) { + _isLockedOut = false; + timer.cancel(); + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureLockoutEndKey, 0); + _notifier.setStatus( + status: GestureStatus.verify, + gestureText: appLocalizations.verifyGestureLock, + ); + } else { + _notifier.setStatus( + status: GestureStatus.verifyFailedCountOverflow, + gestureText: + '${appLocalizations.gestureLockWrong} (${_lockoutRemaining}s)', + ); + } + }); + }); + } else { + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureLockoutEndKey, 0); + } + } + } + bool get _biometricAvailable => canAuthenticateResponse?.isSuccess ?? false; @override @@ -99,6 +139,7 @@ class PinVerifyScreenState extends BaseWindowState scale: 1.5); } }); + _restoreLockoutState(); initBiometricAuthentication(); } @@ -130,7 +171,7 @@ class PinVerifyScreenState extends BaseWindowState ? ResponsiveAppBar( title: appLocalizations.verifyGestureLock, showBack: false, - titleLeftMargin: 15, + titleLeftMargin: ResponsiveUtil.isMacOS() ? 78 : 15, actions: const [ BlankIconButton(), ], @@ -220,6 +261,8 @@ class PinVerifyScreenState extends BaseWindowState : _lockoutDurationSeconds; _isLockedOut = true; _lockoutRemaining = duration; + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureLockoutEndKey, + DateTime.now().millisecondsSinceEpoch + duration * 1000); _lockoutTimer?.cancel(); _lockoutTimer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { @@ -227,6 +270,7 @@ class PinVerifyScreenState extends BaseWindowState if (_lockoutRemaining <= 0) { _isLockedOut = false; timer.cancel(); + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureLockoutEndKey, 0); _notifier.setStatus( status: GestureStatus.verify, gestureText: appLocalizations.verifyGestureLock, @@ -251,12 +295,15 @@ class PinVerifyScreenState extends BaseWindowState success() { _failedAttempts = 0; + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureFailedAttemptsKey, 0); + ChewieHiveUtil.put(CloudOTPHiveUtil.gestureLockoutEndKey, 0); if (widget.onSuccess != null) widget.onSuccess!(); if (widget.jumpToMain) { ShortcutsUtil.jumpToMain(); } else { Navigator.of(context).pop(); } + Utils.initTray(); _gestureUnlockView.currentState?.updateStatus(UnlockStatus.normal); } @@ -270,6 +317,8 @@ class PinVerifyScreenState extends BaseWindowState success(); } else { _failedAttempts++; + ChewieHiveUtil.put( + CloudOTPHiveUtil.gestureFailedAttemptsKey, _failedAttempts); if (_failedAttempts >= _maxFailedAttempts) { _startLockout(); } else { diff --git a/lib/Screens/Setting/setting_safe_screen.dart b/lib/Screens/Setting/setting_safe_screen.dart index b696657d..2e8e4a81 100644 --- a/lib/Screens/Setting/setting_safe_screen.dart +++ b/lib/Screens/Setting/setting_safe_screen.dart @@ -312,8 +312,6 @@ class _SafeSettingScreenState extends BaseDynamicState onChanged: (autoLockOption) { if (autoLockOption == null) return; appProvider.autoLockTime = autoLockOption.autoLockTime; - ChewieHiveUtil.put(CloudOTPHiveUtil.autoLockTimeKey, - autoLockOption.autoLockTime); }, ), ), diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 47a4fc8f..6dd70c73 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -422,12 +422,12 @@ class TokenLayoutState extends BaseDynamicState } _processCopyCode() { - ChewieUtils.copy(context, getCurrentCode()); + ChewieUtils.copy(context, getCurrentCode(), autoClear: true); TokenDao.incTokenCopyTimes(widget.token); } _processCopyNextCode() { - ChewieUtils.copy(context, getNextCode()); + ChewieUtils.copy(context, getNextCode(), autoClear: true); TokenDao.incTokenCopyTimes(widget.token); } diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 4d73a1ce..955e04d0 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -277,24 +277,26 @@ class HomeScreenState extends BasePanelScreenState return MyScaffold( resizeToAvoidBottomInset: false, appBar: ResponsiveUtil.selectByOrientationNullable( - landscape: ResponsiveAppBar( - titleLeftMargin: 10, - titleWidget: Container( - constraints: const BoxConstraints( - maxWidth: 300, minWidth: 200, maxHeight: 36), - child: MySearchBar( - borderRadius: 8, - bottomMargin: 18, - focusNode: appProvider.searchFocusNode, - controller: _searchController, - background: ChewieTheme.scaffoldBackgroundColor, - hintText: appLocalizations.searchToken, - onSubmitted: (text) { - performSearch(text); - }, - ), - ), - ), + landscape: ResponsiveUtil.isMacOS() + ? null + : ResponsiveAppBar( + titleLeftMargin: 10, + titleWidget: Container( + constraints: const BoxConstraints( + maxWidth: 300, minWidth: 200, maxHeight: 36), + child: MySearchBar( + borderRadius: 8, + bottomMargin: 18, + focusNode: appProvider.searchFocusNode, + controller: _searchController, + background: ChewieTheme.scaffoldBackgroundColor, + hintText: appLocalizations.searchToken, + onSubmitted: (text) { + performSearch(text); + }, + ), + ), + ), portrait: null, ) as PreferredSizeWidget?, body: ResponsiveUtil.selectByOrientation( diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 09297024..42020d33 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -108,7 +108,7 @@ class MainScreenState extends BaseWindowState @override void onProtocolUrlReceived(String url) { - ILogger.info("Received protocol url: $url"); + ILogger.info("Received protocol url: ${Uri.parse(url).replace(queryParameters: {}).toString()}"); } Future fetchReleases() async { @@ -220,9 +220,10 @@ class MainScreenState extends BaseWindowState if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoCopyNextCodeKey) && currentProgress < autoCopyNextCodeProgressThrehold) { ChewieUtils.copy(context, CodeGenerator.getNextCode(token), - toastText: appLocalizations.alreadyCopiedNextCode); + toastText: appLocalizations.alreadyCopiedNextCode, autoClear: true); } else { - ChewieUtils.copy(context, CodeGenerator.getCurrentCode(token)); + ChewieUtils.copy(context, CodeGenerator.getCurrentCode(token), + autoClear: true); } TokenDao.incTokenCopyTimes(token); } @@ -245,6 +246,9 @@ class MainScreenState extends BaseWindowState Future jumpToLock({ bool autoAuth = false, }) async { + _menuTokens = []; + _menuCategories = []; + Utils.initSimpleTray(); if (CloudOTPHiveUtil.canDatabaseLock()) { ILogger.debug("Jump to database lock screen"); await DatabaseManager.resetDatabase(); @@ -257,6 +261,8 @@ class MainScreenState extends BaseWindowState PinVerifyScreen( onSuccess: () { appProvider.preventLock = false; + _loadMenuTokenData(); + Utils.initTray(); }, showWindowTitle: true, isModal: true, @@ -688,6 +694,9 @@ class MainScreenState extends BaseWindowState } _buildDesktopBody() { + if (ResponsiveUtil.isMacOS()) { + return _buildMacOSDesktopBody(); + } return MyScaffold( backgroundColor: ChewieTheme.appBarBackgroundColor, resizeToAvoidBottomInset: false, @@ -710,6 +719,28 @@ class MainScreenState extends BaseWindowState ); } + _buildMacOSDesktopBody() { + return MyScaffold( + backgroundColor: ChewieTheme.appBarBackgroundColor, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + _buildMacOSTitleBar(), + Expanded( + child: Row( + children: [ + _buildSideBar(), + Expanded( + child: HomeScreen(key: chewieProvider.panelScreenKey), + ), + ], + ), + ), + ], + ), + ); + } + changeMode() { if (ColorUtil.isDark(context)) { appProvider.themeMode = ActiveThemeMode.light; @@ -979,13 +1010,11 @@ class MainScreenState extends BaseWindowState crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox( - height: ResponsiveUtil.isMacOS() - ? 38 - : ResponsiveUtil.isDesktop() - ? 8 - : ResponsiveUtil.isLandscapeLayout() - ? 12 - : 8), + height: ResponsiveUtil.isDesktop() + ? 8 + : ResponsiveUtil.isLandscapeLayout() + ? 12 + : 8), _buildLogo(), const SizedBox(height: 8), ToolButton( @@ -1197,6 +1226,46 @@ class MainScreenState extends BaseWindowState ); } + Widget _buildMacOSTitleBar() { + return Container( + decoration: BoxDecoration( + color: ChewieTheme.appBarBackgroundColor, + border: ChewieTheme.bottomDivider, + ), + child: WindowTitleWrapper( + height: 48, + backgroundColor: Colors.transparent, + isStayOnTop: isStayOnTop, + isMaximized: isMaximized, + onStayOnTopTap: () { + setState(() { + isStayOnTop = !isStayOnTop; + windowManager.setAlwaysOnTop(isStayOnTop); + }); + }, + leftWidgets: [ + const SizedBox(width: 78), + Container( + constraints: const BoxConstraints( + maxWidth: 300, minWidth: 200, maxHeight: 36), + child: MySearchBar( + borderRadius: 8, + bottomMargin: 18, + focusNode: appProvider.searchFocusNode, + controller: searchController, + background: ChewieTheme.scaffoldBackgroundColor, + hintText: appLocalizations.searchToken, + onSubmitted: (text) { + homeScreenState?.performSearch(text.toString()); + }, + ), + ), + const Spacer(), + ], + ), + ); + } + void cancleTimer() { if (_timer != null) { _timer!.cancel(); diff --git a/lib/TokenUtils/ThirdParty/freeotp_importer.dart b/lib/TokenUtils/ThirdParty/freeotp_importer.dart index 9f9dd14a..d105fade 100644 --- a/lib/TokenUtils/ThirdParty/freeotp_importer.dart +++ b/lib/TokenUtils/ThirdParty/freeotp_importer.dart @@ -281,15 +281,12 @@ class FreeOTPTokenImporter implements BaseTokenImporter { static KeyParameter? decryptMasterKey(MasterKey masterKey, String password) { final salt = masterKey.salt; - debugPrint("Salt: ${base64Encode(Uint8List.fromList(salt).toList())}"); final key = deriveKey(password, masterKey.algorithm, masterKey.iterations, Uint8List.fromList(salt)); - debugPrint("DeriveKey: ${base64Encode(key.key)}"); final master = decryptEncryptedKey(masterKey.encryptedKey, key); if (master == null) { return null; } - debugPrint("Master: ${base64Encode(master)}"); return KeyParameter(master); } @@ -306,7 +303,7 @@ class FreeOTPTokenImporter implements BaseTokenImporter { try { return cipher.process(encryptedData); } catch (e, t) { - debugPrint("$e\n$t"); + ILogger.error("Failed to decrypt key", e, t); return null; } } @@ -323,7 +320,7 @@ class FreeOTPTokenImporter implements BaseTokenImporter { static KeyParameter deriveKey( String password, String algorithm, int iterations, Uint8List salt) { - debugPrint("Derive key from $password, $algorithm, $iterations, $salt"); + ILogger.info("Derive key with algorithm=$algorithm, iterations=$iterations"); Digest digest; switch (algorithm) { case 'PBKDF2withHmacSHA1': diff --git a/lib/TokenUtils/check_token_util.dart b/lib/TokenUtils/check_token_util.dart index 09b2e4e7..ea417ab1 100644 --- a/lib/TokenUtils/check_token_util.dart +++ b/lib/TokenUtils/check_token_util.dart @@ -84,7 +84,7 @@ class CheckTokenUtil { try { base32.decode(str.toUpperCase()); } catch (e, t) { - ILogger.error("Failed to decode base32 from $str", e, t); + ILogger.error("Failed to decode base32", e, t); return false; } return true; diff --git a/lib/TokenUtils/code_generator.dart b/lib/TokenUtils/code_generator.dart index e8216be3..442f3253 100644 --- a/lib/TokenUtils/code_generator.dart +++ b/lib/TokenUtils/code_generator.dart @@ -64,7 +64,7 @@ class CodeGenerator { break; } } catch (e, t) { - ILogger.error("Failed to get current code from token $token", e, t); + ILogger.error("Failed to get current code from token ${token.uid}", e, t); code = "ERROR"; } return code; @@ -113,7 +113,7 @@ class CodeGenerator { break; } } catch (e, t) { - ILogger.error("Failed to get next code from token $token", e, t); + ILogger.error("Failed to get next code from token ${token.uid}", e, t); code = "ERROR"; } return code; diff --git a/lib/TokenUtils/otp_token_parser.dart b/lib/TokenUtils/otp_token_parser.dart index 3b583975..70ea1ac6 100644 --- a/lib/TokenUtils/otp_token_parser.dart +++ b/lib/TokenUtils/otp_token_parser.dart @@ -78,7 +78,7 @@ class OtpTokenParser { return token == null ? [] : [token]; } } catch (e, t) { - ILogger.error("Failed to parse uri $line", e, t); + ILogger.error("Failed to parse uri", e, t); return []; } } diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index 23d1d284..d1f7494b 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -218,7 +218,20 @@ class AppProvider with ChangeNotifier { String latestVersion = ""; - bool preventLock = false; + Timer? _preventLockTimer; + bool _preventLock = false; + + bool get preventLock => _preventLock; + + set preventLock(bool value) { + _preventLockTimer?.cancel(); + _preventLock = value; + if (value) { + _preventLockTimer = Timer(const Duration(minutes: 5), () { + _preventLock = false; + }); + } + } FocusNode shortcutFocusNode = FocusNode(); FocusNode searchFocusNode = FocusNode(); diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 7b974746..23b99a10 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -14,8 +14,10 @@ */ import 'dart:convert'; +import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:cloudotp/Database/config_dao.dart'; import 'package:cloudotp/Screens/home_screen.dart'; import 'package:cloudotp/Utils/app_provider.dart'; @@ -87,6 +89,8 @@ class CloudOTPHiveUtil { static const String enableSafeModeKey = "enableSafeMode"; static const String hideGestureTrailKey = "hideGestureTrail"; static const String followSystemTextScaleKey = "followSystemTextScale"; + static const String gestureFailedAttemptsKey = "gestureFailedAttempts"; + static const String gestureLockoutEndKey = "gestureLockoutEnd"; //System static const String oldVersionKey = "oldVersion"; @@ -119,23 +123,44 @@ class CloudOTPHiveUtil { getEncryptDatabaseStatus() == EncryptDatabaseStatus.customPassword && DatabaseManager.isDatabaseEncrypted; - static String _hashGesturePassword(String password) => - sha256.convert(utf8.encode(password)).hex(); + static String _generateSalt() { + final random = Random.secure(); + final bytes = List.generate(16, (_) => random.nextInt(256)); + return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); + } + + static String _hashWithSalt(String password, String salt) => + sha256.convert(utf8.encode('$salt$password')).hex(); - static bool _isHashed(String value) => + static bool _isLegacyHash(String value) => value.length == 64 && RegExp(r'^[0-9a-f]+$').hasMatch(value); + static bool _isSaltedHash(String value) => + value.length == 97 && value[32] == '\$'; + static void setGesturePassword(String password) { - ChewieHiveUtil.put(guesturePasswdKey, _hashGesturePassword(password)); + final salt = _generateSalt(); + final hash = _hashWithSalt(password, salt); + ChewieHiveUtil.put(guesturePasswdKey, '$salt\$$hash'); } static bool verifyGesturePassword(String input) { final stored = ChewieHiveUtil.getString(guesturePasswdKey); if (stored == null || stored.isEmpty) return false; - if (_isHashed(stored)) { - return stored == _hashGesturePassword(input); + if (_isSaltedHash(stored)) { + final salt = stored.substring(0, 32); + final hash = stored.substring(33); + return hash == _hashWithSalt(input, salt); + } + if (_isLegacyHash(stored)) { + final legacyHash = sha256.convert(utf8.encode(input)).hex(); + if (stored == legacyHash) { + setGesturePassword(input); + return true; + } + return false; } - // Legacy plaintext — migrate to hash on successful match + // Legacy plaintext if (stored == input) { setGesturePassword(input); return true; @@ -169,13 +194,36 @@ class CloudOTPHiveUtil { await ChewieHiveUtil.put(CloudOTPHiveUtil.maxBackupsCountKey, count); } + static const FlutterSecureStorage _secureStorage = FlutterSecureStorage(); + static const String _secureDbPasswordKey = 'cloudotp_db_password'; + static Future regeneratePassword() async { String password = MockUtil.getRandomString(length: 16); + await _secureStorage.write(key: _secureDbPasswordKey, value: password); + // Remove from Hive if it was there before (migration cleanup) await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, password); + CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); return password; } + static Future getDatabasePassword() async { + // Try secure storage first + String? password = + await _secureStorage.read(key: _secureDbPasswordKey); + if (password != null && password.isNotEmpty) return password; + // Migrate from Hive if present + String? hivePassword = + ChewieHiveUtil.getString(CloudOTPHiveUtil.defaultDatabasePasswordKey); + if (hivePassword != null && hivePassword.isNotEmpty) { + await _secureStorage.write( + key: _secureDbPasswordKey, value: hivePassword); + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); + return hivePassword; + } + return ''; + } + static Future getBackupPath() async { String res = ChewieHiveUtil.getString(CloudOTPHiveUtil.backupPathKey, defaultValue: "") ?? diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index 0fe2fe74..fc742fa4 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -312,7 +312,8 @@ class Utils { } else if (menuItem.key == TrayKey.officialWebsite.key) { UriUtil.launchUrlUri(context, officialWebsite); } else if (menuItem.key.notNullOrEmpty && - menuItem.key!.startsWith(TrayKey.copyTokenCode.key)) { + menuItem.key!.startsWith(TrayKey.copyTokenCode.key) && + !isSimple) { String uid = menuItem.key!.split('_').last; OtpToken? token = await TokenDao.getTokenByUid(uid); if (token != null) { @@ -325,14 +326,16 @@ class Utils { if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoCopyNextCodeKey) && currentProgress < autoCopyNextCodeProgressThrehold) { ChewieUtils.copy(context, CodeGenerator.getNextCode(token), - toastText: appLocalizations.alreadyCopiedNextCode); + toastText: appLocalizations.alreadyCopiedNextCode, + autoClear: true); TokenDao.incTokenCopyTimes(token); IToast.showDesktopNotification( appLocalizations.alreadyCopiedNextCode, body: CodeGenerator.getNextCode(token), ); } else { - ChewieUtils.copy(context, CodeGenerator.getCurrentCode(token)); + ChewieUtils.copy(context, CodeGenerator.getCurrentCode(token), + autoClear: true); TokenDao.incTokenCopyTimes(token); IToast.showDesktopNotification( appLocalizations.copySuccess, diff --git a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart index 2d562cb0..64d5673e 100644 --- a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart @@ -272,7 +272,7 @@ class TokenOptionBottomSheetState title: appLocalizations.copyTokenCode, onTap: () { Navigator.pop(context); - ChewieUtils.copy(context, getCurrentCode()); + ChewieUtils.copy(context, getCurrentCode(), autoClear: true); TokenDao.incTokenCopyTimes(widget.token); }, ), @@ -281,7 +281,7 @@ class TokenOptionBottomSheetState title: appLocalizations.copyNextTokenCode, onTap: () { Navigator.pop(context); - ChewieUtils.copy(context, getNextCode()); + ChewieUtils.copy(context, getNextCode(), autoClear: true); TokenDao.incTokenCopyTimes(widget.token); }, ), diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index a600dac2..dc0a7592 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -823,5 +823,6 @@ "colorGroupStatus": "Status", "accentColor": "Accent Color", "resetToDefault": "Default", - "customColor": "Custom" + "customColor": "Custom", + "webDavHttpWarning": "Warning: HTTP is not encrypted. Your credentials may be intercepted. Please use HTTPS." } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index a28090e5..58bbe23d 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -823,5 +823,6 @@ "colorGroupStatus": "ステータス", "accentColor": "アクセントカラー", "resetToDefault": "デフォルト", - "customColor": "カスタム" + "customColor": "カスタム", + "webDavHttpWarning": "警告:HTTP接続は暗号化されていません。資格情報が傍受される可能性があります。HTTPSを使用してください。" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 5f48eaef..70376df1 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -823,5 +823,6 @@ "colorGroupStatus": "状态", "accentColor": "强调色", "resetToDefault": "默认", - "customColor": "自定义" + "customColor": "自定义", + "webDavHttpWarning": "警告:HTTP 连接未加密,您的凭据可能被截获,请使用 HTTPS。" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index 40f94278..e4d21dda 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -823,5 +823,6 @@ "colorGroupStatus": "狀態", "accentColor": "強調色", "resetToDefault": "預設", - "customColor": "自訂" + "customColor": "自訂", + "webDavHttpWarning": "警告:HTTP 連線未加密,您的憑證可能被攔截,請使用 HTTPS。" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 5cf434be..ff71301a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -123,8 +123,7 @@ Future initHive() async { ChewieHiveUtil.put(CloudOTPHiveUtil.oldVersionKey, ResponsiveUtil.version); try { await DatabaseManager.initDataBase( - ChewieHiveUtil.getString(CloudOTPHiveUtil.defaultDatabasePasswordKey) ?? - ""); + await CloudOTPHiveUtil.getDatabasePassword()); } catch (e) { if (DatabaseManager.lib != null) { CloudOTPHiveUtil.setEncryptDatabaseStatus( diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 5be98f29..2ee1f7b3 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -2,6 +2,8 @@ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { + private let trafficLightVerticalOffset: CGFloat = 18.0 + override func awakeFromNib() { let flutterViewController = FlutterViewController() let windowFrame = self.frame @@ -13,4 +15,26 @@ class MainFlutterWindow: NSWindow { super.awakeFromNib() } + + override func layoutIfNeeded() { + super.layoutIfNeeded() + repositionTrafficLights() + } + + override func makeKeyAndOrderFront(_ sender: Any?) { + super.makeKeyAndOrderFront(sender) + repositionTrafficLights() + } + + private func repositionTrafficLights() { + guard let closeButton = standardWindowButton(.closeButton), + let miniaturizeButton = standardWindowButton(.miniaturizeButton), + let zoomButton = standardWindowButton(.zoomButton) else { return } + + for button in [closeButton, miniaturizeButton, zoomButton] { + var frame = button.frame + frame.origin.y = button.superview!.frame.height - frame.height - trafficLightVerticalOffset + button.setFrameOrigin(frame.origin) + } + } } diff --git a/pubspec.lock b/pubspec.lock index 6ecb68b0..8fc9ae3d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -665,7 +665,7 @@ packages: source: hosted version: "2.0.0" flutter_secure_storage: - dependency: transitive + dependency: "direct main" description: name: flutter_secure_storage sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" diff --git a/pubspec.yaml b/pubspec.yaml index 61a1b359..d57d90a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: local_auth: ^2.2.0 # 指纹验证 local_auth_android: ^1.0.42 # 指纹验证 share_plus: ^10.1.4 # 分享 + flutter_secure_storage: ^8.1.0 # 安全存储 protocol_handler: ^0.2.0 # 协议处理 biometric_storage: path: third-party/tool/biometric_storage diff --git a/third-party/awesome_cloud/lib/utils/cloud_logger.dart b/third-party/awesome_cloud/lib/utils/cloud_logger.dart index 52e4dc0e..e6ec04c4 100644 --- a/third-party/awesome_cloud/lib/utils/cloud_logger.dart +++ b/third-party/awesome_cloud/lib/utils/cloud_logger.dart @@ -51,10 +51,10 @@ class CloudLogger { static void infoResponse(String tag, String message, Response response) { if (logInfo != null) { - logInfo!(tag, "$message [${response.statusCode}] [${response.body}]"); + logInfo!(tag, "$message [${response.statusCode}]"); } else { debugPrint( - '[$tag] [INFO RESPONSE] : $message [${response.statusCode}] [${response.body}]'); + '[$tag] [INFO RESPONSE] : $message [${response.statusCode}]'); } } diff --git a/third-party/awesome_cloud/lib/webdav/src/webdav_dio.dart b/third-party/awesome_cloud/lib/webdav/src/webdav_dio.dart index dea3d271..78cb0b55 100644 --- a/third-party/awesome_cloud/lib/webdav/src/webdav_dio.dart +++ b/third-party/awesome_cloud/lib/webdav/src/webdav_dio.dart @@ -57,6 +57,7 @@ class WdDio with DioMixin implements Dio { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, CancelToken? cancelToken, + int retryCount = 0, }) async { // options Options options = Options(method: method); @@ -89,6 +90,10 @@ class WdDio with DioMixin implements Dio { ); if (resp.statusCode == 401) { + if (retryCount >= 3) { + throw newResponseError(resp); + } + String? w3AHeader = resp.headers.value('www-authenticate'); String? lowerW3AHeader = w3AHeader?.toLowerCase(); @@ -118,16 +123,7 @@ class WdDio with DioMixin implements Dio { pwd: self.auth.pwd, dParts: DigestParts(w3AHeader)); } else { - await req( - self, - method, - path, - data: data, - optionsHandler: optionsHandler, - onSendProgress: onSendProgress, - onReceiveProgress: onReceiveProgress, - cancelToken: cancelToken, - ); + throw newResponseError(resp); } // retry @@ -140,6 +136,7 @@ class WdDio with DioMixin implements Dio { onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, cancelToken: cancelToken, + retryCount: retryCount + 1, ); } else if (resp.statusCode == 302) { // 文件位置被重定向到新路径 diff --git a/third-party/chewie/lib/src/Utils/utils.dart b/third-party/chewie/lib/src/Utils/utils.dart index a2b94933..b062d3a0 100644 --- a/third-party/chewie/lib/src/Utils/utils.dart +++ b/third-party/chewie/lib/src/Utils/utils.dart @@ -90,10 +90,14 @@ class ChewieUtils { return data?.text; } + static Timer? _clipboardClearTimer; + static void copy( BuildContext context, dynamic data, { String? toastText, + bool autoClear = false, + Duration autoClearDuration = const Duration(seconds: 30), }) { Clipboard.setData(ClipboardData(text: data.toString())).then((value) { toastText ??= chewieLocalizations.copySuccess; @@ -103,6 +107,12 @@ class ChewieUtils { } }); HapticFeedback.mediumImpact(); + if (autoClear) { + _clipboardClearTimer?.cancel(); + _clipboardClearTimer = Timer(autoClearDuration, () { + Clipboard.setData(const ClipboardData(text: '')); + }); + } } static int binarySearch( diff --git a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart index 9f449bc4..3eb3cf5d 100644 --- a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart +++ b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart @@ -82,7 +82,8 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { const Spacer(), ...[ ...desktopActions, - const SizedBox(width: 44), + if (!ResponsiveUtil.isMacOS()) + const SizedBox(width: 44), ], ], ), From afa2011eea4824ecaaa2128c8838e23cb76e1b9c Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 02:57:36 +0800 Subject: [PATCH 06/36] Fix build errors --- .github/workflows/release.yml | 57 ++++++++++++++----- android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 2 +- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83979732..6466f156 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,20 +43,20 @@ jobs: artifact_path: | build/linux/*.deb build/linux/*.tar.gz -# - target: iOS -# os: macos-latest -# cache_pod_key: ios-pods -# cache_pod_path: ios/Pods -# cache_pod_restore_keys_hash_file: ios/Podfile.lock -# artifact_name: release-iOS -# artifact_path: build/**/*.ipa -# - target: MacOS -# os: macos-latest -# cache_pod_key: macos-pods -# cache_pod_path: macos/Pods -# cache_pod_restore_keys_hash_file: macos/Podfile.lock -# artifact_name: release-MacOS -# artifact_path: /Users/runner/work/CloudOTP/CloudOTP/*.dmg + - target: iOS + os: macos-latest + cache_pod_key: ios-pods + cache_pod_path: ios/Pods + cache_pod_restore_keys_hash_file: ios/Podfile.lock + artifact_name: release-iOS + artifact_path: build/ios/ipa/*.ipa + - target: macOS + os: macos-latest + cache_pod_key: macos-pods + cache_pod_path: macos/Pods + cache_pod_restore_keys_hash_file: macos/Podfile.lock + artifact_name: release-macOS + artifact_path: build/macos/*.dmg outputs: version: ${{ steps.get_version.outputs.version }} date: ${{ steps.get_version.outputs.date}} @@ -93,6 +93,15 @@ jobs: java-version: '17' cache: gradle + # Cache CocoaPods + - name: Cache CocoaPods + if: matrix.cache_pod_key != '' + uses: actions/cache@v4 + with: + path: ${{ matrix.cache_pod_path }} + key: ${{ matrix.cache_pod_key }}-${{ hashFiles(matrix.cache_pod_restore_keys_hash_file) }} + restore-keys: ${{ matrix.cache_pod_key }}- + # Flutter Pub Get - name: Flutter Pub Get run: | @@ -188,6 +197,24 @@ jobs: dpkg-deb --build --root-owner-group CloudOTP-${{ steps.get_version.outputs.version }}-linux-${{ steps.get_version.outputs.arch }} tar -zcvf CloudOTP-${{ steps.get_version.outputs.version }}-linux-${{ steps.get_version.outputs.arch }}.tar.gz -C CloudOTP-${{ steps.get_version.outputs.version }}-linux-${{ steps.get_version.outputs.arch }} . + # Build iOS .ipa (no codesign) + - name: Build iOS + if: matrix.target == 'iOS' + run: | + flutter build ipa --release --no-codesign + cd build/ios/ipa + mv CloudOTP.ipa CloudOTP-${{ steps.get_version.outputs.version }}-ios.ipa 2>/dev/null || true + + # Build macOS .app & .dmg + - name: Build macOS + if: matrix.target == 'macOS' + run: | + flutter build macos --release + APP_PATH="build/macos/Build/Products/Release/CloudOTP.app" + DMG_NAME="CloudOTP-${{ steps.get_version.outputs.version }}-macos-universal.dmg" + mkdir -p build/macos + hdiutil create -volname CloudOTP -srcfolder "$APP_PATH" -ov -format UDZO "build/macos/$DMG_NAME" + # Upload Artifacts - name: Upload Artifacts uses: actions/upload-artifact@v4 @@ -225,6 +252,8 @@ jobs: mv /tmp/artifacts/release-Linux/*.tar.gz /tmp/artifacts/final/ mv /tmp/artifacts/release-Linux-arm/*.deb /tmp/artifacts/final/ mv /tmp/artifacts/release-Linux-arm/*.tar.gz /tmp/artifacts/final/ + mv /tmp/artifacts/release-iOS/*.ipa /tmp/artifacts/final/ 2>/dev/null || true + mv /tmp/artifacts/release-macOS/*.dmg /tmp/artifacts/final/ 2>/dev/null || true cd /tmp/artifacts/final for file in *; do diff --git a/android/build.gradle b/android/build.gradle index ceba1f59..7e764881 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' + classpath 'com.android.tools.build:gradle:8.7.0' // classpath 'com.android.tools:r8:1.6.82' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5e6b5427..afa1e8eb 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index a3010453..2513f907 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.0.2" apply false + id "com.android.application" version "8.7.0" apply false id "org.jetbrains.kotlin.android" version "2.1.10" apply false } From 2de27b359353e7e6630d24942050d87581fc62b3 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 12:26:17 +0800 Subject: [PATCH 07/36] Fix ci failures --- .github/workflows/release.yml | 5 ++++ .../FlutterWindowManagerPlugin.java | 26 +++++-------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6466f156..b76c0eda 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,6 +102,11 @@ jobs: key: ${{ matrix.cache_pod_key }}-${{ hashFiles(matrix.cache_pod_restore_keys_hash_file) }} restore-keys: ${{ matrix.cache_pod_key }}- + # Update CocoaPods repo + - name: Update CocoaPods repo + if: matrix.target == 'iOS' || matrix.target == 'macOS' + run: pod repo update + # Flutter Pub Get - name: Flutter Pub Get run: | diff --git a/third-party/tool/flutter_windowmanager/android/src/main/java/io/adaptant/labs/flutter_windowmanager/FlutterWindowManagerPlugin.java b/third-party/tool/flutter_windowmanager/android/src/main/java/io/adaptant/labs/flutter_windowmanager/FlutterWindowManagerPlugin.java index ae3a95ca..8d3666b1 100644 --- a/third-party/tool/flutter_windowmanager/android/src/main/java/io/adaptant/labs/flutter_windowmanager/FlutterWindowManagerPlugin.java +++ b/third-party/tool/flutter_windowmanager/android/src/main/java/io/adaptant/labs/flutter_windowmanager/FlutterWindowManagerPlugin.java @@ -9,44 +9,32 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; /** FlutterWindowManagerPlugin */ public class FlutterWindowManagerPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { private Activity activity; + private MethodChannel channel; @SuppressWarnings("unused") public FlutterWindowManagerPlugin() { } - private FlutterWindowManagerPlugin(Activity activity) { - this.activity = activity; - } - - /** Plugin registration. */ - @Deprecated - public static void registerWith(Registrar registrar) { - new FlutterWindowManagerPlugin(registrar.activity()).registerWith(registrar.messenger()); - } - - private void registerWith(BinaryMessenger binaryMessenger) { - final MethodChannel channel = new MethodChannel(binaryMessenger, "flutter_windowmanager"); - channel.setMethodCallHandler(this); - } - @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - registerWith(flutterPluginBinding.getBinaryMessenger()); + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_windowmanager"); + channel.setMethodCallHandler(this); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - + if (channel != null) { + channel.setMethodCallHandler(null); + channel = null; + } } /** From 5412b7f3e6476d0f564ee37ce2df0ca9b1e65208 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 16:34:16 +0800 Subject: [PATCH 08/36] Fix macos close error --- lib/Screens/Lock/database_decrypt_screen.dart | 7 ++++++- lib/Screens/Lock/pin_verify_screen.dart | 5 +++++ lib/Screens/home_screen.dart | 6 +++++- lib/Screens/main_screen.dart | 2 +- lib/Utils/utils.dart | 2 +- lib/main.dart | 1 + macos/Runner/AppDelegate.swift | 11 +++++++++++ macos/Runner/MainFlutterWindow.swift | 5 +++++ .../lib/src/Widgets/States/base_window_state.dart | 10 ++++++++++ .../chewie/lib/src/Widgets/Window/window_title.dart | 8 +++++--- 10 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index c3cce293..999c2a9f 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -40,6 +40,11 @@ class DatabaseDecryptScreen extends StatefulWidget { class DatabaseDecryptScreenState extends BaseWindowState with TrayListener { final FocusNode _focusNode = FocusNode(); + + @override + Future onWindowClose() async { + await windowManager.destroy(); + } late InputValidateAsyncController validateAsyncController; GlobalKey formKey = GlobalKey(); bool _isValidated = true; @@ -159,7 +164,7 @@ class DatabaseDecryptScreenState extends BaseWindowState backgroundColor: ChewieTheme.scaffoldBackgroundColor, appBar: ResponsiveUtil.isDesktop() ? ResponsiveAppBar( - title: appLocalizations.appName, + title: appLocalizations.decryptDatabasePassword, showBack: false, titleLeftMargin: ResponsiveUtil.isMacOS() ? 78 : 15, actions: const [ diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 219f2fb5..b8afff43 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -53,6 +53,11 @@ class PinVerifyScreen extends StatefulWidget { class PinVerifyScreenState extends BaseWindowState with TrayListener { static const int _maxFailedAttempts = 5; + + @override + Future onWindowClose() async { + await windowManager.destroy(); + } static const int _lockoutDurationSeconds = 30; static const int _extendedLockoutDurationSeconds = 300; static const int _extendedLockoutThreshold = 10; diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 955e04d0..1e986a1c 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -120,6 +120,7 @@ class HomeScreenState extends BasePanelScreenState OtpToken token, { bool forceAll = false, }) async { + if (tokens.any((element) => element.uid == token.uid)) return; if (currentCategoryUid.isEmpty) { if (!forceAll) { return; @@ -230,7 +231,10 @@ class HomeScreenState extends BasePanelScreenState currentCategoryUid, searchKey: _searchKey, ).then((value) { - tokens = value; + final seen = {}; + tokens = value.where((t) => seen.add(t.uid)).toList(); + final currentUids = seen; + tokenKeyMap.removeWhere((uid, _) => !currentUids.contains(uid)); performSort(); }); } diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 42020d33..9c151685 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -342,7 +342,7 @@ class MainScreenState extends BaseWindowState LogicalKeyboardKey.keyQ, meta: true, ), - onSelected: () => windowManager.close(), + onSelected: () => windowManager.destroy(), ), ]), ], diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index fc742fa4..0b7c20a2 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -369,7 +369,7 @@ class Utils { Utils.initTray(); } } else if (menuItem.key == TrayKey.exitApp.key) { - windowManager.close(); + windowManager.destroy(); } } } diff --git a/lib/main.dart b/lib/main.dart index ff71301a..03e7d207 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -196,6 +196,7 @@ Future initWindow() async { titleBarStyle: TitleBarStyle.hidden, ); await windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.setPreventClose(true); await windowManager.setPosition(position); await windowManager.show(); await windowManager.focus(); diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 5482ca0d..d844889f 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -14,6 +14,17 @@ class AppDelegate: FlutterAppDelegate { } override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return false + } + + override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if !flag { + for window in sender.windows { + if !window.isVisible { + window.makeKeyAndOrderFront(self) + } + } + } return true } diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 2ee1f7b3..49bdf899 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -10,6 +10,11 @@ class MainFlutterWindow: NSWindow { self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) + self.titleVisibility = .hidden + self.titlebarAppearsTransparent = true + self.styleMask.insert(.fullSizeContentView) + self.isOpaque = false + RegisterGeneratedPlugins(registry: flutterViewController) LocalNotifierOverride.register(with: flutterViewController.registrar(forPlugin: "LocalNotifierOverride")) diff --git a/third-party/chewie/lib/src/Widgets/States/base_window_state.dart b/third-party/chewie/lib/src/Widgets/States/base_window_state.dart index 4d6fb9fa..4831f3b1 100644 --- a/third-party/chewie/lib/src/Widgets/States/base_window_state.dart +++ b/third-party/chewie/lib/src/Widgets/States/base_window_state.dart @@ -63,4 +63,14 @@ abstract class BaseWindowState isMaximized = false; }); } + + @override + Future onWindowClose() async { + if (ChewieHiveUtil.getBool(ChewieHiveUtil.showTrayKey) && + ChewieHiveUtil.getBool(ChewieHiveUtil.enableCloseToTrayKey)) { + await windowManager.hide(); + } else { + await windowManager.destroy(); + } + } } diff --git a/third-party/chewie/lib/src/Widgets/Window/window_title.dart b/third-party/chewie/lib/src/Widgets/Window/window_title.dart index 370b8f79..6ec7df79 100644 --- a/third-party/chewie/lib/src/Widgets/Window/window_title.dart +++ b/third-party/chewie/lib/src/Widgets/Window/window_title.dart @@ -13,6 +13,8 @@ * If not, see . */ +import 'dart:io'; + import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; @@ -67,12 +69,12 @@ class WindowTitleWrapper extends StatelessWidget { borderRadius: ChewieDimens.borderRadius8, onPressed: onStayOnTopTap, ), - const SizedBox(width: 3), + if (!Platform.isMacOS) const SizedBox(width: 3), MinimizeWindowButton( colors: ChewieColors.getNormalButtonColors(context), borderRadius: ChewieDimens.borderRadius8, ), - const SizedBox(width: 3), + if (!Platform.isMacOS) const SizedBox(width: 3), isMaximized ? RestoreWindowButton( colors: ChewieColors.getNormalButtonColors(context), @@ -84,7 +86,7 @@ class WindowTitleWrapper extends StatelessWidget { borderRadius: ChewieDimens.borderRadius8, onPressed: ResponsiveUtil.maximizeOrRestore, ), - const SizedBox(width: 3), + if (!Platform.isMacOS) const SizedBox(width: 3), CloseWindowButton( colors: ChewieColors.getCloseButtonColors(context), borderRadius: ChewieDimens.borderRadius8, From 4275e647ee00015c2cf24252f2da2d8577ae615f Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 17:13:10 +0800 Subject: [PATCH 09/36] Fix android errors --- pubspec.lock | 4 +- pubspec.yaml | 2 +- .../Widgets/General/responsive_app_bar.dart | 3 +- .../image_gallery_saver/android/build.gradle | 19 +++----- .../ImageGallerySaverPlugin.kt | 2 +- .../move_to_background/android/build.gradle | 20 ++++---- .../MoveToBackgroundPlugin.java | 47 ++++--------------- 7 files changed, 31 insertions(+), 66 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8fc9ae3d..1bed8251 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2278,10 +2278,10 @@ packages: dependency: "direct overridden" description: name: webview_flutter_android - sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91 + sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f url: "https://pub.flutter-io.cn" source: hosted - version: "3.16.1" + version: "4.3.0" webview_flutter_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d57d90a7..7b0080e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -95,7 +95,7 @@ dependencies: window_manager: ^0.4.3 dependency_overrides: - webview_flutter_android: 3.16.1 + webview_flutter_android: 4.3.0 dev_dependencies: flutter_test: diff --git a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart index 3eb3cf5d..9f449bc4 100644 --- a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart +++ b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart @@ -82,8 +82,7 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { const Spacer(), ...[ ...desktopActions, - if (!ResponsiveUtil.isMacOS()) - const SizedBox(width: 44), + const SizedBox(width: 44), ], ], ), diff --git a/third-party/tool/image_gallery_saver/android/build.gradle b/third-party/tool/image_gallery_saver/android/build.gradle index 2dcc537d..c34e1e8c 100644 --- a/third-party/tool/image_gallery_saver/android/build.gradle +++ b/third-party/tool/image_gallery_saver/android/build.gradle @@ -2,15 +2,13 @@ group 'com.example.imagegallerysaver' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.3.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.android.tools.build:gradle:7.4.2' } } @@ -26,20 +24,17 @@ apply plugin: 'kotlin-android' android { namespace "com.example.imagegallerysaver" - compileSdkVersion 30 + compileSdk 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + minSdk 21 } - lintOptions { - disable 'InvalidPackage' - } -} -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } diff --git a/third-party/tool/image_gallery_saver/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt b/third-party/tool/image_gallery_saver/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt index c88c80fc..00313641 100644 --- a/third-party/tool/image_gallery_saver/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt +++ b/third-party/tool/image_gallery_saver/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt @@ -18,7 +18,7 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar + import java.io.File import java.io.FileInputStream import java.io.IOException diff --git a/third-party/tool/move_to_background/android/build.gradle b/third-party/tool/move_to_background/android/build.gradle index ec9c3ed4..f400ec1a 100644 --- a/third-party/tool/move_to_background/android/build.gradle +++ b/third-party/tool/move_to_background/android/build.gradle @@ -4,21 +4,18 @@ version '1.0-SNAPSHOT' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:7.4.2' } } rootProject.allprojects { repositories { google() - jcenter() - } - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" + mavenCentral() } } @@ -26,13 +23,14 @@ apply plugin: 'com.android.library' android { namespace "com.sayegh.move_to_background" - compileSdkVersion 28 + compileSdk 34 defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + minSdk 21 } - lintOptions { - disable 'InvalidPackage' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } } diff --git a/third-party/tool/move_to_background/android/src/main/java/com/sayegh/move_to_background/MoveToBackgroundPlugin.java b/third-party/tool/move_to_background/android/src/main/java/com/sayegh/move_to_background/MoveToBackgroundPlugin.java index 09843d5d..9c6a4c6c 100644 --- a/third-party/tool/move_to_background/android/src/main/java/com/sayegh/move_to_background/MoveToBackgroundPlugin.java +++ b/third-party/tool/move_to_background/android/src/main/java/com/sayegh/move_to_background/MoveToBackgroundPlugin.java @@ -1,53 +1,29 @@ package com.sayegh.move_to_background; import android.app.Activity; -import android.content.Context; import android.util.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -/** MoveToBackgroundPlugin */ public class MoveToBackgroundPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware { private static final String CHANNEL_NAME = "move_to_background"; private MethodChannel channel; - private static Activity activity; - - /** Plugin registration. */ - public static void registerWith(Registrar registrar) { - if (registrar.activity() != null) { - MoveToBackgroundPlugin.activity = registrar.activity(); - } - MoveToBackgroundPlugin plugin = new MoveToBackgroundPlugin(); - plugin.setupChannel(registrar.messenger(), registrar.context()); - } + private Activity activity; @Override - @SuppressWarnings("deprecation") public void onAttachedToEngine(FlutterPluginBinding binding) { - setupChannel(binding.getFlutterEngine().getDartExecutor(), binding.getApplicationContext()); + channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); + channel.setMethodCallHandler(this); } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { - teardownChannel(); - } - - - private void setupChannel(BinaryMessenger messenger, Context context) { - channel = new MethodChannel(messenger, CHANNEL_NAME); - channel.setMethodCallHandler(this); - - } - - private void teardownChannel() { channel.setMethodCallHandler(null); channel = null; } @@ -55,8 +31,8 @@ private void teardownChannel() { @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("moveTaskToBack")) { - if (MoveToBackgroundPlugin.activity != null) { - MoveToBackgroundPlugin.activity.moveTaskToBack(true); + if (activity != null) { + activity.moveTaskToBack(true); } else { Log.e("MoveToBackgroundPlugin", "moveTaskToBack failed: activity=null"); } @@ -66,26 +42,23 @@ public void onMethodCall(MethodCall call, Result result) { } } - @Override - public void onAttachedToActivity(ActivityPluginBinding binding) - { - MoveToBackgroundPlugin.activity = binding.getActivity(); + public void onAttachedToActivity(ActivityPluginBinding binding) { + activity = binding.getActivity(); } @Override public void onDetachedFromActivityForConfigChanges() { - MoveToBackgroundPlugin.activity = null; + activity = null; } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - MoveToBackgroundPlugin.activity = binding.getActivity(); + activity = binding.getActivity(); } @Override public void onDetachedFromActivity() { - MoveToBackgroundPlugin.activity = null; + activity = null; } - } From 36d86b05ba8770bd59d1b18a9edfc661e8c320a7 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 17 May 2026 17:25:08 +0800 Subject: [PATCH 10/36] feat: Update Chewie localization files and add markdown_widget dependencies - Added localization files for Japanese, Chinese (Simplified and Traditional), and English in Chewie. - Generated localization outputs and cache files for the new localizations. - Updated pubspec.lock for markdown_widget with new dependencies and versions. --- android/app/build.gradle | 2 +- pubspec.lock | 6 +- pubspec.yaml | 3 +- third-party/awesome_cloud/pubspec.lock | 176 ++--- third-party/chewie/build/.last_build_id | 1 + .../.filecache | 1 + .../gen_l10n_inputs_and_outputs.json | 1 + .../gen_localizations.d | 1 + .../gen_localizations.stamp | 1 + .../outputs.json | 1 + third-party/chewie/pubspec.lock | 4 +- third-party/chewie/pubspec.yaml | 2 +- .../tool/biometric_storage/pubspec.lock | 56 +- .../tool/flutter_web_auth_2/pubspec.lock | 134 ++-- .../pubspec.lock | 48 +- .../tool/flutter_windowmanager/pubspec.lock | 42 +- .../tool/image_gallery_saver/pubspec.lock | 42 +- .../tool/move_to_background/pubspec.lock | 42 +- .../screen_capturer/pubspec.lock | 60 +- .../screen_capturer_linux/pubspec.lock | 56 +- .../screen_capturer_macos/pubspec.lock | 56 +- .../pubspec.lock | 134 ++-- .../screen_capturer_windows/pubspec.lock | 58 +- .../widget/desktop_multi_window/pubspec.lock | 46 +- third-party/widget/lucide_icons/pubspec.lock | 50 +- .../.plugin_symlinks/url_launcher_linux | 1 + .../ephemeral/Flutter-Generated.xcconfig | 8 +- .../ephemeral/flutter_export_environment.sh | 8 +- .../widget/markdown_widget/pubspec.lock | 634 ++++++++++++++++++ third-party/widget/pinput/pubspec.lock | 44 +- .../widget/smart_snackbars/pubspec.lock | 46 +- 31 files changed, 1203 insertions(+), 561 deletions(-) create mode 100644 third-party/chewie/build/.last_build_id create mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache create mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json create mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d create mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp create mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json create mode 120000 third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux create mode 100644 third-party/widget/markdown_widget/pubspec.lock diff --git a/android/app/build.gradle b/android/app/build.gradle index 0c3d690f..d06f9cfe 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -50,7 +50,7 @@ android { defaultConfig { applicationId "com.cloudchewie.cloudotp" - minSdkVersion 23 + minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/pubspec.lock b/pubspec.lock index 1bed8251..0f9a77fd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -779,10 +779,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2" url: "https://pub.flutter-io.cn" source: hosted - version: "8.2.12" + version: "9.0.0" freezed_annotation: dependency: transitive description: @@ -1939,7 +1939,7 @@ packages: source: hosted version: "3.1.0+1" sqlite3: - dependency: transitive + dependency: "direct main" description: name: sqlite3 sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e" diff --git a/pubspec.yaml b/pubspec.yaml index 7b0080e0..4897d06b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: awesome_chewie: path: third-party/chewie animations: ^2.0.11 # 动画 - fluttertoast: ^8.2.6 # 吐司 + fluttertoast: ^9.0.0 # 吐司 lottie: ^3.1.2 # Lottie动画 modal_bottom_sheet: ^3.0.0 # 底部弹窗 palette_generator: ^0.3.3+3 # 获取主色调 @@ -42,6 +42,7 @@ dependencies: hive: ^4.0.0-dev.2 # 轻量存储 isar_flutter_libs: ^4.0.0-dev.13 sqflite_sqlcipher: ^3.1.0+1 # SQLite加密 + sqlite3: 2.7.5 # SQLite # 网络 http: ^1.2.1 dio: ^5.4.3+1 # 网络请求 diff --git a/third-party/awesome_cloud/pubspec.lock b/third-party/awesome_cloud/pubspec.lock index 6ad5e33f..929c3dfc 100644 --- a/third-party/awesome_cloud/pubspec.lock +++ b/third-party/awesome_cloud/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: _discoveryapis_commons sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.7" args: @@ -14,7 +14,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: @@ -22,7 +22,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -30,7 +30,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" buffer: @@ -38,7 +38,7 @@ packages: description: name: buffer sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.3" characters: @@ -46,7 +46,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -54,7 +54,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" code_assets: @@ -62,7 +62,7 @@ packages: description: name: code_assets sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" collection: @@ -70,7 +70,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" convert: @@ -78,7 +78,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" crypto: @@ -86,7 +86,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" fake_async: @@ -94,7 +94,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -102,7 +102,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" file: @@ -110,7 +110,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" flutter: @@ -123,7 +123,7 @@ packages: description: name: flutter_lints sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" flutter_secure_storage: @@ -131,7 +131,7 @@ packages: description: name: flutter_secure_storage sha256: "22dbf16f23a4bcf9d35e51be1c84ad5bb6f627750565edd70dab70f3ff5fff8f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.1.0" flutter_secure_storage_linux: @@ -139,7 +139,7 @@ packages: description: name: flutter_secure_storage_linux sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.3" flutter_secure_storage_macos: @@ -147,7 +147,7 @@ packages: description: name: flutter_secure_storage_macos sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" flutter_secure_storage_platform_interface: @@ -155,7 +155,7 @@ packages: description: name: flutter_secure_storage_platform_interface sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" flutter_secure_storage_web: @@ -163,7 +163,7 @@ packages: description: name: flutter_secure_storage_web sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" flutter_secure_storage_windows: @@ -171,7 +171,7 @@ packages: description: name: flutter_secure_storage_windows sha256: "38f9501c7cb6f38961ef0e1eacacee2b2d4715c63cc83fe56449c4d3d0b47255" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" flutter_test: @@ -203,7 +203,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" googleapis: @@ -211,7 +211,7 @@ packages: description: name: googleapis sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "13.2.0" hashlib: @@ -219,7 +219,7 @@ packages: description: name: hashlib sha256: f547a6273fa2bba211e903f32b234a6a7097aca1d218d2391f40021d25e7f200 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.24.0" hashlib_codecs: @@ -227,7 +227,7 @@ packages: description: name: hashlib_codecs sha256: "8cea9ccafcfeaa7324d2ae52c61c69f7ff71f4237507a018caab31b9e416e3b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" hooks: @@ -235,7 +235,7 @@ packages: description: name: hooks sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" http: @@ -243,7 +243,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.0" http_parser: @@ -251,23 +251,23 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" intl: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.dev" + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.19.0" + version: "0.20.2" jni: dependency: transitive description: name: jni sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" jni_flutter: @@ -275,7 +275,7 @@ packages: description: name: jni_flutter sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" js: @@ -283,7 +283,7 @@ packages: description: name: js sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.7" leak_tracker: @@ -291,7 +291,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -299,7 +299,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -307,7 +307,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -315,7 +315,7 @@ packages: description: name: lints sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" logging: @@ -323,7 +323,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" matcher: @@ -331,7 +331,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -339,7 +339,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -347,7 +347,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mime: @@ -355,7 +355,7 @@ packages: description: name: mime sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.6" native_toolchain_c: @@ -363,7 +363,7 @@ packages: description: name: native_toolchain_c sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.6" objective_c: @@ -371,7 +371,7 @@ packages: description: name: objective_c sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.3.0" package_config: @@ -379,7 +379,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" path: @@ -387,7 +387,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_provider: @@ -395,7 +395,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.5" path_provider_android: @@ -403,7 +403,7 @@ packages: description: name: path_provider_android sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" path_provider_foundation: @@ -411,7 +411,7 @@ packages: description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" path_provider_linux: @@ -419,7 +419,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -427,7 +427,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" path_provider_windows: @@ -435,7 +435,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" petitparser: @@ -443,7 +443,7 @@ packages: description: name: petitparser sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.2" platform: @@ -451,7 +451,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -459,7 +459,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" protocol_handler: @@ -467,7 +467,7 @@ packages: description: name: protocol_handler sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_android: @@ -475,7 +475,7 @@ packages: description: name: protocol_handler_android sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_ios: @@ -483,7 +483,7 @@ packages: description: name: protocol_handler_ios sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_macos: @@ -491,7 +491,7 @@ packages: description: name: protocol_handler_macos sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_platform_interface: @@ -499,7 +499,7 @@ packages: description: name: protocol_handler_platform_interface sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_windows: @@ -507,7 +507,7 @@ packages: description: name: protocol_handler_windows sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" pub_semver: @@ -515,7 +515,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" record_use: @@ -523,7 +523,7 @@ packages: description: name: record_use sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.0" sky_engine: @@ -536,7 +536,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -544,7 +544,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -552,7 +552,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -560,7 +560,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -568,7 +568,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -576,7 +576,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" typed_data: @@ -584,7 +584,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" url_launcher: @@ -592,7 +592,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.2" url_launcher_android: @@ -600,7 +600,7 @@ packages: description: name: url_launcher_android sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.29" url_launcher_ios: @@ -608,7 +608,7 @@ packages: description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.4.1" url_launcher_linux: @@ -616,7 +616,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" url_launcher_macos: @@ -624,7 +624,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -632,7 +632,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" url_launcher_web: @@ -640,7 +640,7 @@ packages: description: name: url_launcher_web sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" url_launcher_windows: @@ -648,7 +648,7 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.5" vector_math: @@ -656,7 +656,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -664,7 +664,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" web: @@ -672,7 +672,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" win32: @@ -680,7 +680,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" win32_registry: @@ -688,7 +688,7 @@ packages: description: name: win32_registry sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.5" window_to_front: @@ -696,7 +696,7 @@ packages: description: name: window_to_front sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.3" xdg_directories: @@ -704,7 +704,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" xml: @@ -712,7 +712,7 @@ packages: description: name: xml sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.6.1" yaml: @@ -720,7 +720,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: diff --git a/third-party/chewie/build/.last_build_id b/third-party/chewie/build/.last_build_id new file mode 100644 index 00000000..715c593e --- /dev/null +++ b/third-party/chewie/build/.last_build_id @@ -0,0 +1 @@ +79741fb6e62572cea92da8a6348e1da9 \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache new file mode 100644 index 00000000..5a6727f2 --- /dev/null +++ b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache @@ -0,0 +1 @@ +{"version":2,"files":[{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","hash":"70757ef2352220fc97c3495293d245b4"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","hash":"49aa8735c4a22d9b4ae6e6ec98fcc449"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","hash":"a099aaf6f0811459d67742c6583dd8b0"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb","hash":"e594aa62e96b81e5eb8abdbcf8f88dea"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","hash":"eaf64fea1cb33c4346a4b0302d70df7e"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","hash":"d5e7a6966f654d84623dd8e7b596b0ba"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","hash":"b2ffcfa59377fd2c342eea700847c05d"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","hash":"e5dfa012bb93f9eeeffdb17e75598c76"},{"path":"C:\\Users\\ruida\\Documents\\ProgramData\\flutter\\packages\\flutter_tools\\lib\\src\\build_system\\targets\\localizations.dart","hash":"33a276900ad78ff1cd267a3483f69235"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml","hash":"54799cb52644e7b64505470d47fa55d3"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart","hash":"06a30ff8b2e2b37fc55ea13f26ce4bb2"}]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json new file mode 100644 index 00000000..8463ed7d --- /dev/null +++ b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json @@ -0,0 +1 @@ +{"inputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb"],"outputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d new file mode 100644 index 00000000..84204c3d --- /dev/null +++ b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d @@ -0,0 +1 @@ + D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart: D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp new file mode 100644 index 00000000..237dea6c --- /dev/null +++ b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp @@ -0,0 +1 @@ +{"inputs":["C:\\Users\\ruida\\Documents\\ProgramData\\flutter\\packages\\flutter_tools\\lib\\src\\build_system\\targets\\localizations.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb"],"outputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json new file mode 100644 index 00000000..2801b07f --- /dev/null +++ b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json @@ -0,0 +1 @@ +["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"] \ No newline at end of file diff --git a/third-party/chewie/pubspec.lock b/third-party/chewie/pubspec.lock index d645ef7c..2457a8ad 100644 --- a/third-party/chewie/pubspec.lock +++ b/third-party/chewie/pubspec.lock @@ -584,10 +584,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2" url: "https://pub.flutter-io.cn" source: hosted - version: "8.2.12" + version: "9.0.0" frontend_server_client: dependency: transitive description: diff --git a/third-party/chewie/pubspec.yaml b/third-party/chewie/pubspec.yaml index 73deb280..a54fff30 100644 --- a/third-party/chewie/pubspec.yaml +++ b/third-party/chewie/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter_localizations: sdk: flutter # 控件 - fluttertoast: ^8.2.12 + fluttertoast: ^9.0.0 flutter_animate: ^4.5.2 auto_size_text: ^3.0.0 lottie: ^3.3.1 diff --git a/third-party/tool/biometric_storage/pubspec.lock b/third-party/tool/biometric_storage/pubspec.lock index 7ed3d84f..dfad07fe 100644 --- a/third-party/tool/biometric_storage/pubspec.lock +++ b/third-party/tool/biometric_storage/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -54,7 +54,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" flutter: @@ -67,7 +67,7 @@ packages: description: name: flutter_lints sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" flutter_test: @@ -85,7 +85,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -93,7 +93,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -101,7 +101,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -109,7 +109,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" logging: @@ -117,7 +117,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" matcher: @@ -125,7 +125,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -133,7 +133,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -141,7 +141,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -149,7 +149,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -157,7 +157,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" sky_engine: @@ -170,7 +170,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -178,7 +178,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -186,7 +186,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -194,7 +194,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -202,7 +202,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -210,7 +210,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -218,7 +218,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -226,7 +226,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" web: @@ -234,7 +234,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" win32: @@ -242,7 +242,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" sdks: diff --git a/third-party/tool/flutter_web_auth_2/pubspec.lock b/third-party/tool/flutter_web_auth_2/pubspec.lock index 67ee680d..5c6f5fe2 100644 --- a/third-party/tool/flutter_web_auth_2/pubspec.lock +++ b/third-party/tool/flutter_web_auth_2/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: @@ -14,7 +14,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -22,7 +22,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -30,7 +30,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -38,7 +38,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" code_assets: @@ -46,7 +46,7 @@ packages: description: name: code_assets sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" collection: @@ -54,7 +54,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" crypto: @@ -62,7 +62,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" fake_async: @@ -70,7 +70,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -78,7 +78,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" file: @@ -86,7 +86,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" flutter: @@ -99,7 +99,7 @@ packages: description: name: flutter_lints sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" flutter_test: @@ -124,7 +124,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" hooks: @@ -132,7 +132,7 @@ packages: description: name: hooks sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" jni: @@ -140,7 +140,7 @@ packages: description: name: jni sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" jni_flutter: @@ -148,7 +148,7 @@ packages: description: name: jni_flutter sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" leak_tracker: @@ -156,7 +156,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -164,7 +164,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -172,7 +172,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -180,7 +180,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" logging: @@ -188,7 +188,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" matcher: @@ -196,7 +196,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -204,7 +204,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -212,7 +212,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" native_toolchain_c: @@ -220,7 +220,7 @@ packages: description: name: native_toolchain_c sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.6" objective_c: @@ -228,7 +228,7 @@ packages: description: name: objective_c sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "9.3.0" package_config: @@ -236,7 +236,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" path: @@ -244,7 +244,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_provider: @@ -252,7 +252,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.5" path_provider_android: @@ -260,7 +260,7 @@ packages: description: name: path_provider_android sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.1" path_provider_foundation: @@ -268,7 +268,7 @@ packages: description: name: path_provider_foundation sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.6.0" path_provider_linux: @@ -276,7 +276,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -284,7 +284,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" path_provider_windows: @@ -292,7 +292,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" platform: @@ -300,7 +300,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -308,7 +308,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" protocol_handler: @@ -316,7 +316,7 @@ packages: description: name: protocol_handler sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_android: @@ -324,7 +324,7 @@ packages: description: name: protocol_handler_android sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_ios: @@ -332,7 +332,7 @@ packages: description: name: protocol_handler_ios sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_macos: @@ -340,7 +340,7 @@ packages: description: name: protocol_handler_macos sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_platform_interface: @@ -348,7 +348,7 @@ packages: description: name: protocol_handler_platform_interface sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" protocol_handler_windows: @@ -356,7 +356,7 @@ packages: description: name: protocol_handler_windows sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" pub_semver: @@ -364,7 +364,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" record_use: @@ -372,7 +372,7 @@ packages: description: name: record_use sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.0" sky_engine: @@ -385,7 +385,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -393,7 +393,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -401,7 +401,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -409,7 +409,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -417,7 +417,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -425,7 +425,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" typed_data: @@ -433,7 +433,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" url_launcher: @@ -441,7 +441,7 @@ packages: description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.2" url_launcher_android: @@ -449,7 +449,7 @@ packages: description: name: url_launcher_android sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.3.29" url_launcher_ios: @@ -457,7 +457,7 @@ packages: description: name: url_launcher_ios sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.4.1" url_launcher_linux: @@ -465,7 +465,7 @@ packages: description: name: url_launcher_linux sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" url_launcher_macos: @@ -473,7 +473,7 @@ packages: description: name: url_launcher_macos sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.5" url_launcher_platform_interface: @@ -481,7 +481,7 @@ packages: description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" url_launcher_web: @@ -489,7 +489,7 @@ packages: description: name: url_launcher_web sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" url_launcher_windows: @@ -497,7 +497,7 @@ packages: description: name: url_launcher_windows sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.5" vector_math: @@ -505,7 +505,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -513,7 +513,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" web: @@ -521,7 +521,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" win32: @@ -529,7 +529,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" win32_registry: @@ -537,7 +537,7 @@ packages: description: name: win32_registry sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.5" window_to_front: @@ -545,7 +545,7 @@ packages: description: name: window_to_front sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.3" xdg_directories: @@ -553,7 +553,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" yaml: @@ -561,7 +561,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: diff --git a/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock b/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock index 477abfc3..71e1c1bb 100644 --- a/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock +++ b/third-party/tool/flutter_web_auth_2_platform_interface/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -59,7 +59,7 @@ packages: description: name: flutter_lints sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" flutter_test: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -96,7 +96,7 @@ packages: description: name: lints sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" matcher: @@ -104,7 +104,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -112,7 +112,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -120,7 +120,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -128,7 +128,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -136,7 +136,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" sky_engine: @@ -149,7 +149,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -157,7 +157,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -165,7 +165,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -173,7 +173,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -181,7 +181,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -189,7 +189,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -197,7 +197,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -205,7 +205,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/flutter_windowmanager/pubspec.lock b/third-party/tool/flutter_windowmanager/pubspec.lock index 9fe59906..7568c259 100644 --- a/third-party/tool/flutter_windowmanager/pubspec.lock +++ b/third-party/tool/flutter_windowmanager/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -64,7 +64,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" matcher: @@ -88,7 +88,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -96,7 +96,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -104,7 +104,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -112,7 +112,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -125,7 +125,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -133,7 +133,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -141,7 +141,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -149,7 +149,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -157,7 +157,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -165,7 +165,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -173,7 +173,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -181,7 +181,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/image_gallery_saver/pubspec.lock b/third-party/tool/image_gallery_saver/pubspec.lock index 9fe59906..7568c259 100644 --- a/third-party/tool/image_gallery_saver/pubspec.lock +++ b/third-party/tool/image_gallery_saver/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -64,7 +64,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" matcher: @@ -88,7 +88,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -96,7 +96,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -104,7 +104,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -112,7 +112,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -125,7 +125,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -133,7 +133,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -141,7 +141,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -149,7 +149,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -157,7 +157,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -165,7 +165,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -173,7 +173,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -181,7 +181,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/move_to_background/pubspec.lock b/third-party/tool/move_to_background/pubspec.lock index 9fe59906..7568c259 100644 --- a/third-party/tool/move_to_background/pubspec.lock +++ b/third-party/tool/move_to_background/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -64,7 +64,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" matcher: @@ -88,7 +88,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -96,7 +96,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -104,7 +104,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -112,7 +112,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -125,7 +125,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -133,7 +133,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -141,7 +141,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -149,7 +149,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -157,7 +157,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -165,7 +165,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -173,7 +173,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -181,7 +181,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock index 7b5e7d02..6f57e694 100644 --- a/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock +++ b/third-party/tool/screen_capturer_lib/screen_capturer/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -54,7 +54,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" flutter: @@ -71,16 +71,16 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -96,7 +96,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -104,7 +104,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" matcher: @@ -112,7 +112,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -120,7 +120,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -128,7 +128,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mostly_reasonable_lints: @@ -136,7 +136,7 @@ packages: description: name: mostly_reasonable_lints sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" path: @@ -144,7 +144,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -152,7 +152,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" screen_capturer_linux: @@ -188,7 +188,7 @@ packages: description: name: shell_executor sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.6" sky_engine: @@ -201,7 +201,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -209,7 +209,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -217,7 +217,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -225,7 +225,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -233,7 +233,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -241,7 +241,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -249,7 +249,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -257,7 +257,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" win32: @@ -265,7 +265,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" sdks: diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock index f1346be1..9bd25986 100644 --- a/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock +++ b/third-party/tool/screen_capturer_lib/screen_capturer_linux/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -63,16 +63,16 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -96,7 +96,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" matcher: @@ -104,7 +104,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -112,7 +112,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -120,7 +120,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mostly_reasonable_lints: @@ -128,7 +128,7 @@ packages: description: name: mostly_reasonable_lints sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" path: @@ -136,7 +136,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -144,7 +144,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" screen_capturer_platform_interface: @@ -159,7 +159,7 @@ packages: description: name: shell_executor sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.6" sky_engine: @@ -172,7 +172,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -180,7 +180,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -188,7 +188,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -196,7 +196,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -204,7 +204,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -212,7 +212,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -220,7 +220,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -228,7 +228,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock index f1346be1..9bd25986 100644 --- a/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock +++ b/third-party/tool/screen_capturer_lib/screen_capturer_macos/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -63,16 +63,16 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -96,7 +96,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" matcher: @@ -104,7 +104,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -112,7 +112,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -120,7 +120,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mostly_reasonable_lints: @@ -128,7 +128,7 @@ packages: description: name: mostly_reasonable_lints sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" path: @@ -136,7 +136,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -144,7 +144,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" screen_capturer_platform_interface: @@ -159,7 +159,7 @@ packages: description: name: shell_executor sha256: "9c024546fc96470a6b96be9902f0bc05347a017a7638ed8d93c77e8d77eb3c3c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.6" sky_engine: @@ -172,7 +172,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -180,7 +180,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -188,7 +188,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -196,7 +196,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -204,7 +204,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -212,7 +212,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -220,7 +220,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -228,7 +228,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock index dcca0999..562c2744 100644 --- a/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock +++ b/third-party/tool/screen_capturer_lib/screen_capturer_platform_interface/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: _fe_analyzer_shared sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "93.0.0" analyzer: @@ -14,7 +14,7 @@ packages: description: name: analyzer sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "10.0.1" args: @@ -22,7 +22,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: @@ -30,7 +30,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -38,7 +38,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" build: @@ -46,7 +46,7 @@ packages: description: name: build sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.6" build_config: @@ -54,7 +54,7 @@ packages: description: name: build_config sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" build_daemon: @@ -62,7 +62,7 @@ packages: description: name: build_daemon sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.1" build_runner: @@ -70,7 +70,7 @@ packages: description: name: build_runner sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.15.0" built_collection: @@ -78,7 +78,7 @@ packages: description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" built_value: @@ -86,7 +86,7 @@ packages: description: name: built_value sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "8.12.6" characters: @@ -94,7 +94,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" checked_yaml: @@ -102,7 +102,7 @@ packages: description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" clock: @@ -110,7 +110,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -118,7 +118,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" convert: @@ -126,7 +126,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" crypto: @@ -134,7 +134,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.7" dart_style: @@ -142,7 +142,7 @@ packages: description: name: dart_style sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.7" fake_async: @@ -150,7 +150,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" file: @@ -158,7 +158,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" fixnum: @@ -166,7 +166,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" flutter: @@ -184,7 +184,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" graphs: @@ -192,7 +192,7 @@ packages: description: name: graphs sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" http_multi_server: @@ -200,7 +200,7 @@ packages: description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" http_parser: @@ -208,7 +208,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" io: @@ -216,31 +216,31 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" json_annotation: dependency: "direct main" description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: "2c15e78e1cc6e62aadecf59f81566fd56829713d96a8c4177699e2b2e17f20db" - url: "https://pub.dev" + sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 + url: "https://pub.flutter-io.cn" source: hosted - version: "6.13.2" + version: "6.14.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -248,7 +248,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -256,7 +256,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -264,7 +264,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" logging: @@ -272,7 +272,7 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" matcher: @@ -280,7 +280,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -288,7 +288,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -296,7 +296,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mime: @@ -304,7 +304,7 @@ packages: description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" mostly_reasonable_lints: @@ -312,7 +312,7 @@ packages: description: name: mostly_reasonable_lints sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" package_config: @@ -320,7 +320,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" path: @@ -328,7 +328,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -336,7 +336,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" pool: @@ -344,7 +344,7 @@ packages: description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.2" pub_semver: @@ -352,7 +352,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" pubspec_parse: @@ -360,7 +360,7 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" shelf: @@ -368,7 +368,7 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.2" shelf_web_socket: @@ -376,7 +376,7 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" sky_engine: @@ -389,7 +389,7 @@ packages: description: name: source_gen sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.3" source_helper: @@ -397,7 +397,7 @@ packages: description: name: source_helper sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.12" source_span: @@ -405,7 +405,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -413,7 +413,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -421,7 +421,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" stream_transform: @@ -429,7 +429,7 @@ packages: description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" string_scanner: @@ -437,7 +437,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -445,7 +445,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -453,7 +453,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" typed_data: @@ -461,7 +461,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" vector_math: @@ -469,7 +469,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -477,7 +477,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" watcher: @@ -485,7 +485,7 @@ packages: description: name: watcher sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" web: @@ -493,7 +493,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" web_socket: @@ -501,7 +501,7 @@ packages: description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" web_socket_channel: @@ -509,7 +509,7 @@ packages: description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3" yaml: @@ -517,7 +517,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: diff --git a/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock b/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock index 571e55d6..76b29aea 100644 --- a/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock +++ b/third-party/tool/screen_capturer_lib/screen_capturer_windows/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" ffi: @@ -54,7 +54,7 @@ packages: description: name: ffi sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" flutter: @@ -71,16 +71,16 @@ packages: dependency: transitive description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.11.0" + version: "4.12.0" leak_tracker: dependency: transitive description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -96,7 +96,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -104,7 +104,7 @@ packages: description: name: lints sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" matcher: @@ -112,7 +112,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -120,7 +120,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -128,7 +128,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" mostly_reasonable_lints: @@ -136,7 +136,7 @@ packages: description: name: mostly_reasonable_lints sha256: e19fec63536866ba307b3dfbc258b4bce9b3745129f038006b56b4067c6293d8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" path: @@ -144,7 +144,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" plugin_platform_interface: @@ -152,7 +152,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" screen_capturer_platform_interface: @@ -172,7 +172,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -180,7 +180,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -188,7 +188,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -196,7 +196,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -204,7 +204,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -212,7 +212,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -220,7 +220,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -228,7 +228,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" win32: @@ -236,7 +236,7 @@ packages: description: name: win32 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.15.0" sdks: diff --git a/third-party/widget/desktop_multi_window/pubspec.lock b/third-party/widget/desktop_multi_window/pubspec.lock index c01ef112..66363d5f 100644 --- a/third-party/widget/desktop_multi_window/pubspec.lock +++ b/third-party/widget/desktop_multi_window/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -59,7 +59,7 @@ packages: description: name: flutter_lints sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" flutter_test: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -96,7 +96,7 @@ packages: description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" matcher: @@ -104,7 +104,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -112,7 +112,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -120,7 +120,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -128,7 +128,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -141,7 +141,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -149,7 +149,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -157,7 +157,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -165,7 +165,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -173,7 +173,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -181,7 +181,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -189,7 +189,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -197,7 +197,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/widget/lucide_icons/pubspec.lock b/third-party/widget/lucide_icons/pubspec.lock index 857bff92..1d645336 100644 --- a/third-party/widget/lucide_icons/pubspec.lock +++ b/third-party/widget/lucide_icons/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" csslib: @@ -46,7 +46,7 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" fake_async: @@ -54,7 +54,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -72,7 +72,7 @@ packages: description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.6" leak_tracker: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -96,7 +96,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lint: @@ -104,7 +104,7 @@ packages: description: name: lint sha256: "4a539aa34ec5721a2c7574ae2ca0336738ea4adc2a34887d54b7596310b33c85" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" matcher: @@ -112,7 +112,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -120,7 +120,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -128,7 +128,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -136,7 +136,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" recase: @@ -144,7 +144,7 @@ packages: description: name: recase sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.0" sky_engine: @@ -157,7 +157,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -165,7 +165,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -173,7 +173,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -181,7 +181,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -189,7 +189,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -197,7 +197,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -205,7 +205,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -213,7 +213,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux b/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux new file mode 120000 index 00000000..94bc1499 --- /dev/null +++ b/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux @@ -0,0 +1 @@ +C:/Users/ruida/AppData/Local/Pub/Cache/hosted/pub.flutter-io.cn/url_launcher_linux-3.2.2/ \ No newline at end of file diff --git a/third-party/widget/markdown_widget/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/third-party/widget/markdown_widget/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index 07f042d0..b5d99a95 100644 --- a/third-party/widget/markdown_widget/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/third-party/widget/markdown_widget/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,10 +1,10 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=D:\flutter -FLUTTER_APPLICATION_PATH=D:\GitHub\markdown_widget +FLUTTER_ROOT=C:\Users\ruida\Documents\ProgramData\flutter +FLUTTER_APPLICATION_PATH=D:\Repositories\Flutter_Projects\CloudOTP\third-party\widget\markdown_widget COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=2.1.0 -FLUTTER_BUILD_NUMBER=2.1.0 +FLUTTER_BUILD_NAME=2.3.2 +FLUTTER_BUILD_NUMBER=6 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/third-party/widget/markdown_widget/macos/Flutter/ephemeral/flutter_export_environment.sh b/third-party/widget/markdown_widget/macos/Flutter/ephemeral/flutter_export_environment.sh index af106320..855cc7e3 100644 --- a/third-party/widget/markdown_widget/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/third-party/widget/markdown_widget/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,11 +1,11 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=D:\flutter" -export "FLUTTER_APPLICATION_PATH=D:\GitHub\markdown_widget" +export "FLUTTER_ROOT=C:\Users\ruida\Documents\ProgramData\flutter" +export "FLUTTER_APPLICATION_PATH=D:\Repositories\Flutter_Projects\CloudOTP\third-party\widget\markdown_widget" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=2.1.0" -export "FLUTTER_BUILD_NUMBER=2.1.0" +export "FLUTTER_BUILD_NAME=2.3.2" +export "FLUTTER_BUILD_NUMBER=6" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/third-party/widget/markdown_widget/pubspec.lock b/third-party/widget/markdown_widget/pubspec.lock new file mode 100644 index 00000000..7c55c12b --- /dev/null +++ b/third-party/widget/markdown_widget/pubspec.lock @@ -0,0 +1,634 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "93.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.6" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.15.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.12.6" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.4" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_highlight: + dependency: "direct main" + description: + name: flutter_highlight + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + highlight: + dependency: "direct main" + description: + name: highlight + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + markdown: + dependency: "direct main" + description: + name: markdown + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.6.4" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + path: + dependency: "direct dev" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + scroll_to_index: + dependency: "direct main" + description: + name: scroll_to_index + sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.5" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + visibility_detector: + dependency: "direct main" + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.flutter-io.cn" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/third-party/widget/pinput/pubspec.lock b/third-party/widget/pinput/pubspec.lock index 6d421cd6..8c55c7e6 100644 --- a/third-party/widget/pinput/pubspec.lock +++ b/third-party/widget/pinput/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -64,7 +64,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" matcher: @@ -88,7 +88,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -96,7 +96,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -104,7 +104,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -112,7 +112,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -125,7 +125,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -133,7 +133,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -141,7 +141,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -149,7 +149,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -157,7 +157,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -165,7 +165,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" universal_platform: @@ -173,7 +173,7 @@ packages: description: name: universal_platform sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" vector_math: @@ -181,7 +181,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -189,7 +189,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: diff --git a/third-party/widget/smart_snackbars/pubspec.lock b/third-party/widget/smart_snackbars/pubspec.lock index c01ef112..66363d5f 100644 --- a/third-party/widget/smart_snackbars/pubspec.lock +++ b/third-party/widget/smart_snackbars/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" characters: @@ -22,7 +22,7 @@ packages: description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -30,7 +30,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -38,7 +38,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" fake_async: @@ -46,7 +46,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" flutter: @@ -59,7 +59,7 @@ packages: description: name: flutter_lints sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" flutter_test: @@ -72,7 +72,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -80,7 +80,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -88,7 +88,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -96,7 +96,7 @@ packages: description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" matcher: @@ -104,7 +104,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -112,7 +112,7 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: @@ -120,7 +120,7 @@ packages: description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" path: @@ -128,7 +128,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" sky_engine: @@ -141,7 +141,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -149,7 +149,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -157,7 +157,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -165,7 +165,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -173,7 +173,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: @@ -181,7 +181,7 @@ packages: description: name: test_api sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" vector_math: @@ -189,7 +189,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -197,7 +197,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" sdks: From b774ea0382fa9760faafd5bc368ad4232f6bc702 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 17:44:21 +0800 Subject: [PATCH 11/36] Refactor dependencies and clean up unused packages - Updated `pubspec.yaml` to replace `flutter_widget_from_html` with `flutter_widget_from_html_core`. - Adjusted version constraints for `sqflite_sqlcipher`. - Removed unused dependencies from `pubspec.lock` including `audio_session`, `chewie`, `cupertino_icons`, `dbus`, `flutter_svg`, `just_audio`, `video_player`, and others. - Cleaned up `third-party/chewie` by removing build artifacts and unnecessary files. - Added `.gitignore` to `third-party/chewie` to exclude build and tool directories. --- .gitignore | 3 +- ios/.gitignore | 1 + ios/Podfile.lock | 32 --- macos/Flutter/GeneratedPluginRegistrant.swift | 10 - macos/Podfile.lock | 32 --- pubspec.lock | 218 +--------------- pubspec.yaml | 4 +- third-party/chewie/.gitignore | 2 + third-party/chewie/build/.last_build_id | 1 - .../.filecache | 1 - .../gen_l10n_inputs_and_outputs.json | 1 - .../gen_localizations.d | 1 - .../gen_localizations.stamp | 1 - .../outputs.json | 1 - third-party/chewie/pubspec.lock | 234 +----------------- third-party/chewie/pubspec.yaml | 2 +- .../.plugin_symlinks/url_launcher_linux | 1 - 17 files changed, 10 insertions(+), 535 deletions(-) create mode 100644 third-party/chewie/.gitignore delete mode 100644 third-party/chewie/build/.last_build_id delete mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache delete mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json delete mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d delete mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp delete mode 100644 third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json delete mode 120000 third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux diff --git a/.gitignore b/.gitignore index 40731031..36486003 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,8 @@ migrate_working_dir/ .packages .pub-cache/ .pub/ -/build/ +**/build/ +**/.plugin_symlinks/ # Symbolication related app.*.symbols diff --git a/ios/.gitignore b/ios/.gitignore index 7a7f9873..94e7aef9 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,3 +1,4 @@ +build/ **/dgph *.mode1v3 *.mode2v3 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bede7a18..7275e4ec 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,8 +1,6 @@ PODS: - app_links (0.0.2): - Flutter - - audio_session (0.0.1): - - Flutter - biometric_storage (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -63,8 +61,6 @@ PODS: - Flutter - isar_flutter_libs (1.0.0): - Flutter - - just_audio (0.0.1): - - Flutter - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS @@ -113,18 +109,9 @@ PODS: - SwiftyGif (5.4.5) - url_launcher_ios (0.0.1): - Flutter - - video_player_avfoundation (0.0.1): - - Flutter - - FlutterMacOS - - wakelock_plus (0.0.1): - - Flutter - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - - audio_session (from `.symlinks/plugins/audio_session/ios`) - biometric_storage (from `.symlinks/plugins/biometric_storage/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -136,7 +123,6 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - - just_audio (from `.symlinks/plugins/just_audio/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - move_to_background (from `.symlinks/plugins/move_to_background/ios`) @@ -153,9 +139,6 @@ DEPENDENCIES: - sqflite_sqlcipher (from `.symlinks/plugins/sqflite_sqlcipher/ios`) - SQLCipher (~> 4.5) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -171,8 +154,6 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" - audio_session: - :path: ".symlinks/plugins/audio_session/ios" biometric_storage: :path: ".symlinks/plugins/biometric_storage/ios" device_info_plus: @@ -195,8 +176,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_gallery_saver/ios" isar_flutter_libs: :path: ".symlinks/plugins/isar_flutter_libs/ios" - just_audio: - :path: ".symlinks/plugins/just_audio/ios" local_auth_darwin: :path: ".symlinks/plugins/local_auth_darwin/darwin" mobile_scanner: @@ -227,16 +206,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite_sqlcipher/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - video_player_avfoundation: - :path: ".symlinks/plugins/video_player_avfoundation/darwin" - wakelock_plus: - :path: ".symlinks/plugins/wakelock_plus/ios" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 - audio_session: f08db0697111ac84ba46191b55488c0563bb29c6 biometric_storage: 662167ef947fba48891850f0b3042f892a839e9a device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c @@ -251,7 +223,6 @@ SPEC CHECKSUMS: FMDB: 57486c1117fd8e0e6b947b2f54c3f42bf8e57a4e image_gallery_saver: 14711d79da40581063e8842a11acf1969d781ed7 isar_flutter_libs: 9fc2cfb928c539e1b76c481ba5d143d556d94920 - just_audio: 6c031bb61297cf218b4462be616638e81c058e97 local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710 @@ -272,9 +243,6 @@ SPEC CHECKSUMS: SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b - wakelock_plus: fd58c82b1388f4afe3fe8aa2c856503a262a5b03 - webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c PODFILE CHECKSUM: 59067792e03d7a5801ba684f46eb50a563a19de4 diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e8aa6937..35fb2b4b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,6 @@ import FlutterMacOS import Foundation import app_links -import audio_session import biometric_storage import device_info_plus import file_picker @@ -15,7 +14,6 @@ import flutter_secure_storage_macos import flutter_web_auth_2 import hotkey_manager_macos import isar_flutter_libs -import just_audio import local_auth_darwin import local_notifier import mobile_scanner @@ -30,15 +28,11 @@ import sqflite import sqflite_sqlcipher import tray_manager import url_launcher_macos -import video_player_avfoundation -import wakelock_plus -import webview_flutter_wkwebview import window_manager import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) - AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) BiometricStorageMacOSPlugin.register(with: registry.registrar(forPlugin: "BiometricStorageMacOSPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) @@ -47,7 +41,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) - JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) @@ -62,9 +55,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SqfliteSqlCipherPlugin.register(with: registry.registrar(forPlugin: "SqfliteSqlCipherPlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) - WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) - FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 75c78569..ac5a82a7 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,8 +1,6 @@ PODS: - app_links (1.0.0): - FlutterMacOS - - audio_session (0.0.1): - - FlutterMacOS - biometric_storage (0.0.1): - FlutterMacOS - device_info_plus (0.0.1): @@ -25,8 +23,6 @@ PODS: - HotKey - isar_flutter_libs (1.0.0): - FlutterMacOS - - just_audio (0.0.1): - - FlutterMacOS - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS @@ -67,14 +63,6 @@ PODS: - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS - - video_player_avfoundation (0.0.1): - - Flutter - - FlutterMacOS - - wakelock_plus (0.0.1): - - FlutterMacOS - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS - window_manager (0.2.0): - FlutterMacOS - window_to_front (0.0.1): @@ -82,7 +70,6 @@ PODS: DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - biometric_storage (from `Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) @@ -92,7 +79,6 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`) - isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`) - - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`) - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) @@ -108,9 +94,6 @@ DEPENDENCIES: - SQLCipher (~> 4.5) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) - - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) - - webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) - window_to_front (from `Flutter/ephemeral/.symlinks/plugins/window_to_front/macos`) @@ -124,8 +107,6 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos - audio_session: - :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos biometric_storage: :path: Flutter/ephemeral/.symlinks/plugins/biometric_storage/macos device_info_plus: @@ -144,8 +125,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos isar_flutter_libs: :path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos - just_audio: - :path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos local_auth_darwin: :path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin local_notifier: @@ -174,12 +153,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - video_player_avfoundation: - :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin - wakelock_plus: - :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos - webview_flutter_wkwebview: - :path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos window_to_front: @@ -187,7 +160,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d - audio_session: 48ab6500f7a5e7c64363e206565a5dfe5a0c1441 biometric_storage: 9de0cb4e591e52329ca0da7df42e964db6c526cf device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a @@ -199,7 +171,6 @@ SPEC CHECKSUMS: HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe isar_flutter_libs: a65381780401f81ad6bf3f2e7cd0de5698fb98c4 - just_audio: eb8b016ac4493159ab24db4f7215e55303b39a84 local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 @@ -216,9 +187,6 @@ SPEC CHECKSUMS: SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b - wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 - webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c window_to_front: 9e76fd432e36700a197dac86a0011e49c89abe0a diff --git a/pubspec.lock b/pubspec.lock index 0f9a77fd..db9b65fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,14 +105,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.11.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.21" auto_size_text: dependency: "direct main" description: @@ -270,14 +262,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" - chewie: - dependency: transitive - description: - name: chewie - sha256: "335df378c025588aef400c704bd71f0daea479d4cd57c471c88c056c1144e7cd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.8.5" cli_util: dependency: transitive description: @@ -366,14 +350,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" - cupertino_icons: - dependency: transitive - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.8" dart_style: dependency: transitive description: @@ -382,14 +358,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.6" - dbus: - dependency: transitive - description: - name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.10" decimal: dependency: transitive description: @@ -720,14 +688,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -752,16 +712,8 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_widget_from_html: - dependency: "direct main" - description: - name: flutter_widget_from_html - sha256: "9e2a6201c4d2eb910b6b3ebb2a9f5c490fc61c9a1aa35eafdde38f0fc659cf4c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.2" flutter_widget_from_html_core: - dependency: transitive + dependency: "direct main" description: name: flutter_widget_from_html_core sha256: b1048fd119a14762e2361bd057da608148a895477846d6149109b2151d2f7abf @@ -799,54 +751,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" - fwfh_cached_network_image: - dependency: transitive - description: - name: fwfh_cached_network_image - sha256: "8e44226801bfba27930673953afce8af44da7e92573be93f60385d9865a089dd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.3" - fwfh_chewie: - dependency: transitive - description: - name: fwfh_chewie - sha256: "37bde9cedfb6dc5546176f7f0c56af1e814966cb33ec58f16c9565ed93ccb704" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.8" - fwfh_just_audio: - dependency: transitive - description: - name: fwfh_just_audio - sha256: "38dc2c55803bd3cef33042c473e0c40b891ad4548078424641a32032f6a1245f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.2" - fwfh_svg: - dependency: transitive - description: - name: fwfh_svg - sha256: "550b1014d12b5528d8bdb6e3b44b58721f3fb1f65d7a852d1623a817008bdfc4" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.8.3" - fwfh_url_launcher: - dependency: transitive - description: - name: fwfh_url_launcher - sha256: b9f5d55a5ae2c2c07243ba33f7ba49ac9544bdb2f4c16d8139df9ccbebe3449c - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.1" - fwfh_webview: - dependency: transitive - description: - name: fwfh_webview - sha256: f67890bc0d6278da98bd197469ae9511c859f7db327e92299fe0ea0cf46c4057 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.2" get_it: dependency: transitive description: @@ -1086,30 +990,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.8.0" - just_audio: - dependency: transitive - description: - name: just_audio - sha256: d8e8aaf417d33e345299c17f6457f72bd4ba0c549dc34607abb5183a354edc4d - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.40" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.3.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.4.13" launch_at_startup: dependency: "direct main" description: @@ -2130,30 +2010,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.5.1" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.11+1" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.11+1" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.11+1" vector_math: dependency: "direct main" description: @@ -2162,46 +2018,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" - video_player: - dependency: transitive - description: - name: video_player - sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.9.1" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: "45d21bbba3d10b7182aa08ade5d4c68ed3367016d40f29732bb04a799b26842d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.7.5" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.6.1" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.2.2" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.2" visibility_detector: dependency: transitive description: @@ -2218,22 +2034,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "14.3.0" - wakelock_plus: - dependency: transitive - description: - name: wakelock_plus - sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.8" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.1" watcher: dependency: transitive description: @@ -2266,14 +2066,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" - webview_flutter: - dependency: transitive - description: - name: webview_flutter - sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736 - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.9.0" webview_flutter_android: dependency: "direct overridden" description: @@ -2290,14 +2082,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.10.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.15.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4897d06b..80ec8a25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: auto_size_text: ^3.0.0 # 自适应文本 flex_color_picker: ^3.6.0 # 颜色选择器 flutter_inappwebview: ^6.0.0 # Webview - flutter_widget_from_html: ^0.15.2 # 将HTML渲染成组件 + flutter_widget_from_html_core: ^0.15.2 # 将HTML渲染成组件 lucide_icons: path: third-party/widget/lucide_icons # 二维码 @@ -41,7 +41,7 @@ dependencies: archive: ^4.0.0 # 压缩 hive: ^4.0.0-dev.2 # 轻量存储 isar_flutter_libs: ^4.0.0-dev.13 - sqflite_sqlcipher: ^3.1.0+1 # SQLite加密 + sqflite_sqlcipher: 3.1.0+1 # SQLite加密 sqlite3: 2.7.5 # SQLite # 网络 http: ^1.2.1 diff --git a/third-party/chewie/.gitignore b/third-party/chewie/.gitignore new file mode 100644 index 00000000..2cd47870 --- /dev/null +++ b/third-party/chewie/.gitignore @@ -0,0 +1,2 @@ +build/ +.dart_tool/ diff --git a/third-party/chewie/build/.last_build_id b/third-party/chewie/build/.last_build_id deleted file mode 100644 index 715c593e..00000000 --- a/third-party/chewie/build/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -79741fb6e62572cea92da8a6348e1da9 \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache deleted file mode 100644 index 5a6727f2..00000000 --- a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/.filecache +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"files":[{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","hash":"70757ef2352220fc97c3495293d245b4"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","hash":"49aa8735c4a22d9b4ae6e6ec98fcc449"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","hash":"a099aaf6f0811459d67742c6583dd8b0"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb","hash":"e594aa62e96b81e5eb8abdbcf8f88dea"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","hash":"eaf64fea1cb33c4346a4b0302d70df7e"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","hash":"d5e7a6966f654d84623dd8e7b596b0ba"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","hash":"b2ffcfa59377fd2c342eea700847c05d"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","hash":"e5dfa012bb93f9eeeffdb17e75598c76"},{"path":"C:\\Users\\ruida\\Documents\\ProgramData\\flutter\\packages\\flutter_tools\\lib\\src\\build_system\\targets\\localizations.dart","hash":"33a276900ad78ff1cd267a3483f69235"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml","hash":"54799cb52644e7b64505470d47fa55d3"},{"path":"D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart","hash":"06a30ff8b2e2b37fc55ea13f26ce4bb2"}]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json deleted file mode 100644 index 8463ed7d..00000000 --- a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_l10n_inputs_and_outputs.json +++ /dev/null @@ -1 +0,0 @@ -{"inputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb"],"outputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d deleted file mode 100644 index 84204c3d..00000000 --- a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.d +++ /dev/null @@ -1 +0,0 @@ - D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart: D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp deleted file mode 100644 index 237dea6c..00000000 --- a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/gen_localizations.stamp +++ /dev/null @@ -1 +0,0 @@ -{"inputs":["C:\\Users\\ruida\\Documents\\ProgramData\\flutter\\packages\\flutter_tools\\lib\\src\\build_system\\targets\\localizations.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\l10n.yaml","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_en.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_ja.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_CN.arb","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\l10n\\intl_zh_TW.arb"],"outputs":["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"]} \ No newline at end of file diff --git a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json b/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json deleted file mode 100644 index 2801b07f..00000000 --- a/third-party/chewie/build/79741fb6e62572cea92da8a6348e1da9/outputs.json +++ /dev/null @@ -1 +0,0 @@ -["D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_en.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_ja.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations_zh.dart","D:\\Repositories\\Flutter_Projects\\CloudOTP\\third-party\\chewie\\lib\\src\\generated\\chewie_localizations.dart"] \ No newline at end of file diff --git a/third-party/chewie/pubspec.lock b/third-party/chewie/pubspec.lock index 2457a8ad..5c553d73 100644 --- a/third-party/chewie/pubspec.lock +++ b/third-party/chewie/pubspec.lock @@ -81,14 +81,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.11.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.1.25" auto_size_text: dependency: "direct main" description: @@ -209,14 +201,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" - chewie: - dependency: transitive - description: - name: chewie - sha256: df6711bc3ba165ad19cb496e350250be5673327f79c61c9cc8a15088ed8007ed - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.11.1" clock: dependency: transitive description: @@ -289,14 +273,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" - cupertino_icons: - dependency: transitive - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.8" dart_style: dependency: transitive description: @@ -305,14 +281,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.6" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.11" decimal: dependency: "direct main" description: @@ -539,14 +507,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -557,16 +517,8 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_widget_from_html: - dependency: "direct main" - description: - name: flutter_widget_from_html - sha256: f3967a5b42896662efdd420b5adaf8a7d3692b0f44462a07c80e3b4c173b1a02 - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.3" flutter_widget_from_html_core: - dependency: transitive + dependency: "direct main" description: name: flutter_widget_from_html_core sha256: b1048fd119a14762e2361bd057da608148a895477846d6149109b2151d2f7abf @@ -596,54 +548,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" - fwfh_cached_network_image: - dependency: transitive - description: - name: fwfh_cached_network_image - sha256: "8e44226801bfba27930673953afce8af44da7e92573be93f60385d9865a089dd" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.3" - fwfh_chewie: - dependency: transitive - description: - name: fwfh_chewie - sha256: "37bde9cedfb6dc5546176f7f0c56af1e814966cb33ec58f16c9565ed93ccb704" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.8" - fwfh_just_audio: - dependency: transitive - description: - name: fwfh_just_audio - sha256: "38dc2c55803bd3cef33042c473e0c40b891ad4548078424641a32032f6a1245f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.2" - fwfh_svg: - dependency: transitive - description: - name: fwfh_svg - sha256: "550b1014d12b5528d8bdb6e3b44b58721f3fb1f65d7a852d1623a817008bdfc4" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.8.3" - fwfh_url_launcher: - dependency: transitive - description: - name: fwfh_url_launcher - sha256: b9f5d55a5ae2c2c07243ba33f7ba49ac9544bdb2f4c16d8139df9ccbebe3449c - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.1" - fwfh_webview: - dependency: transitive - description: - name: fwfh_webview - sha256: "894aa7d98ebdc2d86d79ac2309173043dec7f102575de87bf9626ddb26104e49" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.15.4" glob: dependency: transitive description: @@ -827,30 +731,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.8.0" - just_audio: - dependency: transitive - description: - name: just_audio - sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.46" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.5.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "8c7e779892e180cbc9ffb5a3c52f6e90e1cbbf4a63694cc450972a7edbd2bb6d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.4.15" leak_tracker: dependency: transitive description: @@ -1193,14 +1073,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.0.2" pinyin: dependency: "direct main" description: @@ -1669,30 +1541,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.5.1" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.18" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.16" vector_math: dependency: transitive description: @@ -1701,46 +1549,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" - video_player: - dependency: transitive - description: - name: video_player - sha256: "7d78f0cfaddc8c19d4cb2d3bebe1bfef11f2103b0a03e5398b303a1bf65eeb14" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.9.5" - video_player_android: - dependency: transitive - description: - name: video_player_android - sha256: ae7d4f1b41e3ac6d24dd9b9d5d6831b52d74a61bdd90a7a6262a33d8bb97c29a - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.8.2" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.7.0" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.3.0" - video_player_web: - dependency: transitive - description: - name: video_player_web - sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.4" visibility_detector: dependency: transitive description: @@ -1757,22 +1565,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "14.3.0" - wakelock_plus: - dependency: transitive - description: - name: wakelock_plus - sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.11" - wakelock_plus_platform_interface: - dependency: transitive - description: - name: wakelock_plus_platform_interface - sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.2" watcher: dependency: transitive description: @@ -1805,14 +1597,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" - webview_flutter: - dependency: transitive - description: - name: webview_flutter - sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.10.0" webview_flutter_android: dependency: "direct overridden" description: @@ -1829,14 +1613,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.10.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: bf0745adeaca48a3105473440cffade47720fe2d56514de4e86f0d363439c4a7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.18.6" win32: dependency: transitive description: @@ -1869,14 +1645,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.5.0" yaml: dependency: transitive description: diff --git a/third-party/chewie/pubspec.yaml b/third-party/chewie/pubspec.yaml index a54fff30..3ab8be3f 100644 --- a/third-party/chewie/pubspec.yaml +++ b/third-party/chewie/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: flutter_animate: ^4.5.2 auto_size_text: ^3.0.0 lottie: ^3.3.1 - flutter_widget_from_html: ^0.15.0 + flutter_widget_from_html_core: ^0.15.0 smart_snackbars: path: ../widget/smart_snackbars lucide_icons: diff --git a/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux b/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux deleted file mode 120000 index 94bc1499..00000000 --- a/third-party/widget/markdown_widget/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux +++ /dev/null @@ -1 +0,0 @@ -C:/Users/ruida/AppData/Local/Pub/Cache/hosted/pub.flutter-io.cn/url_launcher_linux-3.2.2/ \ No newline at end of file From 8ab629515b3d96fdfb07957fe7d5051ed3075d0e Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 18:12:45 +0800 Subject: [PATCH 12/36] feat: Update macOS codesigning configuration and upgrade compileSdkVersion to 35 for Android modules --- .github/workflows/release.yml | 6 ++++++ android/build.gradle | 2 +- ios/Podfile | 2 -- third-party/tool/image_gallery_saver/android/build.gradle | 2 +- third-party/tool/move_to_background/android/build.gradle | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b76c0eda..3274646d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -211,6 +211,12 @@ jobs: mv CloudOTP.ipa CloudOTP-${{ steps.get_version.outputs.version }}-ios.ipa 2>/dev/null || true # Build macOS .app & .dmg + - name: Disable macOS codesigning + if: matrix.target == 'macOS' + run: | + echo 'CODE_SIGN_IDENTITY=-' > macos/Runner/CI.xcconfig + echo 'CODE_SIGN_ENTITLEMENTS=' >> macos/Runner/CI.xcconfig + echo '#include "Runner/CI.xcconfig"' | cat - macos/Flutter/Release.xcconfig > temp && mv temp macos/Flutter/Release.xcconfig - name: Build macOS if: matrix.target == 'macOS' run: | diff --git a/android/build.gradle b/android/build.gradle index 7e764881..90ad290a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,7 +22,7 @@ rootProject.buildDir = '../build' subprojects { afterEvaluate { android { - compileSdkVersion 34 + compileSdkVersion 35 } if (it.plugins.hasPlugin('com.android.library') || it.plugins.hasPlugin('com.android.application')) { it.android { diff --git a/ios/Podfile b/ios/Podfile index c32d94fd..c0d080d5 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -33,8 +33,6 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - pod 'SQLCipher', '~> 4.5' - target 'RunnerTests' do inherit! :search_paths end diff --git a/third-party/tool/image_gallery_saver/android/build.gradle b/third-party/tool/image_gallery_saver/android/build.gradle index c34e1e8c..663207ab 100644 --- a/third-party/tool/image_gallery_saver/android/build.gradle +++ b/third-party/tool/image_gallery_saver/android/build.gradle @@ -24,7 +24,7 @@ apply plugin: 'kotlin-android' android { namespace "com.example.imagegallerysaver" - compileSdk 34 + compileSdk 35 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/third-party/tool/move_to_background/android/build.gradle b/third-party/tool/move_to_background/android/build.gradle index f400ec1a..acb0cd58 100644 --- a/third-party/tool/move_to_background/android/build.gradle +++ b/third-party/tool/move_to_background/android/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'com.android.library' android { namespace "com.sayegh.move_to_background" - compileSdk 34 + compileSdk 35 defaultConfig { minSdk 21 From eaec98687ee74d7ef8ae3f8691aa01dc8a24ae23 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 18:19:41 +0800 Subject: [PATCH 13/36] feat: Update sqflite_android dependency to version 2.4.0 and fix macOS xcconfig file reference --- .github/workflows/release.yml | 2 +- ios/Podfile.lock | 3 +-- pubspec.lock | 22 +++++++++++++++++++--- pubspec.yaml | 1 + 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3274646d..fca3df63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -216,7 +216,7 @@ jobs: run: | echo 'CODE_SIGN_IDENTITY=-' > macos/Runner/CI.xcconfig echo 'CODE_SIGN_ENTITLEMENTS=' >> macos/Runner/CI.xcconfig - echo '#include "Runner/CI.xcconfig"' | cat - macos/Flutter/Release.xcconfig > temp && mv temp macos/Flutter/Release.xcconfig + echo '#include "Runner/CI.xcconfig"' | cat - macos/Flutter/Flutter-Release.xcconfig > temp && mv temp macos/Flutter/Flutter-Release.xcconfig - name: Build macOS if: matrix.target == 'macOS' run: | diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7275e4ec..dc297fd6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -137,7 +137,6 @@ DEPENDENCIES: - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) - sqflite_sqlcipher (from `.symlinks/plugins/sqflite_sqlcipher/ios`) - - SQLCipher (~> 4.5) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: @@ -244,6 +243,6 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: 59067792e03d7a5801ba684f46eb50a563a19de4 +PODFILE CHECKSUM: 03198fa0e7d6c5ef8c64d284f9d1ea2640ff5b6d COCOAPODS: 1.16.2 diff --git a/pubspec.lock b/pubspec.lock index db9b65fe..520e1254 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1794,14 +1794,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.3+2" + sqflite_android: + dependency: "direct overridden" + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "2d8e607db72e9cb7748c9c6e739e2c9618320a5517de693d5a24609c4671b1a4" + sha256: f8a08a13fb8f0f8c590df89d745000bed44a673ed94bac846739e1a016875c21 url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4+4" + version: "2.5.7" sqflite_common_ffi: dependency: "direct dev" description: @@ -1810,6 +1818,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.3+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" sqflite_sqlcipher: dependency: "direct main" description: @@ -2147,5 +2163,5 @@ packages: source: hosted version: "0.2.3" sdks: - dart: ">=3.9.0 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 80ec8a25..76ff4718 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,6 +97,7 @@ dependencies: dependency_overrides: webview_flutter_android: 4.3.0 + sqflite_android: 2.4.0 dev_dependencies: flutter_test: From 6fb7fc5a5c3390d121b116783dcd3b5882f428a5 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 20:46:45 +0800 Subject: [PATCH 14/36] feat: Integrate LaunchAtLogin package and update macOS project settings --- .github/workflows/release.yml | 8 ++-- android/app/build.gradle | 10 ++++- ios/Podfile | 17 ++++---- ios/Podfile.lock | 2 +- lib/Database/token_category_binding_dao.dart | 5 +++ lib/Screens/Lock/database_decrypt_screen.dart | 10 ++++- lib/Screens/Lock/pin_verify_screen.dart | 10 ++++- .../mobile_setting_navigation_screen.dart | 3 +- .../Setting/setting_general_screen.dart | 9 +--- .../Setting/setting_navigation_screen.dart | 1 - lib/Screens/main_screen.dart | 37 +++++++++++------ lib/Utils/app_provider.dart | 7 ---- lib/Utils/utils.dart | 4 +- macos/Runner.xcodeproj/project.pbxproj | 41 ++++++++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 15 +++++++ .../xcshareddata/swiftpm/Package.resolved | 15 +++++++ macos/Runner/MainFlutterWindow.swift | 18 ++++++++ pubspec.lock | 4 +- pubspec.yaml | 6 +-- 19 files changed, 167 insertions(+), 55 deletions(-) create mode 100644 macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fca3df63..ac3a6a4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -214,9 +214,11 @@ jobs: - name: Disable macOS codesigning if: matrix.target == 'macOS' run: | - echo 'CODE_SIGN_IDENTITY=-' > macos/Runner/CI.xcconfig - echo 'CODE_SIGN_ENTITLEMENTS=' >> macos/Runner/CI.xcconfig - echo '#include "Runner/CI.xcconfig"' | cat - macos/Flutter/Flutter-Release.xcconfig > temp && mv temp macos/Flutter/Flutter-Release.xcconfig + sed -i '' 's/CODE_SIGN_IDENTITY\[sdk=macosx\*\] = "Apple Development"/CODE_SIGN_IDENTITY = "-"/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/CODE_SIGN_STYLE = Automatic/CODE_SIGN_STYLE = Manual/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/DEVELOPMENT_TEAM = 9CAPQ7Q4W8/DEVELOPMENT_TEAM = ""/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/Release.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/DebugProfile.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj - name: Build macOS if: matrix.target == 'macOS' run: | diff --git a/android/app/build.gradle b/android/app/build.gradle index d06f9cfe..52cdb80c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -89,9 +89,17 @@ flutter { dependencies { implementation "androidx.core:core-splashscreen:1.0.1" - implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.core:core-ktx:1.15.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "androidx.interpolator:interpolator:1.0.0" implementation 'com.google.gms:google-services:4.2.0' + + configurations.configureEach { + resolutionStrategy { + force 'androidx.core:core:1.15.0' + force 'androidx.core:core-ktx:1.15.0' + force 'androidx.browser:browser:1.8.0' + } + } } diff --git a/ios/Podfile b/ios/Podfile index c0d080d5..c8c8c58a 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -41,11 +41,17 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + end + # Remove duplicate SQLCipher.bundle targets + installer.pods_project.targets.each do |target| + if target.name == 'FMDB-SQLCipher' || target.name == 'SQLCipher-SQLCipher' + target.remove_from_project + end + end + + installer.pods_project.targets.each do |target| target.build_configurations.each do |config| - # You can remove unused permissions here - # for more information: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h - # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', @@ -73,10 +79,6 @@ post_install do |installer| ## dart: PermissionGroup.photos 'PERMISSION_PHOTOS=1', - ## The 'PERMISSION_LOCATION' macro enables the `locationWhenInUse` and `locationAlways` permission. If - ## the application only requires `locationWhenInUse`, only specify the `PERMISSION_LOCATION_WHENINUSE` - ## macro. - ## ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 'PERMISSION_LOCATION=0', 'PERMISSION_LOCATION_WHENINUSE=0', @@ -102,7 +104,6 @@ post_install do |installer| ## dart: PermissionGroup.criticalAlerts 'PERMISSION_ASSISTANT=0', ] - end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dc297fd6..c51438f5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -243,6 +243,6 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: 03198fa0e7d6c5ef8c64d284f9d1ea2640ff5b6d +PODFILE CHECKSUM: a833d93964cfacfb001478cf662f93736e427c23 COCOAPODS: 1.16.2 diff --git a/lib/Database/token_category_binding_dao.dart b/lib/Database/token_category_binding_dao.dart index 75779c10..7abfc472 100644 --- a/lib/Database/token_category_binding_dao.dart +++ b/lib/Database/token_category_binding_dao.dart @@ -17,6 +17,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/token_dao.dart'; import 'package:cloudotp/Models/opt_token.dart'; import 'package:cloudotp/Models/token_category_binding.dart'; +import 'package:cloudotp/Utils/utils.dart'; import 'package:sqflite_sqlcipher/sqflite.dart'; import 'database_manager.dart'; @@ -66,6 +67,7 @@ class BindingDao { ); } List results = await batch.commit(); + Utils.initTray(); return results.length; } @@ -81,6 +83,7 @@ class BindingDao { ); } List results = await batch.commit(); + Utils.initTray(); return results.length; } @@ -116,6 +119,7 @@ class BindingDao { ); } List results = await batch.commit(); + Utils.initTray(); return results.length; } @@ -131,6 +135,7 @@ class BindingDao { ); } List results = await batch.commit(); + Utils.initTray(); return results.length; } diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index 999c2a9f..cc2a0e23 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -45,6 +45,7 @@ class DatabaseDecryptScreenState extends BaseWindowState Future onWindowClose() async { await windowManager.destroy(); } + late InputValidateAsyncController validateAsyncController; GlobalKey formKey = GlobalKey(); bool _isValidated = true; @@ -124,6 +125,9 @@ class DatabaseDecryptScreenState extends BaseWindowState @override void initState() { super.initState(); + if (ResponsiveUtil.isMacOS()) { + WidgetsBinding.instance.platformMenuDelegate.setMenus([]); + } chewieProvider.loadingWidgetBuilder = (size, forceDark) => LottieFiles.load( LottieFiles.getLoadingPath(chewieProvider.rootContext), scale: 1.5); @@ -154,11 +158,14 @@ class DatabaseDecryptScreenState extends BaseWindowState @override Widget build(BuildContext context) { + if (ResponsiveUtil.isMacOS()) { + WidgetsBinding.instance.platformMenuDelegate.setMenus([]); + } chewieProvider.resetRootContext(); ChewieUtils.setSafeMode(ChewieHiveUtil.getBool( CloudOTPHiveUtil.enableSafeModeKey, defaultValue: defaultEnableSafeMode)); - return Stack( + Widget body = Stack( children: [ MyScaffold( backgroundColor: ChewieTheme.scaffoldBackgroundColor, @@ -205,6 +212,7 @@ class DatabaseDecryptScreenState extends BaseWindowState ), ], ); + return body; } onSubmit() async { diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index b8afff43..0eded17c 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -58,6 +58,7 @@ class PinVerifyScreenState extends BaseWindowState Future onWindowClose() async { await windowManager.destroy(); } + static const int _lockoutDurationSeconds = 30; static const int _extendedLockoutDurationSeconds = 300; static const int _extendedLockoutThreshold = 10; @@ -136,6 +137,9 @@ class PinVerifyScreenState extends BaseWindowState } windowManager.addListener(this); super.initState(); + if (ResponsiveUtil.isMacOS()) { + WidgetsBinding.instance.platformMenuDelegate.setMenus([]); + } WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { chewieProvider.loadingWidgetBuilder = (size, forceDark) => @@ -164,11 +168,14 @@ class PinVerifyScreenState extends BaseWindowState @override Widget build(BuildContext context) { + if (ResponsiveUtil.isMacOS()) { + WidgetsBinding.instance.platformMenuDelegate.setMenus([]); + } chewieProvider.resetRootContext(); ChewieUtils.setSafeMode(ChewieHiveUtil.getBool( CloudOTPHiveUtil.enableSafeModeKey, defaultValue: defaultEnableSafeMode)); - return Stack( + Widget body = Stack( children: [ Scaffold( backgroundColor: ChewieTheme.scaffoldBackgroundColor, @@ -258,6 +265,7 @@ class PinVerifyScreenState extends BaseWindowState ), ], ); + return body; } void _startLockout() { diff --git a/lib/Screens/Setting/mobile_setting_navigation_screen.dart b/lib/Screens/Setting/mobile_setting_navigation_screen.dart index d6d38b6e..b690e3ac 100644 --- a/lib/Screens/Setting/mobile_setting_navigation_screen.dart +++ b/lib/Screens/Setting/mobile_setting_navigation_screen.dart @@ -22,7 +22,6 @@ import 'package:cloudotp/Screens/Setting/setting_safe_screen.dart'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; -import '../../Utils/app_provider.dart'; import '../../l10n/l10n.dart'; class MobileSettingNavigationScreen extends StatefulWidget { @@ -70,7 +69,7 @@ class _MobileSettingNavigationScreenState onTap: () { RouteUtil.pushCupertinoRoute( context, - GeneralSettingScreen(key: generalSettingScreenKey), + GeneralSettingScreen(), ); }, ), diff --git a/lib/Screens/Setting/setting_general_screen.dart b/lib/Screens/Setting/setting_general_screen.dart index 2fa8f5aa..5528b493 100644 --- a/lib/Screens/Setting/setting_general_screen.dart +++ b/lib/Screens/Setting/setting_general_screen.dart @@ -82,15 +82,10 @@ class GeneralSettingScreenState extends BaseDynamicState _currentTrayOption = getTrayOption(); } - refreshLauchAtStartup() { - setState(() { - launchAtStartup = - ChewieHiveUtil.getBool(ChewieHiveUtil.launchAtStartupKey); - }); - } - @override Widget build(BuildContext context) { + launchAtStartup = + ChewieHiveUtil.getBool(ChewieHiveUtil.launchAtStartupKey); return ItemBuilder.buildSettingScreen( context: context, title: appLocalizations.generalSetting, diff --git a/lib/Screens/Setting/setting_navigation_screen.dart b/lib/Screens/Setting/setting_navigation_screen.dart index 92f9305a..d1709e33 100644 --- a/lib/Screens/Setting/setting_navigation_screen.dart +++ b/lib/Screens/Setting/setting_navigation_screen.dart @@ -95,7 +95,6 @@ class _SettingNavigationScreenState switch (index) { case 0: page = GeneralSettingScreen( - key: generalSettingScreenKey, showTitleBar: false, searchText: _searchText, searchConfig: _searchConfig, diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 9c151685..7aac55f0 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -140,7 +140,7 @@ class MainScreenState extends BaseWindowState fetchReleases(); if (ResponsiveUtil.isMacOS()) { _checkNotificationPermission(); - _loadMenuTokenData(); + loadMenuTokenData(); } WidgetsBinding.instance.addPostFrameCallback((_) async { if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoFocusSearchBarKey, @@ -197,7 +197,7 @@ class MainScreenState extends BaseWindowState } } - Future _loadMenuTokenData() async { + Future loadMenuTokenData() async { if (!DatabaseManager.initialized) return; _menuTokens = await TokenDao.listTokens(); _menuTokens.sort((a, b) => a.issuer.compareTo(b.issuer)); @@ -207,7 +207,15 @@ class MainScreenState extends BaseWindowState cat.tokens.sort((a, b) => a.issuer.compareTo(b.issuer)); } _menuCategories = cats.where((e) => e.tokens.isNotEmpty).toList(); - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + WidgetsBinding.instance.platformMenuDelegate + .setMenus(_buildMacMenuBar()); + } + }); + } } Future _copyTokenCode(OtpToken token) async { @@ -248,6 +256,10 @@ class MainScreenState extends BaseWindowState }) async { _menuTokens = []; _menuCategories = []; + if (ResponsiveUtil.isMacOS()) { + setState(() {}); + WidgetsBinding.instance.platformMenuDelegate.setMenus([]); + } Utils.initSimpleTray(); if (CloudOTPHiveUtil.canDatabaseLock()) { ILogger.debug("Jump to database lock screen"); @@ -261,7 +273,7 @@ class MainScreenState extends BaseWindowState PinVerifyScreen( onSuccess: () { appProvider.preventLock = false; - _loadMenuTokenData(); + loadMenuTokenData(); Utils.initTray(); }, showWindowTitle: true, @@ -280,9 +292,16 @@ class MainScreenState extends BaseWindowState CloudOTPHiveUtil.enableSafeModeKey, defaultValue: defaultEnableSafeMode)); super.build(context); - return OrientationBuilder(builder: (ctx, ori) { + Widget result = OrientationBuilder(builder: (ctx, ori) { return _buildBodyByPlatform(); }); + if (ResponsiveUtil.isMacOS()) { + return PlatformMenuBar( + menus: _buildMacMenuBar(), + child: result, + ); + } + return result; } _buildBodyByPlatform() { @@ -291,12 +310,6 @@ class MainScreenState extends BaseWindowState landscape: SafeArea(child: _buildDesktopBody()), portrait: HomeScreen(key: chewieProvider.panelScreenKey), ); - if (ResponsiveUtil.isMacOS()) { - return PlatformMenuBar( - menus: _buildMacMenuBar(), - child: body, - ); - } return body; } @@ -752,7 +765,7 @@ class MainScreenState extends BaseWindowState refresh() { if (ResponsiveUtil.isMacOS()) { - _loadMenuTokenData(); + loadMenuTokenData(); } setState(() {}); } diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index d1f7494b..7b06a790 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -17,7 +17,6 @@ import 'dart:async'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Models/auto_backup_log.dart'; -import 'package:cloudotp/Screens/Setting/setting_general_screen.dart'; import 'package:cloudotp/Screens/main_screen.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -27,12 +26,6 @@ import '../Screens/home_screen.dart'; import '../l10n/l10n.dart'; import 'hive_util.dart'; -GlobalKey generalSettingScreenKey = - GlobalKey(); - -GeneralSettingScreenState? get generalSettingScreenState => - generalSettingScreenKey.currentState; - GlobalKey mainScreenKey = GlobalKey(); MainScreenState? get mainScreenState => mainScreenKey.currentState; diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index 0b7c20a2..08764004 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -220,6 +220,9 @@ class Utils { ); await trayManager.setContextMenu(menu); ILogger.debug("Tray initialized successfully."); + if (ResponsiveUtil.isMacOS()) { + mainScreenState?.loadMenuTokenData(); + } } catch (e, t) { ILogger.error("Failed to initialize simple tray", e, t); } @@ -357,7 +360,6 @@ class Utils { } else if (menuItem.key == TrayKey.launchAtStartup.key) { menuItem.checked = !(menuItem.checked == true); ChewieHiveUtil.put(ChewieHiveUtil.launchAtStartupKey, menuItem.checked); - generalSettingScreenState?.refreshLauchAtStartup(); if (menuItem.checked == true) { await LaunchAtStartup.instance.enable(); } else { diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index a2a33da1..6fdc3f36 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,9 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - AA0001012044BFA00003C045 /* LocalNotifierOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */; }; 7A7A95CE29141F3546CCC15E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8FA190C15AB53FB43E71E4E /* Pods_RunnerTests.framework */; }; + AA0001012044BFA00003C045 /* LocalNotifierOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */; }; + D0C240F82FB9EF34004F7465 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D0C240F72FB9EF34004F7465 /* LaunchAtLogin */; }; F84DEA6E45FA7511E0E158EE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C3F37A8706029947F81B05D /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -74,7 +75,6 @@ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotifierOverride.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; @@ -86,6 +86,7 @@ 69EB9431FA50FFD0E4733583 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AA0001002044BFA00003C045 /* LocalNotifierOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotifierOverride.swift; sourceTree = ""; }; B8C2C13DA79CE708538F2A85 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; D05C8E0DB771D3D18DD38D20 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; F8FA190C15AB53FB43E71E4E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -105,6 +106,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0C240F82FB9EF34004F7465 /* LaunchAtLogin in Frameworks */, F84DEA6E45FA7511E0E158EE /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -241,7 +243,7 @@ 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, + 3399D490228B24CF009A79C7 /* Run Script */, FAAE56945FCD25943C66F528 /* [CP] Embed Pods Frameworks */, ); buildRules = ( @@ -294,6 +296,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + D0C240F62FB9EF34004F7465 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -347,7 +352,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3399D490228B24CF009A79C7 /* ShellScript */ = { + 3399D490228B24CF009A79C7 /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -357,13 +362,14 @@ ); inputPaths = ( ); + name = "Run Script"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -580,6 +586,8 @@ COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CloudOTP; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -714,6 +722,8 @@ COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CloudOTP; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -736,6 +746,8 @@ COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 9CAPQ7Q4W8; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = CloudOTP; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -805,6 +817,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D0C240F62FB9EF34004F7465 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://proxy.cloudchewie.com/proxy/github.com/sindresorhus/LaunchAtLogin"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D0C240F72FB9EF34004F7465 /* LaunchAtLogin */ = { + isa = XCSwiftPackageProductDependency; + package = D0C240F62FB9EF34004F7465 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; + productName = LaunchAtLogin; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..584aede3 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "6dfdf4729a9bdbe387c37aabd0ba7f9a0594a158cb17261d81b5eede25f6a5a5", + "pins" : [ + { + "identity" : "launchatlogin", + "kind" : "remoteSourceControl", + "location" : "https://proxy.cloudchewie.com/proxy/github.com/sindresorhus/LaunchAtLogin", + "state" : { + "branch" : "main", + "revision" : "9a894d799269cb591037f9f9cb0961510d4dca81" + } + } + ], + "version" : 3 +} diff --git a/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..584aede3 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "6dfdf4729a9bdbe387c37aabd0ba7f9a0594a158cb17261d81b5eede25f6a5a5", + "pins" : [ + { + "identity" : "launchatlogin", + "kind" : "remoteSourceControl", + "location" : "https://proxy.cloudchewie.com/proxy/github.com/sindresorhus/LaunchAtLogin", + "state" : { + "branch" : "main", + "revision" : "9a894d799269cb591037f9f9cb0961510d4dca81" + } + } + ], + "version" : 3 +} diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 49bdf899..f42c41ef 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -1,5 +1,6 @@ import Cocoa import FlutterMacOS +import LaunchAtLogin class MainFlutterWindow: NSWindow { private let trafficLightVerticalOffset: CGFloat = 18.0 @@ -18,6 +19,23 @@ class MainFlutterWindow: NSWindow { RegisterGeneratedPlugins(registry: flutterViewController) LocalNotifierOverride.register(with: flutterViewController.registrar(forPlugin: "LocalNotifierOverride")) + FlutterMethodChannel( + name: "launch_at_startup", binaryMessenger: flutterViewController.engine.binaryMessenger + ) + .setMethodCallHandler { (_ call: FlutterMethodCall, result: @escaping FlutterResult) in + switch call.method { + case "launchAtStartupIsEnabled": + result(LaunchAtLogin.isEnabled) + case "launchAtStartupSetEnabled": + if let arguments = call.arguments as? [String: Any] { + LaunchAtLogin.isEnabled = arguments["setEnabledValue"] as! Bool + } + result(nil) + default: + result(FlutterMethodNotImplemented) + } + } + super.awakeFromNib() } diff --git a/pubspec.lock b/pubspec.lock index 520e1254..8e2af5e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2102,10 +2102,10 @@ packages: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.flutter-io.cn" source: hosted - version: "5.5.4" + version: "5.13.0" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76ff4718..168c0b8a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: An awesome two-factor authenticator which supports cloud storage an publish_to: none environment: - sdk: '>=3.1.4 <4.0.0' + sdk: ">=3.1.4 <4.0.0" dependencies: flutter: @@ -60,7 +60,7 @@ dependencies: hashlib: 1.19.2 # Hash pointycastle: ^3.9.1 # 加密 protobuf: ^3.1.0 # Protobuf - protoc_plugin: ^21.1.2 # Protobuf + protoc_plugin: ^21.1.2 # Protobuf flutter_native_splash: ^2.4.1 # Splash屏 queue: ^3.1.0+2 # 队列 logging: ^1.2.0 @@ -171,4 +171,4 @@ flutter: - assets/logo-transparent.svg - assets/logo-transparent.png - assets/logo-transparent-small.png - generate: true # ✅ 启用 l10n 支持 \ No newline at end of file + generate: true # ✅ 启用 l10n 支持 From e696456b120e5f6c3e9651217804ce20e836219a Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 20:54:40 +0800 Subject: [PATCH 15/36] fix: Update macOS codesigning script and enhance Podfile to remove SQLCipher.bundle references --- .github/workflows/release.yml | 2 +- ios/Podfile | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac3a6a4d..afb01fbc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -214,7 +214,7 @@ jobs: - name: Disable macOS codesigning if: matrix.target == 'macOS' run: | - sed -i '' 's/CODE_SIGN_IDENTITY\[sdk=macosx\*\] = "Apple Development"/CODE_SIGN_IDENTITY = "-"/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/"CODE_SIGN_IDENTITY\[sdk=macosx\*\]" = "Apple Development"/CODE_SIGN_IDENTITY = "-"/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/CODE_SIGN_STYLE = Automatic/CODE_SIGN_STYLE = Manual/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/DEVELOPMENT_TEAM = 9CAPQ7Q4W8/DEVELOPMENT_TEAM = ""/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/Release.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj diff --git a/ios/Podfile b/ios/Podfile index c8c8c58a..ba0adc72 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -43,11 +43,16 @@ post_install do |installer| flutter_additional_ios_build_settings(target) end - # Remove duplicate SQLCipher.bundle targets + # Remove duplicate SQLCipher.bundle targets and references installer.pods_project.targets.each do |target| if target.name == 'FMDB-SQLCipher' || target.name == 'SQLCipher-SQLCipher' target.remove_from_project end + target.build_phases.each do |phase| + if phase.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) || phase.is_a?(Xcodeproj::Project::Object::PBXResourcesBuildPhase) + phase.files.delete_if { |file| file.display_name == 'SQLCipher.bundle' } + end + end end installer.pods_project.targets.each do |target| From 45b8dac9726361fe88fac9e95e9a09d07e7fd440 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 21:50:16 +0800 Subject: [PATCH 16/36] refactor: Enhance SQLCipher.bundle removal process and improve secure storage fallback in password management --- .github/workflows/release.yml | 2 - ios/Podfile | 51 ++++++++++++++++--- lib/Utils/hive_util.dart | 37 +++++++++----- macos/Runner/DebugProfile.entitlements | 8 +-- macos/Runner/Release.entitlements | 8 +-- .../lib/src/Utils/System/file_util.dart | 2 +- 6 files changed, 78 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afb01fbc..70fbc09e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -217,8 +217,6 @@ jobs: sed -i '' 's/"CODE_SIGN_IDENTITY\[sdk=macosx\*\]" = "Apple Development"/CODE_SIGN_IDENTITY = "-"/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/CODE_SIGN_STYLE = Automatic/CODE_SIGN_STYLE = Manual/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/DEVELOPMENT_TEAM = 9CAPQ7Q4W8/DEVELOPMENT_TEAM = ""/g' macos/Runner.xcodeproj/project.pbxproj - sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/Release.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj - sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/DebugProfile.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj - name: Build macOS if: matrix.target == 'macOS' run: | diff --git a/ios/Podfile b/ios/Podfile index ba0adc72..123b0a79 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -43,18 +43,57 @@ post_install do |installer| flutter_additional_ios_build_settings(target) end - # Remove duplicate SQLCipher.bundle targets and references - installer.pods_project.targets.each do |target| - if target.name == 'FMDB-SQLCipher' || target.name == 'SQLCipher-SQLCipher' - target.remove_from_project + # Aggressively remove all SQLCipher.bundle targets and references + pods_project = installer.pods_project + + # Step 1: Identify bundle targets to remove + bundle_target_names = Set.new + pods_project.targets.to_a.each do |target| + if target.product_type == 'com.apple.product-type.bundle' && target.name.include?('SQLCipher') + bundle_target_names << target.name + end + end + + # Step 2: Remove dependencies on bundle targets from all other targets + pods_project.targets.each do |target| + next if bundle_target_names.include?(target.name) + target.dependencies.to_a.each do |dep| + if dep.target && bundle_target_names.include?(dep.target.name) + dep.remove_from_project + end end + end + + # Step 3: Remove SQLCipher.bundle from ALL build phases of ALL targets + pods_project.targets.each do |target| + next if bundle_target_names.include?(target.name) target.build_phases.each do |phase| - if phase.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) || phase.is_a?(Xcodeproj::Project::Object::PBXResourcesBuildPhase) - phase.files.delete_if { |file| file.display_name == 'SQLCipher.bundle' } + next unless phase.respond_to?(:files) + phase.files.to_a.each do |build_file| + name = build_file.display_name || '' + if name.include?('SQLCipher.bundle') || name.include?('SQLCipher-SQLCipher') || name.include?('FMDB-SQLCipher') + build_file.remove_from_project + end end end end + # Step 4: Remove the bundle targets themselves + pods_project.targets.to_a.each do |target| + if bundle_target_names.include?(target.name) + target.remove_from_project + end + end + + # Step 5: Remove all file references to SQLCipher.bundle + pods_project.files.to_a.each do |file_ref| + if file_ref.path && file_ref.path.include?('SQLCipher.bundle') + file_ref.remove_from_project + end + end + + pods_project.save + installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 23b99a10..5d03219b 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -199,26 +199,37 @@ class CloudOTPHiveUtil { static Future regeneratePassword() async { String password = MockUtil.getRandomString(length: 16); - await _secureStorage.write(key: _secureDbPasswordKey, value: password); - // Remove from Hive if it was there before (migration cleanup) - await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); + try { + await _secureStorage.write(key: _secureDbPasswordKey, value: password); + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); + } catch (e, t) { + ILogger.error( + "Secure storage unavailable, falling back to Hive", e, t); + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, password); + } return password; } static Future getDatabasePassword() async { - // Try secure storage first - String? password = - await _secureStorage.read(key: _secureDbPasswordKey); - if (password != null && password.isNotEmpty) return password; - // Migrate from Hive if present + try { + String? password = + await _secureStorage.read(key: _secureDbPasswordKey); + if (password != null && password.isNotEmpty) return password; + } catch (e, t) { + ILogger.error( + "Secure storage unavailable, falling back to Hive", e, t); + } String? hivePassword = ChewieHiveUtil.getString(CloudOTPHiveUtil.defaultDatabasePasswordKey); if (hivePassword != null && hivePassword.isNotEmpty) { - await _secureStorage.write( - key: _secureDbPasswordKey, value: hivePassword); - await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); + try { + await _secureStorage.write( + key: _secureDbPasswordKey, value: hivePassword); + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); + } catch (_) {} return hivePassword; } return ''; diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index fd6760c4..cc912d77 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -6,13 +6,13 @@ com.apple.security.cs.allow-jit - com.apple.security.network.server - - com.apple.security.network.client + com.apple.security.files.downloads.read-write com.apple.security.files.user-selected.read-write - com.apple.security.files.downloads.read-write + com.apple.security.network.client + + com.apple.security.network.server keychain-access-groups diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 69b7d913..04dd798d 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,13 +4,13 @@ com.apple.security.app-sandbox - com.apple.security.network.server - - com.apple.security.network.client + com.apple.security.files.downloads.read-write com.apple.security.files.user-selected.read-write - com.apple.security.files.downloads.read-write + com.apple.security.network.client + + com.apple.security.network.server keychain-access-groups diff --git a/third-party/chewie/lib/src/Utils/System/file_util.dart b/third-party/chewie/lib/src/Utils/System/file_util.dart index ce87334b..0f18be27 100644 --- a/third-party/chewie/lib/src/Utils/System/file_util.dart +++ b/third-party/chewie/lib/src/Utils/System/file_util.dart @@ -249,7 +249,7 @@ class FileUtil { ILogger.info("Have migrated data to support directory"); return; } - Hive.closeAllBoxes(); + Hive.box(name: ChewieHiveUtil.settingsBox).close(); } catch (e, t) { ILogger.error("Failed to close all hive boxes", e, t); } From 2292ad3b199d2f46a6cf18f3eaf0fd86dd47ef8e Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 22:11:06 +0800 Subject: [PATCH 17/36] fix: Update code signing entitlements in release workflow and improve SQLCipher target identification in Podfile --- .github/workflows/release.yml | 3 +++ ios/Podfile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70fbc09e..27a3582d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -217,11 +217,14 @@ jobs: sed -i '' 's/"CODE_SIGN_IDENTITY\[sdk=macosx\*\]" = "Apple Development"/CODE_SIGN_IDENTITY = "-"/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/CODE_SIGN_STYLE = Automatic/CODE_SIGN_STYLE = Manual/g' macos/Runner.xcodeproj/project.pbxproj sed -i '' 's/DEVELOPMENT_TEAM = 9CAPQ7Q4W8/DEVELOPMENT_TEAM = ""/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/Release.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj + sed -i '' 's/CODE_SIGN_ENTITLEMENTS = Runner\/DebugProfile.entitlements/CODE_SIGN_ENTITLEMENTS = ""/g' macos/Runner.xcodeproj/project.pbxproj - name: Build macOS if: matrix.target == 'macOS' run: | flutter build macos --release APP_PATH="build/macos/Build/Products/Release/CloudOTP.app" + codesign --force --deep -s - "$APP_PATH" DMG_NAME="CloudOTP-${{ steps.get_version.outputs.version }}-macos-universal.dmg" mkdir -p build/macos hdiutil create -volname CloudOTP -srcfolder "$APP_PATH" -ov -format UDZO "build/macos/$DMG_NAME" diff --git a/ios/Podfile b/ios/Podfile index 123b0a79..235c8cc0 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -49,7 +49,7 @@ post_install do |installer| # Step 1: Identify bundle targets to remove bundle_target_names = Set.new pods_project.targets.to_a.each do |target| - if target.product_type == 'com.apple.product-type.bundle' && target.name.include?('SQLCipher') + if target.respond_to?(:product_type) && target.product_type == 'com.apple.product-type.bundle' && target.name.include?('SQLCipher') bundle_target_names << target.name end end From 62d016f2939f1362b47617c6b5a77ca59aafc335 Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 17 May 2026 23:58:49 +0800 Subject: [PATCH 18/36] feat: Update iOS build process and enhance backup log navigation in settings --- .github/workflows/release.yml | 7 +++++-- ios/Podfile.lock | 2 +- lib/Screens/Setting/backup_log_screen.dart | 7 ++++--- lib/Screens/Setting/setting_backup_screen.dart | 8 ++++++++ pubspec.yaml | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27a3582d..73718559 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -207,8 +207,11 @@ jobs: if: matrix.target == 'iOS' run: | flutter build ipa --release --no-codesign - cd build/ios/ipa - mv CloudOTP.ipa CloudOTP-${{ steps.get_version.outputs.version }}-ios.ipa 2>/dev/null || true + mkdir -p build/ios/ipa + cd build/ios/archive/Runner.xcarchive/Products/Applications + mkdir -p Payload + mv Runner.app Payload/ 2>/dev/null || mv CloudOTP.app Payload/ 2>/dev/null || true + zip -r ../../../../../../build/ios/ipa/CloudOTP-${{ steps.get_version.outputs.version }}-ios.ipa Payload # Build macOS .app & .dmg - name: Disable macOS codesigning diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c51438f5..76434921 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -243,6 +243,6 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: a833d93964cfacfb001478cf662f93736e427c23 +PODFILE CHECKSUM: 4bb9f7e4e6a468a9e1d07ae49e6c4cb7bed36dc1 COCOAPODS: 1.16.2 diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 60467b3d..2e2006c1 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -64,7 +64,9 @@ class BackupLogScreenState extends BaseDynamicState { ? _buildDesktopBody() : Scaffold( appBar: ResponsiveAppBar( - backgroundColor: Colors.transparent, + backgroundColor: ResponsiveUtil.isLandscapeLayout() + ? ChewieTheme.appBarBackgroundColor + : Colors.transparent, title: appLocalizations.backupLogs, showBack: true, showBorder: true, @@ -121,8 +123,7 @@ class BackupLogScreenState extends BaseDynamicState { _buildBody() { return ListView( - padding: EdgeInsets.symmetric( - horizontal: 10, vertical: widget.isOverlay ? 10 : 0), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), physics: widget.isOverlay ? null : const BouncingScrollPhysics( diff --git a/lib/Screens/Setting/setting_backup_screen.dart b/lib/Screens/Setting/setting_backup_screen.dart index 91745292..40fdb012 100644 --- a/lib/Screens/Setting/setting_backup_screen.dart +++ b/lib/Screens/Setting/setting_backup_screen.dart @@ -17,6 +17,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/config_dao.dart'; import 'package:cloudotp/Models/cloud_service_config.dart'; import 'package:cloudotp/Screens/Backup/cloud_service_screen.dart'; +import 'package:cloudotp/Screens/Setting/backup_log_screen.dart'; import 'package:cloudotp/TokenUtils/Cloud/webdav_cloud_service.dart'; import 'package:cloudotp/TokenUtils/export_token_util.dart'; import 'package:cloudotp/Utils/app_provider.dart'; @@ -340,6 +341,13 @@ class _BackupSettingScreenState extends BaseDynamicState }, ), ), + EntryItem( + title: appLocalizations.backupLogs, + trailing: Icons.history_rounded, + onTap: () async { + RouteUtil.pushCupertinoRoute(context, const BackupLogScreen()); + }, + ), ], ), CaptionItem( diff --git a/pubspec.yaml b/pubspec.yaml index 168c0b8a..311cc438 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: cloudotp -version: 3.1.3+313 +version: 3.2.0+320 description: An awesome two-factor authenticator which supports cloud storage and multiple platforms. publish_to: none From 8c3dd22193f2cbba954e2e1b3a7985a1836513e4 Mon Sep 17 00:00:00 2001 From: dd
Date: Mon, 18 May 2026 00:10:01 +0800 Subject: [PATCH 19/36] feat: Add .ipa files to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 36486003..0a3de426 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ app.*.map.json **/generated/ cloudotp.db +*.ipa /assets/fonts/** From ac085f6edbca07bbf0fd282c36d4ca9e3b7d788d Mon Sep 17 00:00:00 2001 From: dd
Date: Mon, 18 May 2026 17:23:10 +0800 Subject: [PATCH 20/36] feat: Enhance biometric authentication and UI improvements - Added auto authentication option in DatabaseDecryptScreen. - Updated biometric authentication logic to trigger automatically based on the new autoAuth parameter. - Improved title margin handling in DatabaseDecryptScreen and PinVerifyScreen for macOS. - Removed redundant biometric existence check in SafeSettingScreen. - Enhanced focus restoration after dialog interactions in MainScreen and AppShortcuts. - Introduced progress tracking for file downloads in various cloud services (AliyunDrive, Box, Dropbox, GoogleDrive, HuaweiCloud, OneDrive). - Updated loading and progress dialogs to support dynamic title updates. - Refactored file migration logic in FileUtil for better directory handling. - Improved error handling and logging in cloud service responses. --- lib/Screens/Lock/database_decrypt_screen.dart | 11 ++-- lib/Screens/Lock/pin_verify_screen.dart | 4 +- lib/Screens/Setting/setting_safe_screen.dart | 6 -- lib/Screens/main_screen.dart | 15 ++++- .../Cloud/aliyundrive_cloud_service.dart | 2 + lib/TokenUtils/Cloud/box_cloud_service.dart | 3 +- .../Cloud/dropbox_cloud_service.dart | 4 +- .../Cloud/googledrive_cloud_service.dart | 4 +- .../Cloud/huawei_cloud_service.dart | 4 +- .../Cloud/onedrive_cloud_service.dart | 4 +- lib/TokenUtils/export_token_util.dart | 22 +++++-- lib/Utils/app_provider.dart | 6 ++ lib/Utils/constant.dart | 2 + lib/Utils/hive_util.dart | 22 ++++--- lib/Utils/shortcuts_util.dart | 33 +++++++--- lib/Utils/utils.dart | 5 +- lib/Widgets/Shortcuts/app_shortcuts.dart | 23 +++---- lib/main.dart | 35 ++++++---- macos/Runner/MainFlutterWindow.swift | 8 +++ .../lib/services/aliyundrive.dart | 23 ++++--- .../lib/services/base_service.dart | 65 ++++++++++++++++++- .../awesome_cloud/lib/services/box.dart | 23 +++---- .../awesome_cloud/lib/services/dropbox.dart | 26 ++++---- .../lib/services/googledrive.dart | 15 ++++- .../lib/services/huaweicloud.dart | 25 +++---- .../awesome_cloud/lib/services/onedrive.dart | 26 +++----- .../lib/src/Utils/System/file_util.dart | 22 +++---- .../lib/src/Widgets/Dialog/custom_dialog.dart | 9 ++- .../Dialog/widgets/loading_dialog_widget.dart | 35 +++++----- .../widgets/progress_dialog_widget.dart | 54 ++++++++------- 30 files changed, 341 insertions(+), 195 deletions(-) diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index cc2a0e23..ac090b3e 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -31,7 +31,9 @@ import '../../Utils/utils.dart'; import '../../l10n/l10n.dart'; class DatabaseDecryptScreen extends StatefulWidget { - const DatabaseDecryptScreen({super.key}); + final bool autoAuth; + + const DatabaseDecryptScreen({super.key, this.autoAuth = false}); @override DatabaseDecryptScreenState createState() => DatabaseDecryptScreenState(); @@ -109,7 +111,7 @@ class DatabaseDecryptScreenState extends BaseWindowState canAuthenticateResponseString = await BiometricUtil.getCanAuthenticateResponseString(); setState(() {}); - if (_biometricAvailable && _allowDatabaseBiometric) { + if (_biometricAvailable && _allowDatabaseBiometric && widget.autoAuth) { auth(); } FocusScope.of(context).requestFocus(_focusNode); @@ -131,7 +133,6 @@ class DatabaseDecryptScreenState extends BaseWindowState chewieProvider.loadingWidgetBuilder = (size, forceDark) => LottieFiles.load( LottieFiles.getLoadingPath(chewieProvider.rootContext), scale: 1.5); - initBiometricAuthentication(); trayManager.addListener(this); windowManager.addListener(this); Utils.initSimpleTray(); @@ -154,6 +155,7 @@ class DatabaseDecryptScreenState extends BaseWindowState }, controller: TextEditingController(), ); + initBiometricAuthentication(); } @override @@ -173,7 +175,8 @@ class DatabaseDecryptScreenState extends BaseWindowState ? ResponsiveAppBar( title: appLocalizations.decryptDatabasePassword, showBack: false, - titleLeftMargin: ResponsiveUtil.isMacOS() ? 78 : 15, + titleLeftMargin: + ResponsiveUtil.isMacOS() ? macosTitleBarLeftMargin : 15, actions: const [ BlankIconButton(), ], diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 0eded17c..bc8ae384 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -18,6 +18,7 @@ import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:biometric_storage/biometric_storage.dart'; +import 'package:cloudotp/Utils/constant.dart'; import 'package:cloudotp/Utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:tray_manager/tray_manager.dart'; @@ -183,7 +184,8 @@ class PinVerifyScreenState extends BaseWindowState ? ResponsiveAppBar( title: appLocalizations.verifyGestureLock, showBack: false, - titleLeftMargin: ResponsiveUtil.isMacOS() ? 78 : 15, + titleLeftMargin: + ResponsiveUtil.isMacOS() ? macosTitleBarLeftMargin : 15, actions: const [ BlankIconButton(), ], diff --git a/lib/Screens/Setting/setting_safe_screen.dart b/lib/Screens/Setting/setting_safe_screen.dart index 2e8e4a81..e0227a0c 100644 --- a/lib/Screens/Setting/setting_safe_screen.dart +++ b/lib/Screens/Setting/setting_safe_screen.dart @@ -339,12 +339,6 @@ class _SafeSettingScreenState extends BaseDynamicState canAuthenticateResponse = await BiometricUtil.canAuthenticate(); canAuthenticateResponseString = await BiometricUtil.getCanAuthenticateResponseString(); - bool exist = await BiometricUtil.exists(); - if (!exist) { - _allowDatabaseBiometric = false; - ChewieHiveUtil.put( - CloudOTPHiveUtil.allowDatabaseBiometricKey, _allowDatabaseBiometric); - } setState(() {}); } diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 7aac55f0..afed9bf2 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -108,7 +108,8 @@ class MainScreenState extends BaseWindowState @override void onProtocolUrlReceived(String url) { - ILogger.info("Received protocol url: ${Uri.parse(url).replace(queryParameters: {}).toString()}"); + ILogger.info( + "Received protocol url: ${Uri.parse(url).replace(queryParameters: {}).toString()}"); } Future fetchReleases() async { @@ -375,6 +376,7 @@ class MainScreenState extends BaseWindowState onSelected: () => RouteUtil.pushDialogRoute( chewieProvider.rootContext, const AddTokenScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), ), ), PlatformMenuItem( @@ -387,6 +389,7 @@ class MainScreenState extends BaseWindowState onSelected: () => RouteUtil.pushDialogRoute( chewieProvider.rootContext, const CategoryScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), ), ), PlatformMenu( @@ -451,6 +454,7 @@ class MainScreenState extends BaseWindowState onSelected: () => RouteUtil.pushDialogRoute( chewieProvider.rootContext, const ImportExportTokenScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), ), ), PlatformMenuItem( @@ -458,6 +462,7 @@ class MainScreenState extends BaseWindowState onSelected: () => RouteUtil.pushDialogRoute( chewieProvider.rootContext, const ImportFromThirdPartyBottomSheet(), + onThen: (_) => ShortcutsUtil.restoreFocus(), ), ), ]), @@ -467,6 +472,7 @@ class MainScreenState extends BaseWindowState onSelected: () => RouteUtil.pushDialogRoute( chewieProvider.rootContext, const CloudServiceScreen(showBack: false), + onThen: (_) => ShortcutsUtil.restoreFocus(), ), ), ]), @@ -1257,7 +1263,7 @@ class MainScreenState extends BaseWindowState }); }, leftWidgets: [ - const SizedBox(width: 78), + const SizedBox(width: macosTitleBarLeftMargin), Container( constraints: const BoxConstraints( maxWidth: 300, minWidth: 200, maxHeight: 36), @@ -1314,6 +1320,11 @@ class MainScreenState extends BaseWindowState } } + @override + void didChangeLocales(List? locales) { + appProvider.refreshSystemLocale(); + } + @override void dispose() { protocolHandler.removeListener(this); diff --git a/lib/TokenUtils/Cloud/aliyundrive_cloud_service.dart b/lib/TokenUtils/Cloud/aliyundrive_cloud_service.dart index abf7461c..7d526020 100644 --- a/lib/TokenUtils/Cloud/aliyundrive_cloud_service.dart +++ b/lib/TokenUtils/Cloud/aliyundrive_cloud_service.dart @@ -130,6 +130,7 @@ class AliyunDriveCloudService extends CloudService { path, driveId: driveId, remotePath: _aliyunDrivePath, + onProgress: onProgress, ); return response.isSuccess ? response.bodyBytes : null; } @@ -176,6 +177,7 @@ class AliyunDriveCloudService extends CloudService { _aliyunDrivePath, fileName, driveId: driveId, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/Cloud/box_cloud_service.dart b/lib/TokenUtils/Cloud/box_cloud_service.dart index 0246ac6e..38944cc8 100644 --- a/lib/TokenUtils/Cloud/box_cloud_service.dart +++ b/lib/TokenUtils/Cloud/box_cloud_service.dart @@ -135,7 +135,7 @@ class BoxCloudService extends CloudService { String path, { Function(int p1, int p2)? onProgress, }) async { - final response = await box.pullById(path); + final response = await box.pullById(path, onProgress: onProgress); return response.isSuccess ? response.bodyBytes : null; } @@ -175,6 +175,7 @@ class BoxCloudService extends CloudService { fileData, _boxPath, fileName, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/Cloud/dropbox_cloud_service.dart b/lib/TokenUtils/Cloud/dropbox_cloud_service.dart index 0e1f4575..1254acc2 100644 --- a/lib/TokenUtils/Cloud/dropbox_cloud_service.dart +++ b/lib/TokenUtils/Cloud/dropbox_cloud_service.dart @@ -119,7 +119,8 @@ class DropboxCloudService extends CloudService { String path, { Function(int p1, int p2)? onProgress, }) async { - DropboxResponse response = await dropbox.pullById(path); + DropboxResponse response = + await dropbox.pullById(path, onProgress: onProgress); return response.isSuccess ? response.bodyBytes ?? Uint8List(0) : null; } @@ -161,6 +162,7 @@ class DropboxCloudService extends CloudService { fileData, _dropboxPath, fileName, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/Cloud/googledrive_cloud_service.dart b/lib/TokenUtils/Cloud/googledrive_cloud_service.dart index c4e0bb2c..8fdfe091 100644 --- a/lib/TokenUtils/Cloud/googledrive_cloud_service.dart +++ b/lib/TokenUtils/Cloud/googledrive_cloud_service.dart @@ -132,7 +132,8 @@ class GoogleDriveCloudService extends CloudService { String path, { Function(int p1, int p2)? onProgress, }) async { - GoogleDriveResponse response = await googledrive.pullById(path); + GoogleDriveResponse response = + await googledrive.pullById(path, onProgress: onProgress); return response.isSuccess ? response.bodyBytes ?? Uint8List(0) : null; } @@ -174,6 +175,7 @@ class GoogleDriveCloudService extends CloudService { fileData, _googledrivePathName, fileName, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/Cloud/huawei_cloud_service.dart b/lib/TokenUtils/Cloud/huawei_cloud_service.dart index 1e6ef2c9..7d2b3b67 100644 --- a/lib/TokenUtils/Cloud/huawei_cloud_service.dart +++ b/lib/TokenUtils/Cloud/huawei_cloud_service.dart @@ -129,7 +129,8 @@ class HuaweiCloudService extends CloudService { String path, { Function(int p1, int p2)? onProgress, }) async { - HuaweiCloudResponse response = await huaweiCloud.pullById(path); + HuaweiCloudResponse response = + await huaweiCloud.pullById(path, onProgress: onProgress); return response.isSuccess ? response.bodyBytes ?? Uint8List(0) : null; } @@ -171,6 +172,7 @@ class HuaweiCloudService extends CloudService { fileData, _huaweiCloudPath, fileName, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/Cloud/onedrive_cloud_service.dart b/lib/TokenUtils/Cloud/onedrive_cloud_service.dart index d3b0e09f..35ff9f6d 100644 --- a/lib/TokenUtils/Cloud/onedrive_cloud_service.dart +++ b/lib/TokenUtils/Cloud/onedrive_cloud_service.dart @@ -113,7 +113,8 @@ class OneDriveCloudService extends CloudService { String path, { Function(int p1, int p2)? onProgress, }) async { - OneDriveResponse response = await onedrive.pullById(path); + OneDriveResponse response = + await onedrive.pullById(path, onProgress: onProgress); return response.isSuccess ? response.bodyBytes ?? Uint8List(0) : null; } @@ -155,6 +156,7 @@ class OneDriveCloudService extends CloudService { fileData, _onedrivePath, fileName, + onProgress: onProgress, ); deleteOldBackup(); return response.isSuccess; diff --git a/lib/TokenUtils/export_token_util.dart b/lib/TokenUtils/export_token_util.dart index c6dbdc0e..b715bdae 100644 --- a/lib/TokenUtils/export_token_util.dart +++ b/lib/TokenUtils/export_token_util.dart @@ -253,7 +253,7 @@ class ExportTokenUtil { ProgressDialog? dialog; if (showLoading) { dialog = showProgressDialog( - appLocalizations.backuping, + appLocalizations.encryptingBackupFileShort, showProgress: false, ); } @@ -270,6 +270,12 @@ class ExportTokenUtil { if (canLocalBackup) { try { log.addStatus(AutoBackupStatus.saving); + if (showLoading && dialog != null) { + dialog.updateMessage( + msg: appLocalizations.savingBackupFileShort, + showProgress: false, + ); + } String backupPath = await CloudOTPHiveUtil.getBackupPath(); Directory directory = Directory(backupPath); if (!directory.existsSync()) { @@ -291,14 +297,19 @@ class ExportTokenUtil { if (canCloudBackup) { if (cloudServices != null && cloudServices.isNotEmpty) { bool uploadStatus = false; - for (CloudService cloudService in cloudServices) { + final count = cloudServices.length; + for (int i = 0; i < count; i++) { + final cloudService = cloudServices[i]; try { log.addStatus(AutoBackupStatus.uploading, type: cloudService.type); if (showLoading && dialog != null) { + final serviceMsg = count > 1 + ? "${appLocalizations.cloudPushingTo(cloudService.type.label)} (${i + 1}/$count)" + : appLocalizations + .cloudPushingTo(cloudService.type.label); dialog.updateMessage( - msg: appLocalizations - .cloudPushingTo(cloudService.type.label), + msg: serviceMsg, showProgress: true, ); dialog.updateProgress(progress: 0); @@ -411,7 +422,7 @@ class ExportTokenUtil { ProgressDialog? dialog; if (showLoading) { dialog = showProgressDialog( - appLocalizations.backuping, + appLocalizations.encryptingBackupFileShort, showProgress: false, ); } @@ -424,6 +435,7 @@ class ExportTokenUtil { if (showLoading && dialog != null) { dialog.updateMessage( msg: appLocalizations.cloudPushing, showProgress: true); + dialog.updateProgress(progress: 0); } bool uploadStatus = await cloudService.uploadFile( ExportTokenUtil.getExportFileName("bin"), diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index 7b06a790..1784cb28 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -614,4 +614,10 @@ class AppProvider with ChangeNotifier { ChewieHiveUtil.setLocale(value); } } + + void refreshSystemLocale() { + if (_locale == null) { + notifyListeners(); + } + } } diff --git a/lib/Utils/constant.dart b/lib/Utils/constant.dart index 8dfd76b5..08ef1ff4 100644 --- a/lib/Utils/constant.dart +++ b/lib/Utils/constant.dart @@ -53,3 +53,5 @@ RegExp cloudotpauthMigrationReg = RegExp(r"^cloudotpauth-migration://offline\?tokens=(.*)$"); RegExp cloudotpauthCategoryMigrationReg = RegExp(r"^cloudotpauth-migration://offline\?categories=(.*)$"); + +const double macosTitleBarLeftMargin = 92; diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 5d03219b..4f89d2a2 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -199,36 +199,38 @@ class CloudOTPHiveUtil { static Future regeneratePassword() async { String password = MockUtil.getRandomString(length: 16); + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, password); try { await _secureStorage.write(key: _secureDbPasswordKey, value: password); - await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); } catch (e, t) { - ILogger.error( - "Secure storage unavailable, falling back to Hive", e, t); - await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, password); + ILogger.error("Secure storage unavailable", e, t); } return password; } static Future getDatabasePassword() async { + String? securePassword; try { - String? password = + securePassword = await _secureStorage.read(key: _secureDbPasswordKey); - if (password != null && password.isNotEmpty) return password; } catch (e, t) { ILogger.error( "Secure storage unavailable, falling back to Hive", e, t); } String? hivePassword = ChewieHiveUtil.getString(CloudOTPHiveUtil.defaultDatabasePasswordKey); + if (securePassword != null && securePassword.isNotEmpty) { + if (hivePassword != securePassword) { + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, securePassword); + } + return securePassword; + } if (hivePassword != null && hivePassword.isNotEmpty) { try { await _secureStorage.write( key: _secureDbPasswordKey, value: hivePassword); - await ChewieHiveUtil.put( - CloudOTPHiveUtil.defaultDatabasePasswordKey, ''); } catch (_) {} return hivePassword; } diff --git a/lib/Utils/shortcuts_util.dart b/lib/Utils/shortcuts_util.dart index 480146c9..757ea41d 100644 --- a/lib/Utils/shortcuts_util.dart +++ b/lib/Utils/shortcuts_util.dart @@ -194,6 +194,8 @@ extension HotKeyExt on HotKey { } class ShortcutsUtil { + static bool _searchListenerAdded = false; + static final shortcuts = [ CloudOTPShortcut.all( key: HotKey( @@ -274,13 +276,24 @@ class ShortcutsUtil { ), ]; + static void restoreFocus() { + if (!appProvider.searchFocusNode.hasFocus) { + appProvider.shortcutFocusNode.requestFocus(); + } + } + + static void _onSearchFocusChanged() { + if (!appProvider.searchFocusNode.hasFocus) { + appProvider.shortcutFocusNode.requestFocus(); + } + } + static void focusSearch() { appProvider.searchFocusNode.requestFocus(); - appProvider.searchFocusNode.addListener(() { - if (!appProvider.searchFocusNode.hasFocus) { - appProvider.shortcutFocusNode.requestFocus(); - } - }); + if (!_searchListenerAdded) { + _searchListenerAdded = true; + appProvider.searchFocusNode.addListener(_onSearchFocusChanged); + } } static void lock(BuildContext context) { @@ -316,21 +329,25 @@ class ShortcutsUtil { style: ChewieTheme.titleLarge, ), ), + onThen: (_) => restoreFocus(), ); } static void jumpToSetting(BuildContext context) { - RouteUtil.pushDialogRoute(context, const SettingNavigationScreen()); + RouteUtil.pushDialogRoute(context, const SettingNavigationScreen(), + onThen: (_) => restoreFocus()); } static void jumpToSetLock(BuildContext context) { RouteUtil.pushDialogRoute( - context, const SettingNavigationScreen(initPageIndex: 4)); + context, const SettingNavigationScreen(initPageIndex: 4), + onThen: (_) => restoreFocus()); } static void jumpToAbout(BuildContext context) { RouteUtil.pushDialogRoute( - context, const SettingNavigationScreen(initPageIndex: 5)); + context, const SettingNavigationScreen(initPageIndex: 5), + onThen: (_) => restoreFocus()); } static void jumpToMain() { diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index 08764004..5ed56a40 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -253,7 +253,8 @@ class Utils { try { lauchAtStartup = await LaunchAtStartup.instance.isEnabled(); } catch (e, t) { - ILogger.error("Failed to check LaunchAtStartup in initSimpleTray", e, t); + ILogger.error( + "Failed to check LaunchAtStartup in initSimpleTray", e, t); } if (!ResponsiveUtil.isLinux()) { await trayManager.setToolTip(ResponsiveUtil.appName); @@ -313,7 +314,7 @@ class Utils { ChewieUtils.displayApp(); ShortcutsUtil.jumpToAbout(context); } else if (menuItem.key == TrayKey.officialWebsite.key) { - UriUtil.launchUrlUri(context, officialWebsite); + UriUtil.launchUrlUri(context, cloudotpOfficialWebsite); } else if (menuItem.key.notNullOrEmpty && menuItem.key!.startsWith(TrayKey.copyTokenCode.key) && !isSimple) { diff --git a/lib/Widgets/Shortcuts/app_shortcuts.dart b/lib/Widgets/Shortcuts/app_shortcuts.dart index c4b6dbdb..630ff8a9 100644 --- a/lib/Widgets/Shortcuts/app_shortcuts.dart +++ b/lib/Widgets/Shortcuts/app_shortcuts.dart @@ -19,14 +19,6 @@ class AppShortcuts extends StatelessWidget { .map((e) => MapEntry(e.triggerForPlatform(), e.intent))), child: Actions( actions: >{ - EscapeIntent: CallbackAction( - onInvoke: (intent) { - if (Navigator.of(chewieProvider.rootContext).canPop()) { - Navigator.of(chewieProvider.rootContext).pop(); - } - return null; - }, - ), LockIntent: CallbackAction( onInvoke: (_) { ShortcutsUtil.lock(context); @@ -54,14 +46,20 @@ class AppShortcuts extends StatelessWidget { AddTokenIntent: CallbackAction( onInvoke: (_) { RouteUtil.pushDialogRoute( - chewieProvider.rootContext, const AddTokenScreen()); + chewieProvider.rootContext, + const AddTokenScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), + ); return null; }, ), ImportExportIntent: CallbackAction( onInvoke: (_) { RouteUtil.pushDialogRoute( - chewieProvider.rootContext, const ImportExportTokenScreen()); + chewieProvider.rootContext, + const ImportExportTokenScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), + ); return null; }, ), @@ -74,7 +72,10 @@ class AppShortcuts extends StatelessWidget { CategoryIntent: CallbackAction( onInvoke: (_) { RouteUtil.pushDialogRoute( - chewieProvider.rootContext, const CategoryScreen()); + chewieProvider.rootContext, + const CategoryScreen(), + onThen: (_) => ShortcutsUtil.restoreFocus(), + ); return null; }, ), diff --git a/lib/main.dart b/lib/main.dart index 03e7d207..b7107608 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:ui' as ui; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:awesome_cloud/awesome_cloud.dart'; @@ -125,6 +126,7 @@ Future initHive() async { await DatabaseManager.initDataBase( await CloudOTPHiveUtil.getDatabasePassword()); } catch (e) { + await DatabaseManager.resetDatabase(); if (DatabaseManager.lib != null) { CloudOTPHiveUtil.setEncryptDatabaseStatus( EncryptDatabaseStatus.customPassword); @@ -265,6 +267,22 @@ class MyApp extends StatelessWidget { } } + static Locale _resolveSystemLocale(Iterable supportedLocales) { + final systemLocale = ui.PlatformDispatcher.instance.locale; + for (final supported in supportedLocales) { + if (supported.languageCode == systemLocale.languageCode && + supported.countryCode == systemLocale.countryCode) { + return supported; + } + } + for (final supported in supportedLocales) { + if (supported.languageCode == systemLocale.languageCode) { + return supported; + } + } + return supportedLocales.first; + } + @override Widget build(BuildContext context) { moveToCenter(context); @@ -289,27 +307,16 @@ class MyApp extends StatelessWidget { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - locale: context.watch().locale, + locale: context.watch().locale ?? + _resolveSystemLocale(AppLocalizations.supportedLocales), supportedLocales: AppLocalizations.supportedLocales, localeResolutionCallback: (locale, supportedLocales) { ILogger.debug( "Locale: $locale, Supported: $supportedLocales, appProvider.locale: ${appProvider.locale}"); if (appProvider.locale != null) { return appProvider.locale; - } else if (locale != null && supportedLocales.contains(locale)) { - return locale; - } else { - try { - return Localizations.localeOf(context); - } catch (e, t) { - ILogger.error( - "Failed to get locale by Localizations.localeOf(context)", - e, - t, - ); - return const Locale("en", "US"); - } } + return _resolveSystemLocale(supportedLocales); }, home: home, builder: (context, widget) { diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index f42c41ef..fd026f4c 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -4,6 +4,7 @@ import LaunchAtLogin class MainFlutterWindow: NSWindow { private let trafficLightVerticalOffset: CGFloat = 18.0 + private let trafficLightHorizontalOffset: CGFloat = 4.0 override func awakeFromNib() { let flutterViewController = FlutterViewController() @@ -16,6 +17,13 @@ class MainFlutterWindow: NSWindow { self.styleMask.insert(.fullSizeContentView) self.isOpaque = false + let toolbar = NSToolbar(identifier: "main") + toolbar.showsBaselineSeparator = false + self.toolbar = toolbar + if #available(macOS 11.0, *) { + self.toolbarStyle = .unified + } + RegisterGeneratedPlugins(registry: flutterViewController) LocalNotifierOverride.register(with: flutterViewController.registrar(forPlugin: "LocalNotifierOverride")) diff --git a/third-party/awesome_cloud/lib/services/aliyundrive.dart b/third-party/awesome_cloud/lib/services/aliyundrive.dart index db833660..216b60d6 100644 --- a/third-party/awesome_cloud/lib/services/aliyundrive.dart +++ b/third-party/awesome_cloud/lib/services/aliyundrive.dart @@ -162,6 +162,7 @@ class AliyunDriveCloud extends BaseCloudService { String id, { String remotePath = "", String driveId = "", + Function(int, int)? onProgress, }) async { try { final resp = await post( @@ -173,18 +174,16 @@ class AliyunDriveCloud extends BaseCloudService { if (isSuccess(resp)) { CloudLogger.infoResponse(serviceName, "Get download url", resp); final url = jsonDecode(resp.body)["url"]; - final binary = await http.get(Uri.parse(url)); - - if (binary.statusCode == 200) { - CloudLogger.info(serviceName, "Downloaded file successfully."); - return AliyunDriveResponse.success( - bodyBytes: binary.bodyBytes, - message: "Downloaded file successfully.", - ); - } else { - CloudLogger.error(serviceName, "Failed to download file."); - return AliyunDriveResponse.error(message: "Failed to download file."); - } + final bodyBytes = await BaseCloudService.downloadFromUrl( + Uri.parse(url), + onProgress: onProgress, + ); + + CloudLogger.info(serviceName, "Downloaded file successfully."); + return AliyunDriveResponse.success( + bodyBytes: bodyBytes, + message: "Downloaded file successfully.", + ); } CloudLogger.errorResponse( diff --git a/third-party/awesome_cloud/lib/services/base_service.dart b/third-party/awesome_cloud/lib/services/base_service.dart index d23d8264..136ae5f5 100644 --- a/third-party/awesome_cloud/lib/services/base_service.dart +++ b/third-party/awesome_cloud/lib/services/base_service.dart @@ -303,6 +303,40 @@ abstract class BaseCloudService with ChangeNotifier { )); } + Future getStreamed( + Uri url, { + dynamic headers, + Function(int current, int total)? onProgress, + }) async { + final accessToken = await checkToken(); + var tmpHeaders = {"Authorization": "Bearer $accessToken"}; + if (headers != null) { + tmpHeaders.addAll(headers); + } + final request = http.Request('GET', url); + request.headers.addAll(tmpHeaders); + final client = http.Client(); + try { + final streamedResponse = await client.send(request); + if (streamedResponse.statusCode == 401) { + disconnect(); + } + if (!isStreamSuccess(streamedResponse)) { + throw Exception( + 'Download failed: ${streamedResponse.statusCode} ${streamedResponse.reasonPhrase}'); + } + final total = streamedResponse.contentLength ?? -1; + final bytes = []; + await for (final chunk in streamedResponse.stream) { + bytes.addAll(chunk); + onProgress?.call(bytes.length, total); + } + return Uint8List.fromList(bytes); + } finally { + client.close(); + } + } + Future delete( Uri url, { dynamic headers, @@ -322,11 +356,40 @@ abstract class BaseCloudService with ChangeNotifier { Future list(String remotePath); - Future pullById(String id); + Future pullById(String id, {Function(int, int)? onProgress}); Future deleteById(String id); Future push(Uint8List bytes, String remotePath, String fileName); Future checkFolder(String remotePath); + + static Future downloadFromUrl( + Uri url, { + Map? headers, + Function(int current, int total)? onProgress, + }) async { + final request = http.Request('GET', url); + if (headers != null) { + request.headers.addAll(headers); + } + final client = http.Client(); + try { + final streamedResponse = await client.send(request); + if (streamedResponse.statusCode != 200 && + streamedResponse.statusCode != 201) { + throw Exception( + 'Download failed: ${streamedResponse.statusCode} ${streamedResponse.reasonPhrase}'); + } + final total = streamedResponse.contentLength ?? -1; + final bytes = []; + await for (final chunk in streamedResponse.stream) { + bytes.addAll(chunk); + onProgress?.call(bytes.length, total); + } + return Uint8List.fromList(bytes); + } finally { + client.close(); + } + } } diff --git a/third-party/awesome_cloud/lib/services/box.dart b/third-party/awesome_cloud/lib/services/box.dart index 7004445c..e34c2dcd 100644 --- a/third-party/awesome_cloud/lib/services/box.dart +++ b/third-party/awesome_cloud/lib/services/box.dart @@ -146,7 +146,10 @@ class BoxCloud extends BaseCloudService { } @override - Future pullById(String id) async { + Future pullById( + String id, { + Function(int, int)? onProgress, + }) async { try { final url = Uri.parse("https://api.box.com/2.0/files/$id/content"); CloudLogger.info(serviceName, "Pulling file by ID: $id"); @@ -158,23 +161,17 @@ class BoxCloud extends BaseCloudService { serviceName, "Redirected to download URL: $downloadUrl", ); - final binary = await http.get(downloadUrl); - if (binary.statusCode != 200) { - CloudLogger.error( - serviceName, - "Failed to download file: ${binary.statusCode} ${binary.reasonPhrase}", - ); - return BoxResponse.error( - message: "Failed to download file: ${binary.reasonPhrase}", - ); - } + final bodyBytes = await BaseCloudService.downloadFromUrl( + downloadUrl, + onProgress: onProgress, + ); CloudLogger.info( serviceName, - "File downloaded successfully: ${binary.bodyBytes.length} bytes.", + "File downloaded successfully: ${bodyBytes.length} bytes.", ); return BoxResponse.success( message: "Download success.", - bodyBytes: binary.bodyBytes, + bodyBytes: bodyBytes, ); } else if (isSuccess(resp)) { CloudLogger.info(serviceName, "Downloaded file"); diff --git a/third-party/awesome_cloud/lib/services/dropbox.dart b/third-party/awesome_cloud/lib/services/dropbox.dart index 72ff77de..e5b489f6 100644 --- a/third-party/awesome_cloud/lib/services/dropbox.dart +++ b/third-party/awesome_cloud/lib/services/dropbox.dart @@ -184,32 +184,28 @@ class Dropbox extends BaseCloudService { } @override - Future pullById(String id) async { + Future pullById( + String id, { + Function(int, int)? onProgress, + }) async { try { final url = Uri.parse("$apiContentEndpoint/files/download"); - final resp = await get( + final bodyBytes = await getStreamed( url, headers: { "Dropbox-API-Arg": jsonEncode({ "path": id, }), }, + onProgress: onProgress, ); - if (resp.statusCode == 200 || resp.statusCode == 201) { - CloudLogger.infoResponse(serviceName, "pull successfully", resp); - return DropboxResponse.fromResponse( - response: resp, - message: "Download successfully.", - ); - } else { - CloudLogger.errorResponse(serviceName, "pull failed", resp); - return DropboxResponse.fromResponse( - response: resp, - message: "Error while downloading file.", - ); - } + CloudLogger.info(serviceName, "pull successfully"); + return DropboxResponse.success( + bodyBytes: bodyBytes, + message: "Download successfully.", + ); } catch (err, trace) { CloudLogger.error(serviceName, "pull error", err, trace); return DropboxResponse.error(message: "Unexpected exception: $err"); diff --git a/third-party/awesome_cloud/lib/services/googledrive.dart b/third-party/awesome_cloud/lib/services/googledrive.dart index 59052a7d..c418997f 100644 --- a/third-party/awesome_cloud/lib/services/googledrive.dart +++ b/third-party/awesome_cloud/lib/services/googledrive.dart @@ -202,7 +202,10 @@ class GoogleDrive extends BaseCloudService { } @override - Future pullById(String id) async { + Future pullById( + String id, { + Function(int, int)? onProgress, + }) async { try { drive.DriveApi driveApi = await getClient(); @@ -217,10 +220,18 @@ class GoogleDrive extends BaseCloudService { CloudLogger.error(serviceName, "Media stream is null for file ID: $id"); return GoogleDriveResponse.error(message: "File not found or empty."); } + + final total = media.length ?? -1; + final bytes = []; + await for (final chunk in media.stream) { + bytes.addAll(chunk); + onProgress?.call(bytes.length, total); + } + CloudLogger.info(serviceName, "Download successfully for file ID: $id"); return GoogleDriveResponse.success( message: "Download successfully.", - bodyBytes: await (media.stream as http.ByteStream).toBytes(), + bodyBytes: Uint8List.fromList(bytes), ); } catch (err) { CloudLogger.error(serviceName, "Exception while downloading file: $err"); diff --git a/third-party/awesome_cloud/lib/services/huaweicloud.dart b/third-party/awesome_cloud/lib/services/huaweicloud.dart index 1cff7d69..6bc8cddc 100644 --- a/third-party/awesome_cloud/lib/services/huaweicloud.dart +++ b/third-party/awesome_cloud/lib/services/huaweicloud.dart @@ -237,25 +237,20 @@ class HuaweiCloud extends BaseCloudService { } @override - Future pullById(String id) async { + Future pullById( + String id, { + Function(int, int)? onProgress, + }) async { CloudLogger.info(serviceName, "Start pull file by ID: $id"); try { final pullUri = Uri.parse("$apiEndpoint/files/$id?form=content"); - final resp = await get(pullUri); - if (isSuccess(resp)) { - CloudLogger.infoResponse(serviceName, "Pull file success", resp); - return HuaweiCloudResponse.fromResponse( - message: "Download successfully.", - response: resp, - ); - } else { - CloudLogger.errorResponse(serviceName, "Pull file failed", resp); - return HuaweiCloudResponse.fromResponse( - response: resp, - message: "Error when pulling file.", - ); - } + final bodyBytes = await getStreamed(pullUri, onProgress: onProgress); + CloudLogger.info(serviceName, "Pull file success"); + return HuaweiCloudResponse.success( + bodyBytes: bodyBytes, + message: "Download successfully.", + ); } catch (err) { CloudLogger.error(serviceName, "Pull file error: $err", err); return HuaweiCloudResponse.error(message: "Unexpected exception: $err"); diff --git a/third-party/awesome_cloud/lib/services/onedrive.dart b/third-party/awesome_cloud/lib/services/onedrive.dart index bb36141f..4642c23d 100644 --- a/third-party/awesome_cloud/lib/services/onedrive.dart +++ b/third-party/awesome_cloud/lib/services/onedrive.dart @@ -147,25 +147,19 @@ class OneDrive extends BaseCloudService { } @override - Future pullById(String id) async { + Future pullById( + String id, { + Function(int, int)? onProgress, + }) async { try { final pullUri = Uri.parse("$apiEndpoint/me/drive/items/$id/content"); - final resp = await get(pullUri); - - if (isSuccess(resp)) { - CloudLogger.info(serviceName, "Pull file successfully"); - return OneDriveResponse.fromResponse( - response: resp, - message: "Pull file successfully.", - ); - } else { - CloudLogger.errorResponse(serviceName, "Pull file failed", resp); - return OneDriveResponse.fromResponse( - response: resp, - message: "Error when pulling file.", - ); - } + final bodyBytes = await getStreamed(pullUri, onProgress: onProgress); + CloudLogger.info(serviceName, "Pull file successfully"); + return OneDriveResponse.success( + bodyBytes: bodyBytes, + message: "Pull file successfully.", + ); } catch (err, trace) { CloudLogger.error(serviceName, "Pull file error", err, trace); return OneDriveResponse.error(message: "Pull file error: $err"); diff --git a/third-party/chewie/lib/src/Utils/System/file_util.dart b/third-party/chewie/lib/src/Utils/System/file_util.dart index 0f18be27..aec160a4 100644 --- a/third-party/chewie/lib/src/Utils/System/file_util.dart +++ b/third-party/chewie/lib/src/Utils/System/file_util.dart @@ -240,18 +240,13 @@ class FileUtil { } static Future migrationDataToSupportDirectory() async { - try { - Hive.defaultDirectory = await FileUtil.getHiveDir(); - bool haveMigratedToSupportDirectoryFromHive = ChewieHiveUtil.getBool( - ChewieHiveUtil.haveMigratedToSupportDirectoryKey, - defaultValue: false); - if (haveMigratedToSupportDirectoryFromHive) { - ILogger.info("Have migrated data to support directory"); - return; - } - Hive.box(name: ChewieHiveUtil.settingsBox).close(); - } catch (e, t) { - ILogger.error("Failed to close all hive boxes", e, t); + var newDirPath = (await getApplicationSupportDirectory()).path; + if (kDebugMode) newDirPath += "-Debug"; + File marker = File(join(newDirPath, '.migration_done')); + if (await marker.exists()) { + ILogger.info("Have migrated data to support directory"); + haveMigratedToSupportDirectory = true; + return; } Directory oldDir = Directory(await getOldApplicationDir()); Directory newDir = Directory(await getApplicationDir()); @@ -288,6 +283,9 @@ class FileUtil { ILogger.info( "Finish to migrate data from old application directory $oldDir to new application directory $newDir"); } + try { + await marker.create(recursive: true); + } catch (_) {} await Future.delayed(const Duration(milliseconds: 200)); } diff --git a/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart b/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart index 4806a3be..39278e1b 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart @@ -219,12 +219,15 @@ class CustomConfirmDialog { } class CustomLoadingDialog { + static final ValueNotifier _titleNotifier = ValueNotifier(null); + static void showLoading({ bool barrierDismissible = false, String? title, double size = 40, double scale = 1, }) { + _titleNotifier.value = title; showGeneralDialog( barrierColor: ChewieTheme.barrierColor, barrierDismissible: barrierDismissible, @@ -237,13 +240,17 @@ class CustomLoadingDialog { animation: animation, child: LoadingDialogWidget( dismissible: barrierDismissible, - title: title, + titleNotifier: _titleNotifier, size: size, ), ), ); } + static void updateTitle(String title) { + _titleNotifier.value = title; + } + static Future dismissLoading() async { return Future.sync(() => Navigator.pop(chewieProvider.rootContext)); } diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart index af18ef64..06033a68 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart @@ -4,14 +4,14 @@ import 'package:flutter/material.dart'; class LoadingDialogWidget extends StatefulWidget { final bool dismissible; - final String? title; + final ValueNotifier titleNotifier; final double size; const LoadingDialogWidget({ super.key, this.dismissible = false, - this.title, + required this.titleNotifier, this.size = 40, }); @@ -27,25 +27,28 @@ class LoadingDialogWidgetState extends State { children: [ PopScope( canPop: widget.dismissible, - onPopInvoked: (_) => Future.value(widget.dismissible), + onPopInvokedWithResult: (_, __) => Future.value(widget.dismissible), child: Container( decoration: ChewieTheme.defaultDecoration.copyWith( color: ChewieTheme.scaffoldBackgroundColor, ), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - chewieProvider.loadingWidgetBuilder(widget.size, false), - if (widget.title != null) const SizedBox(height: 16), - if (widget.title != null) - Text( - widget.title!, - style: ChewieTheme.labelLarge, - ), - ], + child: ValueListenableBuilder( + valueListenable: widget.titleNotifier, + builder: (context, title, _) => Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + chewieProvider.loadingWidgetBuilder(widget.size, false), + if (title != null) const SizedBox(height: 16), + if (title != null) + Text( + title, + style: ChewieTheme.labelLarge, + ), + ], + ), ), ), ) diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart index 48e07ca6..8ddf02cd 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart @@ -160,12 +160,31 @@ class ProgressDialog { crossAxisAlignment: CrossAxisAlignment.center, children: [ LoadingDialogIndicator( - complete: _data.value.complete, - error: _data.value.error, + complete: value.complete, + error: value.error, ), + if (value.showProgress) ...[ + const SizedBox(height: 12), + SizedBox( + width: 200, + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: value.progress, + minHeight: 4, + backgroundColor: ChewieTheme.canvasColor, + valueColor: AlwaysStoppedAnimation( + value.error + ? Colors.red + : ChewieTheme.primaryColor, + ), + ), + ), + ), + ], const SizedBox(height: 16), Text( - '${_data.value.msg}${showProgress ? (_data.value.progress * 100).toStringAsFixed(1) : ''}%', + '${value.msg}${value.showProgress ? " ${(value.progress * 100).toStringAsFixed(1)}%" : ""}', maxLines: 2, overflow: TextOverflow.ellipsis, style: ChewieTheme.labelLarge, @@ -183,7 +202,7 @@ class ProgressDialog { } } -class LoadingDialogIndicator extends StatefulWidget { +class LoadingDialogIndicator extends StatelessWidget { final bool complete; final bool error; @@ -193,28 +212,15 @@ class LoadingDialogIndicator extends StatefulWidget { required this.error, }); - @override - State createState() => _LoadingDialogIndicatorState(); -} - -class _LoadingDialogIndicatorState extends State - with TickerProviderStateMixin { - late final AnimationController _controller; - - @override - void initState() { - _controller = AnimationController(vsync: this); - super.initState(); - } - @override Widget build(BuildContext context) { + if (complete) { + return Icon(Icons.check_circle_rounded, + size: 40, color: ChewieTheme.primaryColor); + } + if (error) { + return const Icon(Icons.error_rounded, size: 40, color: Colors.red); + } return chewieProvider.loadingWidgetBuilder(40, false); } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } } From 3beb69f718bd52053ee88764ce812b79296700e6 Mon Sep 17 00:00:00 2001 From: dd
Date: Tue, 19 May 2026 16:39:59 +0800 Subject: [PATCH 21/36] feat: Implement CoachMarkManager for guided tours and add localization strings - Added CoachMarkManager class to manage tutorial coach marks in the app. - Integrated tutorial_coach_mark package for displaying coach marks. - Added localization strings for coach marks in English, Japanese, Simplified Chinese, and Traditional Chinese. - Updated pubspec.yaml to include tutorial_coach_mark dependency. - Fixed potential issues in FlutterSlidable by ensuring mounted state is checked before handling animations. --- lib/Database/token_dao.dart | 40 +- lib/Screens/Setting/backup_log_screen.dart | 319 ++--- .../Setting/setting_backup_screen.dart | 2 +- lib/Screens/Token/token_layout.dart | 198 +++- lib/Screens/feature_showcase_screen.dart | 1044 +++++++++++++++++ lib/Screens/home_screen.dart | 960 +++++++++++++-- lib/Screens/layout_select_screen.dart | 301 +++++ lib/Screens/main_screen.dart | 11 +- lib/Screens/sort_select_screen.dart | 303 +++++ lib/TokenUtils/export_token_util.dart | 73 +- lib/Utils/hive_util.dart | 1 + ...lect_category_for_tokens_bottom_sheet.dart | 173 +++ .../token_option_bottom_sheet.dart | 74 +- lib/Widgets/CoachMark/coach_mark_manager.dart | 350 ++++++ lib/l10n/intl_en.arb | 144 ++- lib/l10n/intl_ja.arb | 144 ++- lib/l10n/intl_zh.arb | 144 ++- lib/l10n/intl_zh_TW.arb | 144 ++- pubspec.lock | 8 + pubspec.yaml | 1 + .../src/auto_close_behavior.dart | 4 +- 21 files changed, 4105 insertions(+), 333 deletions(-) create mode 100644 lib/Screens/feature_showcase_screen.dart create mode 100644 lib/Screens/layout_select_screen.dart create mode 100644 lib/Screens/sort_select_screen.dart create mode 100644 lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart create mode 100644 lib/Widgets/CoachMark/coach_mark_manager.dart diff --git a/lib/Database/token_dao.dart b/lib/Database/token_dao.dart index a6e2f28f..bc9342e4 100644 --- a/lib/Database/token_dao.dart +++ b/lib/Database/token_dao.dart @@ -199,6 +199,22 @@ class TokenDao { return id; } + static Future deleteTokens(List tokens) async { + if (tokens.isEmpty) return 0; + final db = await DatabaseManager.getDataBase(); + for (OtpToken token in tokens) { + await BindingDao.removeTokenBindings(token.uid); + } + Batch batch = db.batch(); + for (OtpToken token in tokens) { + batch.delete(tableName, where: 'id = ?', whereArgs: [token.id]); + } + List results = await batch.commit(); + ExportTokenUtil.autoBackup(triggerType: AutoBackupTriggerType.tokenDeleted); + Utils.initTray(); + return results.length; + } + static Future> listTokens({ String searchKey = "", String orderBy = "", @@ -208,8 +224,12 @@ class TokenDao { final List> maps = await db.query( tableName, orderBy: "pinned DESC, seq DESC${orderBy.isEmpty ? "" : ", $orderBy"}", - where: searchKey.isEmpty ? null : 'issuer LIKE ?', - whereArgs: searchKey.isEmpty ? null : ["%$searchKey%"], + where: searchKey.isEmpty + ? null + : '(issuer LIKE ? OR account LIKE ? OR description LIKE ?)', + whereArgs: searchKey.isEmpty + ? null + : ["%$searchKey%", "%$searchKey%", "%$searchKey%"], ); return List.generate(maps.length, (i) { return OtpToken.fromMap(maps[i]); @@ -224,8 +244,12 @@ class TokenDao { final db = await DatabaseManager.getDataBase(); List> maps = await db.query( tableName, - where: searchKey.isEmpty ? 'id = ?' : 'id = ? AND issuer LIKE ?', - whereArgs: searchKey.isEmpty ? [id] : [id, "%$searchKey%"], + where: searchKey.isEmpty + ? 'id = ?' + : 'id = ? AND (issuer LIKE ? OR account LIKE ? OR description LIKE ?)', + whereArgs: searchKey.isEmpty + ? [id] + : [id, "%$searchKey%", "%$searchKey%", "%$searchKey%"], ); return OtpToken.fromMap(maps[0]); } catch (e, t) { @@ -243,8 +267,12 @@ class TokenDao { final db = await DatabaseManager.getDataBase(); List> maps = await db.query( tableName, - where: searchKey.isEmpty ? 'uid = ?' : 'uid = ? AND issuer LIKE ?', - whereArgs: searchKey.isEmpty ? [uid] : [uid, "%$searchKey%"], + where: searchKey.isEmpty + ? 'uid = ?' + : 'uid = ? AND (issuer LIKE ? OR account LIKE ? OR description LIKE ?)', + whereArgs: searchKey.isEmpty + ? [uid] + : [uid, "%$searchKey%", "%$searchKey%", "%$searchKey%"], ); return maps.isNotEmpty ? OtpToken.fromMap(maps[0]) : null; } catch (e, t) { diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 2e2006c1..82c2fc7c 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -27,12 +27,27 @@ import '../../Database/config_dao.dart'; import '../../l10n/l10n.dart'; class BackupLogScreen extends StatefulWidget { + final bool isOverlay; + const BackupLogScreen({ super.key, this.isOverlay = false, }); - final bool isOverlay; + static void show(BuildContext context) { + if (ResponsiveUtil.isLandscapeLayout()) { + BottomSheetBuilder.showGenericContextMenu( + context, + const BackupLogScreen(isOverlay: true), + ); + } else { + BottomSheetBuilder.showBottomSheet( + context, + (ctx) => const BackupLogScreen(), + responsive: true, + ); + } + } @override BackupLogScreenState createState() => BackupLogScreenState(); @@ -43,6 +58,10 @@ class BackupLogScreenState extends BaseDynamicState { bool get canBackup => _autoBackupPassword.isNotEmpty; + Color get _accent => ChewieTheme.primaryColor; + + Radius get _radius => ChewieDimens.defaultRadius; + @override void initState() { super.initState(); @@ -60,153 +79,185 @@ class BackupLogScreenState extends BaseDynamicState { @override Widget build(BuildContext context) { - return widget.isOverlay - ? _buildDesktopBody() - : Scaffold( - appBar: ResponsiveAppBar( - backgroundColor: ResponsiveUtil.isLandscapeLayout() - ? ChewieTheme.appBarBackgroundColor - : Colors.transparent, - title: appLocalizations.backupLogs, - showBack: true, - showBorder: true, - onTapBack: () { - Navigator.pop(context); - }, - actions: [ - canBackup && appProvider.autoBackupLogs.isNotEmpty - ? CircleIconButton( - icon: Icon( - LucideIcons.trash2, - color: ChewieTheme.iconColor, - size: 20, - ), - padding: const EdgeInsets.all(10), - onTap: clear, - ) - : const BlankIconButton(), - const SizedBox(width: 5), - if (ResponsiveUtil.isLandscapeLayout()) - Container( - margin: const EdgeInsets.only(right: 5), - child: const BlankIconButton(), - ), - ], - ), - body: _buildBody(), - ); - } - - _buildDesktopBody() { - return Container( - decoration: BoxDecoration( - color: ChewieTheme.scaffoldBackgroundColor, - borderRadius: ChewieDimens.borderRadius8, - border: ChewieTheme.border, - boxShadow: ChewieTheme.defaultBoxShadow, - ), - width: !ResponsiveUtil.isLandscapeLayout() - ? null - : min(300, MediaQuery.sizeOf(context).width - 80), - height: !ResponsiveUtil.isLandscapeLayout() - ? null - : min(appProvider.autoBackupLogs.isEmpty ? 200 : 400, - MediaQuery.sizeOf(context).height - 80), - child: _buildBody(), + Widget header = Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: _buildHeader(), ); - } - clear() { - appProvider.clearAutoBackupLogs(); - appProvider.autoBackupLoadingStatus = LoadingStatus.none; - } + Widget body = _buildLogList(); + + if (widget.isOverlay) { + final overlayHeight = appProvider.autoBackupLogs.isEmpty || !canBackup + ? 200.0 + : 400.0; + return Container( + width: min(300, MediaQuery.sizeOf(context).width - 80), + height: min(overlayHeight, MediaQuery.sizeOf(context).height - 80), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + borderRadius: ChewieDimens.borderRadius8, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + header, + Expanded(child: body), + ], + ), + ); + } - _buildBody() { - return ListView( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - physics: widget.isOverlay - ? null - : const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), + return Wrap( + runAlignment: WrapAlignment.center, children: [ - if (widget.isOverlay) - Row( + Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.65, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: _radius, + bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, + ), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(width: 5), - Container( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Text( - appLocalizations.backupLogs, - style: Theme.of(context) - .textTheme - .titleMedium - ?.apply(fontWeightDelta: 2), - ), - ), - const Spacer(), - if (canBackup && appProvider.autoBackupLogs.isNotEmpty) - CircleIconButton( - icon: const Icon( - LucideIcons.trash2, - size: 16, - ), - onTap: clear, - ), + header, + Flexible(child: body), ], ), - if (widget.isOverlay && appProvider.autoBackupLogs.isNotEmpty) - const SizedBox(height: 10), - ...List.generate( - appProvider.autoBackupLogs.length, - (index) { - return BackupLogItem( - log: appProvider.autoBackupLogs[index], - isOverlay: widget.isOverlay, - ); - }, ), - if (!canBackup) - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + ], + ); + } + + Widget _buildHeader() { + return Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: _accent.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.history, color: _accent, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 20), Text( - appLocalizations.haveNotSetBackupPassword, - style: ChewieTheme.bodyMedium, + appLocalizations.backupLogs, + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), ), - const SizedBox(height: 10), - RoundIconTextButton( - height: 36, - text: appLocalizations.goToSetBackupPassword, - background: ChewieTheme.primaryColor, - onPressed: () { - if (widget.isOverlay) { - RouteUtil.pushDialogRoute(context, - const SettingNavigationScreen(initPageIndex: 3)); - } else { - RouteUtil.pushCupertinoRoute( - context, - const BackupSettingScreen( - jumpToAutoBackupPassword: true)); - } - }, + const SizedBox(height: 1), + Text( + appLocalizations.backupLogSubtitle, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), ), ], ), - if (canBackup && appProvider.autoBackupLogs.isEmpty) - EmptyPlaceholder(text: appLocalizations.noBackupLogs, topPadding: 30), + ), + if (canBackup && appProvider.autoBackupLogs.isNotEmpty) + CircleIconButton( + icon: Icon(LucideIcons.trash2, size: 16, color: _accent), + onTap: clear, + ), ], ); } + + clear() { + appProvider.clearAutoBackupLogs(); + appProvider.autoBackupLoadingStatus = LoadingStatus.none; + setState(() {}); + } + + Widget _buildLogList() { + if (!canBackup) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + appLocalizations.haveNotSetBackupPassword, + style: ChewieTheme.bodyMedium, + ), + const SizedBox(height: 10), + RoundIconTextButton( + height: 36, + text: appLocalizations.goToSetBackupPassword, + background: _accent, + onPressed: () { + if (widget.isOverlay) { + RouteUtil.pushDialogRoute(context, + const SettingNavigationScreen(initPageIndex: 3)); + } else { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, + const BackupSettingScreen( + jumpToAutoBackupPassword: true)); + } + }, + ), + ], + ), + ); + } + + if (appProvider.autoBackupLogs.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 14), + child: + EmptyPlaceholder(text: appLocalizations.noBackupLogs, topPadding: 10), + ); + } + + if (widget.isOverlay) { + return SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + child: Column( + mainAxisSize: MainAxisSize.min, + children: List.generate( + appProvider.autoBackupLogs.length, + (index) => BackupLogItem( + log: appProvider.autoBackupLogs[index], + ), + ), + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + shrinkWrap: true, + itemCount: appProvider.autoBackupLogs.length, + itemBuilder: (context, index) { + return BackupLogItem( + log: appProvider.autoBackupLogs[index], + ); + }, + ); + } } class BackupLogItem extends StatefulWidget { final AutoBackupLog log; - final bool isOverlay; - - const BackupLogItem({super.key, required this.log, required this.isOverlay}); + const BackupLogItem({super.key, required this.log}); @override BackupLogItemState createState() => BackupLogItemState(); @@ -232,7 +283,7 @@ class BackupLogItemState extends BaseDynamicState { } : null, child: Container( - padding: EdgeInsets.all(widget.isOverlay ? 8 : 12), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( borderRadius: ChewieDimens.borderRadius8, ), @@ -244,9 +295,7 @@ class BackupLogItemState extends BaseDynamicState { children: [ Text( widget.log.triggerType.label, - style: ChewieTheme.bodyMedium.apply( - fontSizeDelta: widget.isOverlay ? 0 : 1, - ), + style: ChewieTheme.bodyMedium, ), const Spacer(), RoundIconTextButton( @@ -255,9 +304,8 @@ class BackupLogItemState extends BaseDynamicState { padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), text: widget.log.lastStatusItem.labelShort, - textStyle: ChewieTheme.labelSmall?.apply( - color: Colors.white, - fontSizeDelta: widget.isOverlay ? 0 : 1), + textStyle: ChewieTheme.labelSmall + ?.apply(color: Colors.white), background: widget.log.lastStatus.color, ), const SizedBox(width: 5), @@ -296,10 +344,7 @@ class BackupLogItemState extends BaseDynamicState { return '[${TimeUtil.timestampToDateString(statusItem.timestamp)}]: ${statusItem.label(widget.log)}'; }, ).join('
'), - style: Theme.of(context) - .textTheme - .labelSmall - ?.apply(fontSizeDelta: widget.isOverlay ? 0 : 1), + style: ChewieTheme.labelSmall, ); } } diff --git a/lib/Screens/Setting/setting_backup_screen.dart b/lib/Screens/Setting/setting_backup_screen.dart index 40fdb012..6f8dcd64 100644 --- a/lib/Screens/Setting/setting_backup_screen.dart +++ b/lib/Screens/Setting/setting_backup_screen.dart @@ -345,7 +345,7 @@ class _BackupSettingScreenState extends BaseDynamicState title: appLocalizations.backupLogs, trailing: Icons.history_rounded, onTap: () async { - RouteUtil.pushCupertinoRoute(context, const BackupLogScreen()); + BackupLogScreen.show(context); }, ), ], diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 6dd70c73..241bb323 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -46,12 +46,24 @@ class TokenLayout extends StatefulWidget { super.key, required this.token, required this.layoutType, + this.multiSelectMode = false, + this.isSelected = false, + this.onToggleSelect, + this.onEnterMultiSelect, }); final OtpToken token; final LayoutType layoutType; + final bool multiSelectMode; + + final bool isSelected; + + final VoidCallback? onToggleSelect; + + final VoidCallback? onEnterMultiSelect; + @override State createState() => TokenLayoutState(); } @@ -92,6 +104,46 @@ class TokenLayoutState extends BaseDynamicState TokenLayoutNotifier tokenLayoutNotifier = TokenLayoutNotifier(); + AnimationController? _wobbleController; + late Animation _wobbleAnimation; + + SlidableController? _slidableController; + + Future openEndActionPane() async { + await _slidableController?.openEndActionPane(); + } + + Future closeSlidable() async { + await _slidableController?.close(); + } + + void _startWobble() { + if (_wobbleController != null) return; + _wobbleController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + ); + final phase = (widget.token.uid.hashCode % 1000) / 1000.0; + _wobbleAnimation = TweenSequence([ + TweenSequenceItem(tween: Tween(begin: 0, end: 0.008), weight: 1), + TweenSequenceItem(tween: Tween(begin: 0.008, end: -0.008), weight: 2), + TweenSequenceItem(tween: Tween(begin: -0.008, end: 0), weight: 1), + ]).animate(CurvedAnimation( + parent: _wobbleController!, + curve: Curves.easeInOut, + )); + Future.delayed(Duration(milliseconds: (phase * 300).toInt()), () { + if (_wobbleController != null && mounted) { + _wobbleController!.repeat(); + } + }); + } + + void _stopWobble() { + _wobbleController?.dispose(); + _wobbleController = null; + } + final ValueNotifier progressNotifier = ValueNotifier(0); int get remainingMilliseconds => widget.token.period == 0 @@ -111,10 +163,28 @@ class TokenLayoutState extends BaseDynamicState @override void dispose() { _tickerSubscription?.cancel(); + _stopWobble(); + final sc = _slidableController; + _slidableController = null; + if (sc != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + sc.dispose(); + }); + } tokenLayoutNotifier.dispose(); super.dispose(); } + @override + void didUpdateWidget(covariant TokenLayout oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.multiSelectMode && !oldWidget.multiSelectMode) { + _startWobble(); + } else if (!widget.multiSelectMode && oldWidget.multiSelectMode) { + _stopWobble(); + } + } + updateInfo({ bool counterChanged = false, }) { @@ -168,6 +238,14 @@ class TokenLayoutState extends BaseDynamicState FlutterContextMenu _buildContextMenuButtons() { return FlutterContextMenu( entries: [ + if (widget.onEnterMultiSelect != null) ...[ + FlutterContextMenuItem( + iconData: LucideIcons.listChecks, + appLocalizations.select, + onPressed: () => widget.onEnterMultiSelect?.call(), + ), + FlutterContextMenuItem.divider(), + ], FlutterContextMenuItem( iconData: LucideIcons.copy, appLocalizations.copyTokenCode, @@ -215,6 +293,11 @@ class TokenLayoutState extends BaseDynamicState appLocalizations.copyTokenUri, onPressed: _processCopyUri, ), + FlutterContextMenuItem( + iconData: LucideIcons.share2, + appLocalizations.shareTokenUri, + onPressed: _processShareUri, + ), FlutterContextMenuItem( iconData: LucideIcons.copySlash, appLocalizations.copyNextTokenCode, @@ -246,31 +329,18 @@ class TokenLayoutState extends BaseDynamicState button: true, child: ContextMenuRegion( key: ValueKey("contextMenuRegion${widget.token.keyString}"), - enable: ResponsiveUtil.isDesktop(), + enable: ResponsiveUtil.isDesktop() && !widget.multiSelectMode, enableOnLongPress: false, contextMenu: _buildContextMenuButtons(), - child: Selector( - selector: (context, provider) => provider.dragToReorder, - builder: (context, dragToReorder, child) => - Selector( - selector: (context, provider) => - provider.issuerAndAccountShowOption, - builder: (context, issuerAndAccountShowOption, child) { - return GestureDetector( - onLongPress: dragToReorder && !ResponsiveUtil.isDesktop() - ? () { - showContextMenu(); - HapticFeedback.lightImpact(); - } - : null, - child: PressableAnimation( - child: _buildBody( - issuerAndAccountShowOption: issuerAndAccountShowOption, - ), - ), - ); - }, - ), + child: Selector( + selector: (context, provider) => provider.issuerAndAccountShowOption, + builder: (context, issuerAndAccountShowOption, child) { + return PressableAnimation( + child: _buildBody( + issuerAndAccountShowOption: issuerAndAccountShowOption, + ), + ); + }, ), ), ); @@ -282,9 +352,11 @@ class TokenLayoutState extends BaseDynamicState double startExtentRatio = 0.16, double endExtentRatio = 0.64, }) { + _slidableController ??= SlidableController(this); return Slidable( + controller: _slidableController, groupTag: "TokenLayout", - enabled: !ResponsiveUtil.isWideDevice(), + enabled: !widget.multiSelectMode && !ResponsiveUtil.isWideDevice(), startActionPane: ActionPane( extentRatio: startExtentRatio, motion: const ScrollMotion(), @@ -372,13 +444,14 @@ class TokenLayoutState extends BaseDynamicState _buildBody({ required IssuerAndAccountShowOption issuerAndAccountShowOption, }) { + Widget body; switch (widget.layoutType) { case LayoutType.Simple: - return _buildSimpleLayout( + body = _buildSimpleLayout( issuerAndAccountShowOption: issuerAndAccountShowOption, ); case LayoutType.Compact: - return _buildCompactLayout( + body = _buildCompactLayout( issuerAndAccountShowOption: issuerAndAccountShowOption, ); // case LayoutType.Tile: @@ -388,14 +461,14 @@ class TokenLayoutState extends BaseDynamicState // child: _buildTileLayout(), // ); case LayoutType.List: - return _buildSlidable( + body = _buildSlidable( simple: true, child: _buildListLayout( issuerAndAccountShowOption: issuerAndAccountShowOption, ), ); case LayoutType.Spotlight: - return _buildSlidable( + body = _buildSlidable( startExtentRatio: 0.21, endExtentRatio: 0.8, child: _buildSpotlightLayout( @@ -403,6 +476,51 @@ class TokenLayoutState extends BaseDynamicState ), ); } + if (widget.multiSelectMode) { + Widget result = Stack( + children: [ + body, + Positioned( + top: 6, + right: 6, + child: IgnorePointer( + child: Container( + decoration: BoxDecoration( + color: widget.isSelected + ? ChewieTheme.primaryColor + : ChewieTheme.cardColor, + shape: BoxShape.circle, + border: Border.all( + color: widget.isSelected + ? ChewieTheme.primaryColor + : Colors.grey, + width: 2, + ), + ), + padding: const EdgeInsets.all(2), + child: Icon( + Icons.check, + size: 14, + color: widget.isSelected ? Colors.white : Colors.transparent, + ), + ), + ), + ), + ], + ); + if (_wobbleController != null) { + return AnimatedBuilder( + animation: _wobbleAnimation, + builder: (context, child) => Transform.rotate( + angle: _wobbleAnimation.value, + child: child, + ), + child: result, + ); + } + return result; + } + return body; } showContextMenu() { @@ -486,6 +604,18 @@ class TokenLayoutState extends BaseDynamicState ); } + _processShareUri() { + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.copyUriClearWarningTitle, + message: appLocalizations.copyUriClearWarningTip, + onTapConfirm: () { + UriUtil.share(OtpTokenParser.toUri(widget.token).toString()); + }, + onTapCancel: () {}, + ); + } + _processResetCopyTimes() { DialogBuilder.showConfirmDialog( context, @@ -546,6 +676,7 @@ class TokenLayoutState extends BaseDynamicState selector: (context, provider) => provider.showEye, builder: (context, showEye, child) => showEye ? CircleIconButton( + tooltip: appLocalizations.toggleCodeVisibility, onTap: () { HapticFeedback.lightImpact(); tokenLayoutNotifier.codeVisiable = @@ -576,6 +707,7 @@ class TokenLayoutState extends BaseDynamicState tokenLayoutNotifier.haveToResetHOTP, builder: (context, haveToResetHOTP, child) => haveToResetHOTP ? CircleIconButton( + tooltip: appLocalizations.refreshHOTP, onTap: () { HapticFeedback.lightImpact(); widget.token.counterString = @@ -640,6 +772,7 @@ class TokenLayoutState extends BaseDynamicState constraints: const BoxConstraints(minHeight: 2, maxHeight: 2), child: LinearProgressIndicator( value: progress, + semanticsLabel: appLocalizations.tokenProgressLabel, minHeight: 2, color: progress > autoCopyNextCodeProgressThrehold ? ChewieTheme.primaryColor @@ -668,6 +801,7 @@ class TokenLayoutState extends BaseDynamicState builder: (context, value, child) { return CircularProgressIndicator( value: progressNotifier.value, + semanticsLabel: appLocalizations.tokenProgressLabel, color: progressNotifier.value > autoCopyNextCodeProgressThrehold ? ChewieTheme.primaryColor @@ -710,6 +844,10 @@ class TokenLayoutState extends BaseDynamicState } _processTap() { + if (widget.multiSelectMode) { + widget.onToggleSelect?.call(); + return; + } if (!appProvider.showEye) { tokenLayoutNotifier.codeVisiable = true; } @@ -888,13 +1026,15 @@ class TokenLayoutState extends BaseDynamicState if (isHOTP) _buildHOTPRefreshButton(padding: 4), if (!isHOTP) _buildEyeButton(padding: 4), CircleIconButton( + tooltip: appLocalizations.moreOptionShort, padding: const EdgeInsets.all(4), icon: Icon( LucideIcons.ellipsisVertical, color: ChewieTheme.labelSmall.color, size: 20, ), - onTap: showContextMenu, + onTap: + widget.multiSelectMode ? null : showContextMenu, ), ], ), diff --git a/lib/Screens/feature_showcase_screen.dart b/lib/Screens/feature_showcase_screen.dart new file mode 100644 index 00000000..3e191f58 --- /dev/null +++ b/lib/Screens/feature_showcase_screen.dart @@ -0,0 +1,1044 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../Utils/app_provider.dart'; +import '../l10n/l10n.dart'; + +class FeatureShowcaseScreen extends StatefulWidget { + final bool isDialog; + + const FeatureShowcaseScreen({super.key, this.isDialog = false}); + + static const String routeName = "/feature/showcase"; + + /// Show as a compact dialog suitable for landscape/desktop layouts. + static void showAsDialog(BuildContext context) { + DialogBuilder.showPageDialog( + context, + preferMinWidth: 460, + preferMinHeight: 640, + child: const FeatureShowcaseScreen(isDialog: true), + ); + } + + @override + State createState() => _FeatureShowcaseScreenState(); +} + +class _FeatureShowcaseScreenState + extends BaseDynamicState { + final PageController _pageController = PageController(); + int _currentPage = 0; + + // Page accent colors + static const Color _tokenColor = Color(0xFF4A6CF7); + static const Color _cloudColor = Color(0xFF42A5F5); + static const Color _iconColor = Color(0xFFFF7043); + static const Color _securityColor = Color(0xFF66BB6A); + static const Color _platformColor = Color(0xFF26C6DA); + static const Color _migrationColor = Color(0xFFAB47BC); + static const Color _convenienceColor = Color(0xFFFFA726); + static const Color _customizeColor = Color(0xFFEC407A); + + late final List<({Color color, Widget Function() build})> _pages = [ + (color: _tokenColor, build: _buildTokenPage), + (color: _platformColor, build: _buildPlatformPage), + (color: _cloudColor, build: _buildCloudPage), + (color: _iconColor, build: _buildIconPage), + (color: _securityColor, build: _buildSecurityPage), + (color: _migrationColor, build: _buildImportExportPage), + (color: _convenienceColor, build: _buildConveniencePage), + (color: _customizeColor, build: _buildCustomizePage), + ]; + + @override + void initState() { + super.initState(); + _pageController.addListener(() { + final page = _pageController.page?.round() ?? 0; + if (page != _currentPage) { + setState(() => _currentPage = page); + } + }); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final body = Column( + children: [ + Expanded( + child: PageView.builder( + controller: _pageController, + itemCount: _pages.length, + itemBuilder: (context, index) => _pages[index].build(), + ), + ), + const SizedBox(height: 12), + _buildPageIndicator(_pages.length), + const SizedBox(height: 16), + _buildStartTourButton(), + SizedBox(height: widget.isDialog + ? 16 + : MediaQuery.of(context).padding.bottom + 24), + ], + ); + + if (widget.isDialog) { + return body; + } + + return Scaffold( + backgroundColor: ChewieTheme.scaffoldBackgroundColor, + appBar: ResponsiveAppBar( + title: appLocalizations.featureShowcaseTitle, + showBack: true, + showBorder: false, + ), + body: body, + ); + } + + // ============================================================ + // Shared components + // ============================================================ + + Widget _buildHero({ + required IconData icon, + required Color color, + required String title, + required String subtitle, + }) { + return Column( + children: [ + Container( + width: 72, + height: 72, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + color.withAlpha(50), + color.withAlpha(15), + ], + ), + shape: BoxShape.circle, + border: Border.all(color: color.withAlpha(70), width: 1), + ), + child: Icon(icon, size: 34, color: color), + ), + const SizedBox(height: 14), + Text( + title, + style: ChewieTheme.titleLarge.copyWith( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 6), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + subtitle, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(170), + fontSize: 12.5, + height: 1.45, + ), + textAlign: TextAlign.center, + ), + ), + ], + ); + } + + Widget _buildChip({ + required String label, + required Color color, + IconData? icon, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 6), + decoration: BoxDecoration( + color: color.withAlpha(25), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withAlpha(60), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 12, color: color), + const SizedBox(width: 4), + ], + Text( + label, + style: TextStyle( + color: color, + fontSize: 11.5, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + /// Each page: hero + content centered together as one block. + Widget _buildPage({ + required Widget hero, + required Widget content, + }) { + return LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + hero, + const SizedBox(height: 20), + content, + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget _buildSectionLabel(IconData icon, String label, Color color) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 14, color: color), + const SizedBox(width: 5), + Text( + label, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ); + } + + // ============================================================ + // Page 1 — Multi-Token Support + // ============================================================ + + Widget _buildTokenPage() { + final types = [ + ('TOTP', appLocalizations.featureTokenTOTPDesc, LucideIcons.clock), + ('HOTP', appLocalizations.featureTokenHOTPDesc, LucideIcons.hash), + ('MOTP', appLocalizations.featureTokenMOTPDesc, LucideIcons.smartphone), + ('Steam', appLocalizations.featureTokenSteamDesc, LucideIcons.gamepad2), + ('Yandex', appLocalizations.featureTokenYandexDesc, LucideIcons.atSign), + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.keyRound, + color: _tokenColor, + title: appLocalizations.featureTokenTitle, + subtitle: appLocalizations.featureTokenDescription, + ), + content: Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: types + .map((t) => _buildTokenTypeCard(t.$1, t.$2, t.$3)) + .toList(), + ), + const SizedBox(height: 18), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + _buildChip(label: 'SHA1', color: _tokenColor), + _buildChip(label: 'SHA256', color: _tokenColor), + _buildChip(label: 'SHA512', color: _tokenColor), + _buildChip(label: '5–8 digits', color: _tokenColor), + ], + ), + ], + ), + ); + } + + Widget _buildTokenTypeCard(String name, String desc, IconData icon) { + return Container( + width: 92, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 6), + decoration: BoxDecoration( + color: _tokenColor.withAlpha(20), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: _tokenColor.withAlpha(50), width: 1), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 22, color: _tokenColor), + const SizedBox(height: 6), + Text( + name, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: ChewieTheme.titleLarge.color, + ), + ), + const SizedBox(height: 2), + Text( + desc, + style: TextStyle( + fontSize: 10.5, + color: ChewieTheme.bodyMedium.color?.withAlpha(160), + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + // ============================================================ + // Page 2 — Platforms + // ============================================================ + + Widget _buildPlatformPage() { + final platforms = [ + (LucideIcons.smartphone, 'Android'), + (LucideIcons.smartphone, 'iOS'), + (LucideIcons.monitor, 'Windows'), + (LucideIcons.laptop, 'macOS'), + (LucideIcons.monitor, 'Linux'), + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.layoutPanelTop, + color: _platformColor, + title: appLocalizations.featurePlatformTitle, + subtitle: appLocalizations.featurePlatformDescription, + ), + content: Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: platforms + .map((p) => _buildPlatformCard(p.$1, p.$2)) + .toList(), + ), + const SizedBox(height: 18), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + _buildChip( + label: appLocalizations.featurePlatformSync, + color: _platformColor, + icon: LucideIcons.refreshCw, + ), + _buildChip( + label: appLocalizations.featurePlatformResponsive, + color: _platformColor, + icon: LucideIcons.maximize2, + ), + ], + ), + ], + ), + ); + } + + Widget _buildPlatformCard(IconData icon, String name) { + return Container( + width: 78, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: _platformColor.withAlpha(20), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: _platformColor.withAlpha(50)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 24, color: _platformColor), + const SizedBox(height: 6), + Text( + name, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + ), + ], + ), + ); + } + + // ============================================================ + // Page 3 — Cloud Backup + // ============================================================ + + Widget _buildCloudPage() { + final services = [ + 'WebDAV', + 'OneDrive', + 'Google Drive', + 'Dropbox', + 'S3', + 'Huawei Cloud', + 'Box', + 'Aliyun Drive', + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.cloudUpload, + color: _cloudColor, + title: appLocalizations.featureCloudTitle, + subtitle: appLocalizations.featureCloudDescription, + ), + content: Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: services + .map((s) => _buildChip( + label: s, + color: _cloudColor, + icon: LucideIcons.cloud, + )) + .toList(), + ), + const SizedBox(height: 18), + Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + decoration: BoxDecoration( + color: _cloudColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: _cloudColor.withAlpha(50)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildBadge( + LucideIcons.refreshCw, + appLocalizations.featureCloudAuto, + _cloudColor), + _buildBadge( + LucideIcons.lock, + appLocalizations.featureCloudEncrypted, + _cloudColor), + _buildBadge( + LucideIcons.fileText, + appLocalizations.featureCloudLog, + _cloudColor), + ], + ), + ), + ], + ), + ); + } + + Widget _buildBadge(IconData icon, String label, Color color) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: color), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: color, + ), + ), + ], + ); + } + + // ============================================================ + // Page 4 — Smart Icons + // ============================================================ + + Widget _buildIconPage() { + final brands = [ + 'google.png', + 'github.png', + 'microsoft.png', + 'apple.png', + 'amazon.png', + 'discord.png', + 'steam.png', + 'slack.png', + 'dropbox.png', + 'paypal.png', + 'netflix.png', + 'spotify.png', + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.image, + color: _iconColor, + title: appLocalizations.featureIconTitle, + subtitle: appLocalizations.featureIconDescription(2500), + ), + content: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: GridView.count( + crossAxisCount: 4, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 1, + children: brands.map(_buildBrandIcon).toList(), + ), + ), + const SizedBox(height: 18), + _buildChip( + label: '2500+', + color: _iconColor, + icon: LucideIcons.sparkles, + ), + ], + ), + ); + } + + Widget _buildBrandIcon(String filename) { + return Container( + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: ChewieTheme.borderColor), + ), + padding: const EdgeInsets.all(8), + child: Image.asset( + 'assets/brand/$filename', + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => + Icon(LucideIcons.image, color: _iconColor, size: 18), + ), + ); + } + + // ============================================================ + // Page 5 — Security (+ Open Source) + // ============================================================ + + Widget _buildSecurityPage() { + final features = [ + (LucideIcons.databaseBackup, appLocalizations.featureSecurityEncryption), + (LucideIcons.fingerprint, appLocalizations.featureSecurityBiometric), + (LucideIcons.lockKeyhole, appLocalizations.featureSecurityGesture), + (LucideIcons.timer, appLocalizations.featureSecurityAutoLock), + (LucideIcons.eyeOff, appLocalizations.featureSecuritySafeMode), + (LucideIcons.shieldEllipsis, + appLocalizations.featureSecurityBackupEncrypt), + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.shieldCheck, + color: _securityColor, + title: appLocalizations.featureSecurityTitle, + subtitle: appLocalizations.featureSecurityDescription, + ), + content: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: GridView.count( + crossAxisCount: 2, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 3.4, + children: features + .map((f) => _buildSecurityItem(f.$1, f.$2)) + .toList(), + ), + ), + const SizedBox(height: 16), + // Open source highlight + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + _securityColor.withAlpha(40), + _securityColor.withAlpha(15), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: _securityColor.withAlpha(80)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(LucideIcons.github, size: 18, color: _securityColor), + const SizedBox(width: 8), + Flexible( + child: Text( + appLocalizations.featureSecurityOpenSource, + style: TextStyle( + color: _securityColor, + fontSize: 12.5, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + _buildChip( + label: 'AES-256-GCM', + color: _securityColor, + icon: LucideIcons.lock, + ), + _buildChip( + label: 'Argon2', + color: _securityColor, + icon: LucideIcons.key, + ), + ], + ), + ], + ), + ); + } + + Widget _buildSecurityItem(IconData icon, String label) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: _securityColor.withAlpha(18), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: _securityColor.withAlpha(45)), + ), + child: Row( + children: [ + Icon(icon, size: 16, color: _securityColor), + const SizedBox(width: 6), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 11.5, + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + + // ============================================================ + // Page 6 — Import & Export + // ============================================================ + + Widget _buildImportExportPage() { + final imports = [ + 'Google Auth', + 'Aegis', + '2FAS', + 'Bitwarden', + 'andOTP', + 'Ente Auth', + 'FreeOTP+', + 'TOTP Auth', + 'WinAuth', + ]; + final exports = [ + appLocalizations.featureExportEncrypted, + appLocalizations.featureExportUri, + appLocalizations.featureExportCloudOTPQR, + appLocalizations.featureExportGoogleQR, + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.arrowLeftRight, + color: _migrationColor, + title: appLocalizations.featureImportExportTitle, + subtitle: appLocalizations.featureImportExportDescription, + ), + content: Column( + children: [ + _buildSectionLabel( + LucideIcons.download, + appLocalizations.featureImportSection, + _migrationColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: imports + .map((s) => _buildChip(label: s, color: _migrationColor)) + .toList(), + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + _buildChip( + label: appLocalizations.featureImportScan, + color: _migrationColor, + icon: LucideIcons.scanLine, + ), + _buildChip( + label: appLocalizations.featureImportClipboard, + color: _migrationColor, + icon: LucideIcons.clipboard, + ), + ], + ), + const SizedBox(height: 16), + _buildSectionLabel( + LucideIcons.upload, + appLocalizations.featureExportSection, + _migrationColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: exports + .map((s) => _buildChip( + label: s, + color: _migrationColor, + icon: s.contains('QR') || + s.contains('二维') || + s.contains('二維') || + s.contains('QR') + ? LucideIcons.qrCode + : LucideIcons.fileText, + )) + .toList(), + ), + ], + ), + ); + } + + // ============================================================ + // Page 7 — Convenience + // ============================================================ + + Widget _buildConveniencePage() { + final features = [ + (LucideIcons.search, appLocalizations.featureQuickSearch), + (LucideIcons.arrowUpDown, appLocalizations.featureSort), + (LucideIcons.shapes, appLocalizations.featureCategories), + (LucideIcons.listChecks, appLocalizations.featureMultiSelect), + (LucideIcons.pin, appLocalizations.featurePinTop), + (LucideIcons.arrowLeftRight, appLocalizations.featureSwipe), + (LucideIcons.move, appLocalizations.featureDragReorder), + (LucideIcons.copy, appLocalizations.featureAutoCopy), + (LucideIcons.qrCode, appLocalizations.featureQRScanner), + ]; + return _buildPage( + hero: _buildHero( + icon: LucideIcons.zap, + color: _convenienceColor, + title: appLocalizations.featureConvenienceTitle, + subtitle: appLocalizations.featureConvenienceDescription, + ), + content: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 320), + child: GridView.count( + crossAxisCount: 3, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 1, + children: features + .map((f) => _buildConvenienceCard(f.$1, f.$2)) + .toList(), + ), + ), + ); + } + + Widget _buildConvenienceCard(IconData icon, String label) { + return Container( + decoration: BoxDecoration( + color: _convenienceColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: _convenienceColor.withAlpha(50)), + ), + padding: const EdgeInsets.all(6), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 22, color: _convenienceColor), + const SizedBox(height: 6), + Text( + label, + style: TextStyle( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ], + ), + ); + } + + // ============================================================ + // Page 8 — Customization (+ custom themes) + // ============================================================ + + Widget _buildCustomizePage() { + const themeColors = [ + Color(0xFF009BFF), + Color(0xFF3790A4), + Color(0xFFF588A8), + Color(0xFF11B667), + Color(0xFF454D66), + Color(0xFF272643), + Color(0xFFE74645), + Color(0xFF361D32), + Color(0xFFF8BE5F), + Color(0xFF0084FF), + ]; + + return _buildPage( + hero: _buildHero( + icon: LucideIcons.palette, + color: _customizeColor, + title: appLocalizations.featureCustomizeTitle, + subtitle: appLocalizations.featureCustomizeDescription, + ), + content: Column( + children: [ + _buildSectionLabel( + LucideIcons.droplet, + appLocalizations.featureThemeColors, + _customizeColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: themeColors + .map((c) => Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: c, + shape: BoxShape.circle, + border: Border.all( + color: ChewieTheme.borderColor, width: 1), + ), + )) + .toList(), + ), + const SizedBox(height: 6), + _buildChip( + label: appLocalizations.featureCustomThemeEditor, + color: _customizeColor, + icon: LucideIcons.pencilLine, + ), + const SizedBox(height: 14), + _buildSectionLabel( + LucideIcons.layoutGrid, + appLocalizations.featureLayoutStyles, + _customizeColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + _buildLayoutPreview( + LucideIcons.layoutGrid, appLocalizations.simpleLayoutType), + _buildLayoutPreview(LucideIcons.layoutDashboard, + appLocalizations.compactLayoutType), + _buildLayoutPreview(LucideIcons.layoutTemplate, + appLocalizations.spotlightLayoutType), + _buildLayoutPreview( + LucideIcons.layoutList, appLocalizations.listLayoutType), + ], + ), + const SizedBox(height: 14), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + _buildChip( + label: appLocalizations.featureGlassEffect, + color: _customizeColor, + icon: LucideIcons.sparkles), + _buildChip( + label: appLocalizations.featureCustomFont, + color: _customizeColor, + icon: LucideIcons.type), + _buildChip( + label: appLocalizations.featureMultiLang, + color: _customizeColor, + icon: LucideIcons.languages), + ], + ), + ], + ), + ); + } + + Widget _buildLayoutPreview(IconData icon, String label) { + return Container( + width: 58, + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: _customizeColor.withAlpha(20), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: _customizeColor.withAlpha(50)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: _customizeColor), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 9.5, + fontWeight: FontWeight.w600, + color: _customizeColor, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } + + // ============================================================ + // Indicators & Buttons + // ============================================================ + + Widget _buildPageIndicator(int total) { + final activeColor = _pages[_currentPage].color; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(total, (i) { + final isActive = i == _currentPage; + return AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: isActive ? 18 : 6, + height: 6, + margin: const EdgeInsets.symmetric(horizontal: 3), + decoration: BoxDecoration( + color: isActive + ? activeColor + : activeColor.withAlpha(50), + borderRadius: BorderRadius.circular(3), + ), + ); + }), + ); + } + + Widget _buildStartTourButton() { + if (!ResponsiveUtil.isMobile() || widget.isDialog) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + homeScreenState?.showCoachMark(); + }); + }, + style: TextButton.styleFrom( + backgroundColor: _pages[_currentPage].color, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + appLocalizations.featureShowcaseStartTour, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ); + } +} diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 1e986a1c..c78c993e 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -14,7 +14,7 @@ */ import 'dart:async'; -import 'dart:ui'; +import 'dart:ui' as ui; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/category_dao.dart'; @@ -22,6 +22,9 @@ import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:cloudotp/Models/opt_token.dart'; import 'package:cloudotp/Screens/Backup/cloud_service_screen.dart'; import 'package:cloudotp/Screens/Setting/about_setting_screen.dart'; +import 'package:cloudotp/Screens/feature_showcase_screen.dart'; +import 'package:cloudotp/Screens/layout_select_screen.dart'; +import 'package:cloudotp/Screens/sort_select_screen.dart'; import 'package:cloudotp/Screens/Setting/backup_log_screen.dart'; import 'package:cloudotp/Screens/Setting/mobile_setting_navigation_screen.dart'; import 'package:cloudotp/Screens/main_screen.dart'; @@ -37,8 +40,12 @@ import 'package:provider/provider.dart'; import '../Database/token_dao.dart'; import '../Models/token_category.dart'; +import '../TokenUtils/export_token_util.dart'; +import '../TokenUtils/otp_token_parser.dart'; import '../Utils/app_provider.dart'; +import '../Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart'; import '../Widgets/BottomSheet/select_token_bottom_sheet.dart'; +import '../Widgets/CoachMark/coach_mark_manager.dart'; import '../l10n/l10n.dart'; import 'Token/category_screen.dart'; import 'Token/token_layout.dart'; @@ -78,6 +85,16 @@ class HomeScreenState extends BasePanelScreenState GridItemsNotifier gridItemsNotifier = GridItemsNotifier(); final ValueNotifier _shownSearchbarNotifier = ValueNotifier(false); + bool _multiSelectMode = false; + final Set _selectedTokenUids = {}; + final GlobalKey _cardStackKey = GlobalKey(); + late AnimationController _pulseController; + final List _flyOverlays = []; + + final GlobalKey _appBarTitleKey = GlobalKey(); + final GlobalKey _moreButtonKey = GlobalKey(); + final GlobalKey _firstCategoryTabKey = GlobalKey(); + bool get hasSearchFocus => appProvider.searchFocusNode.hasFocus; String get currentCategoryUid { @@ -94,6 +111,591 @@ class HomeScreenState extends BasePanelScreenState bool get shouldCloseSearchBar => _shownSearchbarNotifier.value; + List get selectedTokens => + tokens.where((t) => _selectedTokenUids.contains(t.uid)).toList(); + + bool get _allSelectedPinned => + selectedTokens.isNotEmpty && selectedTokens.every((t) => t.pinned); + + void enterMultiSelectMode(String tokenUid) { + Offset? sourcePos; + Size? sourceSize; + CapturedThemes? capturedThemes; + OtpToken? token; + + final key = tokenKeyMap[tokenUid]; + if (key?.currentContext != null) { + final box = key!.currentContext!.findRenderObject() as RenderBox; + sourcePos = box.localToGlobal(Offset.zero); + sourceSize = box.size; + final overlayState = Overlay.of(context); + capturedThemes = InheritedTheme.capture( + from: key.currentContext!, + to: overlayState.context, + ); + token = tokens.firstWhere((t) => t.uid == tokenUid); + } + + setState(() { + _multiSelectMode = true; + _selectedTokenUids.clear(); + _selectedTokenUids.add(tokenUid); + }); + + if (sourcePos != null && sourceSize != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _animateFlyToCardStack( + sourcePos: sourcePos!, + sourceSize: sourceSize!, + capturedThemes: capturedThemes, + token: token, + ); + }); + } + } + + void exitMultiSelectMode() { + for (final entry in _flyOverlays) { + entry.remove(); + } + _flyOverlays.clear(); + setState(() { + _multiSelectMode = false; + _selectedTokenUids.clear(); + }); + } + + void toggleTokenSelection(String tokenUid) { + final isAdding = !_selectedTokenUids.contains(tokenUid); + + Offset? sourcePos; + Size? sourceSize; + CapturedThemes? capturedThemes; + OtpToken? token; + + if (isAdding) { + final key = tokenKeyMap[tokenUid]; + if (key?.currentContext != null) { + final box = key!.currentContext!.findRenderObject() as RenderBox; + sourcePos = box.localToGlobal(Offset.zero); + sourceSize = box.size; + final overlayState = Overlay.of(context); + capturedThemes = InheritedTheme.capture( + from: key.currentContext!, + to: overlayState.context, + ); + token = tokens.firstWhere((t) => t.uid == tokenUid); + } + } + + setState(() { + if (isAdding) { + _selectedTokenUids.add(tokenUid); + } else { + _selectedTokenUids.remove(tokenUid); + if (_selectedTokenUids.isEmpty) { + _multiSelectMode = false; + } + } + }); + + if (isAdding && sourcePos != null && sourceSize != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _animateFlyToCardStack( + sourcePos: sourcePos!, + sourceSize: sourceSize!, + capturedThemes: capturedThemes, + token: token, + ); + }); + } + } + + void selectAllTokens() { + setState(() { + if (_selectedTokenUids.length == tokens.length) { + _selectedTokenUids.clear(); + } else { + _selectedTokenUids.addAll(tokens.map((t) => t.uid)); + } + }); + } + + void _processMultiDelete() { + List selected = selectedTokens; + if (selected.isEmpty) return; + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.batchDeleteTitle, + message: appLocalizations.batchDeleteMessage(selected.length), + confirmButtonText: appLocalizations.confirm, + cancelButtonText: appLocalizations.cancel, + onTapConfirm: () async { + await TokenDao.deleteTokens(selected); + IToast.showTop(appLocalizations.batchDeleteSuccess(selected.length)); + exitMultiSelectMode(); + getTokens(); + }, + onTapCancel: () {}, + ); + } + + void _processMultiMoveCategory() { + List selected = selectedTokens; + if (selected.isEmpty) return; + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => SelectCategoryForTokensBottomSheet( + tokens: selected, + onCompleted: () { + exitMultiSelectMode(); + getTokens(); + }, + ), + ); + } + + void _processMultiExport() { + List selected = selectedTokens; + if (selected.isEmpty) return; + BottomSheetBuilder.showContextMenu( + context, + FlutterContextMenu( + entries: [ + FlutterContextMenuItem( + appLocalizations.exportToFile, + iconData: LucideIcons.fileOutput, + onPressed: () { + ExportTokenUtil.exportSelectedTokensUri(selected); + }, + ), + FlutterContextMenuItem( + appLocalizations.exportQrcode, + iconData: LucideIcons.qrCode, + onPressed: () async { + List? qrcodes = await ExportTokenUtil.exportToQrcodes( + selectedTokens: selected, + ); + if (qrcodes != null && qrcodes.isNotEmpty && mounted) { + CloudOTPItemBuilder.showQrcodesDialog( + context, + title: appLocalizations.multiSelectCount(selected.length), + qrcodes: qrcodes, + ); + } + }, + ), + FlutterContextMenuItem( + appLocalizations.exportGoogleAuthenticatorQrcode, + iconData: LucideIcons.qrCode, + onPressed: () async { + List? result = + await ExportTokenUtil.exportToGoogleAuthentcatorQrcodes( + selectedTokens: selected, + ); + if (result != null && mounted) { + List qrcodes = result[0] as List; + int passCount = result[1] as int; + if (qrcodes.isNotEmpty) { + CloudOTPItemBuilder.showQrcodesDialog( + context, + title: appLocalizations.multiSelectCount(selected.length), + qrcodes: qrcodes, + ); + } + if (passCount > 0) { + IToast.showTop(appLocalizations + .exportGoogleAuthenticatorNoCompatibleCount(passCount)); + } + } + }, + ), + FlutterContextMenuItem( + appLocalizations.shareTokenUri, + iconData: LucideIcons.share2, + onPressed: () { + ExportTokenUtil.shareSelectedTokensUri(selected); + }, + ), + ], + ), + ); + } + + Future _processMultiPin() async { + List selected = selectedTokens; + if (selected.isEmpty) return; + bool pinAll = !_allSelectedPinned; + for (OtpToken token in selected) { + await TokenDao.updateTokenPinned(token, pinAll); + } + IToast.showTop(pinAll + ? appLocalizations.alreadyPinnedSelectedTokens(selected.length) + : appLocalizations.alreadyUnPinnedSelectedTokens(selected.length)); + exitMultiSelectMode(); + getTokens(); + } + + Widget _buildMultiSelectDockContent() { + return Center( + key: const ValueKey('dock'), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + decoration: BoxDecoration( + color: ChewieTheme.appBarBackgroundColor.withAlpha(230), + borderRadius: BorderRadius.circular(12), + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildCardStack(), + _buildDockDivider(), + _buildDockItem( + icon: LucideIcons.x, + label: appLocalizations.cancel, + onTap: exitMultiSelectMode, + ), + _buildDockDivider(), + _buildDockItem( + icon: _selectedTokenUids.length == tokens.length + ? Icons.deselect + : Icons.select_all, + label: _selectedTokenUids.length == tokens.length + ? appLocalizations.deselectAll + : appLocalizations.selectAll, + onTap: selectAllTokens, + ), + _buildDockDivider(), + _buildDockItem( + icon: LucideIcons.shapes, + label: appLocalizations.category, + onTap: + _selectedTokenUids.isEmpty ? null : _processMultiMoveCategory, + ), + _buildDockItem( + icon: _allSelectedPinned ? LucideIcons.pinOff : LucideIcons.pin, + label: _allSelectedPinned + ? appLocalizations.unPinTokenShort + : appLocalizations.pinTokenShort, + onTap: _selectedTokenUids.isEmpty ? null : _processMultiPin, + ), + _buildDockItem( + icon: LucideIcons.fileOutput, + label: appLocalizations.export, + onTap: _selectedTokenUids.isEmpty ? null : _processMultiExport, + ), + _buildDockItem( + icon: LucideIcons.trash2, + label: appLocalizations.delete, + onTap: _selectedTokenUids.isEmpty ? null : _processMultiDelete, + color: _selectedTokenUids.isEmpty ? Colors.grey : Colors.red, + ), + ], + ), + ), + ); + } + + Widget _buildAnimatedDock() { + return Positioned( + left: 0, + right: 0, + bottom: MediaQuery.of(context).padding.bottom + 16, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (child, animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: _multiSelectMode + ? _buildMultiSelectDockContent() + : const SizedBox.shrink(key: ValueKey('empty')), + ), + ); + } + + Widget _buildDockItem({ + required IconData icon, + required String label, + VoidCallback? onTap, + Color? color, + int? badge, + }) { + final enabled = onTap != null; + Widget iconWidget = Icon( + icon, + color: enabled + ? (color ?? ChewieTheme.iconColor) + : Colors.grey.withAlpha(100), + size: 20, + ); + if (badge != null && badge > 0) { + iconWidget = Badge( + label: Text( + '$badge', + style: const TextStyle(fontSize: 10, color: Colors.white), + ), + backgroundColor: ChewieTheme.primaryColor, + child: iconWidget, + ); + } + return Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + child: ToolTipWrapper( + message: label, + position: TooltipPosition.top, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + clipBehavior: Clip.antiAlias, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: iconWidget, + ), + ), + ), + ), + ); + } + + Widget _buildDockDivider() { + return Container( + width: 1, + height: 24, + margin: const EdgeInsets.symmetric(horizontal: 4), + color: Colors.grey.withAlpha(60), + ); + } + + Widget _buildStackCard({ + required double rotation, + required Offset offset, + required int alpha, + bool isFront = false, + }) { + final primary = ChewieTheme.primaryColor; + return Transform.translate( + offset: offset, + child: Transform.rotate( + angle: rotation, + alignment: Alignment.bottomCenter, + child: Container( + width: 26, + height: 18, + decoration: BoxDecoration( + color: ChewieTheme.cardColor.withAlpha(alpha), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: isFront + ? primary.withAlpha(120) + : Colors.grey.withAlpha(60 + alpha ~/ 4), + width: isFront ? 1.0 : 0.5, + ), + boxShadow: isFront + ? [ + BoxShadow( + color: Colors.black.withAlpha(20), + blurRadius: 3, + offset: const Offset(0, 1), + ), + ] + : null, + ), + child: isFront + ? Column( + children: [ + Container( + height: 4, + margin: const EdgeInsets.fromLTRB(3, 3, 3, 0), + decoration: BoxDecoration( + color: primary.withAlpha(180), + borderRadius: BorderRadius.circular(1), + ), + ), + Container( + height: 2, + margin: const EdgeInsets.fromLTRB(3, 2, 8, 0), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(60), + borderRadius: BorderRadius.circular(1), + ), + ), + ], + ) + : null, + ), + ), + ); + } + + Widget _buildCardStack() { + return ScaleTransition( + scale: Tween(begin: 0.8, end: 1).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeOut), + ), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 6), + child: Badge( + label: Text( + '${_selectedTokenUids.length}', + style: const TextStyle( + fontSize: 9, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + backgroundColor: ChewieTheme.primaryColor, + child: SizedBox( + key: _cardStackKey, + width: 38, + height: 30, + child: Center( + child: SizedBox( + width: 34, + height: 26, + child: Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: _buildStackCard( + rotation: -0.15, + offset: const Offset(-2, 0), + alpha: 120, + ), + ), + Positioned.fill( + child: _buildStackCard( + rotation: 0.08, + offset: const Offset(2, -1), + alpha: 180, + ), + ), + Positioned.fill( + child: _buildStackCard( + rotation: 0, + offset: const Offset(0, 0), + alpha: 255, + isFront: true, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + void _animateFlyToCardStack({ + required Offset sourcePos, + required Size sourceSize, + CapturedThemes? capturedThemes, + OtpToken? token, + }) { + final stackContext = _cardStackKey.currentContext; + if (stackContext == null) return; + + final targetBox = stackContext.findRenderObject() as RenderBox; + final targetPos = targetBox.localToGlobal(Offset.zero); + final targetSize = targetBox.size; + + final controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 400), + ); + + final curved = CurvedAnimation( + parent: controller, + curve: Curves.easeInOut, + ); + + Widget flyChild; + if (token != null && capturedThemes != null) { + flyChild = capturedThemes.wrap( + TokenLayout( + token: token, + layoutType: layoutType, + ), + ); + } else { + flyChild = Container( + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + ), + ); + } + + late OverlayEntry entry; + entry = OverlayEntry( + builder: (context) => AnimatedBuilder( + animation: curved, + builder: (context, child) { + final t = curved.value; + final left = ui.lerpDouble(sourcePos.dx, targetPos.dx, t)!; + final top = ui.lerpDouble(sourcePos.dy, targetPos.dy, t)!; + final width = ui.lerpDouble(sourceSize.width, targetSize.width, t)!; + final height = + ui.lerpDouble(sourceSize.height, targetSize.height, t)!; + final opacity = ui.lerpDouble(1.0, 0.3, t)!; + + return Positioned( + left: left, + top: top, + child: IgnorePointer( + child: Opacity( + opacity: opacity, + child: SizedBox( + width: width, + height: height, + child: FittedBox( + fit: BoxFit.fill, + child: SizedBox( + width: sourceSize.width, + height: sourceSize.height, + child: child, + ), + ), + ), + ), + ), + ); + }, + child: flyChild, + ), + ); + + Overlay.of(context).insert(entry); + _flyOverlays.add(entry); + + controller.forward().then((_) { + entry.remove(); + _flyOverlays.remove(entry); + controller.dispose(); + if (mounted) { + _pulseController.forward(from: 0); + } + }); + } + @override void initState() { super.initState(); @@ -107,6 +709,10 @@ class HomeScreenState extends BasePanelScreenState value: 1, duration: const Duration(milliseconds: 300), ); + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (!ResponsiveUtil.isLandscapeLayout() && ChewieHiveUtil.getBool(CloudOTPHiveUtil.autoFocusSearchBarKey, @@ -116,6 +722,17 @@ class HomeScreenState extends BasePanelScreenState }); } + @override + void dispose() { + _animationController.dispose(); + _pulseController.dispose(); + for (final entry in _flyOverlays) { + entry.remove(); + } + _flyOverlays.clear(); + super.dispose(); + } + insertToken( OtpToken token, { bool forceAll = false, @@ -236,9 +853,90 @@ class HomeScreenState extends BasePanelScreenState final currentUids = seen; tokenKeyMap.removeWhere((uid, _) => !currentUids.contains(uid)); performSort(); + _maybeShowCoachMark(); + }); + } + + void _maybeShowCoachMark() { + if (!ResponsiveUtil.isMobile()) return; + if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownCoachMarkKey, + defaultValue: false)) return; + if (tokens.isEmpty) return; + if (_multiSelectMode || _shownSearchbarNotifier.value) return; + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _showCoachMarkInternal(force: false); }); } + void showCoachMark() { + _showCoachMarkInternal(force: true); + } + + void _showCoachMarkInternal({required bool force}) { + CoachMarkManager( + context: context, + appBarTitleKey: _appBarTitleKey, + firstTokenKey: tokenKeyMap.isNotEmpty ? tokenKeyMap.values.first : null, + categoryTabKey: categories.isNotEmpty ? _firstCategoryTabKey : null, + moreButtonKey: _moreButtonKey, + layoutType: layoutType, + tokenCount: tokens.length, + categoryCount: categories.length, + onDemoAction: _performCoachDemo, + onUndoDemoAction: _undoCoachDemo, + ).show(force: force); + } + + Future _performCoachDemo(String identify) async { + switch (identify) { + case 'search_title': + changeSearchBar(true); + break; + case 'swipe_token': + final key = tokenKeyMap.values.firstOrNull; + await key?.currentState?.openEndActionPane(); + break; + case 'category_tab': + if (categories.isNotEmpty) { + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (ctx) => SelectTokenBottomSheet(category: categories.first), + ); + } + break; + case 'more_menu': + if (tokens.isNotEmpty) { + setState(() { + _multiSelectMode = true; + _selectedTokenUids.clear(); + _selectedTokenUids.add(tokens.first.uid); + }); + } + break; + } + } + + Future _undoCoachDemo(String identify) async { + switch (identify) { + case 'search_title': + changeSearchBar(false); + break; + case 'swipe_token': + final key = tokenKeyMap.values.firstOrNull; + await key?.currentState?.closeSlidable(); + break; + case 'category_tab': + if (Navigator.of(context).canPop()) Navigator.of(context).pop(); + break; + case 'more_menu': + exitMultiSelectMode(); + break; + } + } + getCategories([bool isInit = false]) async { String oldUid = currentCategoryUid; await CategoryDao.listCategories().then((value) async { @@ -304,31 +1002,45 @@ class HomeScreenState extends BasePanelScreenState portrait: null, ) as PreferredSizeWidget?, body: ResponsiveUtil.selectByOrientation( - landscape: Column( - crossAxisAlignment: CrossAxisAlignment.start, + landscape: Stack( children: [ - _buildTabBar(), - Expanded(child: _buildMainContent()), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTabBar(), + Expanded(child: _buildMainContent()), + ], + ), + _buildAnimatedDock(), ], ), portrait: PopScope( canPop: false, onPopInvokedWithResult: (_, __) { - if (mounted && _shownSearchbarNotifier.value) { + if (_multiSelectMode) { + exitMultiSelectMode(); + } else if (mounted && _shownSearchbarNotifier.value) { changeSearchBar(false); } else { MoveToBackground.moveTaskToBack(); } }, - child: _buildMobileBody(), + child: Stack( + children: [ + _buildMobileBody(), + _buildAnimatedDock(), + ], + ), ), ), bottomNavigationBar: ResponsiveUtil.selectByPlatform( mobile: _buildMobileBottombar(), ), - floatingActionButton: ResponsiveUtil.selectByPlatform( - mobile: _buildFloatingActionButton(), - ), + floatingActionButton: _multiSelectMode + ? null + : ResponsiveUtil.selectByPlatform( + mobile: _buildFloatingActionButton(), + ), floatingActionButtonLocation: FloatingActionButtonLocation.endContained, extendBody: true, ); @@ -403,6 +1115,7 @@ class HomeScreenState extends BasePanelScreenState Container( margin: const EdgeInsets.only(right: 5), child: CircleIconButton( + tooltip: appLocalizations.backupLogs, padding: EdgeInsets.zero, icon: Selector( selector: (context, appProvider) => @@ -414,7 +1127,7 @@ class HomeScreenState extends BasePanelScreenState ), ), onTap: () { - RouteUtil.pushCupertinoRoute(context, const BackupLogScreen()); + BackupLogScreen.show(context); }, ), ), @@ -422,6 +1135,7 @@ class HomeScreenState extends BasePanelScreenState Container( margin: const EdgeInsets.only(right: 5), child: CircleIconButton( + tooltip: appLocalizations.cloudBackupServiceSetting, icon: Icon( LucideIcons.cloud, color: ChewieTheme.iconColor, @@ -438,10 +1152,7 @@ class HomeScreenState extends BasePanelScreenState context: context, icon: layoutType.icon, onPressed: () { - BottomSheetBuilder.showContextMenu( - context, - MainScreenState.buildLayoutContextMenuButtons(), - ); + LayoutSelectScreen.show(context); }, ), ), @@ -452,14 +1163,12 @@ class HomeScreenState extends BasePanelScreenState context: context, icon: orderType.icon, onPressed: () { - BottomSheetBuilder.showContextMenu( - context, - MainScreenState.buildSortContextMenuButtons(), - ); + SortSelectScreen.show(context); }, ), ), ToolButton( + key: _moreButtonKey, context: context, icon: LucideIcons.ellipsisVertical, onPressed: () { @@ -467,6 +1176,14 @@ class HomeScreenState extends BasePanelScreenState context, FlutterContextMenu( entries: [ + if (tokens.length > 1) + FlutterContextMenuItem( + appLocalizations.select, + iconData: LucideIcons.listChecks, + onPressed: () { + enterMultiSelectMode(tokens.first.uid); + }, + ), FlutterContextMenuItem( appLocalizations.category, iconData: LucideIcons.shapes, @@ -491,6 +1208,18 @@ class HomeScreenState extends BasePanelScreenState context, const AboutSettingScreen()); }, ), + FlutterContextMenuItem( + appLocalizations.featureShowcase, + iconData: LucideIcons.telescope, + onPressed: () { + if (ResponsiveUtil.isLandscapeLayout()) { + FeatureShowcaseScreen.showAsDialog(context); + } else { + RouteUtil.pushCupertinoRoute( + context, const FeatureShowcaseScreen()); + } + }, + ), ], ), ); @@ -501,6 +1230,23 @@ class HomeScreenState extends BasePanelScreenState } _buildMobileAppbar() { + if (_multiSelectMode) { + return SliverAppBar( + floating: true, + pinned: true, + elevation: 0, + scrolledUnderElevation: 0, + backgroundColor: ChewieTheme.scaffoldBackgroundColor, + leading: IconButton( + icon: Icon(LucideIcons.x, color: ChewieTheme.iconColor), + onPressed: exitMultiSelectMode, + ), + title: Text( + appLocalizations.multiSelectCount(_selectedTokenUids.length), + style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + ), + ); + } return Consumer( builder: (context, provider, child) => ValueListenableBuilder( valueListenable: _shownSearchbarNotifier, @@ -521,6 +1267,7 @@ class HomeScreenState extends BasePanelScreenState return Align( alignment: Alignment.centerLeft, child: GestureDetector( + key: _appBarTitleKey, onTap: () { if (!_shownSearchbarNotifier.value) { changeSearchBar(true); @@ -542,6 +1289,7 @@ class HomeScreenState extends BasePanelScreenState crossAxisAlignment: CrossAxisAlignment.center, children: [ CircleIconButton( + tooltip: appLocalizations.cancel, icon: Icon( Icons.arrow_back_rounded, color: ChewieTheme.iconColor, @@ -587,6 +1335,9 @@ class HomeScreenState extends BasePanelScreenState } _buildMobileBottombar({double verticalPadding = 10}) { + if (_multiSelectMode) { + return const SizedBox.shrink(); + } double height = kToolbarHeight + verticalPadding * 2 + (ResponsiveUtil.isLandscapeTablet() ? 24 : 0); @@ -632,7 +1383,7 @@ class HomeScreenState extends BasePanelScreenState : enableFrostedGlassEffect ? ClipRRect( child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: container, ), ) @@ -644,91 +1395,87 @@ class HomeScreenState extends BasePanelScreenState } _buildMainContent() { - Widget gridView = Selector( - selector: (context, provider) => provider.dragToReorder, - builder: (context, dragToReorder, child) => Selector( - selector: (context, provider) => provider.hideBottombarWhenScrolling, - builder: (context, hideBottombarWhenScrolling, child) => - Selector( - selector: (context, provider) => provider.hideProgressBar, - builder: (context, hideProgressBar, child) => - ReorderableGridView.builder( - // controller: _scrollController, - gridItemsNotifier: gridItemsNotifier, - autoScroll: true, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - left: 10, - right: 10, - top: 10, - bottom: - hideBottombarWhenScrolling || categories.isEmpty ? 10 : 85), - gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: layoutType.maxCrossAxisExtent, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - preferredHeight: layoutType.getHeight(hideProgressBar), - ), - dragToReorder: dragToReorder, - cacheExtent: 9999, - // itemDragEnable: (index) { - // if (tokens[index].pinnedInt == 1) { - // return false; - // } - // return true; - // }, - onReorderStart: (_) { - _fabScrollToHideController.hide(); - _bottombarScrollToHideController.hide(); - }, - onReorderEnd: (_, __) { - _fabScrollToHideController.show(); - _bottombarScrollToHideController.show(); - }, - onReorder: (int oldIndex, int newIndex) async { - final selectedToken = tokens[oldIndex]; - int pinnedCount = tokens.where((e) => e.pinned).length; - if (selectedToken.pinned) { - if (newIndex >= pinnedCount) newIndex = pinnedCount - 1; - } else { - if (newIndex < pinnedCount) newIndex = pinnedCount; - } - final item = tokens.removeAt(oldIndex); - tokens.insert(newIndex, item); - for (int i = 0; i < tokens.length; i++) { - tokens[i].seq = tokens.length - i; - } - await TokenDao.updateTokens(tokens, autoBackup: false); - changeOrderType(type: OrderType.Default, doPerformSort: false); - }, - proxyDecorator: - (Widget child, int index, Animation animation) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: ChewieTheme.shadowColor, - offset: const Offset(0, 4), - blurRadius: 10, - spreadRadius: 1, - ).scale(2) - ], - ), - child: child, - ); - }, - itemCount: tokens.length, - itemBuilder: (context, index) { - return TokenLayout( - key: tokenKeyMap.putIfAbsent( - tokens[index].uid, () => GlobalKey()), - token: tokens[index], - layoutType: layoutType, - ); - }, - ), + Widget gridView = Selector( + selector: (context, provider) => ( + dragToReorder: provider.dragToReorder, + hideBottombar: provider.hideBottombarWhenScrolling, + hideProgress: provider.hideProgressBar, + ), + builder: (context, settings, child) => ReorderableGridView.builder( + // controller: _scrollController, + gridItemsNotifier: gridItemsNotifier, + autoScroll: true, + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only( + left: 10, + right: 10, + top: 10, + bottom: _multiSelectMode + ? 80 + : settings.hideBottombar || categories.isEmpty + ? 10 + : 85), + gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: layoutType.maxCrossAxisExtent, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + preferredHeight: layoutType.getHeight(settings.hideProgress), ), + dragToReorder: _multiSelectMode ? false : settings.dragToReorder, + cacheExtent: 9999, + onReorderStart: (_) { + _fabScrollToHideController.hide(); + _bottombarScrollToHideController.hide(); + }, + onReorderEnd: (_, __) { + _fabScrollToHideController.show(); + _bottombarScrollToHideController.show(); + }, + onReorder: (int oldIndex, int newIndex) async { + final selectedToken = tokens[oldIndex]; + int pinnedCount = tokens.where((e) => e.pinned).length; + if (selectedToken.pinned) { + if (newIndex >= pinnedCount) newIndex = pinnedCount - 1; + } else { + if (newIndex < pinnedCount) newIndex = pinnedCount; + } + final item = tokens.removeAt(oldIndex); + tokens.insert(newIndex, item); + for (int i = 0; i < tokens.length; i++) { + tokens[i].seq = tokens.length - i; + } + await TokenDao.updateTokens(tokens, autoBackup: false); + changeOrderType(type: OrderType.Default, doPerformSort: false); + }, + proxyDecorator: (Widget child, int index, Animation animation) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: ChewieTheme.shadowColor, + offset: const Offset(0, 4), + blurRadius: 10, + spreadRadius: 1, + ).scale(2) + ], + ), + child: child, + ); + }, + itemCount: tokens.length, + itemBuilder: (context, index) { + return TokenLayout( + key: tokenKeyMap.putIfAbsent(tokens[index].uid, () => GlobalKey()), + token: tokens[index], + layoutType: layoutType, + multiSelectMode: _multiSelectMode, + isSelected: _selectedTokenUids.contains(tokens[index].uid), + onToggleSelect: () => toggleTokenSelection(tokens[index].uid), + onEnterMultiSelect: () => enterMultiSelectMode(tokens[index].uid), + ); + }, ), ); Widget body = tokens.isEmpty @@ -761,6 +1508,7 @@ class HomeScreenState extends BasePanelScreenState unselectedLabelStyle: ChewieTheme.titleMedium.apply(color: Colors.grey), indicator: UnderlinedTabIndicator(borderColor: ChewieTheme.primaryColor), onTap: (index) { + if (_multiSelectMode) exitMultiSelectMode(); if (_nestScrollController.hasClients) { _nestScrollController.animateTo(0, duration: const Duration(milliseconds: 300), @@ -778,6 +1526,9 @@ class HomeScreenState extends BasePanelScreenState child: ContextMenuRegion( contextMenu: _buildTabContextMenuButtons(category), child: GestureDetector( + key: (category != null && categories.indexOf(category) == 0) + ? _firstCategoryTabKey + : null, onLongPress: () { if (category != null) { HapticFeedback.lightImpact(); @@ -952,6 +1703,7 @@ class HomeScreenState extends BasePanelScreenState } performSearch(String searchKey) { + if (_multiSelectMode) exitMultiSelectMode(); _searchKey = searchKey; getTokens(); } diff --git a/lib/Screens/layout_select_screen.dart b/lib/Screens/layout_select_screen.dart new file mode 100644 index 00000000..4a3cb017 --- /dev/null +++ b/lib/Screens/layout_select_screen.dart @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'dart:math'; + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../Utils/app_provider.dart'; +import '../l10n/l10n.dart'; +import 'home_screen.dart'; + +class LayoutSelectScreen extends StatefulWidget { + final bool isOverlay; + + const LayoutSelectScreen({super.key, this.isOverlay = false}); + + static void show(BuildContext context) { + if (ResponsiveUtil.isLandscapeLayout()) { + BottomSheetBuilder.showGenericContextMenu( + context, + const LayoutSelectScreen(isOverlay: true), + ); + } else { + BottomSheetBuilder.showBottomSheet( + context, + (ctx) => const LayoutSelectScreen(), + responsive: true, + ); + } + } + + @override + State createState() => _LayoutSelectScreenState(); +} + +class _LayoutSelectScreenState extends BaseDynamicState { + Color get _accent => ChewieTheme.primaryColor; + + Radius get _radius => ChewieDimens.defaultRadius; + + @override + Widget build(BuildContext context) { + final currentType = homeScreenState?.layoutType ?? LayoutType.Simple; + + final content = Padding( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(), + const SizedBox(height: 14), + _buildGrid(currentType), + ], + ), + ); + + if (widget.isOverlay) { + return Container( + width: min(300, MediaQuery.sizeOf(context).width - 80), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + borderRadius: ChewieDimens.borderRadius8, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: content, + ); + } + + return Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: _radius, + bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, + ), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: content, + ), + ], + ); + } + + Widget _buildHeader() { + return Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: _accent.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.layoutGrid, color: _accent, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appLocalizations.layoutType, + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 1), + Text( + appLocalizations.layoutTypeSubtitle, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildGrid(LayoutType currentType) { + final values = LayoutType.values; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: _buildLayoutCard(values[0], values[0] == currentType)), + const SizedBox(width: 10), + Expanded( + child: _buildLayoutCard(values[1], values[1] == currentType)), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: _buildLayoutCard(values[2], values[2] == currentType)), + const SizedBox(width: 10), + Expanded( + child: _buildLayoutCard(values[3], values[3] == currentType)), + ], + ), + ], + ); + } + + Widget _buildLayoutCard(LayoutType type, bool selected) { + return GestureDetector( + onTap: () { + homeScreenState?.changeLayoutType(type); + setState(() {}); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: selected ? _accent.withAlpha(22) : ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: selected ? _accent : ChewieTheme.borderColor, + width: selected ? 1.5 : 1, + ), + ), + padding: const EdgeInsets.all(10), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1.6, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor.withAlpha(160), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(6), + child: _buildPreview(type), + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon(type.icon, + size: 14, + color: selected + ? _accent + : ChewieTheme.titleLarge.color?.withAlpha(180)), + const SizedBox(width: 5), + Expanded( + child: Text( + type.title, + style: ChewieTheme.labelMedium.copyWith( + fontWeight: FontWeight.w600, + color: + selected ? _accent : ChewieTheme.titleLarge.color, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (selected) + Icon(LucideIcons.check, size: 14, color: _accent), + ], + ), + ], + ), + ), + ); + } + + Widget _buildPreview(LayoutType type) { + switch (type) { + case LayoutType.Simple: + return _gridPreview(crossAxis: 2, rows: 2, itemHeight: 12); + case LayoutType.Compact: + return _gridPreview(crossAxis: 2, rows: 3, itemHeight: 8); + case LayoutType.Spotlight: + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _previewBar(height: 18, widthFactor: 0.85), + const SizedBox(height: 3), + _previewBar(height: 12, widthFactor: 0.85), + ], + ); + case LayoutType.List: + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 4, + (_) => Padding( + padding: const EdgeInsets.symmetric(vertical: 1.5), + child: _previewBar(height: 5, widthFactor: 1), + ), + ), + ); + } + } + + Widget _gridPreview({ + required int crossAxis, + required int rows, + required double itemHeight, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(rows, (r) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 1.5), + child: Row( + children: List.generate(crossAxis, (c) { + return Expanded( + child: Container( + margin: EdgeInsets.only(right: c == crossAxis - 1 ? 0 : 3), + height: itemHeight, + decoration: BoxDecoration( + color: _accent.withAlpha(70), + borderRadius: BorderRadius.circular(3), + ), + ), + ); + }), + ), + ); + }), + ); + } + + Widget _previewBar({required double height, required double widthFactor}) { + return FractionallySizedBox( + widthFactor: widthFactor, + child: Container( + height: height, + decoration: BoxDecoration( + color: _accent.withAlpha(70), + borderRadius: BorderRadius.circular(3), + ), + ), + ); + } +} diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index afed9bf2..90911a35 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -21,6 +21,8 @@ import 'package:cloudotp/Database/database_manager.dart'; import 'package:cloudotp/Screens/Token/add_token_screen.dart'; import 'package:cloudotp/Screens/Token/import_export_token_screen.dart'; import 'package:cloudotp/Screens/home_screen.dart'; +import 'package:cloudotp/Screens/layout_select_screen.dart'; +import 'package:cloudotp/Screens/sort_select_screen.dart'; import 'package:cloudotp/Utils/shortcuts_util.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -1138,8 +1140,7 @@ class MainScreenState extends BaseWindowState ), ), onPressed: () { - BottomSheetBuilder.showGenericContextMenu( - context, const BackupLogScreen(isOverlay: true)); + BackupLogScreen.show(context); }, ), const SizedBox(height: 4), @@ -1154,8 +1155,7 @@ class MainScreenState extends BaseWindowState padding: const EdgeInsets.all(8), iconSize: 22, onPressed: () { - BottomSheetBuilder.showContextMenu( - context, buildSortContextMenuButtons()); + SortSelectScreen.show(context); }, ), const SizedBox(height: 4), @@ -1170,8 +1170,7 @@ class MainScreenState extends BaseWindowState padding: const EdgeInsets.all(8), iconSize: 22, onPressed: () { - BottomSheetBuilder.showContextMenu( - context, buildLayoutContextMenuButtons()); + LayoutSelectScreen.show(context); }, ), const SizedBox(height: 4), diff --git a/lib/Screens/sort_select_screen.dart b/lib/Screens/sort_select_screen.dart new file mode 100644 index 00000000..23379c2e --- /dev/null +++ b/lib/Screens/sort_select_screen.dart @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'dart:math'; + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../Utils/app_provider.dart'; +import '../l10n/l10n.dart'; +import 'home_screen.dart'; + +class SortSelectScreen extends StatefulWidget { + final bool isOverlay; + + const SortSelectScreen({super.key, this.isOverlay = false}); + + static void show(BuildContext context) { + if (ResponsiveUtil.isLandscapeLayout()) { + BottomSheetBuilder.showGenericContextMenu( + context, + const SortSelectScreen(isOverlay: true), + ); + } else { + BottomSheetBuilder.showBottomSheet( + context, + (ctx) => const SortSelectScreen(), + responsive: true, + ); + } + } + + @override + State createState() => _SortSelectScreenState(); +} + +class _SortSelectScreenState extends BaseDynamicState { + Color get _accent => ChewieTheme.primaryColor; + + Radius get _radius => ChewieDimens.defaultRadius; + + List<({String label, List types})> _groups() => [ + ( + label: appLocalizations.sortGroupDefault, + types: [OrderType.Default], + ), + ( + label: appLocalizations.sortGroupAlphabetical, + types: [ + OrderType.AlphabeticalASC, + OrderType.AlphabeticalDESC, + ], + ), + ( + label: appLocalizations.sortGroupCopyTimes, + types: [ + OrderType.CopyTimesDESC, + OrderType.CopyTimesASC, + ], + ), + ( + label: appLocalizations.sortGroupLastCopy, + types: [ + OrderType.LastCopyTimeDESC, + OrderType.LastCopyTimeASC, + ], + ), + ( + label: appLocalizations.sortGroupCreate, + types: [ + OrderType.CreateTimeDESC, + OrderType.CreateTimeASC, + ], + ), + ]; + + @override + Widget build(BuildContext context) { + final groups = _groups(); + + Widget header = Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: _buildHeader(), + ); + + if (widget.isOverlay) { + final leftGroups = groups.sublist(0, 3); + final rightGroups = groups.sublist(3); + return Container( + width: min(480, MediaQuery.sizeOf(context).width - 80), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + borderRadius: ChewieDimens.borderRadius8, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + header, + Padding( + padding: const EdgeInsets.fromLTRB(14, 0, 14, 14), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildGroupColumn(leftGroups)), + const SizedBox(width: 10), + Expanded(child: _buildGroupColumn(rightGroups)), + ], + ), + ), + ], + ), + ); + } + + Widget body = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final g in groups) + Padding( + padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), + child: _buildGroup(g), + ), + const SizedBox(height: 14), + ], + ); + + return Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: _radius, + bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, + ), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [header, body], + ), + ), + ], + ); + } + + Widget _buildGroupColumn( + List<({String label, List types})> groups) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final g in groups) + Padding( + padding: const EdgeInsets.only(top: 6), + child: _buildGroup(g), + ), + ], + ); + } + + Widget _buildHeader() { + return Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: _accent.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.arrowUpDown, color: _accent, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appLocalizations.sortType, + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 1), + Text( + appLocalizations.sortTypeSubtitle, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildGroup(({String label, List types}) group) { + final currentType = homeScreenState?.orderType; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 4, bottom: 4), + child: Text( + group.label, + style: ChewieTheme.labelSmall.copyWith( + fontWeight: FontWeight.w600, + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), + ), + ), + Container( + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: ChewieTheme.borderColor), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < group.types.length; i++) ...[ + if (i > 0) + Divider( + height: 1, + indent: 44, + color: ChewieTheme.borderColor.withAlpha(120), + ), + _buildSortItem( + group.types[i], group.types[i] == currentType), + ], + ], + ), + ), + ], + ); + } + + Widget _buildSortItem(OrderType type, bool selected) { + return GestureDetector( + onTap: () { + homeScreenState?.changeOrderType(type: type); + setState(() {}); + }, + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 9), + child: Row( + children: [ + Container( + width: 26, + height: 26, + decoration: BoxDecoration( + color: selected + ? _accent.withAlpha(30) + : ChewieTheme.scaffoldBackgroundColor.withAlpha(150), + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + type.icon, + size: 14, + color: selected + ? _accent + : ChewieTheme.titleLarge.color?.withAlpha(180), + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + type.title, + style: ChewieTheme.bodyMedium.copyWith( + fontWeight: selected ? FontWeight.w600 : FontWeight.w500, + color: + selected ? _accent : ChewieTheme.titleLarge.color, + ), + ), + ), + if (selected) + Icon(LucideIcons.check, size: 16, color: _accent), + ], + ), + ), + ); + } +} diff --git a/lib/TokenUtils/export_token_util.dart b/lib/TokenUtils/export_token_util.dart index b715bdae..d030491d 100644 --- a/lib/TokenUtils/export_token_util.dart +++ b/lib/TokenUtils/export_token_util.dart @@ -105,6 +105,39 @@ class ExportTokenUtil { } } + static exportSelectedTokensUri(List tokens) async { + if (tokens.isEmpty) return; + CustomLoadingDialog.showLoading(title: appLocalizations.exporting); + String content = await compute((_) async { + List uris = + tokens.map((e) => OtpTokenParser.toUri(e).toString()).toList(); + return uris.join("\n"); + }, null); + String? filePath = await FileUtil.saveFile( + dialogTitle: appLocalizations.exportUriFileTitle, + fileName: ExportTokenUtil.getExportFileName("txt"), + type: FileType.custom, + allowedExtensions: ['txt'], + ); + if (filePath != null) { + File(filePath).writeAsStringSync(content); + } + CustomLoadingDialog.dismissLoading(); + if (filePath != null) { + IToast.showTop(appLocalizations.exportSuccess); + } + } + + static String getTokensUriString(List tokens) { + return tokens.map((e) => OtpTokenParser.toUri(e).toString()).join("\n"); + } + + static shareSelectedTokensUri(List tokens) { + if (tokens.isEmpty) return; + String content = getTokensUriString(tokens); + UriUtil.share(content); + } + static Future getUint8List({ String? password, }) async { @@ -515,6 +548,7 @@ class ExportTokenUtil { static Future?> exportToGoogleAuthentcatorQrcodes({ bool showLoading = true, + List? selectedTokens, }) async { if (showLoading) { CustomLoadingDialog.showLoading(title: appLocalizations.exporting); @@ -523,7 +557,7 @@ class ExportTokenUtil { int passCount = 0; List payloads = []; try { - List tokens = await TokenDao.listTokens(); + List tokens = selectedTokens ?? await TokenDao.listTokens(); OtpMigrationPayload payload = OtpMigrationPayload.create(); String preRes = ""; for (OtpToken token in tokens) { @@ -568,6 +602,7 @@ class ExportTokenUtil { static Future?> exportToQrcodes({ bool showLoading = true, + List? selectedTokens, }) async { if (showLoading) { CustomLoadingDialog.showLoading(title: appLocalizations.exporting); @@ -578,7 +613,7 @@ class ExportTokenUtil { int batchId = Random().nextInt(1000000000) * -1; try { //Tokens - List tokens = await TokenDao.listTokens(); + List tokens = selectedTokens ?? await TokenDao.listTokens(); CloudOtpTokenPayload payload = CloudOtpTokenPayload.create(); String preRes = ""; for (OtpToken token in tokens) { @@ -593,24 +628,26 @@ class ExportTokenUtil { } } if (preRes.isNotEmpty) payloads.add(payload); - //Categories - List categories = await CategoryDao.listCategories(); - TokenCategoryPayload categoryPayload = TokenCategoryPayload.create(); - preRes = ""; - for (TokenCategory category in categories) { - TokenCategoryParameters parameters = - await category.toCategoryParameters(); - categoryPayload.categoryParameters.add(parameters); - String currentRes = base64Encode(categoryPayload.writeToBuffer()); - if (currentRes.bytesLength > maxBytesLength) { - categoryPayloads.add(categoryPayload); - preRes = currentRes = ""; - categoryPayload = TokenCategoryPayload.create(); - } else { - preRes = currentRes; + //Categories (skip when exporting selected tokens only) + if (selectedTokens == null) { + List categories = await CategoryDao.listCategories(); + TokenCategoryPayload categoryPayload = TokenCategoryPayload.create(); + preRes = ""; + for (TokenCategory category in categories) { + TokenCategoryParameters parameters = + await category.toCategoryParameters(); + categoryPayload.categoryParameters.add(parameters); + String currentRes = base64Encode(categoryPayload.writeToBuffer()); + if (currentRes.bytesLength > maxBytesLength) { + categoryPayloads.add(categoryPayload); + preRes = currentRes = ""; + categoryPayload = TokenCategoryPayload.create(); + } else { + preRes = currentRes; + } } + if (preRes.isNotEmpty) categoryPayloads.add(categoryPayload); } - if (preRes.isNotEmpty) categoryPayloads.add(categoryPayload); for (CloudOtpTokenPayload payload in payloads) { payload.version = 1; payload.batchSize = payloads.length + categoryPayloads.length; diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 4f89d2a2..d244d325 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -95,6 +95,7 @@ class CloudOTPHiveUtil { //System static const String oldVersionKey = "oldVersion"; static const String haveShowQAuthDialogKey = "haveShowQAuthDialog"; + static const String haveShownCoachMarkKey = "haveShownCoachMark"; static initConfig() async { await ChewieHiveUtil.put( diff --git a/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart new file mode 100644 index 00000000..68a9a67b --- /dev/null +++ b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/token_category_binding_dao.dart'; +import 'package:cloudotp/Screens/home_screen.dart'; +import 'package:flutter/material.dart'; + +import '../../Database/category_dao.dart'; +import '../../Models/opt_token.dart'; +import '../../Models/token_category.dart'; +import '../../l10n/l10n.dart'; + +class SelectCategoryForTokensBottomSheet extends StatefulWidget { + const SelectCategoryForTokensBottomSheet({ + super.key, + required this.tokens, + required this.onCompleted, + }); + + final List tokens; + final VoidCallback onCompleted; + + @override + State createState() => + _SelectCategoryForTokensBottomSheetState(); +} + +class _SelectCategoryForTokensBottomSheetState + extends BaseDynamicState { + List categories = []; + GroupButtonController controller = GroupButtonController(); + + @override + void initState() { + super.initState(); + getCategories(); + } + + getCategories() async { + await CategoryDao.listCategories().then((value) { + setState(() { + categories = value; + }); + }); + } + + Radius radius = ChewieDimens.defaultRadius; + + @override + Widget build(BuildContext context) { + return Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: radius, + bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildHeader(), + const SizedBox(height: 10), + _buildButtons(), + _buildFooter(), + ], + ), + ), + ], + ); + } + + _buildHeader() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 20), + alignment: Alignment.center, + decoration: + BoxDecoration(borderRadius: BorderRadius.vertical(top: radius)), + child: Text( + textAlign: TextAlign.center, + appLocalizations.setCategoryForTokens(widget.tokens.length), + style: ChewieTheme.titleLarge, + ), + ); + } + + _buildButtons() { + return categories.isNotEmpty + ? ItemBuilder.buildGroupButtons( + isRadio: false, + enableDeselect: true, + constraintWidth: false, + buttons: categories.map((e) => e.title).toList(), + controller: controller, + radius: 8, + ) + : EmptyPlaceholder( + text: appLocalizations.noCategory, + onTap: () { + HomeScreenState.addCategory( + context, + onAdded: (category) { + setState(() { + categories.add(category); + }); + }, + ); + }, + ); + } + + _buildFooter() { + return Container( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Expanded(flex: 2, child: SizedBox(height: 50)), + if (categories.isNotEmpty) + Expanded( + flex: 1, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + text: appLocalizations.save, + onPressed: () async { + List selectedIndexes = + controller.selectedIndexes.toList(); + List selectedCategoryUids = + selectedIndexes.map((e) => categories[e].uid).toList(); + Navigator.of(context).pop(); + for (OtpToken token in widget.tokens) { + List existingUids = + await BindingDao.getCategoryUids(token.uid); + List newUids = selectedCategoryUids + .where((uid) => !existingUids.contains(uid)) + .toList(); + if (newUids.isNotEmpty) { + await BindingDao.bingdingsForToken(token.uid, newUids); + } + } + IToast.showTop(appLocalizations.saveSuccess); + widget.onCompleted(); + }, + fontSizeDelta: 2, + ), + ), + ], + ), + ); + } +} diff --git a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart index 64d5673e..984495ce 100644 --- a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart @@ -345,6 +345,22 @@ class TokenOptionBottomSheetState ); }, ), + _buildItem( + leading: LucideIcons.share2, + title: appLocalizations.shareTokenUri, + onTap: () { + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.copyUriClearWarningTitle, + message: appLocalizations.copyUriClearWarningTip, + onTapConfirm: () { + Navigator.pop(context); + UriUtil.share(OtpTokenParser.toUri(widget.token).toString()); + }, + onTapCancel: () {}, + ); + }, + ), _buildItem( leading: LucideIcons.shapes, title: appLocalizations.editTokenCategory, @@ -440,35 +456,39 @@ class TokenOptionBottomSheetState Color? backgroundColor, Function()? onTap, }) { - return PressableAnimation( - child: Material( - color: backgroundColor ?? ChewieTheme.cardColor, - borderRadius: ChewieDimens.defaultBorderRadius, - child: InkWell( - onTap: onTap, + return Semantics( + button: true, + label: title, + child: PressableAnimation( + child: Material( + color: backgroundColor ?? ChewieTheme.cardColor, borderRadius: ChewieDimens.defaultBorderRadius, - child: Container( - padding: const EdgeInsets.all(12), - decoration: const BoxDecoration( - borderRadius: ChewieDimens.defaultBorderRadius), - child: Column( - children: [ - Icon( - leading, - size: 24, - color: leadingColor ?? ChewieTheme.bodyMedium.color, - ), - const SizedBox(height: 10), - SizedBox( - height: 20, - child: AutoSizeText( - title, - maxLines: 1, - style: ChewieTheme.bodyMedium.copyWith( - color: titleColor ?? ChewieTheme.bodyMedium.color), + child: InkWell( + onTap: onTap, + borderRadius: ChewieDimens.defaultBorderRadius, + child: Container( + padding: const EdgeInsets.all(12), + decoration: const BoxDecoration( + borderRadius: ChewieDimens.defaultBorderRadius), + child: Column( + children: [ + Icon( + leading, + size: 24, + color: leadingColor ?? ChewieTheme.bodyMedium.color, ), - ), - ], + const SizedBox(height: 10), + SizedBox( + height: 20, + child: AutoSizeText( + title, + maxLines: 1, + style: ChewieTheme.bodyMedium.copyWith( + color: titleColor ?? ChewieTheme.bodyMedium.color), + ), + ), + ], + ), ), ), ), diff --git a/lib/Widgets/CoachMark/coach_mark_manager.dart b/lib/Widgets/CoachMark/coach_mark_manager.dart new file mode 100644 index 00000000..3292feda --- /dev/null +++ b/lib/Widgets/CoachMark/coach_mark_manager.dart @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Utils/hive_util.dart'; +import 'package:flutter/material.dart'; +import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; + +import '../../Screens/home_screen.dart'; +import '../../l10n/l10n.dart'; + +class CoachMarkManager { + final BuildContext context; + final GlobalKey appBarTitleKey; + final GlobalKey? firstTokenKey; + final GlobalKey? categoryTabKey; + final GlobalKey moreButtonKey; + final LayoutType layoutType; + final int tokenCount; + final int categoryCount; + final Future Function(String identify)? onDemoAction; + final Future Function(String identify)? onUndoDemoAction; + + CoachMarkManager({ + required this.context, + required this.appBarTitleKey, + this.firstTokenKey, + this.categoryTabKey, + required this.moreButtonKey, + required this.layoutType, + required this.tokenCount, + required this.categoryCount, + this.onDemoAction, + this.onUndoDemoAction, + }); + + Future show({bool force = false}) async { + if (!ResponsiveUtil.isMobile()) return; + if (!force && + ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownCoachMarkKey, + defaultValue: false)) { + return; + } + + List<_CoachStep> steps = _buildSteps(); + if (steps.isEmpty) return; + + bool skipped = false; + + for (int i = 0; i < steps.length; i++) { + if (skipped || !context.mounted) break; + final step = steps[i]; + final completer = Completer(); + + final tcm = TutorialCoachMark( + targets: [_buildTarget(step, i, steps.length, completer)], + colorShadow: Colors.black, + opacityShadow: 0.75, + paddingFocus: 8, + pulseEnable: true, + focusAnimationDuration: const Duration(milliseconds: 300), + unFocusAnimationDuration: const Duration(milliseconds: 300), + showSkipInLastTarget: true, + hideSkip: true, + onFinish: () { + if (!completer.isCompleted) completer.complete(true); + }, + onSkip: () { + if (!completer.isCompleted) completer.complete(false); + return true; + }, + ); + + tcm.show(context: context); + final proceed = await completer.future; + + if (!proceed) { + skipped = true; + break; + } + + if (onDemoAction != null && context.mounted) { + await Future.delayed(const Duration(milliseconds: 200)); + await onDemoAction!(step.identify); + await Future.delayed(const Duration(milliseconds: 1200)); + if (context.mounted) { + await onUndoDemoAction?.call(step.identify); + } + await Future.delayed(const Duration(milliseconds: 400)); + } + } + _markShown(); + } + + void _markShown() { + ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownCoachMarkKey, true); + } + + List<_CoachStep> _buildSteps() { + List<_CoachStep> steps = []; + + steps.add(_CoachStep( + key: appBarTitleKey, + identify: "search_title", + shape: ShapeLightFocus.RRect, + radius: 8, + title: appLocalizations.coachMarkSearchTitle, + description: appLocalizations.coachMarkSearchDescription, + )); + + bool swipeApplicable = tokenCount >= 1 && + firstTokenKey != null && + (layoutType == LayoutType.List || layoutType == LayoutType.Spotlight); + if (swipeApplicable) { + steps.add(_CoachStep( + key: firstTokenKey!, + identify: "swipe_token", + shape: ShapeLightFocus.RRect, + radius: 12, + title: appLocalizations.coachMarkSwipeTitle, + description: appLocalizations.coachMarkSwipeDescription, + )); + } + + if (categoryCount >= 1 && categoryTabKey != null) { + steps.add(_CoachStep( + key: categoryTabKey!, + identify: "category_tab", + shape: ShapeLightFocus.RRect, + radius: 8, + title: appLocalizations.coachMarkCategoryTitle, + description: appLocalizations.coachMarkCategoryDescription, + )); + } + + if (tokenCount > 1) { + steps.add(_CoachStep( + key: moreButtonKey, + identify: "more_menu", + shape: ShapeLightFocus.Circle, + radius: 0, + title: appLocalizations.coachMarkMultiSelectTitle, + description: appLocalizations.coachMarkMultiSelectDescription, + )); + } + + return steps; + } + + TargetFocus _buildTarget( + _CoachStep step, int index, int total, Completer completer) { + return TargetFocus( + identify: step.identify, + keyTarget: step.key, + alignSkip: Alignment.topRight, + shape: step.shape, + radius: step.radius, + enableOverlayTab: false, + enableTargetTab: false, + contents: [ + TargetContent( + align: _contentAlign(step.key), + builder: (context, controller) { + return _buildContent( + title: step.title, + description: step.description, + stepIndex: index, + totalSteps: total, + isLast: index == total - 1, + onNext: () { + if (!completer.isCompleted) { + controller.next(); + } + }, + onSkip: () { + if (!completer.isCompleted) { + completer.complete(false); + controller.skip(); + } + }, + ); + }, + ), + ], + ); + } + + ContentAlign _contentAlign(GlobalKey key) { + try { + final renderBox = + key.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null) return ContentAlign.bottom; + final pos = renderBox.localToGlobal(Offset.zero); + final screenHeight = MediaQuery.of(context).size.height; + return pos.dy < screenHeight / 2 + ? ContentAlign.bottom + : ContentAlign.top; + } catch (_) { + return ContentAlign.bottom; + } + } + + Widget _buildContent({ + required String title, + required String description, + required int stepIndex, + required int totalSteps, + required bool isLast, + required VoidCallback onNext, + required VoidCallback onSkip, + }) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.black.withAlpha(100), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white.withAlpha(25)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildProgressDots(stepIndex, totalSteps), + const SizedBox(height: 16), + Text( + title, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + description, + style: TextStyle( + color: Colors.white.withAlpha(200), + fontSize: 14, + height: 1.5, + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: onSkip, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + appLocalizations.coachMarkSkip, + style: TextStyle( + color: Colors.white.withAlpha(180), + fontSize: 14, + ), + ), + ), + const SizedBox(width: 8), + TextButton( + onPressed: onNext, + style: TextButton.styleFrom( + backgroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + isLast + ? appLocalizations.coachMarkGotIt + : appLocalizations.coachMarkNext, + style: const TextStyle( + color: Colors.black87, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildProgressDots(int current, int total) { + return Row( + children: List.generate( + total, + (i) => Container( + width: i == current ? 16 : 6, + height: 6, + margin: const EdgeInsets.only(right: 4), + decoration: BoxDecoration( + color: i == current ? Colors.white : Colors.white.withAlpha(80), + borderRadius: BorderRadius.circular(3), + ), + ), + ), + ); + } +} + +class _CoachStep { + final GlobalKey key; + final String identify; + final ShapeLightFocus shape; + final double radius; + final String title; + final String description; + + _CoachStep({ + required this.key, + required this.identify, + required this.shape, + required this.radius, + required this.title, + required this.description, + }); +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index dc0a7592..68c697bc 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -157,6 +157,7 @@ "autoBackupTip": "Automatically back up to the specified location when tokens or categories change; effective after setting a backup password", "autoBackupPath": "Local Backup Location", "backupLogs": "Backup Logs", + "backupLogSubtitle": "View automatic backup history", "noBackupLogs": "No Backup Logs Available", "haveNotSetBackupPassword": "Backup password not set, cannot back up", "notSetBackupPasswordTip": "Please set a backup password before using the backup feature.", @@ -672,6 +673,15 @@ "createTimeDESCOrder": "Creation time descending", "lastCopyTimeASCOrder": "Last copy time ascending", "lastCopyTimeDESCOrder": "Last copy time descending", + "layoutType": "Layout", + "layoutTypeSubtitle": "Choose how your tokens are displayed", + "sortType": "Sort by", + "sortTypeSubtitle": "Choose the order in which tokens appear", + "sortGroupDefault": "Default", + "sortGroupAlphabetical": "By name", + "sortGroupCopyTimes": "By copy count", + "sortGroupLastCopy": "By last copy time", + "sortGroupCreate": "By creation time", "simpleLayoutType": "Simple", "compactLayoutType": "Compact", "tileLayoutType": "Tile", @@ -824,5 +834,137 @@ "accentColor": "Accent Color", "resetToDefault": "Default", "customColor": "Custom", - "webDavHttpWarning": "Warning: HTTP is not encrypted. Your credentials may be intercepted. Please use HTTPS." + "webDavHttpWarning": "Warning: HTTP is not encrypted. Your credentials may be intercepted. Please use HTTPS.", + "shareTokenUri": "Share Token URI", + "toggleCodeVisibility": "Toggle code visibility", + "tokenProgressLabel": "Time remaining for code", + "multiSelectCount": "{count} selected", + "@multiSelectCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "deselectAll": "Deselect All", + "batchDeleteTitle": "Delete Selected Tokens", + "batchDeleteMessage": "Are you sure you want to delete {count} tokens? This action cannot be undone.", + "@batchDeleteMessage": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "batchDeleteSuccess": "Successfully deleted {count} tokens", + "@batchDeleteSuccess": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "setCategoryForTokens": "Set category for {count} tokens", + "@setCategoryForTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "coachMarkSkip": "Skip", + "coachMarkSearchTitle": "Tap to Search", + "coachMarkSearchDescription": "Tap the app title to quickly open the search bar and find your tokens.", + "coachMarkSwipeTitle": "Swipe for Actions", + "coachMarkSwipeDescription": "Swipe a token left to pin/unpin, or swipe right for QR code, edit, and more.", + "coachMarkCategoryTitle": "Manage Category Tokens", + "coachMarkCategoryDescription": "Long-press a category tab to select which tokens belong to it.", + "coachMarkMultiSelectTitle": "Multi-Select Mode", + "coachMarkMultiSelectDescription": "Tap the more menu and choose \"Select\" to enter multi-select mode for batch operations.", + "showGuidedTour": "Feature Guide", + "showGuidedTourDescription": "Show the feature guide tour again", + "coachMarkNext": "Next", + "coachMarkGotIt": "Got it", + "alreadyPinnedSelectedTokens": "Pinned {count} tokens", + "@alreadyPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "alreadyUnPinnedSelectedTokens": "Unpinned {count} tokens", + "@alreadyUnPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureShowcase": "Features", + "featureShowcaseTitle": "Discover CloudOTP", + "featureShowcaseStartTour": "Start Feature Guide", + "featureTokenTitle": "Multi-Token Support", + "featureTokenDescription": "Supports 5 OTP types with SHA1, SHA256, SHA512 algorithms and 5–8 digit codes.", + "featureTokenTOTPDesc": "Time-based", + "featureTokenHOTPDesc": "Counter-based", + "featureTokenMOTPDesc": "Mobile OTP", + "featureTokenSteamDesc": "Steam Guard", + "featureTokenYandexDesc": "Yandex", + "featureCloudTitle": "Cloud Backup", + "featureCloudDescription": "Back up to 8 cloud services with auto-backup, encryption, and backup history.", + "featureCloudAuto": "Auto Backup", + "featureCloudEncrypted": "Encrypted", + "featureCloudLog": "Backup Log", + "featureIconTitle": "Smart Icons", + "featureIconDescription": "Auto-match brand logos from {count}+ icons. Pick or search for the perfect icon.", + "@featureIconDescription": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureSecurityTitle": "Security First", + "featureSecurityDescription": "Multiple layers of protection with industry-standard encryption keep your tokens safe.", + "featureSecurityEncryption": "Database Encryption", + "featureSecurityBiometric": "Biometric Unlock", + "featureSecurityGesture": "Gesture Lock", + "featureSecurityAutoLock": "Auto Lock", + "featureSecuritySafeMode": "Safe Mode", + "featureSecurityBackupEncrypt": "Backup Encryption", + "featureSecurityOpenSource": "Open Source & Auditable", + "featurePlatformTitle": "Cross-Platform", + "featurePlatformDescription": "Runs on all your devices — Android, iOS, Windows, macOS, and Linux.", + "featurePlatformSync": "Cross-Device Sync", + "featurePlatformResponsive": "Responsive Layout", + "featureImportSection": "Import", + "featureExportSection": "Export", + "featureImportScan": "Scan QR", + "featureImportClipboard": "From Clipboard", + "featureExportEncrypted": "Encrypted File", + "featureExportUri": "URI File", + "featureExportCloudOTPQR": "CloudOTP QR", + "featureExportGoogleQR": "Google Auth QR", + "featureImportExportTitle": "Easy Migration", + "featureImportExportDescription": "Import from 9+ authenticator apps and export in multiple formats.", + "featureConvenienceTitle": "Smart & Efficient", + "featureConvenienceDescription": "Packed with convenient features for the best experience.", + "featureQuickSearch": "Quick Search", + "featureSort": "Sort & Filter", + "featureCategories": "Categories", + "featureMultiSelect": "Multi-Select", + "featurePinTop": "Pin to Top", + "featureSwipe": "Swipe Actions", + "featureDragReorder": "Drag Reorder", + "featureAutoCopy": "Auto Copy", + "featureQRScanner": "QR Scanner", + "featureCustomizeTitle": "Personalization", + "featureCustomizeDescription": "Choose from multiple layout styles, themes, fonts, and icon packs to make CloudOTP truly yours.", + "featureGlassEffect": "Glass Effect", + "featureCustomFont": "Custom Fonts", + "featureMultiLang": "4 Languages", + "featureThemeColors": "Theme Colors", + "featureLayoutStyles": "Layout Styles", + "featureCustomThemeEditor": "Custom Theme Editor" } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 58bbe23d..7aa82287 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -157,6 +157,7 @@ "autoBackupTip": "トークンやカテゴリが変更されたときに指定の場所に自動バックアップします。バックアップパスワード設定後に有効", "autoBackupPath": "ローカルバックアップ位置", "backupLogs": "バックアップログ", + "backupLogSubtitle": "自動バックアップの履歴を表示", "noBackupLogs": "バックアップログはありません", "haveNotSetBackupPassword": "バックアップパスワードが設定されていないため、バックアップできません", "notSetBackupPasswordTip": "バックアップ機能を使用する前に、バックアップパスワードを設定してください。", @@ -672,6 +673,15 @@ "createTimeDESCOrder": "作成時間降順", "lastCopyTimeASCOrder": "最終コピー時間昇順", "lastCopyTimeDESCOrder": "最終コピー時間降順", + "layoutType": "レイアウト", + "layoutTypeSubtitle": "トークンの表示方法を選択", + "sortType": "並び順", + "sortTypeSubtitle": "トークンの並び順を選択", + "sortGroupDefault": "デフォルト", + "sortGroupAlphabetical": "名前順", + "sortGroupCopyTimes": "コピー回数順", + "sortGroupLastCopy": "最終コピー時間順", + "sortGroupCreate": "作成時間順", "simpleLayoutType": "シンプル", "compactLayoutType": "コンパクト", "tileLayoutType": "タイル", @@ -824,5 +834,137 @@ "accentColor": "アクセントカラー", "resetToDefault": "デフォルト", "customColor": "カスタム", - "webDavHttpWarning": "警告:HTTP接続は暗号化されていません。資格情報が傍受される可能性があります。HTTPSを使用してください。" + "webDavHttpWarning": "警告:HTTP接続は暗号化されていません。資格情報が傍受される可能性があります。HTTPSを使用してください。", + "shareTokenUri": "トークンURIを共有", + "toggleCodeVisibility": "コードの表示切替", + "tokenProgressLabel": "コードの残り時間", + "multiSelectCount": "{count}件選択中", + "@multiSelectCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "deselectAll": "すべて解除", + "batchDeleteTitle": "選択したトークンを削除", + "batchDeleteMessage": "{count}件のトークンを削除しますか?この操作は取り消せません。", + "@batchDeleteMessage": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "batchDeleteSuccess": "{count}件のトークンを削除しました", + "@batchDeleteSuccess": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "setCategoryForTokens": "{count}件のトークンのカテゴリを設定", + "@setCategoryForTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "coachMarkSkip": "スキップ", + "coachMarkSearchTitle": "タップして検索", + "coachMarkSearchDescription": "アプリのタイトルをタップすると検索バーが開き、トークンをすばやく検索できます。", + "coachMarkSwipeTitle": "スワイプ操作", + "coachMarkSwipeDescription": "トークンを左にスワイプでピン留め/解除、右にスワイプで QR コード表示、編集などの操作ができます。", + "coachMarkCategoryTitle": "カテゴリトークンの管理", + "coachMarkCategoryDescription": "カテゴリタブを長押しすると、そのカテゴリに含まれるトークンを選択できます。", + "coachMarkMultiSelectTitle": "複数選択モード", + "coachMarkMultiSelectDescription": "その他メニューから「選択」をタップすると、複数選択モードで一括操作ができます。", + "showGuidedTour": "機能ガイド", + "showGuidedTourDescription": "機能ガイドツアーを再表示する", + "coachMarkNext": "次へ", + "coachMarkGotIt": "わかった", + "alreadyPinnedSelectedTokens": "{count} 件のトークンをピン留めしました", + "@alreadyPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "alreadyUnPinnedSelectedTokens": "{count} 件のトークンのピン留めを解除しました", + "@alreadyUnPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureShowcase": "機能紹介", + "featureShowcaseTitle": "CloudOTP を探索", + "featureShowcaseStartTour": "機能ガイドを開始", + "featureTokenTitle": "多様なトークン対応", + "featureTokenDescription": "5種類のOTPに対応。SHA1/SHA256/SHA512アルゴリズムと5〜8桁のコードに対応。", + "featureTokenTOTPDesc": "時刻ベース", + "featureTokenHOTPDesc": "カウンタベース", + "featureTokenMOTPDesc": "モバイルOTP", + "featureTokenSteamDesc": "Steamガード", + "featureTokenYandexDesc": "Yandex", + "featureCloudTitle": "クラウドバックアップ", + "featureCloudDescription": "8種類のクラウドサービスへの自動バックアップ、暗号化保護、バックアップ履歴に対応。", + "featureCloudAuto": "自動バックアップ", + "featureCloudEncrypted": "暗号化", + "featureCloudLog": "バックアップログ", + "featureIconTitle": "スマートアイコン", + "featureIconDescription": "{count}個以上のブランドアイコンから自動で一致。手動検索も可能。", + "@featureIconDescription": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureSecurityTitle": "セキュリティ重視", + "featureSecurityDescription": "業界標準の暗号化と多層的な保護でトークンを守ります。", + "featureSecurityEncryption": "DB暗号化", + "featureSecurityBiometric": "生体認証", + "featureSecurityGesture": "ジェスチャーロック", + "featureSecurityAutoLock": "自動ロック", + "featureSecuritySafeMode": "セーフモード", + "featureSecurityBackupEncrypt": "バックアップ暗号化", + "featureSecurityOpenSource": "オープンソースで監査可能", + "featurePlatformTitle": "マルチプラットフォーム", + "featurePlatformDescription": "Android、iOS、Windows、macOS、Linux のすべてのデバイスで動作。", + "featurePlatformSync": "デバイス間同期", + "featurePlatformResponsive": "レスポンシブレイアウト", + "featureImportSection": "インポート", + "featureExportSection": "エクスポート", + "featureImportScan": "QRスキャン", + "featureImportClipboard": "クリップボード", + "featureExportEncrypted": "暗号化ファイル", + "featureExportUri": "URIファイル", + "featureExportCloudOTPQR": "CloudOTP QR", + "featureExportGoogleQR": "Google認証QR", + "featureImportExportTitle": "簡単移行", + "featureImportExportDescription": "9種類以上の認証アプリからのインポートと、複数フォーマットでのエクスポートに対応。", + "featureConvenienceTitle": "スマートで効率的", + "featureConvenienceDescription": "便利な機能を満載し、最高の体験を提供します。", + "featureQuickSearch": "クイック検索", + "featureSort": "並べ替え", + "featureCategories": "カテゴリ", + "featureMultiSelect": "複数選択", + "featurePinTop": "ピン留め", + "featureSwipe": "スワイプ操作", + "featureDragReorder": "ドラッグ並べ替え", + "featureAutoCopy": "自動コピー", + "featureQRScanner": "QRスキャナ", + "featureCustomizeTitle": "カスタマイズ", + "featureCustomizeDescription": "複数のレイアウト、テーマ、フォント、アイコンパックから選んで、自分だけの CloudOTP に。", + "featureGlassEffect": "グラス効果", + "featureCustomFont": "カスタムフォント", + "featureMultiLang": "4言語対応", + "featureThemeColors": "テーマカラー", + "featureLayoutStyles": "レイアウト", + "featureCustomThemeEditor": "カスタムテーマエディタ" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 70376df1..151a3813 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -157,6 +157,7 @@ "autoBackupTip": "当令牌或分类发生更改时自动备份至指定位置;设置备份密码后生效", "autoBackupPath": "本地备份位置", "backupLogs": "备份日志", + "backupLogSubtitle": "查看自动备份历史记录", "noBackupLogs": "暂无备份日志", "haveNotSetBackupPassword": "尚未设置备份密码,无法备份", "notSetBackupPasswordTip": "请设置备份密码后使用备份功能", @@ -672,6 +673,15 @@ "createTimeDESCOrder": "创建时间降序", "lastCopyTimeASCOrder": "最后复制时间升序", "lastCopyTimeDESCOrder": "最后复制时间降序", + "layoutType": "布局", + "layoutTypeSubtitle": "选择令牌的展示方式", + "sortType": "排序方式", + "sortTypeSubtitle": "选择令牌的排列顺序", + "sortGroupDefault": "默认", + "sortGroupAlphabetical": "按名称", + "sortGroupCopyTimes": "按复制次数", + "sortGroupLastCopy": "按最近复制时间", + "sortGroupCreate": "按创建时间", "simpleLayoutType": "简洁", "compactLayoutType": "紧凑", "tileLayoutType": "平铺", @@ -824,5 +834,137 @@ "accentColor": "强调色", "resetToDefault": "默认", "customColor": "自定义", - "webDavHttpWarning": "警告:HTTP 连接未加密,您的凭据可能被截获,请使用 HTTPS。" + "webDavHttpWarning": "警告:HTTP 连接未加密,您的凭据可能被截获,请使用 HTTPS。", + "shareTokenUri": "分享令牌URI", + "toggleCodeVisibility": "切换验证码可见性", + "tokenProgressLabel": "验证码剩余时间", + "multiSelectCount": "已选择 {count} 个", + "@multiSelectCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "deselectAll": "取消全选", + "batchDeleteTitle": "删除选中的令牌", + "batchDeleteMessage": "确定要删除 {count} 个令牌吗?此操作不可撤销。", + "@batchDeleteMessage": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "batchDeleteSuccess": "成功删除 {count} 个令牌", + "@batchDeleteSuccess": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "setCategoryForTokens": "为 {count} 个令牌设置分类", + "@setCategoryForTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "coachMarkSkip": "跳过", + "coachMarkSearchTitle": "点击搜索", + "coachMarkSearchDescription": "点击应用标题即可打开搜索栏,快速查找令牌。", + "coachMarkSwipeTitle": "滑动操作", + "coachMarkSwipeDescription": "向左滑动令牌可置顶/取消置顶,向右滑动可查看二维码、编辑等更多操作。", + "coachMarkCategoryTitle": "管理分类令牌", + "coachMarkCategoryDescription": "长按分类标签可选择该分类中包含的令牌。", + "coachMarkMultiSelectTitle": "多选模式", + "coachMarkMultiSelectDescription": "点击更多菜单中的「选择」进入多选模式,进行批量删除、导出、分类等操作。", + "showGuidedTour": "功能引导", + "showGuidedTourDescription": "重新显示功能引导教程", + "coachMarkNext": "下一步", + "coachMarkGotIt": "知道了", + "alreadyPinnedSelectedTokens": "已置顶 {count} 个令牌", + "@alreadyPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "alreadyUnPinnedSelectedTokens": "已取消置顶 {count} 个令牌", + "@alreadyUnPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureShowcase": "功能特色", + "featureShowcaseTitle": "探索 CloudOTP", + "featureShowcaseStartTour": "开始功能引导", + "featureTokenTitle": "多种令牌支持", + "featureTokenDescription": "支持 5 种 OTP 令牌类型,SHA1、SHA256、SHA512 算法及 5-8 位验证码。", + "featureTokenTOTPDesc": "基于时间", + "featureTokenHOTPDesc": "基于计数", + "featureTokenMOTPDesc": "移动 OTP", + "featureTokenSteamDesc": "Steam 令牌", + "featureTokenYandexDesc": "Yandex", + "featureCloudTitle": "云端备份", + "featureCloudDescription": "支持 8 种云服务备份,自动备份、加密保护、备份历史记录一应俱全。", + "featureCloudAuto": "自动备份", + "featureCloudEncrypted": "加密存储", + "featureCloudLog": "备份日志", + "featureIconTitle": "智能图标", + "featureIconDescription": "从 {count}+ 品牌图标中自动匹配令牌图标,也可手动搜索选择。", + "@featureIconDescription": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureSecurityTitle": "安全优先", + "featureSecurityDescription": "多重安全防护,采用业界标准加密算法保障令牌安全。", + "featureSecurityEncryption": "数据库加密", + "featureSecurityBiometric": "生物识别", + "featureSecurityGesture": "手势锁", + "featureSecurityAutoLock": "自动锁定", + "featureSecuritySafeMode": "安全模式", + "featureSecurityBackupEncrypt": "备份加密", + "featureSecurityOpenSource": "开源透明,代码可审计", + "featurePlatformTitle": "全平台支持", + "featurePlatformDescription": "在所有设备上运行 — Android、iOS、Windows、macOS、Linux。", + "featurePlatformSync": "跨设备同步", + "featurePlatformResponsive": "响应式布局", + "featureImportSection": "导入", + "featureExportSection": "导出", + "featureImportScan": "扫码导入", + "featureImportClipboard": "剪贴板导入", + "featureExportEncrypted": "加密文件", + "featureExportUri": "URI 文件", + "featureExportCloudOTPQR": "CloudOTP 二维码", + "featureExportGoogleQR": "Google 二维码", + "featureImportExportTitle": "轻松迁移", + "featureImportExportDescription": "支持从 9 种以上认证器导入,多种格式导出。", + "featureConvenienceTitle": "智能高效", + "featureConvenienceDescription": "丰富的便捷功能,带来最佳使用体验。", + "featureQuickSearch": "快速搜索", + "featureSort": "排序筛选", + "featureCategories": "智能分类", + "featureMultiSelect": "多选操作", + "featurePinTop": "置顶令牌", + "featureSwipe": "滑动操作", + "featureDragReorder": "拖拽排序", + "featureAutoCopy": "自动复制", + "featureQRScanner": "二维码扫描", + "featureCustomizeTitle": "个性定制", + "featureCustomizeDescription": "提供多种布局、主题、字体和图标包选择,打造专属的 CloudOTP。", + "featureGlassEffect": "毛玻璃效果", + "featureCustomFont": "自定义字体", + "featureMultiLang": "4 种语言", + "featureThemeColors": "主题色", + "featureLayoutStyles": "布局样式", + "featureCustomThemeEditor": "自定义主题编辑器" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index e4d21dda..e5798337 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -157,6 +157,7 @@ "autoBackupTip": "當令牌或分類發生變更時自動備份至指定位置;設定備份密碼後生效", "autoBackupPath": "本地備份位置", "backupLogs": "備份日誌", + "backupLogSubtitle": "查看自動備份歷史記錄", "noBackupLogs": "暫無備份日誌", "haveNotSetBackupPassword": "尚未設定備份密碼,無法備份", "notSetBackupPasswordTip": "請先設定備份密碼後再使用備份功能。", @@ -672,6 +673,15 @@ "createTimeDESCOrder": "建立時間降序", "lastCopyTimeASCOrder": "最後複製時間升序", "lastCopyTimeDESCOrder": "最後複製時間降序", + "layoutType": "佈局", + "layoutTypeSubtitle": "選擇令牌的展示方式", + "sortType": "排序方式", + "sortTypeSubtitle": "選擇令牌的排列順序", + "sortGroupDefault": "預設", + "sortGroupAlphabetical": "按名稱", + "sortGroupCopyTimes": "按複製次數", + "sortGroupLastCopy": "按最近複製時間", + "sortGroupCreate": "按建立時間", "simpleLayoutType": "簡潔", "compactLayoutType": "緊湊", "tileLayoutType": "平鋪", @@ -824,5 +834,137 @@ "accentColor": "強調色", "resetToDefault": "預設", "customColor": "自訂", - "webDavHttpWarning": "警告:HTTP 連線未加密,您的憑證可能被攔截,請使用 HTTPS。" + "webDavHttpWarning": "警告:HTTP 連線未加密,您的憑證可能被攔截,請使用 HTTPS。", + "shareTokenUri": "分享令牌URI", + "toggleCodeVisibility": "切換驗證碼可見性", + "tokenProgressLabel": "驗證碼剩餘時間", + "multiSelectCount": "已選擇 {count} 個", + "@multiSelectCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "deselectAll": "取消全選", + "batchDeleteTitle": "刪除選中的令牌", + "batchDeleteMessage": "確定要刪除 {count} 個令牌嗎?此操作不可撤銷。", + "@batchDeleteMessage": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "batchDeleteSuccess": "成功刪除 {count} 個令牌", + "@batchDeleteSuccess": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "setCategoryForTokens": "為 {count} 個令牌設定分類", + "@setCategoryForTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "coachMarkSkip": "跳過", + "coachMarkSearchTitle": "點擊搜尋", + "coachMarkSearchDescription": "點擊應用標題即可打開搜尋欄,快速查找令牌。", + "coachMarkSwipeTitle": "滑動操作", + "coachMarkSwipeDescription": "向左滑動令牌可置頂/取消置頂,向右滑動可查看 QR Code、編輯等更多操作。", + "coachMarkCategoryTitle": "管理分類令牌", + "coachMarkCategoryDescription": "長按分類標籤可選擇該分類中包含的令牌。", + "coachMarkMultiSelectTitle": "多選模式", + "coachMarkMultiSelectDescription": "點擊更多選單中的「選擇」進入多選模式,進行批次刪除、匯出、分類等操作。", + "showGuidedTour": "功能引導", + "showGuidedTourDescription": "重新顯示功能引導教學", + "coachMarkNext": "下一步", + "coachMarkGotIt": "知道了", + "alreadyPinnedSelectedTokens": "已置頂 {count} 個令牌", + "@alreadyPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "alreadyUnPinnedSelectedTokens": "已取消置頂 {count} 個令牌", + "@alreadyUnPinnedSelectedTokens": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureShowcase": "功能特色", + "featureShowcaseTitle": "探索 CloudOTP", + "featureShowcaseStartTour": "開始功能引導", + "featureTokenTitle": "多種令牌支援", + "featureTokenDescription": "支援 5 種 OTP 令牌類型,SHA1、SHA256、SHA512 演算法及 5-8 位驗證碼。", + "featureTokenTOTPDesc": "基於時間", + "featureTokenHOTPDesc": "基於計數", + "featureTokenMOTPDesc": "行動 OTP", + "featureTokenSteamDesc": "Steam 令牌", + "featureTokenYandexDesc": "Yandex", + "featureCloudTitle": "雲端備份", + "featureCloudDescription": "支援 8 種雲端服務備份,自動備份、加密保護、備份歷史記錄一應俱全。", + "featureCloudAuto": "自動備份", + "featureCloudEncrypted": "加密儲存", + "featureCloudLog": "備份日誌", + "featureIconTitle": "智慧圖示", + "featureIconDescription": "從 {count}+ 品牌圖示中自動配對令牌圖示,也可手動搜尋選擇。", + "@featureIconDescription": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "featureSecurityTitle": "安全優先", + "featureSecurityDescription": "多重安全防護,採用業界標準加密演算法保障令牌安全。", + "featureSecurityEncryption": "資料庫加密", + "featureSecurityBiometric": "生物辨識", + "featureSecurityGesture": "手勢鎖", + "featureSecurityAutoLock": "自動鎖定", + "featureSecuritySafeMode": "安全模式", + "featureSecurityBackupEncrypt": "備份加密", + "featureSecurityOpenSource": "開源透明,程式碼可審計", + "featurePlatformTitle": "全平台支援", + "featurePlatformDescription": "在所有裝置上運行 — Android、iOS、Windows、macOS、Linux。", + "featurePlatformSync": "跨裝置同步", + "featurePlatformResponsive": "響應式佈局", + "featureImportSection": "匯入", + "featureExportSection": "匯出", + "featureImportScan": "掃碼匯入", + "featureImportClipboard": "剪貼簿匯入", + "featureExportEncrypted": "加密檔案", + "featureExportUri": "URI 檔案", + "featureExportCloudOTPQR": "CloudOTP 二維碼", + "featureExportGoogleQR": "Google 二維碼", + "featureImportExportTitle": "輕鬆遷移", + "featureImportExportDescription": "支援從 9 種以上驗證器匯入,多種格式匯出。", + "featureConvenienceTitle": "智慧高效", + "featureConvenienceDescription": "豐富的便捷功能,帶來最佳使用體驗。", + "featureQuickSearch": "快速搜尋", + "featureSort": "排序篩選", + "featureCategories": "智慧分類", + "featureMultiSelect": "多選操作", + "featurePinTop": "置頂令牌", + "featureSwipe": "滑動操作", + "featureDragReorder": "拖曳排序", + "featureAutoCopy": "自動複製", + "featureQRScanner": "二維碼掃描", + "featureCustomizeTitle": "個人化", + "featureCustomizeDescription": "提供多種佈局、主題、字體和圖示包選擇,打造專屬的 CloudOTP。", + "featureGlassEffect": "毛玻璃效果", + "featureCustomFont": "自訂字體", + "featureMultiLang": "4 種語言", + "featureThemeColors": "主題色", + "featureLayoutStyles": "佈局樣式", + "featureCustomThemeEditor": "自訂主題編輯器" } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 8e2af5e2..75f37cce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1930,6 +1930,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" + tutorial_coach_mark: + dependency: "direct main" + description: + name: tutorial_coach_mark + sha256: "5a325d53bcf16ce7a969e2ab8d4dc9610f39ee3eab2b3cc57d5c98936129b891" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 311cc438..7a697dbe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: awesome_chewie: path: third-party/chewie animations: ^2.0.11 # 动画 + tutorial_coach_mark: ^1.2.11 # 引导教程 fluttertoast: ^9.0.0 # 吐司 lottie: ^3.1.2 # Lottie动画 modal_bottom_sheet: ^3.0.0 # 底部弹窗 diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/auto_close_behavior.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/auto_close_behavior.dart index c2314dc4..752c2cd9 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/auto_close_behavior.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/auto_close_behavior.dart @@ -352,6 +352,7 @@ class _SlidableAutoCloseBarrierNotificationSenderState SlidableGroupNotificationDispatcher? dispatcher; void _handleStatusChanged(AnimationStatus status) { + if (!mounted) return; //TODO(romain): There is a bug if more than one try to open at the same time. final willBarrierBeEnabled = status != AnimationStatus.dismissed; final barrierEnabled = dispatcher != null; @@ -527,6 +528,7 @@ class _SlidableNotificationSenderState } void handleStatusChanged(AnimationStatus status) { + if (!mounted) return; if (widget.enabled) { widget.onStatusChanged(status); } @@ -537,7 +539,7 @@ class _SlidableNotificationSenderState } void removeListeners(_SlidableNotificationSender widget) { - widget.controller.animation.addStatusListener(handleStatusChanged); + widget.controller.animation.removeStatusListener(handleStatusChanged); } @override From 1404c5596e0ad925f9e108b527846272ce676d90 Mon Sep 17 00:00:00 2001 From: dd
Date: Wed, 20 May 2026 14:59:17 +0800 Subject: [PATCH 22/36] feat: Enhance backup log functionality with historical log loading and improved log merging --- lib/Database/auto_backup_log_dao.dart | 23 +- lib/Models/auto_backup_log.dart | 38 ++- lib/Screens/Setting/backup_log_screen.dart | 277 ++++++++++++++---- .../generic/generic_context_menu_overlay.dart | 6 +- 4 files changed, 282 insertions(+), 62 deletions(-) diff --git a/lib/Database/auto_backup_log_dao.dart b/lib/Database/auto_backup_log_dao.dart index 1c9d31ca..8a54dc8e 100644 --- a/lib/Database/auto_backup_log_dao.dart +++ b/lib/Database/auto_backup_log_dao.dart @@ -38,7 +38,7 @@ class AutoBackupLogDao { List> maps = await db.rawQuery( "SELECT MAX(id) as id FROM $tableName", ); - return maps[0]["id"] ?? -1; + return maps[0]["id"] ?? 0; } static Future> getLogs({ @@ -48,7 +48,7 @@ class AutoBackupLogDao { final db = await DatabaseManager.getDataBase(); List> maps = await db.query( tableName, - orderBy: "create_timestamp DESC", + orderBy: "start_timestamp DESC", limit: limit, offset: offset, ); @@ -56,4 +56,23 @@ class AutoBackupLogDao { return AutoBackupLog.fromMap(maps[i]); }); } + + static Future deleteCompletedLogs(List ids) async { + if (ids.isEmpty) return; + final db = await DatabaseManager.getDataBase(); + final placeholders = ids.map((_) => '?').join(','); + await db.delete( + tableName, + where: 'id IN ($placeholders)', + whereArgs: ids, + ); + } + + static Future getLogCount() async { + final db = await DatabaseManager.getDataBase(); + List> maps = await db.rawQuery( + "SELECT COUNT(*) as count FROM $tableName", + ); + return maps[0]["count"] as int? ?? 0; + } } diff --git a/lib/Models/auto_backup_log.dart b/lib/Models/auto_backup_log.dart index d54c6540..d212804e 100644 --- a/lib/Models/auto_backup_log.dart +++ b/lib/Models/auto_backup_log.dart @@ -18,6 +18,7 @@ import 'dart:convert'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Utils/app_provider.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../l10n/l10n.dart'; import 'cloud_service_config.dart'; @@ -59,20 +60,24 @@ enum AutoBackupStatus { return ChewieTheme.primaryColor; case AutoBackupStatus.encryptFailed: return Colors.red; + case AutoBackupStatus.encrpytSuccess: + return Colors.green; case AutoBackupStatus.saving: return ChewieTheme.primaryColor; case AutoBackupStatus.saveFailed: return Colors.red; + case AutoBackupStatus.saveSuccess: + return Colors.green; case AutoBackupStatus.uploading: return ChewieTheme.primaryColor; case AutoBackupStatus.uploadFailed: return Colors.red; + case AutoBackupStatus.uploadSuccess: + return Colors.green; case AutoBackupStatus.complete: return Colors.green; case AutoBackupStatus.failed: return Colors.red; - default: - return Colors.grey; } } } @@ -156,6 +161,35 @@ enum AutoBackupTriggerType { return appLocalizations.triggerAutoBackupByOther; } } + + IconData get icon { + switch (this) { + case AutoBackupTriggerType.manual: + return LucideIcons.hand; + case AutoBackupTriggerType.configInited: + case AutoBackupTriggerType.configUpdated: + return LucideIcons.settings; + case AutoBackupTriggerType.tokenInserted: + case AutoBackupTriggerType.tokensInserted: + case AutoBackupTriggerType.tokenUpdated: + case AutoBackupTriggerType.tokensUpdated: + case AutoBackupTriggerType.tokenDeleted: + return LucideIcons.keyRound; + case AutoBackupTriggerType.categoryInserted: + case AutoBackupTriggerType.categoriesInserted: + case AutoBackupTriggerType.categoryUpdated: + case AutoBackupTriggerType.categoriesUpdated: + case AutoBackupTriggerType.categoryDeleted: + case AutoBackupTriggerType.categoriesUpdatedForToken: + return LucideIcons.folderOpen; + case AutoBackupTriggerType.cloudServiceConfigInserted: + case AutoBackupTriggerType.cloudServiceConfigUpdated: + case AutoBackupTriggerType.cloudServiceConfigDeleted: + return LucideIcons.cloud; + case AutoBackupTriggerType.other: + return LucideIcons.circleHelp; + } + } } class AutoBackupLog { diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 82c2fc7c..87867999 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -16,11 +16,13 @@ import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/auto_backup_log_dao.dart'; import 'package:cloudotp/Models/auto_backup_log.dart'; import 'package:cloudotp/Screens/Setting/setting_backup_screen.dart'; import 'package:cloudotp/Screens/Setting/setting_navigation_screen.dart'; import 'package:cloudotp/Utils/app_provider.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../Database/config_dao.dart'; @@ -34,8 +36,15 @@ class BackupLogScreen extends StatefulWidget { this.isOverlay = false, }); + static bool _hasContextMenuOverlay(BuildContext context) { + return context + .findAncestorStateOfType() != + null; + } + static void show(BuildContext context) { - if (ResponsiveUtil.isLandscapeLayout()) { + if (ResponsiveUtil.isLandscapeLayout() && + _hasContextMenuOverlay(context)) { BottomSheetBuilder.showGenericContextMenu( context, const BackupLogScreen(isOverlay: true), @@ -55,6 +64,9 @@ class BackupLogScreen extends StatefulWidget { class BackupLogScreenState extends BaseDynamicState { String _autoBackupPassword = ""; + List _mergedLogs = []; + bool _isLoadingHistory = false; + bool _hasLoadedHistory = false; bool get canBackup => _autoBackupPassword.isNotEmpty; @@ -69,6 +81,9 @@ class BackupLogScreenState extends BaseDynamicState { setState(() { _autoBackupPassword = config.backupPassword; }); + if (_autoBackupPassword.isNotEmpty) { + _loadHistoricalLogs(); + } }); Future.delayed(const Duration(milliseconds: 500), () { if (appProvider.autoBackupLoadingStatus == LoadingStatus.failed) { @@ -77,6 +92,45 @@ class BackupLogScreenState extends BaseDynamicState { }); } + Future _loadHistoricalLogs() async { + if (_isLoadingHistory || _hasLoadedHistory) return; + setState(() { + _isLoadingHistory = true; + }); + + try { + final dbLogs = await AutoBackupLogDao.getLogs(limit: 50); + _mergeLogs(dbLogs); + } catch (_) { + _mergedLogs = List.from(appProvider.autoBackupLogs); + } + + setState(() { + _isLoadingHistory = false; + _hasLoadedHistory = true; + }); + } + + void _mergeLogs(List dbLogs) { + final inMemoryLogs = appProvider.autoBackupLogs; + final List result = []; + final Set seenDbIds = {}; + + for (final log in inMemoryLogs) { + result.add(log); + if (log.id > 0) seenDbIds.add(log.id); + } + + for (final log in dbLogs) { + if (!seenDbIds.contains(log.id)) { + result.add(log); + } + } + + result.sort((a, b) => b.startTimestamp.compareTo(a.startTimestamp)); + _mergedLogs = result; + } + @override Widget build(BuildContext context) { Widget header = Padding( @@ -87,11 +141,10 @@ class BackupLogScreenState extends BaseDynamicState { Widget body = _buildLogList(); if (widget.isOverlay) { - final overlayHeight = appProvider.autoBackupLogs.isEmpty || !canBackup - ? 200.0 - : 400.0; + final overlayHeight = + _mergedLogs.isEmpty || !canBackup ? 200.0 : 400.0; return Container( - width: min(300, MediaQuery.sizeOf(context).width - 80), + width: min(400, MediaQuery.sizeOf(context).width - 80), height: min(overlayHeight, MediaQuery.sizeOf(context).height - 80), decoration: BoxDecoration( color: ChewieTheme.scaffoldBackgroundColor, @@ -169,7 +222,7 @@ class BackupLogScreenState extends BaseDynamicState { ], ), ), - if (canBackup && appProvider.autoBackupLogs.isNotEmpty) + if (canBackup && _mergedLogs.isNotEmpty) CircleIconButton( icon: Icon(LucideIcons.trash2, size: 16, color: _accent), onTap: clear, @@ -178,9 +231,21 @@ class BackupLogScreenState extends BaseDynamicState { ); } - clear() { + clear() async { + final completedIds = _mergedLogs + .where((log) => log.lastStatus.isCompleted && log.id >= 0) + .map((log) => log.id) + .toList(); + appProvider.clearAutoBackupLogs(); appProvider.autoBackupLoadingStatus = LoadingStatus.none; + + if (completedIds.isNotEmpty) { + await AutoBackupLogDao.deleteCompletedLogs(completedIds); + } + + final remainingDbLogs = await AutoBackupLogDao.getLogs(limit: 50); + _mergeLogs(remainingDbLogs); setState(() {}); } @@ -218,11 +283,18 @@ class BackupLogScreenState extends BaseDynamicState { ); } - if (appProvider.autoBackupLogs.isEmpty) { + if (_isLoadingHistory) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 30), + child: Center(child: CircularProgressIndicator(strokeWidth: 2)), + ); + } + + if (_mergedLogs.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(vertical: 14), - child: - EmptyPlaceholder(text: appLocalizations.noBackupLogs, topPadding: 10), + child: EmptyPlaceholder( + text: appLocalizations.noBackupLogs, topPadding: 10), ); } @@ -232,9 +304,9 @@ class BackupLogScreenState extends BaseDynamicState { child: Column( mainAxisSize: MainAxisSize.min, children: List.generate( - appProvider.autoBackupLogs.length, + _mergedLogs.length, (index) => BackupLogItem( - log: appProvider.autoBackupLogs[index], + log: _mergedLogs[index], ), ), ), @@ -244,10 +316,10 @@ class BackupLogScreenState extends BaseDynamicState { return ListView.builder( padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), shrinkWrap: true, - itemCount: appProvider.autoBackupLogs.length, + itemCount: _mergedLogs.length, itemBuilder: (context, index) { return BackupLogItem( - log: appProvider.autoBackupLogs[index], + log: _mergedLogs[index], ); }, ); @@ -266,49 +338,83 @@ class BackupLogItem extends StatefulWidget { class BackupLogItemState extends BaseDynamicState { bool expanded = false; + static final DateFormat _timeFormat = DateFormat("HH:mm:ss"); + static final DateFormat _dateTimeFormat = DateFormat("yyyy-MM-dd HH:mm"); + @override Widget build(BuildContext context) { + final statusColor = widget.log.lastStatus.color; + return Container( margin: const EdgeInsets.only(bottom: 8), - child: Material( - color: ChewieTheme.canvasColor, - borderRadius: ChewieDimens.borderRadius8, - child: InkWell( - borderRadius: ChewieDimens.borderRadius8, - onTap: !expanded - ? () { - setState(() { - expanded = true; - }); - } - : null, - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: ChewieDimens.borderRadius8, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + child: GestureDetector( + onTap: () { + setState(() { + expanded = !expanded; + }); + }, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - widget.log.triggerType.label, - style: ChewieTheme.bodyMedium, + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: statusColor.withAlpha(30), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + widget.log.triggerType.icon, + size: 15, + color: statusColor, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.log.triggerType.label, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + '${_dateTimeFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.log.startTimestamp))} · ${widget.log.type.label}', + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color + ?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), - const Spacer(), + const SizedBox(width: 6), RoundIconTextButton( radius: 5, height: 24, padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), text: widget.log.lastStatusItem.labelShort, - textStyle: ChewieTheme.labelSmall - ?.apply(color: Colors.white), - background: widget.log.lastStatus.color, + textStyle: + ChewieTheme.labelSmall.apply(color: Colors.white), + background: statusColor, ), - const SizedBox(width: 5), + const SizedBox(width: 4), CircleIconButton( padding: const EdgeInsets.all(4), icon: Icon( @@ -316,7 +422,7 @@ class BackupLogItemState extends BaseDynamicState { ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded, size: 16, - color: ChewieTheme.labelSmall?.color), + color: ChewieTheme.labelSmall.color), onTap: () { setState(() { expanded = !expanded; @@ -325,26 +431,83 @@ class BackupLogItemState extends BaseDynamicState { ), ], ), - if (expanded) const SizedBox(height: 5), - if (expanded) _buildList(), + AnimatedSize( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + alignment: Alignment.topCenter, + child: expanded + ? Padding( + padding: const EdgeInsets.only(top: 10, left: 4), + child: _buildStatusTimeline(), + ) + : const SizedBox.shrink(), + ), ], ), ), ), - ), ); } - _buildList() { - return CustomHtmlWidget( - content: List.generate( - widget.log.status.length, - (i) { - AutoBackupLogStatusItem statusItem = widget.log.status[i]; - return '[${TimeUtil.timestampToDateString(statusItem.timestamp)}]: ${statusItem.label(widget.log)}'; - }, - ).join('
'), - style: ChewieTheme.labelSmall, + Widget _buildStatusTimeline() { + return Column( + mainAxisSize: MainAxisSize.min, + children: List.generate(widget.log.status.length, (i) { + final statusItem = widget.log.status[i]; + final isLast = i == widget.log.status.length - 1; + final dotColor = statusItem.status.color; + final timeStr = _timeFormat.format( + DateTime.fromMillisecondsSinceEpoch(statusItem.timestamp)); + + return IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 16, + child: Column( + children: [ + Container( + width: 6, + height: 6, + margin: const EdgeInsets.only(top: 5), + decoration: BoxDecoration( + color: dotColor, + shape: BoxShape.circle, + ), + ), + if (!isLast) + Expanded( + child: Container( + width: 1, + color: ChewieTheme.dividerColor, + ), + ), + ], + ), + ), + const SizedBox(width: 6), + Text( + timeStr, + style: ChewieTheme.labelSmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontFeatures: [const FontFeature.tabularFigures()], + ), + ), + const SizedBox(width: 8), + Expanded( + child: Padding( + padding: EdgeInsets.only(bottom: isLast ? 0 : 10), + child: Text( + statusItem.label(widget.log), + style: ChewieTheme.labelSmall, + ), + ), + ), + ], + ), + ); + }), ); } } diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/generic/generic_context_menu_overlay.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/generic/generic_context_menu_overlay.dart index d938a493..4d34c055 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/generic/generic_context_menu_overlay.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterContextMenu/generic/generic_context_menu_overlay.dart @@ -114,7 +114,11 @@ class GenericContextMenuOverlayState extends State child: ColoredBox( color: Colors.transparent, child: Listener( - onPointerDown: (e) => _mousePos = e.localPosition, + onPointerDown: (e) { + if (_currentMenu == null) { + _mousePos = e.localPosition; + } + }, child: Stack( alignment: Alignment.topLeft, children: [ From 0af92a991c2395b1463a226df9e97f0b0e4e21d1 Mon Sep 17 00:00:00 2001 From: dd
Date: Wed, 20 May 2026 16:44:48 +0800 Subject: [PATCH 23/36] Refactor backup bottom sheets and improve UI consistency - Updated the header layout in local, OneDrive, S3, and WebDAV backup bottom sheets for better visual hierarchy and alignment. - Enhanced button padding and margins for improved touch targets and aesthetics. - Changed file item layouts to include icons with consistent styling across all backup types. - Updated the color theme in ChewieTheme for better contrast and readability. - Added a callback in SelectTokenBottomSheet to notify changes. - Improved system UI overlay styles in main.dart for better navigation visibility. - Adjusted padding and margins in update log screen for a cleaner layout. - Refined context menu bottom sheet item design for better user interaction. - Updated dropdown and selection item components to use primary color for selected icons. --- lib/Screens/Lock/database_decrypt_screen.dart | 3 +- lib/Screens/Lock/pin_verify_screen.dart | 4 +- lib/Screens/Setting/backup_log_screen.dart | 3 +- lib/Screens/Setting/egg_screen.dart | 7 +- .../mobile_setting_navigation_screen.dart | 13 +- lib/Screens/Token/add_token_screen.dart | 8 +- lib/Screens/Token/category_screen.dart | 341 +++++++++++------- .../Token/import_export_token_screen.dart | 7 +- lib/Screens/Token/import_preview_screen.dart | 5 +- lib/Screens/feature_showcase_screen.dart | 74 ++-- lib/Screens/home_screen.dart | 17 +- lib/Screens/main_screen.dart | 10 +- lib/Utils/lottie_util.dart | 14 + .../aliyundrive_backups_bottom_sheet.dart | 163 +++++---- .../Backups/box_backups_bottom_sheet.dart | 163 +++++---- .../Backups/dropbox_backups_bottom_sheet.dart | 163 +++++---- .../googledrive_backups_bottom_sheet.dart | 163 +++++---- .../Backups/huawei_backups_bottom_sheet.dart | 173 +++++---- .../Backups/local_backups_bottom_sheet.dart | 170 +++++---- .../onedrive_backups_bottom_sheet.dart | 80 ++-- .../Backups/s3_backups_bottom_sheet.dart | 161 +++++---- .../Backups/webdav_backups_bottom_sheet.dart | 161 +++++---- .../select_token_bottom_sheet.dart | 3 + lib/main.dart | 4 +- .../lib/src/Resources/theme_color_data.dart | 62 ++-- .../lib/src/Screens/update_log_screen.dart | 196 ++++++---- .../lib/src/Widgets/Basic/item_builder.dart | 13 +- .../context_menu_bottom_sheet.dart | 114 +++--- .../src/Widgets/General/dropdown_wrapper.dart | 2 +- .../Widgets/Tile/inline_selection_item.dart | 2 +- .../lib/src/Widgets/Tile/selection_item.dart | 4 +- 31 files changed, 1374 insertions(+), 929 deletions(-) diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index ac090b3e..14ef3aaf 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -132,7 +132,8 @@ class DatabaseDecryptScreenState extends BaseWindowState } chewieProvider.loadingWidgetBuilder = (size, forceDark) => LottieFiles.load( LottieFiles.getLoadingPath(chewieProvider.rootContext), - scale: 1.5); + scale: 1.5, + delegates: LottieFiles.loadingDelegates(ChewieTheme.primaryColor)); trayManager.addListener(this); windowManager.addListener(this); Utils.initSimpleTray(); diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index bc8ae384..2a8c87ce 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -146,7 +146,9 @@ class PinVerifyScreenState extends BaseWindowState chewieProvider.loadingWidgetBuilder = (size, forceDark) => LottieFiles.load( LottieFiles.getLoadingPath(chewieProvider.rootContext), - scale: 1.5); + scale: 1.5, + delegates: + LottieFiles.loadingDelegates(ChewieTheme.primaryColor)); } }); _restoreLockoutState(); diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 87867999..f28c8cc4 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -142,7 +142,7 @@ class BackupLogScreenState extends BaseDynamicState { if (widget.isOverlay) { final overlayHeight = - _mergedLogs.isEmpty || !canBackup ? 200.0 : 400.0; + _mergedLogs.isEmpty || !canBackup ? 300.0 : 400.0; return Container( width: min(400, MediaQuery.sizeOf(context).width - 80), height: min(overlayHeight, MediaQuery.sizeOf(context).height - 80), @@ -168,6 +168,7 @@ class BackupLogScreenState extends BaseDynamicState { Container( constraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * 0.65, + minHeight: MediaQuery.sizeOf(context).height * 0.3, ), decoration: BoxDecoration( borderRadius: BorderRadius.vertical( diff --git a/lib/Screens/Setting/egg_screen.dart b/lib/Screens/Setting/egg_screen.dart index ea4104c3..e85e18b6 100644 --- a/lib/Screens/Setting/egg_screen.dart +++ b/lib/Screens/Setting/egg_screen.dart @@ -79,8 +79,10 @@ class _EggScreenState extends BaseDynamicState showBorder: true, showBack: true, ), - body: EasyRefresh( - child: ListView( + body: SafeArea( + top: false, + child: EasyRefresh( + child: ListView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 20), children: [ @@ -125,6 +127,7 @@ class _EggScreenState extends BaseDynamicState ], ), ), + ), ), Visibility( visible: _showCelebrate, diff --git a/lib/Screens/Setting/mobile_setting_navigation_screen.dart b/lib/Screens/Setting/mobile_setting_navigation_screen.dart index b690e3ac..3e2e7525 100644 --- a/lib/Screens/Setting/mobile_setting_navigation_screen.dart +++ b/lib/Screens/Setting/mobile_setting_navigation_screen.dart @@ -57,11 +57,13 @@ class _MobileSettingNavigationScreenState SizedBox(width: 5), ], ), - body: EasyRefresh( - child: ListView( - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 10), - children: [ + body: SafeArea( + top: false, + child: EasyRefresh( + child: ListView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 10), + children: [ EntryItem( title: appLocalizations.generalSetting, leading: LucideIcons.settings2, @@ -113,6 +115,7 @@ class _MobileSettingNavigationScreenState ], ), ), + ), ), ); } diff --git a/lib/Screens/Token/add_token_screen.dart b/lib/Screens/Token/add_token_screen.dart index 761f5913..54c9fd23 100644 --- a/lib/Screens/Token/add_token_screen.dart +++ b/lib/Screens/Token/add_token_screen.dart @@ -195,8 +195,11 @@ class _AddTokenScreenState extends BaseDynamicState ), ], ), - body: EasyRefresh( - child: _buildBody(), + body: SafeArea( + top: false, + child: EasyRefresh( + child: _buildBody(), + ), ), ); } @@ -434,6 +437,7 @@ class _AddTokenScreenState extends BaseDynamicState ? Wrap( spacing: 5, runSpacing: 5, + alignment: WrapAlignment.end, children: selectedCategoryUids .map( (e) => RoundIconTextButton( diff --git a/lib/Screens/Token/category_screen.dart b/lib/Screens/Token/category_screen.dart index e3043410..8f150728 100644 --- a/lib/Screens/Token/category_screen.dart +++ b/lib/Screens/Token/category_screen.dart @@ -14,6 +14,8 @@ */ import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/token_category_binding_dao.dart'; +import 'package:cloudotp/Models/opt_token.dart'; import 'package:cloudotp/Utils/app_provider.dart'; import 'package:cloudotp/Widgets/BottomSheet/select_token_bottom_sheet.dart'; import 'package:flutter/material.dart'; @@ -37,6 +39,7 @@ class CategoryScreen extends StatefulWidget { class _CategoryScreenState extends BaseDynamicState with TickerProviderStateMixin { List categories = []; + Map> _categoryTokens = {}; @override void initState() { @@ -49,6 +52,17 @@ class _CategoryScreenState extends BaseDynamicState setState(() { categories = value; }); + _loadTokensForCategories(); + }); + } + + Future _loadTokensForCategories() async { + final Map> result = {}; + for (final category in categories) { + result[category.uid] = await BindingDao.getTokens(category.uid); + } + setState(() { + _categoryTokens = result; }); } @@ -75,7 +89,10 @@ class _CategoryScreenState extends BaseDynamicState ), ], ), - body: _buildBody(), + body: SafeArea( + top: false, + child: _buildBody(), + ), ); } @@ -107,6 +124,7 @@ class _CategoryScreenState extends BaseDynamicState TokenCategory category = TokenCategory.title(title: text); await CategoryDao.insertCategory(category); categories.add(category); + _categoryTokens[category.uid] = []; setState(() {}); homeScreenState?.refreshCategories(); }, @@ -115,152 +133,231 @@ class _CategoryScreenState extends BaseDynamicState } _buildBody() { + if (categories.isEmpty) { + return EasyRefresh( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + children: [ + EmptyPlaceholder(text: appLocalizations.noCategory), + ], + ), + ); + } + return _buildGrid(); + } + + Widget _buildGrid() { return EasyRefresh( - child: categories.isEmpty - ? ListView( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - children: [ - EmptyPlaceholder(text: appLocalizations.noCategory), - ], - ) - : ReorderableListView.builder( - itemBuilder: (context, index) { - return _buildCategoryItem(categories[index]); - }, - cacheExtent: 9999, - padding: const EdgeInsets.only( - top: 6, - left: 12, - right: 12, - bottom: 30, - ), - buildDefaultDragHandles: false, - itemCount: categories.length, - onReorder: (oldIndex, newIndex) { - if (newIndex > oldIndex) newIndex -= 1; - TokenCategory oldCategory = categories[oldIndex]; - categories.removeAt(oldIndex); - categories.insert(newIndex, oldCategory); - for (int i = 0; i < categories.length; i++) { - categories[i].seq = i; - } - CategoryDao.updateCategories(categories, backup: true); - setState(() {}); - homeScreenState?.refreshCategories(); - }, - proxyDecorator: - (Widget child, int index, Animation animation) { - return Container( - decoration: BoxDecoration( - boxShadow: ChewieTheme.defaultBoxShadow, - ), - child: child, - ); - }, + child: ReorderableGridView.builder( + padding: const EdgeInsets.fromLTRB(12, 6, 12, 30), + gridDelegate: const SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 480, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + preferredHeight: 72, + ), + cacheExtent: 9999, + itemCount: categories.length, + onReorder: _onReorder, + proxyDecorator: + (Widget child, int index, Animation animation) { + return Container( + decoration: BoxDecoration( + boxShadow: ChewieTheme.defaultBoxShadow, ), + child: child, + ); + }, + itemBuilder: (context, index) { + return _buildCategoryItem(categories[index]); + }, + ), ); } - _buildCategoryItem(TokenCategory category) { + void _onReorder(int oldIndex, int newIndex) { + if (newIndex > oldIndex) newIndex -= 1; + TokenCategory oldCategory = categories[oldIndex]; + categories.removeAt(oldIndex); + categories.insert(newIndex, oldCategory); + for (int i = 0; i < categories.length; i++) { + categories[i].seq = i; + } + CategoryDao.updateCategories(categories, backup: true); + setState(() {}); + homeScreenState?.refreshCategories(); + } + + String _tokenSummary(TokenCategory category) { + final tokens = _categoryTokens[category.uid] ?? []; + if (tokens.isEmpty) return appLocalizations.noToken; + final names = tokens.map((t) => t.issuer).take(3).toList(); + if (tokens.length > 3) { + return '${names.join(', ')} ...'; + } + return names.join(', '); + } + + int _tokenCount(TokenCategory category) { + return _categoryTokens[category.uid]?.length ?? 0; + } + + Widget _buildCategoryItem(TokenCategory category) { + final accent = ChewieTheme.primaryColor; + final count = _tokenCount(category); + final summary = _tokenSummary(category); return Container( key: ValueKey("${category.id}${category.title}"), - margin: const EdgeInsets.symmetric(vertical: 5), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: ChewieTheme.canvasColor, - borderRadius: ChewieDimens.borderRadius8, - // border: Border.all(color: Theme.of(context).dividerColor, width: 0.5), + borderRadius: ChewieDimens.borderRadius12, ), child: Row( children: [ - ReorderableDragStartListener( - index: categories.indexOf(category), - child: CircleIconButton( - icon: const Icon(Icons.dehaze_rounded, size: 20), - onTap: () {}, + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: accent.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.shapes, size: 17, color: accent), ), - const SizedBox(width: 5), + const SizedBox(width: 10), Expanded( - child: Text( - category.title, - style: ChewieTheme.titleMedium, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Flexible( + child: Text( + category.title, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: accent.withAlpha(25), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '$count', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: accent, + ), + ), + ), + ], + ), + const SizedBox(height: 2), + Text( + summary, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ), ), + const SizedBox(width: 6), CircleIconButton( - icon: const Icon(Icons.edit_rounded, size: 20), - onTap: () { - InputValidateAsyncController validateAsyncController = - InputValidateAsyncController( - validator: (text) async { - if (text.isEmpty) { - return appLocalizations.categoryNameCannotBeEmpty; - } - if (text != category.title && - await CategoryDao.isCategoryExist(text)) { - return appLocalizations.categoryNameDuplicate; - } - return null; - }, - controller: TextEditingController(), - ); - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => InputBottomSheet( - title: appLocalizations.editCategoryName, - hint: appLocalizations.inputCategory, - style: InputItemStyle( - maxLength: 32, - ), - text: category.title, - validateAsyncController: validateAsyncController, - onValidConfirm: (text) async { - category.title = text; - await CategoryDao.updateCategory(category); - setState(() {}); - homeScreenState?.refreshCategories(); - }, - ), - ); - }, + icon: + Icon(LucideIcons.pencil, size: 16, color: ChewieTheme.iconColor), + onTap: () => _editCategory(category), ), - const SizedBox(width: 5), CircleIconButton( - icon: const Icon(Icons.checklist_rounded, size: 20), - onTap: () { - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => SelectTokenBottomSheet(category: category), - ); - }, + icon: Icon(LucideIcons.listChecks, + size: 16, color: ChewieTheme.iconColor), + onTap: () => _editTokens(category), ), - const SizedBox(width: 5), CircleIconButton( - icon: const Icon(Icons.delete_outline_rounded, - size: 20, color: Colors.red), - onTap: () { - DialogBuilder.showConfirmDialog( - context, - title: appLocalizations.deleteCategory, - message: appLocalizations.deleteCategoryHint(category.title), - confirmButtonText: appLocalizations.confirm, - cancelButtonText: appLocalizations.cancel, - onTapConfirm: () async { - await CategoryDao.deleteCategory(category); - IToast.showTop( - appLocalizations.deleteCategorySuccess(category.title)); - categories.remove(category); - setState(() {}); - homeScreenState?.refreshCategories(); - }, - onTapCancel: () {}, - ); - }, + icon: const Icon(LucideIcons.trash2, size: 16, color: Colors.red), + onTap: () => _deleteCategory(category), ), ], ), ); } + + void _editCategory(TokenCategory category) { + InputValidateAsyncController validateAsyncController = + InputValidateAsyncController( + validator: (text) async { + if (text.isEmpty) { + return appLocalizations.categoryNameCannotBeEmpty; + } + if (text != category.title && + await CategoryDao.isCategoryExist(text)) { + return appLocalizations.categoryNameDuplicate; + } + return null; + }, + controller: TextEditingController(), + ); + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => InputBottomSheet( + title: appLocalizations.editCategoryName, + hint: appLocalizations.inputCategory, + style: InputItemStyle( + maxLength: 32, + ), + text: category.title, + validateAsyncController: validateAsyncController, + onValidConfirm: (text) async { + category.title = text; + await CategoryDao.updateCategory(category); + setState(() {}); + homeScreenState?.refreshCategories(); + }, + ), + ); + } + + void _editTokens(TokenCategory category) { + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => SelectTokenBottomSheet( + category: category, + onChanged: () { + _loadTokensForCategories(); + }, + ), + ); + } + + void _deleteCategory(TokenCategory category) { + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.deleteCategory, + message: appLocalizations.deleteCategoryHint(category.title), + confirmButtonText: appLocalizations.confirm, + cancelButtonText: appLocalizations.cancel, + onTapConfirm: () async { + await CategoryDao.deleteCategory(category); + IToast.showTop( + appLocalizations.deleteCategorySuccess(category.title)); + categories.remove(category); + _categoryTokens.remove(category.uid); + setState(() {}); + homeScreenState?.refreshCategories(); + }, + onTapCancel: () {}, + ); + } } diff --git a/lib/Screens/Token/import_export_token_screen.dart b/lib/Screens/Token/import_export_token_screen.dart index 4ddd7e3e..8c14d4aa 100644 --- a/lib/Screens/Token/import_export_token_screen.dart +++ b/lib/Screens/Token/import_export_token_screen.dart @@ -56,8 +56,11 @@ class _ImportExportTokenScreenState actions: ResponsiveUtil.isLandscapeLayout() ? [] : [const BlankIconButton()], ), - body: EasyRefresh( - child: _buildBody(), + body: SafeArea( + top: false, + child: EasyRefresh( + child: _buildBody(), + ), ), ); } diff --git a/lib/Screens/Token/import_preview_screen.dart b/lib/Screens/Token/import_preview_screen.dart index 652a22b7..9e161b26 100644 --- a/lib/Screens/Token/import_preview_screen.dart +++ b/lib/Screens/Token/import_preview_screen.dart @@ -462,12 +462,13 @@ class _ImportPreviewScreenState extends BaseDynamicState { } Widget _buildBottomBar() { + final bottomInset = MediaQuery.of(context).viewPadding.bottom; return Container( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( left: 16, right: 16, top: 12, - bottom: 12, + bottom: 12 + bottomInset, ), decoration: BoxDecoration( color: ChewieTheme.scaffoldBackgroundColor, diff --git a/lib/Screens/feature_showcase_screen.dart b/lib/Screens/feature_showcase_screen.dart index 3e191f58..5ca7ee2a 100644 --- a/lib/Screens/feature_showcase_screen.dart +++ b/lib/Screens/feature_showcase_screen.dart @@ -440,42 +440,48 @@ class _FeatureShowcaseScreenState ), content: Column( children: [ - Wrap( - alignment: WrapAlignment.center, - spacing: 8, - runSpacing: 8, - children: services - .map((s) => _buildChip( - label: s, - color: _cloudColor, - icon: LucideIcons.cloud, - )) - .toList(), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: services + .map((s) => _buildChip( + label: s, + color: _cloudColor, + icon: LucideIcons.cloud, + )) + .toList(), + ), ), const SizedBox(height: 18), - Container( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), - decoration: BoxDecoration( - color: _cloudColor.withAlpha(20), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: _cloudColor.withAlpha(50)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildBadge( - LucideIcons.refreshCw, - appLocalizations.featureCloudAuto, - _cloudColor), - _buildBadge( - LucideIcons.lock, - appLocalizations.featureCloudEncrypted, - _cloudColor), - _buildBadge( - LucideIcons.fileText, - appLocalizations.featureCloudLog, - _cloudColor), - ], + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + decoration: BoxDecoration( + color: _cloudColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: _cloudColor.withAlpha(50)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildBadge( + LucideIcons.refreshCw, + appLocalizations.featureCloudAuto, + _cloudColor), + _buildBadge( + LucideIcons.lock, + appLocalizations.featureCloudEncrypted, + _cloudColor), + _buildBadge( + LucideIcons.fileText, + appLocalizations.featureCloudLog, + _cloudColor), + ], + ), ), ), ], diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index c78c993e..37fd6530 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -1338,8 +1338,10 @@ class HomeScreenState extends BasePanelScreenState if (_multiSelectMode) { return const SizedBox.shrink(); } + double bottomInset = MediaQuery.of(context).viewPadding.bottom; double height = kToolbarHeight + verticalPadding * 2 + + bottomInset + (ResponsiveUtil.isLandscapeTablet() ? 24 : 0); return Selector( selector: (context, provider) => provider.hideBottombarWhenScrolling, @@ -1363,7 +1365,7 @@ class HomeScreenState extends BasePanelScreenState // border: ChewieTheme.topDivider, ), padding: EdgeInsets.symmetric(vertical: 5 + verticalPadding) - .copyWith(right: 70), + .copyWith(right: 70, bottom: 5 + verticalPadding + bottomInset), child: _buildTabBar(const EdgeInsets.only(left: 10, right: 10)), ); return ScrollToHide( @@ -1402,7 +1404,9 @@ class HomeScreenState extends BasePanelScreenState hideBottombar: provider.hideBottombarWhenScrolling, hideProgress: provider.hideProgressBar, ), - builder: (context, settings, child) => ReorderableGridView.builder( + builder: (context, settings, child) { + double bottomPadding = MediaQuery.of(context).viewPadding.bottom; + return ReorderableGridView.builder( // controller: _scrollController, gridItemsNotifier: gridItemsNotifier, autoScroll: true, @@ -1412,10 +1416,10 @@ class HomeScreenState extends BasePanelScreenState right: 10, top: 10, bottom: _multiSelectMode - ? 80 + ? 80 + bottomPadding : settings.hideBottombar || categories.isEmpty - ? 10 - : 85), + ? 10 + bottomPadding + : 85 + bottomPadding), gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: layoutType.maxCrossAxisExtent, crossAxisSpacing: 8, @@ -1476,7 +1480,8 @@ class HomeScreenState extends BasePanelScreenState onEnterMultiSelect: () => enterMultiSelectMode(tokens[index].uid), ); }, - ), + ); + }, ); Widget body = tokens.isEmpty ? ListView( diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 90911a35..ec2c4c37 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -153,17 +153,23 @@ class MainScreenState extends BaseWindowState EasyRefresh.defaultHeaderBuilder = () => LottieCupertinoHeader( backgroundColor: ChewieTheme.canvasColor, indicator: LottieFiles.load(LottieFiles.getLoadingPath(context), - scale: 1.5), + scale: 1.5, + delegates: + LottieFiles.loadingDelegates(ChewieTheme.primaryColor)), hapticFeedback: true, triggerOffset: 40, ); EasyRefresh.defaultFooterBuilder = () => LottieCupertinoFooter( - indicator: LottieFiles.load(LottieFiles.getLoadingPath(context)), + indicator: LottieFiles.load(LottieFiles.getLoadingPath(context), + delegates: + LottieFiles.loadingDelegates(ChewieTheme.primaryColor)), ); chewieProvider.loadingWidgetBuilder = (size, forceDark) => LottieFiles.load( LottieFiles.getLoadingPath(chewieProvider.rootContext), scale: 1.5, + delegates: + LottieFiles.loadingDelegates(ChewieTheme.primaryColor), ); }); initGlobalConfig(); diff --git a/lib/Utils/lottie_util.dart b/lib/Utils/lottie_util.dart index c41b2946..049d1a30 100644 --- a/lib/Utils/lottie_util.dart +++ b/lib/Utils/lottie_util.dart @@ -15,6 +15,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; class LottieFiles { @@ -25,6 +26,17 @@ class LottieFiles { static const String moonLight = "assets/lottie/moon_light.json"; static const String sunLight = "assets/lottie/sun_light.json"; + static LottieDelegates loadingDelegates(Color color) { + return LottieDelegates( + values: [ + ValueDelegate.strokeColor( + const ['**'], + value: color, + ), + ], + ); + } + static Transform load( String path, { double size = 40, @@ -33,6 +45,7 @@ class LottieFiles { Function()? onLoaded, BoxFit? fit, double scale = 1.0, + LottieDelegates? delegates, }) { return Transform.scale( scale: scale, @@ -42,6 +55,7 @@ class LottieFiles { height: size, fit: fit, controller: controller, + delegates: delegates, alignment: Alignment.bottomCenter, addRepaintBoundary: true, renderCache: diff --git a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart index cc47f718..9f34a000 100644 --- a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart @@ -81,16 +81,29 @@ class AliyunDriveBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -98,7 +111,7 @@ class AliyunDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -107,69 +120,79 @@ class AliyunDriveBackupsBottomSheetState _buildItem(AliyunDriveFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.id); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error( - "Failed to delete backup file from aliyunDrive", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.id); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error( + "Failed to delete backup file from aliyunDrive", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart index 2a6443df..df70c724 100644 --- a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart @@ -80,16 +80,29 @@ class BoxBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -97,7 +110,7 @@ class BoxBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -106,69 +119,79 @@ class BoxBackupsBottomSheetState _buildItem(BoxFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.id); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error( - "Failed to delete backup file from box", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.id); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error( + "Failed to delete backup file from box", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart index dfa7daf4..3f1b880c 100644 --- a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart @@ -82,16 +82,29 @@ class DropboxBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -99,7 +112,7 @@ class DropboxBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -108,69 +121,79 @@ class DropboxBackupsBottomSheetState _buildItem(DropboxFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.name); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error( - "Failed to delete backup file from dropbox", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.name); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error( + "Failed to delete backup file from dropbox", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart index a86c582b..097245f0 100644 --- a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart @@ -82,16 +82,29 @@ class GoogleDriveBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -99,7 +112,7 @@ class GoogleDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -108,69 +121,79 @@ class GoogleDriveBackupsBottomSheetState _buildItem(GoogleDriveFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.id); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error( - "Failed to delete file from google drive", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.id); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error( + "Failed to delete file from google drive", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart index 5072828b..86988194 100644 --- a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart @@ -81,16 +81,29 @@ class HuaweiCloudBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -98,7 +111,7 @@ class HuaweiCloudBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -107,74 +120,84 @@ class HuaweiCloudBackupsBottomSheetState _buildItem(HuaweiCloudFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - bool success = - await widget.cloudService.deleteFile(file.id); - if (success) { - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } else { - IToast.showTop(appLocalizations.deleteFailed); - } - } catch (e, t) { - ILogger.error( - "Failed to delete file from huawei cloud", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + bool success = + await widget.cloudService.deleteFile(file.id); + if (success) { + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } else { + IToast.showTop(appLocalizations.deleteFailed); + } + } catch (e, t) { + ILogger.error( + "Failed to delete file from huawei cloud", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart index 407d2e31..72fc5ce9 100644 --- a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart @@ -87,17 +87,35 @@ class LocalBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations - .cloudBackupFiles(files.length + defaultPathBackupFiles.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.hardDrive, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + appLocalizations.cloudBackupFiles( + files.length + defaultPathBackupFiles.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ], ), ); } @@ -105,7 +123,7 @@ class LocalBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem( index < files.length ? files[index] @@ -121,69 +139,79 @@ class LocalBackupsBottomSheetState fractionDigits: 0); String time = TimeUtil.formatTimestamp( file.statSync().modified.millisecondsSinceEpoch); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - FileUtil.getFileNameWithExtension(file.path), - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size${isDefaultPath ? " ${appLocalizations.fromInternalBackupPath}" : ""}", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + FileUtil.getFileNameWithExtension(file.path), + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await file.delete(); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error( - "Failed to delete backup file from local", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size${isDefaultPath ? " · ${appLocalizations.fromInternalBackupPath}" : ""}", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await file.delete(); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error( + "Failed to delete backup file from local", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart index 05c59fde..8bce72be 100644 --- a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart @@ -81,16 +81,29 @@ class OneDriveBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -98,7 +111,7 @@ class OneDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -108,43 +121,60 @@ class OneDriveBackupsBottomSheetState String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.lastModifiedDateTime); return Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), - ), + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, ), child: Row( children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), + ), + const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( file.name, - style: ChewieTheme.titleMedium, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), + const SizedBox(height: 2), Text( - "$time $size", - style: ChewieTheme.bodySmall, + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), ), + const SizedBox(width: 6), CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), - const SizedBox(width: 5), CircleIconButton( - icon: const Icon(LucideIcons.trash, color: Colors.red, size: 20), + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { diff --git a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart index cb9e3a1c..1f0866f3 100644 --- a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart @@ -82,16 +82,29 @@ class S3CloudBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -99,7 +112,7 @@ class S3CloudBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -108,68 +121,78 @@ class S3CloudBackupsBottomSheetState _buildItem(S3CloudFileInfo file) { String size = CacheUtil.renderSize(file.size.toDouble(), fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.modifyTimestamp); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name, - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.path); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error("Failed to delete file from s3 cloud", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + ], + ), ), - ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, + ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.path); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error("Failed to delete file from s3 cloud", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart index bba5294b..c30810d1 100644 --- a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart @@ -81,16 +81,29 @@ class WebDavBackupsBottomSheetState } _buildHeader() { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical(top: radius), - color: ChewieTheme.canvasColor, - ), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), - alignment: Alignment.center, - child: Text( - appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium.apply(fontWeightDelta: 2), + return Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.cloud, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.cloudBackupFiles(widget.files.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } @@ -98,7 +111,7 @@ class WebDavBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -109,68 +122,78 @@ class WebDavBackupsBottomSheetState CacheUtil.renderSize(file.size?.toDouble() ?? 0, fractionDigits: 0); String time = TimeUtil.formatTimestamp(file.mTime?.millisecondsSinceEpoch ?? 0); - return Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - width: 0.5, - color: Theme.of(context).dividerColor, - ), + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(10), ), + child: Icon(LucideIcons.fileArchive, + size: 17, color: ChewieTheme.primaryColor), ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.name ?? "", - style: ChewieTheme.titleMedium, - ), - Text( - "$time $size", - style: ChewieTheme.bodySmall, - ), - ], + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.name ?? "", + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - CircleIconButton( - icon: const Icon(LucideIcons.import, size: 20), - onTap: () async { - Navigator.pop(context); - widget.onSelected(file); - }, - ), - const SizedBox(width: 5), - CircleIconButton( - icon: - const Icon(LucideIcons.trash, color: Colors.red, size: 20), - onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); - try { - await widget.cloudService.deleteFile(file.path ?? ""); - setState(() { - files.remove(file); - }); - IToast.showTop(appLocalizations.deleteSuccess); - } catch (e, t) { - ILogger.error("Failed to delete file from webdav", e, t); - IToast.showTop(appLocalizations.deleteFailed); - } - CustomLoadingDialog.dismissLoading(); - }, - ), - ], + const SizedBox(height: 2), + Text( + "$time · $size", + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 6), + CircleIconButton( + icon: Icon(LucideIcons.import, size: 18, + color: ChewieTheme.primaryColor), + onTap: () async { + Navigator.pop(context); + widget.onSelected(file); + }, ), - ), + CircleIconButton( + icon: + const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + onTap: () async { + CustomLoadingDialog.showLoading( + title: appLocalizations.deleting); + try { + await widget.cloudService.deleteFile(file.path ?? ""); + setState(() { + files.remove(file); + }); + IToast.showTop(appLocalizations.deleteSuccess); + } catch (e, t) { + ILogger.error("Failed to delete file from webdav", e, t); + IToast.showTop(appLocalizations.deleteFailed); + } + CustomLoadingDialog.dismissLoading(); + }, + ), + ], ), ); } diff --git a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart index 70d0be9d..162bc172 100644 --- a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart @@ -29,9 +29,11 @@ class SelectTokenBottomSheet extends StatefulWidget { const SelectTokenBottomSheet({ super.key, required this.category, + this.onChanged, }); final TokenCategory category; + final VoidCallback? onChanged; @override SelectTokenBottomSheetState createState() => SelectTokenBottomSheetState(); @@ -156,6 +158,7 @@ class SelectTokenBottomSheetState widget.category.uid, unSelectedUids); await CategoryDao.updateCategory(widget.category); homeScreenState?.changeTokensForCategory(widget.category); + widget.onChanged?.call(); IToast.showTop(appLocalizations.saveSuccess); Navigator.of(context).pop(); }, diff --git a/lib/main.dart b/lib/main.dart index b7107608..f4bd7752 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -141,7 +141,9 @@ Future initAndroid() async { await initDisplayMode(); SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, - statusBarIconBrightness: Brightness.dark); + statusBarIconBrightness: Brightness.dark, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: Brightness.dark); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); } diff --git a/third-party/chewie/lib/src/Resources/theme_color_data.dart b/third-party/chewie/lib/src/Resources/theme_color_data.dart index f8adb148..1f1c6878 100644 --- a/third-party/chewie/lib/src/Resources/theme_color_data.dart +++ b/third-party/chewie/lib/src/Resources/theme_color_data.dart @@ -320,40 +320,40 @@ class ChewieThemeColorData { isDarkMode: true, id: "BlueIron", name: "蓝铁", - scaffoldBackgroundColor: const Color(0xFF1D2733), - canvasColor: const Color(0xFF242E39), - cardColor: const Color(0xFF2E3A45), - primaryColor: const Color(0xFF14C2BB), - hintColor: const Color(0xFF14C2BB), - cursorColor: const Color(0xFF14C2BB), - indicatorColor: const Color(0xFF14C2BB), - textSelectionColor: const Color(0xFF14C2BB).withAlpha(70), - textSelectionHandleColor: const Color(0xFF14C2BB), - textColor: const Color(0xFFB8B8B8), - textLightGreyColor: const Color(0xFF94A3B8), - textDarkGreyColor: const Color(0xFF5C677D), - iconColor: const Color(0xFFB8B8B8), + scaffoldBackgroundColor: const Color(0xFF1A2332), + canvasColor: const Color(0xFF212D3B), + cardColor: const Color(0xFF2A3847), + primaryColor: const Color(0xFF4FC3C3), + hintColor: const Color(0xFF4FC3C3), + cursorColor: const Color(0xFF4FC3C3), + indicatorColor: const Color(0xFF4FC3C3), + textSelectionColor: const Color(0xFF4FC3C3).withAlpha(70), + textSelectionHandleColor: const Color(0xFF4FC3C3), + textColor: const Color(0xFFDCE4EC), + textLightGreyColor: const Color(0xFFA8B8C8), + textDarkGreyColor: const Color(0xFF7A8FA3), + iconColor: const Color(0xFFC5D0DB), hoverColor: const Color(0x22C8C8C8), splashColor: const Color(0x22CCCCCC), highlightColor: const Color(0x22CFCFCF), - shadowColor: const Color(0xFF1B2530), - appBarShadowColor: const Color(0xFF1B2530), - appBarBackgroundColor: const Color(0xFF242E39), - appBarSurfaceTintColor: const Color(0xFF242E39), - buttonPrimaryColor: const Color(0xFFF2F2F2), - buttonSecondaryColor: const Color(0xFF333333), - buttonHoverColor: const Color(0xFF333333), - buttonLightHoverColor: const Color(0xFF2C2C2C), - buttonDisabledColor: const Color(0xFF4A4A4A), - dividerColor: const Color(0xFF2D3743), - borderColor: const Color(0xFF2D3743), - scrollBarThumbColor: const Color(0xFF5A5A5A), - scrollBarThumbHoverColor: const Color(0xFF242E39), - scrollBarTrackColor: const Color(0xFF2D3743), - scrollBarTrackHoverColor: const Color(0xFF404040), - successColor: const Color(0xFF81C784), - warningColor: const Color(0xFFFFA726), - errorColor: const Color(0xFFCF6679), + shadowColor: const Color(0xFF131C28), + appBarShadowColor: const Color(0xFF131C28), + appBarBackgroundColor: const Color(0xFF212D3B), + appBarSurfaceTintColor: const Color(0xFF212D3B), + buttonPrimaryColor: const Color(0xFFE8EDF2), + buttonSecondaryColor: const Color(0xFF2E3D4D), + buttonHoverColor: const Color(0xFF2E3D4D), + buttonLightHoverColor: const Color(0xFF283545), + buttonDisabledColor: const Color(0xFF3A4A5A), + dividerColor: const Color(0xFF2C3A4A), + borderColor: const Color(0xFF2C3A4A), + scrollBarThumbColor: const Color(0xFF4A5A6A), + scrollBarThumbHoverColor: const Color(0xFF5A6A7A), + scrollBarTrackColor: const Color(0xFF253040), + scrollBarTrackHoverColor: const Color(0xFF2E3D4D), + successColor: const Color(0xFF6ECF8E), + warningColor: const Color(0xFFFFB74D), + errorColor: const Color(0xFFEF7B7B), ), ChewieThemeColorData( isDarkMode: true, diff --git a/third-party/chewie/lib/src/Screens/update_log_screen.dart b/third-party/chewie/lib/src/Screens/update_log_screen.dart index 91f14268..0cbcabe7 100644 --- a/third-party/chewie/lib/src/Screens/update_log_screen.dart +++ b/third-party/chewie/lib/src/Screens/update_log_screen.dart @@ -106,7 +106,7 @@ class _UpdateLogScreenState extends BaseDynamicState }, child: ListView.builder( padding: widget.padding - .add(const EdgeInsets.symmetric(horizontal: 8, vertical: 20)), + .add(const EdgeInsets.symmetric(horizontal: 4, vertical: 10)), itemBuilder: (context, index) => _buildItem( releaseItems[index], index, @@ -122,81 +122,126 @@ class _UpdateLogScreenState extends BaseDynamicState final isCurrent = ChewieUtils.compareVersion( item.tagName.replaceAll(RegExp(r'[a-zA-Z]'), ''), currentVersion) == 0; + final isLatest = index == 0; final releaseDate = item.publishedAt != null ? TimeUtil.formatDate(item.publishedAt!) : ""; - final color = HSLColor.fromAHSL( - 1.0, - 140 + (index * 220 / (releaseItems.length + 1)), - 0.6, - isCurrent ? 0.5 : 0.4, - ).toColor(); + final accent = isCurrent ? ChewieTheme.primaryColor : ChewieTheme.iconColor; - return IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - Container( - width: 14, - height: 14, + return Stack( + children: [ + if (!isLast) + Positioned( + left: 9, + top: 26, + bottom: 0, + child: Container( + width: 1.5, + color: ChewieTheme.dividerColor, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 16), + child: Container( + width: 10, + height: 10, + margin: const EdgeInsets.only(left: 5), decoration: BoxDecoration( shape: BoxShape.circle, - color: isCurrent ? ChewieTheme.primaryColor : color, - border: Border.all(color: Colors.grey.shade300, width: 2), - ), - ).animate().fadeIn(duration: 400.ms).scale(delay: 50.ms), - if (!isLast) - Expanded( - child: Container( + color: isCurrent ? accent : accent.withAlpha(60), + border: Border.all( + color: isCurrent ? accent : ChewieTheme.dividerColor, width: 2, - margin: const EdgeInsets.only(top: 2), - color: Colors.grey.shade300, ), ), - ], - ), - const SizedBox(width: 16), - Expanded( - child: Container( - margin: const EdgeInsets.only(bottom: 16), + ).animate().fadeIn(duration: 300.ms).scale(delay: 30.ms), + ), + const SizedBox(width: 13), + Expanded( + child: Container( + margin: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - "${item.tagName} $releaseDate", - style: ChewieTheme.bodyMedium, - ), - const SizedBox(width: 6), - if (isCurrent) - RoundIconTextButton( - height: 20, - text: chewieLocalizations.currentVersion, - background: ChewieTheme.primaryColor, - textStyle: ChewieTheme.labelMedium.apply( - color: ChewieTheme.primaryButtonColor, - ), - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - radius: 4, + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: accent.withAlpha(30), + borderRadius: BorderRadius.circular(8), ), - const Spacer(), - ClickableGestureDetector( - // padding: const EdgeInsets.symmetric( - // horizontal: 6, - // vertical: 2, - // ), child: Icon( - LucideIcons.chevronRight, - size: 16, - color: ChewieTheme.labelMedium.color, + isLatest + ? LucideIcons.sparkles + : LucideIcons.tag, + size: 15, + color: accent, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + item.tagName, + style: ChewieTheme.bodyMedium.copyWith( + fontWeight: FontWeight.w600, + ), + ), + if (isCurrent) ...[ + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: accent.withAlpha(25), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + chewieLocalizations.currentVersion, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: accent, + ), + ), + ), + ], + ], + ), + if (releaseDate.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + releaseDate, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color + ?.withAlpha(120), + fontSize: 11, + ), + ), + ], + ], + ), + ), + CircleIconButton( + icon: Icon( + LucideIcons.externalLink, + size: 14, + color: ChewieTheme.iconColor, ), onTap: () { UriUtil.launchUrlUri(context, item.htmlUrl); @@ -205,18 +250,22 @@ class _UpdateLogScreenState extends BaseDynamicState ], ), if ((item.body ?? "").isNotEmpty) ...[ - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: ChewieTheme.cardColor, - borderRadius: ChewieDimens.borderRadius8, - ), - child: SelectableAreaWrapper( - focusNode: FocusNode(), - child: CustomMarkdownWidget( - item.body ?? "", - baseStyle: ChewieTheme.bodyMedium, + Padding( + padding: const EdgeInsets.only(top: 10), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + borderRadius: ChewieDimens.borderRadius8, + ), + child: SelectableAreaWrapper( + focusNode: FocusNode(), + child: CustomMarkdownWidget( + item.body ?? "", + baseStyle: ChewieTheme.bodySmall, + ), ), ), ), @@ -225,8 +274,9 @@ class _UpdateLogScreenState extends BaseDynamicState ), ), ), - ], - ), + ], + ), + ], ); } } diff --git a/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart b/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart index c6e4a13c..bde5c424 100644 --- a/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart +++ b/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart @@ -49,10 +49,13 @@ class ItemBuilder { ) : null, body: overrideBody ?? - EasyRefresh( - child: ListView( - padding: padding, - children: children, + SafeArea( + top: false, + child: EasyRefresh( + child: ListView( + padding: padding, + children: children, + ), ), ), ); @@ -251,7 +254,7 @@ class ItemBuilder { mainGroupAlignment: mainGroupAlignment, ), onSelected: onSelected, - maxSelected: 1, + maxSelected: isRadio ? 1 : null, controller: controller, buttons: buttons, buttonBuilder: (selected, label, context, onTap, __) { diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart index 64b627d9..2cac8e0a 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart @@ -61,7 +61,7 @@ class ContextMenuBottomSheetState extends State { Stack( alignment: Alignment.center, children: [ - const SizedBox(height: 40), + const SizedBox(height: 36), Container( width: 50, height: 5, @@ -72,12 +72,21 @@ class ContextMenuBottomSheetState extends State { ), ], ), - for (var config in widget.menu.entries) - _buildConfigItem( - config as FlutterContextMenuItem, - config == widget.menu.entries.first, - config == widget.menu.entries.last, + Padding( + padding: EdgeInsets.fromLTRB( + 14, ResponsiveUtil.isWideDevice() ? 14 : 0, 14, 14), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (var config in widget.menu.entries) + _buildConfigItem( + config as FlutterContextMenuItem, + config == widget.menu.entries.first, + config == widget.menu.entries.last, + ), + ], ), + ), ], ), ), @@ -92,64 +101,77 @@ class ContextMenuBottomSheetState extends State { ]) { Color? textColor; if (config == null || config.type == MenuItemType.divider) { - return const MyDivider(width: 1.5, vertical: 12, horizontal: 16); + return const MyDivider(width: 1.5, vertical: 4, horizontal: 4); } else { + Color iconColor = ChewieTheme.primaryColor; switch (config.status) { case MenuItemStatus.success: textColor = ChewieTheme.successColor; + iconColor = ChewieTheme.successColor; break; case MenuItemStatus.warning: textColor = ChewieTheme.warningColor; + iconColor = ChewieTheme.warningColor; break; case MenuItemStatus.error: textColor = ChewieTheme.errorColor; + iconColor = ChewieTheme.errorColor; break; default: textColor = null; + iconColor = ChewieTheme.primaryColor; break; } - var borderRadius = BorderRadius.vertical( - top: isFirst - ? ResponsiveUtil.isWideDevice() - ? radius - : Radius.zero - : Radius.zero, - bottom: isLast - ? ResponsiveUtil.isWideDevice() - ? radius - : Radius.zero - : Radius.zero, - ); - return Material( - color: ChewieTheme.scaffoldBackgroundColor, - borderRadius: borderRadius, - child: InkWell( - borderRadius: borderRadius, - onTap: () { - Navigator.of(context).pop(); - config.onPressed?.call(); - }, - child: Container( - decoration: BoxDecoration(borderRadius: borderRadius), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: Row( - children: [ - if (config.type != MenuItemType.checkbox && - config.iconData != null) ...[ - Icon(config.iconData, size: 24, color: textColor), - const SizedBox(width: 10), - ], - if (config.type == MenuItemType.checkbox && config.checked) - Icon(Icons.check_rounded, size: 20, color: textColor), - if (config.type == MenuItemType.checkbox && !config.checked) - const SizedBox(width: 20, height: 20), - if (config.iconData != null) const SizedBox(width: 10), - Text( + return GestureDetector( + onTap: () { + Navigator.of(context).pop(); + config.onPressed?.call(); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 6), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Row( + children: [ + if (config.type != MenuItemType.checkbox && + config.iconData != null) ...[ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: iconColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(config.iconData, size: 17, color: iconColor), + ), + const SizedBox(width: 12), + ], + if (config.type == MenuItemType.checkbox && config.checked) ...[ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: iconColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(Icons.check_rounded, size: 17, color: iconColor), + ), + const SizedBox(width: 12), + ], + if (config.type == MenuItemType.checkbox && !config.checked) ...[ + const SizedBox(width: 34, height: 34), + const SizedBox(width: 12), + ], + Expanded( + child: Text( config.label, style: ChewieTheme.bodyLarge.apply(color: textColor), ), - ], - ), + ), + ], ), ), ); diff --git a/third-party/chewie/lib/src/Widgets/General/dropdown_wrapper.dart b/third-party/chewie/lib/src/Widgets/General/dropdown_wrapper.dart index 34c6ce4c..52defd79 100644 --- a/third-party/chewie/lib/src/Widgets/General/dropdown_wrapper.dart +++ b/third-party/chewie/lib/src/Widgets/General/dropdown_wrapper.dart @@ -47,7 +47,7 @@ class DropdownWrapper extends StatelessWidget { closedBorder: ChewieTheme.borderWithWidth(1), closedBorderRadius: ChewieDimens.borderRadius8, listItemDecoration: ListItemDecoration( - selectedIconColor: ChewieTheme.successColor, + selectedIconColor: ChewieTheme.primaryColor, splashColor: ChewieTheme.splashColor, highlightColor: ChewieTheme.highlightColor, selectedColor: ChewieTheme.hoverColor, diff --git a/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart b/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart index 979814c5..641eb82a 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart @@ -296,7 +296,7 @@ class InlineSelectionItemState splashColor: ChewieTheme.splashColor, highlightColor: ChewieTheme.highlightColor, selectedColor: ChewieTheme.hoverColor, - selectedIconColor: ChewieTheme.successColor, + selectedIconColor: ChewieTheme.primaryColor, ), closedFillColor: ChewieTheme.canvasColor, expandedFillColor: ChewieTheme.scaffoldBackgroundColor, diff --git a/third-party/chewie/lib/src/Widgets/Tile/selection_item.dart b/third-party/chewie/lib/src/Widgets/Tile/selection_item.dart index 07a120b0..0806d03c 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/selection_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/selection_item.dart @@ -280,7 +280,7 @@ class SelectionItemState splashColor: Theme.of(context).splashColor, highlightColor: Theme.of(context).highlightColor, selectedColor: Theme.of(context).hoverColor, - selectedIconColor: ChewieTheme.successColor, + selectedIconColor: ChewieTheme.primaryColor, ), searchFieldDecoration: SearchFieldDecoration( hintStyle: @@ -337,7 +337,7 @@ class SelectionItemState splashColor: ChewieTheme.splashColor, highlightColor: ChewieTheme.highlightColor, selectedColor: ChewieTheme.hoverColor, - selectedIconColor: ChewieTheme.successColor, + selectedIconColor: ChewieTheme.primaryColor, ), searchFieldDecoration: SearchFieldDecoration( hintStyle: From b4fdecd2c0adfcb4cde82b65eb204ae075cfe8df Mon Sep 17 00:00:00 2001 From: dd
Date: Wed, 20 May 2026 18:17:14 +0800 Subject: [PATCH 24/36] feat: Add search query parser and enhance token filtering with tags and category support --- lib/Database/category_dao.dart | 22 +- lib/Database/create_table_sql.dart | 3 +- lib/Database/database_manager.dart | 6 +- lib/Database/token_category_binding_dao.dart | 19 +- lib/Database/token_dao.dart | 84 ++++++-- lib/Models/opt_token.dart | 7 +- lib/Screens/home_screen.dart | 199 ++++++++++--------- lib/Utils/search_query_parser.dart | 50 +++++ 8 files changed, 275 insertions(+), 115 deletions(-) create mode 100644 lib/Utils/search_query_parser.dart diff --git a/lib/Database/category_dao.dart b/lib/Database/category_dao.dart index e10c4bda..67a0e890 100644 --- a/lib/Database/category_dao.dart +++ b/lib/Database/category_dao.dart @@ -189,12 +189,28 @@ class CategoryDao { static Future> getTokensByCategoryUid( String uid, { String searchKey = "", + List tags = const [], + String? tokenType, }) async { - if (uid.isEmpty) return await TokenDao.listTokens(searchKey: searchKey); + if (uid.isEmpty) { + return await TokenDao.listTokens( + searchKey: searchKey, tags: tags, tokenType: tokenType); + } TokenCategory category = await getCategoryByUid(uid); - List tokens = - await BindingDao.getTokens(category.uid, searchKey: searchKey); + List tokens = await BindingDao.getTokens(category.uid, + searchKey: searchKey, tags: tags, tokenType: tokenType); tokens.sort((a, b) => -a.pinnedInt.compareTo(b.pinnedInt)); return tokens; } + + static Future> getCategoryUidsByName(String name) async { + final db = await DatabaseManager.getDataBase(); + final maps = await db.query( + tableName, + columns: ['uid'], + where: 'title LIKE ?', + whereArgs: ['%$name%'], + ); + return maps.map((m) => m['uid'] as String).toList(); + } } diff --git a/lib/Database/create_table_sql.dart b/lib/Database/create_table_sql.dart index b82809d6..321c0acf 100644 --- a/lib/Database/create_table_sql.dart +++ b/lib/Database/create_table_sql.dart @@ -36,7 +36,8 @@ enum Sql { copy_times INTEGER NOT NULL, last_copy_timestamp INTEGER NOT NULL, pin TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '' + description TEXT NOT NULL DEFAULT '', + tags TEXT NOT NULL DEFAULT '' ); ''', ), diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index b9f2872d..04737cf1 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -39,7 +39,7 @@ enum EncryptDatabaseStatus { defaultPassword, customPassword } class DatabaseManager { static const _dbName = "cloudotp.db"; static const _unencrypedFileHeader = "SQLite format 3"; - static const _dbVersion = 6; + static const _dbVersion = 7; static Database? _database; static final dbFactory = createDatabaseFactoryFfi(); static DynamicLibrary? lib = loadSqlcipher(); @@ -206,6 +206,10 @@ class DatabaseManager { await db.execute("alter table temp rename to token_category"); } } + if (oldVersion < 7) { + await db.execute( + "alter table otp_token add column tags TEXT NOT NULL DEFAULT ''"); + } } static updateToV6(Database db) async { diff --git a/lib/Database/token_category_binding_dao.dart b/lib/Database/token_category_binding_dao.dart index 7abfc472..8940e76f 100644 --- a/lib/Database/token_category_binding_dao.dart +++ b/lib/Database/token_category_binding_dao.dart @@ -142,6 +142,8 @@ class BindingDao { static Future> getTokens( String categoryUid, { String searchKey = "", + List tags = const [], + String? tokenType, }) async { final db = await DatabaseManager.getDataBase(); List> maps = await db.query( @@ -155,7 +157,8 @@ class BindingDao { uids.removeWhere((e) => !StringUtil.isUid(e)); List tokens = []; for (String uid in uids) { - OtpToken? token = await TokenDao.getTokenByUid(uid, searchKey: searchKey); + OtpToken? token = await TokenDao.getTokenByUid(uid, + searchKey: searchKey, tags: tags, tokenType: tokenType); if (token != null) { tokens.add(token); } @@ -163,6 +166,20 @@ class BindingDao { return tokens; } + static Future> getTokenUidsByCategoryUids( + List categoryUids) async { + if (categoryUids.isEmpty) return {}; + final db = await DatabaseManager.getDataBase(); + final placeholders = categoryUids.map((_) => '?').join(','); + final maps = await db.query( + tableName, + columns: ["token_uid"], + where: "category_uid IN ($placeholders)", + whereArgs: categoryUids, + ); + return maps.map((m) => m["token_uid"] as String).toSet(); + } + static Future> getTokenUids(String categoryUid) async { final db = await DatabaseManager.getDataBase(); List> maps = await db.query( diff --git a/lib/Database/token_dao.dart b/lib/Database/token_dao.dart index bc9342e4..a4bb3f24 100644 --- a/lib/Database/token_dao.dart +++ b/lib/Database/token_dao.dart @@ -215,21 +215,59 @@ class TokenDao { return results.length; } + static ({String? where, List? whereArgs}) _buildSearchWhere({ + String searchKey = "", + List tags = const [], + String? tokenType, + String? uidCondition, + Object? uidArg, + }) { + final conditions = []; + final args = []; + + if (uidCondition != null) { + conditions.add(uidCondition); + if (uidArg != null) args.add(uidArg); + } + + if (searchKey.isNotEmpty) { + conditions + .add('(issuer LIKE ? OR account LIKE ? OR description LIKE ?)'); + args.addAll(["%$searchKey%", "%$searchKey%", "%$searchKey%"]); + } + + for (final tag in tags) { + conditions.add('tags LIKE ?'); + args.add('%$tag%'); + } + + if (tokenType != null) { + try { + final typeIndex = OtpTokenType.fromString(tokenType).index; + conditions.add('token_type = ?'); + args.add(typeIndex); + } catch (_) {} + } + + if (conditions.isEmpty) return (where: null, whereArgs: null); + return (where: conditions.join(' AND '), whereArgs: args); + } + static Future> listTokens({ String searchKey = "", + List tags = const [], + String? tokenType, String orderBy = "", Database? overrideDb, }) async { final db = overrideDb ?? await DatabaseManager.getDataBase(); + final search = _buildSearchWhere( + searchKey: searchKey, tags: tags, tokenType: tokenType); final List> maps = await db.query( tableName, orderBy: "pinned DESC, seq DESC${orderBy.isEmpty ? "" : ", $orderBy"}", - where: searchKey.isEmpty - ? null - : '(issuer LIKE ? OR account LIKE ? OR description LIKE ?)', - whereArgs: searchKey.isEmpty - ? null - : ["%$searchKey%", "%$searchKey%", "%$searchKey%"], + where: search.where, + whereArgs: search.whereArgs, ); return List.generate(maps.length, (i) { return OtpToken.fromMap(maps[i]); @@ -239,17 +277,22 @@ class TokenDao { static Future getTokenById( int id, { String searchKey = "", + List tags = const [], + String? tokenType, }) async { try { final db = await DatabaseManager.getDataBase(); + final search = _buildSearchWhere( + searchKey: searchKey, + tags: tags, + tokenType: tokenType, + uidCondition: 'id = ?', + uidArg: id, + ); List> maps = await db.query( tableName, - where: searchKey.isEmpty - ? 'id = ?' - : 'id = ? AND (issuer LIKE ? OR account LIKE ? OR description LIKE ?)', - whereArgs: searchKey.isEmpty - ? [id] - : [id, "%$searchKey%", "%$searchKey%", "%$searchKey%"], + where: search.where, + whereArgs: search.whereArgs, ); return OtpToken.fromMap(maps[0]); } catch (e, t) { @@ -262,17 +305,22 @@ class TokenDao { static Future getTokenByUid( String uid, { String searchKey = "", + List tags = const [], + String? tokenType, }) async { try { final db = await DatabaseManager.getDataBase(); + final search = _buildSearchWhere( + searchKey: searchKey, + tags: tags, + tokenType: tokenType, + uidCondition: 'uid = ?', + uidArg: uid, + ); List> maps = await db.query( tableName, - where: searchKey.isEmpty - ? 'uid = ?' - : 'uid = ? AND (issuer LIKE ? OR account LIKE ? OR description LIKE ?)', - whereArgs: searchKey.isEmpty - ? [uid] - : [uid, "%$searchKey%", "%$searchKey%", "%$searchKey%"], + where: search.where, + whereArgs: search.whereArgs, ); return maps.isNotEmpty ? OtpToken.fromMap(maps[0]) : null; } catch (e, t) { diff --git a/lib/Models/opt_token.dart b/lib/Models/opt_token.dart index 210f0f68..c31ad564 100644 --- a/lib/Models/opt_token.dart +++ b/lib/Models/opt_token.dart @@ -80,6 +80,7 @@ enum OtpTokenType { case "STEAM": return OtpTokenType.Steam; case "YAOTP": + case "YANDEX": return OtpTokenType.Yandex; default: throw Exception("Invalid OtpTokenType"); @@ -534,6 +535,7 @@ class OtpToken { 'pin': pin, 'last_copy_timestamp': lastCopyTimeStamp, "description": description, + 'tags': tags.join(','), }; } @@ -559,7 +561,10 @@ class OtpToken { lastCopyTimeStamp: map['last_copy_timestamp'] ?? 0, pin: map['pin'], description: map['description'] ?? "", - ); + )..tags = (map['tags'] as String? ?? '') + .split(',') + .where((e) => e.isNotEmpty) + .toList(); } String toJson() { diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 37fd6530..f73ddb73 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -29,6 +29,7 @@ import 'package:cloudotp/Screens/Setting/backup_log_screen.dart'; import 'package:cloudotp/Screens/Setting/mobile_setting_navigation_screen.dart'; import 'package:cloudotp/Screens/main_screen.dart'; import 'package:cloudotp/Utils/hive_util.dart'; +import 'package:cloudotp/Utils/search_query_parser.dart'; import 'package:cloudotp/Widgets/BottomSheet/add_bottom_sheet.dart'; import 'package:cloudotp/Widgets/cloudotp/cloudotp_item_builder.dart'; import 'package:flutter/material.dart'; @@ -844,12 +845,28 @@ class HomeScreenState extends BasePanelScreenState } getTokens() async { + final query = SearchQueryParser.parse(_searchKey); + + Set? categoryTokenUids; + if (query.categoryName != null) { + final catUids = + await CategoryDao.getCategoryUidsByName(query.categoryName!); + categoryTokenUids = await BindingDao.getTokenUidsByCategoryUids(catUids); + } + await CategoryDao.getTokensByCategoryUid( currentCategoryUid, - searchKey: _searchKey, + searchKey: query.text, + tags: query.tags, + tokenType: query.tokenType, ).then((value) { final seen = {}; - tokens = value.where((t) => seen.add(t.uid)).toList(); + tokens = value.where((t) { + if (!seen.add(t.uid)) return false; + if (categoryTokenUids != null && !categoryTokenUids.contains(t.uid)) + return false; + return true; + }).toList(); final currentUids = seen; tokenKeyMap.removeWhere((uid, _) => !currentUids.contains(uid)); performSort(); @@ -1111,6 +1128,20 @@ class HomeScreenState extends BasePanelScreenState getActions(AppProvider provider) { return [ + if (provider.canShowCloudBackupButton && provider.showCloudBackupButton) + Container( + margin: const EdgeInsets.only(right: 5), + child: CircleIconButton( + tooltip: appLocalizations.cloudBackupServiceSetting, + icon: Icon( + LucideIcons.cloud, + color: ChewieTheme.iconColor, + ), + onTap: () { + RouteUtil.pushCupertinoRoute(context, const CloudServiceScreen()); + }, + ), + ), if (provider.showBackupLogButton) Container( margin: const EdgeInsets.only(right: 5), @@ -1131,20 +1162,6 @@ class HomeScreenState extends BasePanelScreenState }, ), ), - if (provider.canShowCloudBackupButton && provider.showCloudBackupButton) - Container( - margin: const EdgeInsets.only(right: 5), - child: CircleIconButton( - tooltip: appLocalizations.cloudBackupServiceSetting, - icon: Icon( - LucideIcons.cloud, - color: ChewieTheme.iconColor, - ), - onTap: () { - RouteUtil.pushCupertinoRoute(context, const CloudServiceScreen()); - }, - ), - ), if (provider.showLayoutButton) Container( margin: const EdgeInsets.only(right: 5), @@ -1407,80 +1424,82 @@ class HomeScreenState extends BasePanelScreenState builder: (context, settings, child) { double bottomPadding = MediaQuery.of(context).viewPadding.bottom; return ReorderableGridView.builder( - // controller: _scrollController, - gridItemsNotifier: gridItemsNotifier, - autoScroll: true, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - left: 10, - right: 10, - top: 10, - bottom: _multiSelectMode - ? 80 + bottomPadding - : settings.hideBottombar || categories.isEmpty - ? 10 + bottomPadding - : 85 + bottomPadding), - gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: layoutType.maxCrossAxisExtent, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - preferredHeight: layoutType.getHeight(settings.hideProgress), - ), - dragToReorder: _multiSelectMode ? false : settings.dragToReorder, - cacheExtent: 9999, - onReorderStart: (_) { - _fabScrollToHideController.hide(); - _bottombarScrollToHideController.hide(); - }, - onReorderEnd: (_, __) { - _fabScrollToHideController.show(); - _bottombarScrollToHideController.show(); - }, - onReorder: (int oldIndex, int newIndex) async { - final selectedToken = tokens[oldIndex]; - int pinnedCount = tokens.where((e) => e.pinned).length; - if (selectedToken.pinned) { - if (newIndex >= pinnedCount) newIndex = pinnedCount - 1; - } else { - if (newIndex < pinnedCount) newIndex = pinnedCount; - } - final item = tokens.removeAt(oldIndex); - tokens.insert(newIndex, item); - for (int i = 0; i < tokens.length; i++) { - tokens[i].seq = tokens.length - i; - } - await TokenDao.updateTokens(tokens, autoBackup: false); - changeOrderType(type: OrderType.Default, doPerformSort: false); - }, - proxyDecorator: (Widget child, int index, Animation animation) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: ChewieTheme.shadowColor, - offset: const Offset(0, 4), - blurRadius: 10, - spreadRadius: 1, - ).scale(2) - ], - ), - child: child, - ); - }, - itemCount: tokens.length, - itemBuilder: (context, index) { - return TokenLayout( - key: tokenKeyMap.putIfAbsent(tokens[index].uid, () => GlobalKey()), - token: tokens[index], - layoutType: layoutType, - multiSelectMode: _multiSelectMode, - isSelected: _selectedTokenUids.contains(tokens[index].uid), - onToggleSelect: () => toggleTokenSelection(tokens[index].uid), - onEnterMultiSelect: () => enterMultiSelectMode(tokens[index].uid), - ); - }, - ); + // controller: _scrollController, + gridItemsNotifier: gridItemsNotifier, + autoScroll: true, + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only( + left: 10, + right: 10, + top: 10, + bottom: _multiSelectMode + ? 80 + bottomPadding + : settings.hideBottombar || categories.isEmpty + ? 10 + bottomPadding + : 85 + bottomPadding), + gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: layoutType.maxCrossAxisExtent, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + preferredHeight: layoutType.getHeight(settings.hideProgress), + ), + dragToReorder: _multiSelectMode ? false : settings.dragToReorder, + cacheExtent: 9999, + onReorderStart: (_) { + _fabScrollToHideController.hide(); + _bottombarScrollToHideController.hide(); + }, + onReorderEnd: (_, __) { + _fabScrollToHideController.show(); + _bottombarScrollToHideController.show(); + }, + onReorder: (int oldIndex, int newIndex) async { + final selectedToken = tokens[oldIndex]; + int pinnedCount = tokens.where((e) => e.pinned).length; + if (selectedToken.pinned) { + if (newIndex >= pinnedCount) newIndex = pinnedCount - 1; + } else { + if (newIndex < pinnedCount) newIndex = pinnedCount; + } + final item = tokens.removeAt(oldIndex); + tokens.insert(newIndex, item); + for (int i = 0; i < tokens.length; i++) { + tokens[i].seq = tokens.length - i; + } + await TokenDao.updateTokens(tokens, autoBackup: false); + changeOrderType(type: OrderType.Default, doPerformSort: false); + }, + proxyDecorator: + (Widget child, int index, Animation animation) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: ChewieTheme.shadowColor, + offset: const Offset(0, 4), + blurRadius: 10, + spreadRadius: 1, + ).scale(2) + ], + ), + child: child, + ); + }, + itemCount: tokens.length, + itemBuilder: (context, index) { + return TokenLayout( + key: + tokenKeyMap.putIfAbsent(tokens[index].uid, () => GlobalKey()), + token: tokens[index], + layoutType: layoutType, + multiSelectMode: _multiSelectMode, + isSelected: _selectedTokenUids.contains(tokens[index].uid), + onToggleSelect: () => toggleTokenSelection(tokens[index].uid), + onEnterMultiSelect: () => enterMultiSelectMode(tokens[index].uid), + ); + }, + ); }, ); Widget body = tokens.isEmpty diff --git a/lib/Utils/search_query_parser.dart b/lib/Utils/search_query_parser.dart new file mode 100644 index 00000000..d4c82590 --- /dev/null +++ b/lib/Utils/search_query_parser.dart @@ -0,0 +1,50 @@ +class SearchQuery { + final String text; + final List tags; + final String? categoryName; + final String? tokenType; + + const SearchQuery({ + this.text = '', + this.tags = const [], + this.categoryName, + this.tokenType, + }); + + bool get hasFilters => + tags.isNotEmpty || categoryName != null || tokenType != null; +} + +class SearchQueryParser { + static final _tagPattern = RegExp(r'#(\S+)'); + static final _catPattern = RegExp(r'cat:(\S+)'); + static final _typePattern = RegExp(r'type:(\S+)'); + + static SearchQuery parse(String input) { + if (input.trim().isEmpty) return const SearchQuery(); + + final tags = _tagPattern + .allMatches(input) + .map((m) => m.group(1)!) + .toList(); + + final catMatch = _catPattern.firstMatch(input); + final categoryName = catMatch?.group(1); + + final typeMatch = _typePattern.firstMatch(input); + final tokenType = typeMatch?.group(1); + + var text = input + .replaceAll(_tagPattern, '') + .replaceAll(_catPattern, '') + .replaceAll(_typePattern, '') + .trim(); + + return SearchQuery( + text: text, + tags: tags, + categoryName: categoryName, + tokenType: tokenType, + ); + } +} From 7f9abba9200a1f41588ea24810905d4ee35ab5fe Mon Sep 17 00:00:00 2001 From: dd
Date: Thu, 21 May 2026 15:45:33 +0800 Subject: [PATCH 25/36] feat: Add auto backup on launch and periodic backup functionality with UI options --- lib/Models/auto_backup_log.dart | 12 ++- .../Setting/setting_backup_screen.dart | 97 +++++++++++++++++++ lib/Screens/home_screen.dart | 10 +- lib/Screens/main_screen.dart | 29 ++++++ lib/Utils/constant.dart | 2 + lib/Utils/hive_util.dart | 3 + lib/l10n/intl_en.arb | 7 ++ lib/l10n/intl_ja.arb | 7 ++ lib/l10n/intl_zh.arb | 7 ++ lib/l10n/intl_zh_TW.arb | 7 ++ 10 files changed, 174 insertions(+), 7 deletions(-) diff --git a/lib/Models/auto_backup_log.dart b/lib/Models/auto_backup_log.dart index d212804e..d1a7a97f 100644 --- a/lib/Models/auto_backup_log.dart +++ b/lib/Models/auto_backup_log.dart @@ -117,7 +117,9 @@ enum AutoBackupTriggerType { categoriesUpdatedForToken, cloudServiceConfigInserted, cloudServiceConfigUpdated, - cloudServiceConfigDeleted; + cloudServiceConfigDeleted, + appStartup, + scheduled; String get label { switch (this) { @@ -157,6 +159,10 @@ enum AutoBackupTriggerType { return appLocalizations.triggerAutoBackupByCloudServiceConfigUpdated; case AutoBackupTriggerType.cloudServiceConfigDeleted: return appLocalizations.triggerAutoBackupByCloudServiceConfigDeleted; + case AutoBackupTriggerType.appStartup: + return appLocalizations.triggerAutoBackupByAppStartup; + case AutoBackupTriggerType.scheduled: + return appLocalizations.triggerAutoBackupByScheduled; default: return appLocalizations.triggerAutoBackupByOther; } @@ -186,6 +192,10 @@ enum AutoBackupTriggerType { case AutoBackupTriggerType.cloudServiceConfigUpdated: case AutoBackupTriggerType.cloudServiceConfigDeleted: return LucideIcons.cloud; + case AutoBackupTriggerType.appStartup: + return LucideIcons.power; + case AutoBackupTriggerType.scheduled: + return LucideIcons.clock; case AutoBackupTriggerType.other: return LucideIcons.circleHelp; } diff --git a/lib/Screens/Setting/setting_backup_screen.dart b/lib/Screens/Setting/setting_backup_screen.dart index 6f8dcd64..bc93ac19 100644 --- a/lib/Screens/Setting/setting_backup_screen.dart +++ b/lib/Screens/Setting/setting_backup_screen.dart @@ -73,6 +73,15 @@ class _BackupSettingScreenState extends BaseDynamicState ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableCloudBackupKey); CloudServiceConfig? _cloudServiceConfig; int _maxBackupsCount = CloudOTPHiveUtil.getMaxBackupsCount(); + bool _enableBackupOnLaunch = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBackupOnLaunchKey, + defaultValue: false); + bool _enablePeriodicBackup = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.enablePeriodicBackupKey, + defaultValue: false); + int _periodicBackupInterval = ChewieHiveUtil.getInt( + CloudOTPHiveUtil.periodicBackupIntervalKey, + defaultValue: defaultPeriodicBackupIntervalHours * 60); final GlobalKey _setAutoBackupPasswordKey = GlobalKey(); String validConfigs = ""; @@ -247,6 +256,55 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), + Visibility( + visible: _enableAutoBackup, + child: CheckboxItem( + value: _enableBackupOnLaunch, + title: appLocalizations.backupOnLaunch, + description: appLocalizations.backupOnLaunchTip, + onTap: () { + setState(() { + _enableBackupOnLaunch = !_enableBackupOnLaunch; + ChewieHiveUtil.put(CloudOTPHiveUtil.enableBackupOnLaunchKey, + _enableBackupOnLaunch); + }); + }, + ), + ), + Visibility( + visible: _enableAutoBackup, + child: CheckboxItem( + value: _enablePeriodicBackup, + title: appLocalizations.periodicBackup, + description: appLocalizations.periodicBackupTip, + onTap: () { + setState(() { + _enablePeriodicBackup = !_enablePeriodicBackup; + ChewieHiveUtil.put(CloudOTPHiveUtil.enablePeriodicBackupKey, + _enablePeriodicBackup); + }); + }, + ), + ), + Visibility( + visible: _enableAutoBackup && _enablePeriodicBackup, + child: InlineSelectionItem( + title: appLocalizations.periodicBackupInterval, + hint: appLocalizations.periodicBackupInterval, + selections: BackupIntervalOption.getOptions(), + selected: BackupIntervalOption.fromMinutes( + _periodicBackupInterval), + onChanged: (option) { + if (option == null) return; + setState(() { + _periodicBackupInterval = option.minutes; + ChewieHiveUtil.put( + CloudOTPHiveUtil.periodicBackupIntervalKey, + _periodicBackupInterval); + }); + }, + ), + ), Visibility( visible: canImmediateBackup, child: EntryItem( @@ -448,3 +506,42 @@ class _BackupSettingScreenState extends BaseDynamicState ]; } } + +class BackupIntervalOption implements DropdownMixin { + final String label; + final int minutes; + + const BackupIntervalOption(this.label, this.minutes); + + static List getOptions() { + return const [ + BackupIntervalOption("1h", 60), + BackupIntervalOption("6h", 360), + BackupIntervalOption("12h", 720), + BackupIntervalOption("24h", 1440), + BackupIntervalOption("48h", 2880), + BackupIntervalOption("7d", 10080), + ]; + } + + static BackupIntervalOption? fromMinutes(int minutes) { + return getOptions().firstWhere( + (option) => option.minutes == minutes, + orElse: () => getOptions()[3], + ); + } + + @override + String get display => label; + + @override + String get selection => display; + + @override + bool operator ==(Object other) { + return other is BackupIntervalOption && minutes == other.minutes; + } + + @override + int get hashCode => minutes.hashCode; +} diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index f73ddb73..a345f348 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -1131,13 +1131,11 @@ class HomeScreenState extends BasePanelScreenState if (provider.canShowCloudBackupButton && provider.showCloudBackupButton) Container( margin: const EdgeInsets.only(right: 5), - child: CircleIconButton( + child: ToolButton( + context: context, tooltip: appLocalizations.cloudBackupServiceSetting, - icon: Icon( - LucideIcons.cloud, - color: ChewieTheme.iconColor, - ), - onTap: () { + icon: LucideIcons.cloud, + onPressed: () { RouteUtil.pushCupertinoRoute(context, const CloudServiceScreen()); }, ), diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index ec2c4c37..7b397a46 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -43,6 +43,8 @@ import '../Models/opt_token.dart'; import '../Models/token_category.dart'; import '../TokenUtils/code_generator.dart'; import '../TokenUtils/import_token_util.dart'; +import '../TokenUtils/export_token_util.dart'; +import '../Models/auto_backup_log.dart'; import '../Utils/app_provider.dart'; import '../Utils/constant.dart'; import '../Utils/hive_util.dart'; @@ -78,6 +80,7 @@ class MainScreenState extends BaseWindowState ProtocolListener, AutomaticKeepAliveClientMixin { Timer? _timer; + Timer? _periodicBackupTimer; TextEditingController searchController = TextEditingController(); List _menuTokens = []; List _menuCategories = []; @@ -176,6 +179,8 @@ class MainScreenState extends BaseWindowState searchController.addListener(() { homeScreenState?.performSearch(searchController.text); }); + _startAutoBackupOnLaunch(); + _startPeriodicBackupTimer(); } static const _notifierChannel = MethodChannel('local_notifier'); @@ -1307,6 +1312,29 @@ class MainScreenState extends BaseWindowState ); } + void _startAutoBackupOnLaunch() { + if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBackupOnLaunchKey)) { + ExportTokenUtil.autoBackup( + triggerType: AutoBackupTriggerType.appStartup); + } + } + + void _startPeriodicBackupTimer() { + _periodicBackupTimer?.cancel(); + if (!ChewieHiveUtil.getBool(CloudOTPHiveUtil.enablePeriodicBackupKey)) { + return; + } + final minutes = ChewieHiveUtil.getInt( + CloudOTPHiveUtil.periodicBackupIntervalKey, + defaultValue: defaultPeriodicBackupIntervalHours * 60, + ); + _periodicBackupTimer = Timer.periodic( + Duration(minutes: minutes), + (_) => ExportTokenUtil.autoBackup( + triggerType: AutoBackupTriggerType.scheduled), + ); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { @@ -1332,6 +1360,7 @@ class MainScreenState extends BaseWindowState @override void dispose() { + _periodicBackupTimer?.cancel(); protocolHandler.removeListener(this); trayManager.removeListener(this); WidgetsBinding.instance.removeObserver(this); diff --git a/lib/Utils/constant.dart b/lib/Utils/constant.dart index 08ef1ff4..e25078b8 100644 --- a/lib/Utils/constant.dart +++ b/lib/Utils/constant.dart @@ -21,6 +21,8 @@ const defaultMaxBackupCount = 100; const maxBackupCountThrehold = 500; +const defaultPeriodicBackupIntervalHours = 24; + const maxBytesLength = 1000; const double autoCopyNextCodeProgressThrehold = 0.25; diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index d244d325..6145cb7b 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -75,6 +75,9 @@ class CloudOTPHiveUtil { static const String backupPathKey = "backupPath"; static const String useBackupPasswordToExportImportKey = "useBackupPasswordToExportImport"; + static const String enableBackupOnLaunchKey = "enableBackupOnLaunch"; + static const String enablePeriodicBackupKey = "enablePeriodicBackup"; + static const String periodicBackupIntervalKey = "periodicBackupInterval"; //Encrypt static const String encryptDatabaseStatusKey = "encryptDatabaseStatus"; diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 68c697bc..163c5e39 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -207,6 +207,13 @@ "triggerAutoBackupByCloudServiceConfigInserted": "Cloud Service Added", "triggerAutoBackupByCloudServiceConfigUpdated": "Cloud Service Updated", "triggerAutoBackupByCloudServiceConfigDeleted": "Cloud Service Deleted", + "triggerAutoBackupByAppStartup": "App Startup", + "triggerAutoBackupByScheduled": "Scheduled Backup", + "backupOnLaunch": "Backup on Launch", + "backupOnLaunchTip": "Automatically backup once when the app launches", + "periodicBackup": "Periodic Backup", + "periodicBackupTip": "Automatically backup at set intervals", + "periodicBackupInterval": "Backup Interval", "immediatelyBackup": "Backup Now", "immediatelyBackupTip": "Immediately back up to the specified location and cloud service", "enableCloudBackup": "Enable Cloud Backup", diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 7aa82287..894269b6 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -207,6 +207,13 @@ "triggerAutoBackupByCloudServiceConfigInserted": "クラウドサービスの追加", "triggerAutoBackupByCloudServiceConfigUpdated": "クラウドサービスの変更", "triggerAutoBackupByCloudServiceConfigDeleted": "クラウドサービスの削除", + "triggerAutoBackupByAppStartup": "アプリ起動", + "triggerAutoBackupByScheduled": "定期バックアップ", + "backupOnLaunch": "起動時にバックアップ", + "backupOnLaunchTip": "アプリ起動時に自動的にバックアップを実行します", + "periodicBackup": "定期バックアップ", + "periodicBackupTip": "設定した間隔で自動的にバックアップを実行します", + "periodicBackupInterval": "バックアップ間隔", "immediatelyBackup": "今すぐバックアップ", "immediatelyBackupTip": "指定の場所とクラウドサービスに今すぐバックアップ", "enableCloudBackup": "クラウドバックアップを有効化", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 151a3813..8db03367 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -207,6 +207,13 @@ "triggerAutoBackupByCloudServiceConfigInserted": "添加云服务", "triggerAutoBackupByCloudServiceConfigUpdated": "修改云服务", "triggerAutoBackupByCloudServiceConfigDeleted": "删除云服务", + "triggerAutoBackupByAppStartup": "应用启动", + "triggerAutoBackupByScheduled": "定时备份", + "backupOnLaunch": "启动时备份", + "backupOnLaunchTip": "每次启动应用时自动备份一次", + "periodicBackup": "定时备份", + "periodicBackupTip": "按设定的时间间隔自动进行备份", + "periodicBackupInterval": "备份间隔", "immediatelyBackup": "立即备份", "immediatelyBackupTip": "立即备份到指定位置和云服务", "enableCloudBackup": "启用云备份", diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index e5798337..f7557b1e 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -207,6 +207,13 @@ "triggerAutoBackupByCloudServiceConfigInserted": "新增雲端服務", "triggerAutoBackupByCloudServiceConfigUpdated": "修改雲端服務", "triggerAutoBackupByCloudServiceConfigDeleted": "刪除雲端服務", + "triggerAutoBackupByAppStartup": "應用啟動", + "triggerAutoBackupByScheduled": "定時備份", + "backupOnLaunch": "啟動時備份", + "backupOnLaunchTip": "每次啟動應用時自動備份一次", + "periodicBackup": "定時備份", + "periodicBackupTip": "按設定的時間間隔自動進行備份", + "periodicBackupInterval": "備份間隔", "immediatelyBackup": "立即備份", "immediatelyBackupTip": "立即備份到指定位置和雲端服務", "enableCloudBackup": "啟用雲端備份", From 87dd9004cb7fc963ec00f1837d0798257a2d2568 Mon Sep 17 00:00:00 2001 From: dd
Date: Thu, 21 May 2026 20:06:35 +0800 Subject: [PATCH 26/36] Refactor UI components and improve localization - Adjusted padding and layout in OneDrive, S3, and WebDAV backup bottom sheets for consistency. - Enhanced the CheckboxItem and EntryItem widgets with improved leading icon handling and layout adjustments. - Added a new MoreBottomSheet widget for additional settings and actions, including navigation to various screens. - Updated localization files to include new strings for improved user experience. - Refactored CacheUtil and ILogger classes for better error handling and code clarity. - Minor style adjustments across various widgets for a more cohesive design. --- lib/Database/create_table_sql.dart | 3 +- lib/Database/database_manager.dart | 18 +- lib/Database/token_category_binding_dao.dart | 2 +- lib/Screens/Setting/backup_log_screen.dart | 182 ++++---- lib/Screens/Setting/base_setting_screen.dart | 2 +- .../mobile_setting_navigation_screen.dart | 97 ++--- lib/Screens/Setting/select_font_screen.dart | 4 +- lib/Screens/Setting/select_theme_screen.dart | 4 +- .../Setting/setting_backup_screen.dart | 178 +++----- .../Setting/setting_general_screen.dart | 4 +- lib/Screens/Setting/setting_safe_screen.dart | 43 +- lib/Screens/Token/add_token_screen.dart | 191 ++++----- lib/Screens/Token/import_preview_screen.dart | 239 +++++------ lib/Screens/home_screen.dart | 72 +--- lib/Screens/layout_select_screen.dart | 200 ++++++--- lib/Screens/main_screen.dart | 19 - lib/Screens/sort_select_screen.dart | 17 +- lib/TokenUtils/export_token_util.dart | 7 +- lib/Utils/constant.dart | 2 - .../aliyundrive_backups_bottom_sheet.dart | 18 +- .../Backups/box_backups_bottom_sheet.dart | 21 +- .../Backups/dropbox_backups_bottom_sheet.dart | 18 +- .../googledrive_backups_bottom_sheet.dart | 21 +- .../Backups/huawei_backups_bottom_sheet.dart | 24 +- .../Backups/local_backups_bottom_sheet.dart | 29 +- .../onedrive_backups_bottom_sheet.dart | 15 +- .../Backups/s3_backups_bottom_sheet.dart | 18 +- .../Backups/webdav_backups_bottom_sheet.dart | 18 +- .../BottomSheet/more_bottom_sheet.dart | 262 ++++++++++++ lib/l10n/intl_en.arb | 5 + lib/l10n/intl_ja.arb | 5 + lib/l10n/intl_zh.arb | 5 + lib/l10n/intl_zh_TW.arb | 5 + .../lib/src/Utils/System/cache_util.dart | 17 +- third-party/chewie/lib/src/Utils/ilogger.dart | 6 +- .../lib/src/Widgets/Tile/checkbox_item.dart | 58 +-- .../lib/src/Widgets/Tile/entry_item.dart | 390 ++++++++++-------- .../Widgets/Tile/inline_selection_item.dart | 17 +- .../lib/src/Widgets/Tile/tip_banner.dart | 2 +- 39 files changed, 1250 insertions(+), 988 deletions(-) create mode 100644 lib/Widgets/BottomSheet/more_bottom_sheet.dart diff --git a/lib/Database/create_table_sql.dart b/lib/Database/create_table_sql.dart index 321c0acf..0e756803 100644 --- a/lib/Database/create_table_sql.dart +++ b/lib/Database/create_table_sql.dart @@ -97,7 +97,8 @@ enum Sql { createTokenCategoryBindingTable(''' CREATE TABLE token_category_binding ( token_uid INTEGER NOT NULL, - category_uid INTEGER NOT NULL + category_uid INTEGER NOT NULL, + UNIQUE(token_uid, category_uid) ); '''); diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index 04737cf1..76529874 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -39,7 +39,7 @@ enum EncryptDatabaseStatus { defaultPassword, customPassword } class DatabaseManager { static const _dbName = "cloudotp.db"; static const _unencrypedFileHeader = "SQLite format 3"; - static const _dbVersion = 7; + static const _dbVersion = 8; static Database? _database; static final dbFactory = createDatabaseFactoryFfi(); static DynamicLibrary? lib = loadSqlcipher(); @@ -210,6 +210,22 @@ class DatabaseManager { await db.execute( "alter table otp_token add column tags TEXT NOT NULL DEFAULT ''"); } + if (oldVersion < 8) { + await db.execute(''' + CREATE TABLE token_category_binding_new ( + token_uid INTEGER NOT NULL, + category_uid INTEGER NOT NULL, + UNIQUE(token_uid, category_uid) + ) + '''); + await db.execute(''' + INSERT OR IGNORE INTO token_category_binding_new (token_uid, category_uid) + SELECT DISTINCT token_uid, category_uid FROM token_category_binding + '''); + await db.execute('DROP TABLE token_category_binding'); + await db.execute( + 'ALTER TABLE token_category_binding_new RENAME TO token_category_binding'); + } } static updateToV6(Database db) async { diff --git a/lib/Database/token_category_binding_dao.dart b/lib/Database/token_category_binding_dao.dart index 8940e76f..65a1684d 100644 --- a/lib/Database/token_category_binding_dao.dart +++ b/lib/Database/token_category_binding_dao.dart @@ -204,7 +204,7 @@ class BindingDao { List uids = List.generate(maps.length, (i) => maps[i]["category_uid"]); uids.removeWhere((e) => !StringUtil.isUid(e)); - return uids; + return uids.toSet().toList(); } static Future> listBindings() async { diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index f28c8cc4..ff670e26 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -37,14 +37,12 @@ class BackupLogScreen extends StatefulWidget { }); static bool _hasContextMenuOverlay(BuildContext context) { - return context - .findAncestorStateOfType() != + return context.findAncestorStateOfType() != null; } static void show(BuildContext context) { - if (ResponsiveUtil.isLandscapeLayout() && - _hasContextMenuOverlay(context)) { + if (ResponsiveUtil.isLandscapeLayout() && _hasContextMenuOverlay(context)) { BottomSheetBuilder.showGenericContextMenu( context, const BackupLogScreen(isOverlay: true), @@ -134,15 +132,14 @@ class BackupLogScreenState extends BaseDynamicState { @override Widget build(BuildContext context) { Widget header = Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: _buildHeader(), ); Widget body = _buildLogList(); if (widget.isOverlay) { - final overlayHeight = - _mergedLogs.isEmpty || !canBackup ? 300.0 : 400.0; + final overlayHeight = _mergedLogs.isEmpty || !canBackup ? 300.0 : 400.0; return Container( width: min(400, MediaQuery.sizeOf(context).width - 80), height: min(overlayHeight, MediaQuery.sizeOf(context).height - 80), @@ -253,7 +250,7 @@ class BackupLogScreenState extends BaseDynamicState { Widget _buildLogList() { if (!canBackup) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 20), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -268,8 +265,8 @@ class BackupLogScreenState extends BaseDynamicState { background: _accent, onPressed: () { if (widget.isOverlay) { - RouteUtil.pushDialogRoute(context, - const SettingNavigationScreen(initPageIndex: 3)); + RouteUtil.pushDialogRoute( + context, const SettingNavigationScreen(initPageIndex: 3)); } else { Navigator.pop(context); RouteUtil.pushCupertinoRoute( @@ -293,7 +290,7 @@ class BackupLogScreenState extends BaseDynamicState { if (_mergedLogs.isEmpty) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 14), + padding: const EdgeInsets.symmetric(vertical: 12), child: EmptyPlaceholder( text: appLocalizations.noBackupLogs, topPadding: 10), ); @@ -301,7 +298,7 @@ class BackupLogScreenState extends BaseDynamicState { if (widget.isOverlay) { return SingleChildScrollView( - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(10, 4, 10, 12), child: Column( mainAxisSize: MainAxisSize.min, children: List.generate( @@ -315,7 +312,7 @@ class BackupLogScreenState extends BaseDynamicState { } return ListView.builder( - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(10, 4, 10, 12), shrinkWrap: true, itemCount: _mergedLogs.length, itemBuilder: (context, index) { @@ -364,89 +361,88 @@ class BackupLogItemState extends BaseDynamicState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: statusColor.withAlpha(30), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - widget.log.triggerType.icon, - size: 15, - color: statusColor, - ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.log.triggerType.label, - style: ChewieTheme.bodyMedium, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - '${_dateTimeFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.log.startTimestamp))} · ${widget.log.type.label}', - style: ChewieTheme.bodySmall.copyWith( - color: ChewieTheme.bodyMedium.color - ?.withAlpha(120), - fontSize: 11, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: statusColor.withAlpha(30), + borderRadius: BorderRadius.circular(8), ), - const SizedBox(width: 6), - RoundIconTextButton( - radius: 5, - height: 24, - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - text: widget.log.lastStatusItem.labelShort, - textStyle: - ChewieTheme.labelSmall.apply(color: Colors.white), - background: statusColor, + child: Icon( + widget.log.triggerType.icon, + size: 15, + color: statusColor, ), - const SizedBox(width: 4), - CircleIconButton( - padding: const EdgeInsets.all(4), - icon: Icon( - expanded - ? Icons.keyboard_arrow_up_rounded - : Icons.keyboard_arrow_down_rounded, - size: 16, - color: ChewieTheme.labelSmall.color), - onTap: () { - setState(() { - expanded = !expanded; - }); - }, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.log.triggerType.label, + style: ChewieTheme.bodyMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + '${_dateTimeFormat.format(DateTime.fromMillisecondsSinceEpoch(widget.log.startTimestamp))} · ${widget.log.type.label}', + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ), - ], - ), - AnimatedSize( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - alignment: Alignment.topCenter, - child: expanded - ? Padding( - padding: const EdgeInsets.only(top: 10, left: 4), - child: _buildStatusTimeline(), - ) - : const SizedBox.shrink(), - ), - ], - ), + ), + const SizedBox(width: 6), + RoundIconTextButton( + radius: 5, + height: 24, + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + text: widget.log.lastStatusItem.labelShort, + textStyle: + ChewieTheme.labelSmall.apply(color: Colors.white), + background: statusColor, + ), + const SizedBox(width: 4), + CircleIconButton( + padding: const EdgeInsets.all(4), + icon: Icon( + expanded + ? Icons.keyboard_arrow_up_rounded + : Icons.keyboard_arrow_down_rounded, + size: 16, + color: ChewieTheme.labelSmall.color), + onTap: () { + setState(() { + expanded = !expanded; + }); + }, + ), + ], + ), + AnimatedSize( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + alignment: Alignment.topCenter, + child: expanded + ? Padding( + padding: const EdgeInsets.only(top: 10, left: 4), + child: _buildStatusTimeline(), + ) + : const SizedBox.shrink(), + ), + ], ), ), + ), ); } @@ -457,8 +453,8 @@ class BackupLogItemState extends BaseDynamicState { final statusItem = widget.log.status[i]; final isLast = i == widget.log.status.length - 1; final dotColor = statusItem.status.color; - final timeStr = _timeFormat.format( - DateTime.fromMillisecondsSinceEpoch(statusItem.timestamp)); + final timeStr = _timeFormat + .format(DateTime.fromMillisecondsSinceEpoch(statusItem.timestamp)); return IntrinsicHeight( child: Row( diff --git a/lib/Screens/Setting/base_setting_screen.dart b/lib/Screens/Setting/base_setting_screen.dart index cef8c508..178b5bbc 100644 --- a/lib/Screens/Setting/base_setting_screen.dart +++ b/lib/Screens/Setting/base_setting_screen.dart @@ -5,7 +5,7 @@ abstract class BaseSettingScreen extends StatefulWidget { const BaseSettingScreen({ super.key, this.showTitleBar = true, - this.padding = const EdgeInsets.symmetric(horizontal: 6), + this.padding = const EdgeInsets.symmetric(horizontal: 10), this.searchText = "", this.searchConfig, }); diff --git a/lib/Screens/Setting/mobile_setting_navigation_screen.dart b/lib/Screens/Setting/mobile_setting_navigation_screen.dart index 3e2e7525..dcf90c1a 100644 --- a/lib/Screens/Setting/mobile_setting_navigation_screen.dart +++ b/lib/Screens/Setting/mobile_setting_navigation_screen.dart @@ -64,52 +64,57 @@ class _MobileSettingNavigationScreenState physics: const BouncingScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 10), children: [ - EntryItem( - title: appLocalizations.generalSetting, - leading: LucideIcons.settings2, - showLeading: true, - onTap: () { - RouteUtil.pushCupertinoRoute( - context, - GeneralSettingScreen(), - ); - }, - ), - EntryItem( - title: appLocalizations.appearanceSetting, - leading: LucideIcons.paintbrushVertical, - showLeading: true, - onTap: () { - RouteUtil.pushCupertinoRoute( - context, const AppearanceSettingScreen()); - }, - ), - EntryItem( - title: appLocalizations.operationSetting, - leading: LucideIcons.pointer, - showLeading: true, - onTap: () { - RouteUtil.pushCupertinoRoute( - context, const OperationSettingScreen()); - }, - ), - EntryItem( - title: appLocalizations.backupSetting, - leading: LucideIcons.cloudUpload, - showLeading: true, - onTap: () { - RouteUtil.pushCupertinoRoute( - context, const BackupSettingScreen()); - }, - ), - EntryItem( - title: appLocalizations.safeSetting, - leading: LucideIcons.shieldCheck, - showLeading: true, - onTap: () { - RouteUtil.pushCupertinoRoute( - context, const SafeSettingScreen()); - }, + CaptionItem( + title: appLocalizations.setting, + children: [ + EntryItem( + title: appLocalizations.generalSetting, + leading: LucideIcons.settings2, + showLeading: true, + onTap: () { + RouteUtil.pushCupertinoRoute( + context, + GeneralSettingScreen(), + ); + }, + ), + EntryItem( + title: appLocalizations.appearanceSetting, + leading: LucideIcons.paintbrushVertical, + showLeading: true, + onTap: () { + RouteUtil.pushCupertinoRoute( + context, const AppearanceSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.operationSetting, + leading: LucideIcons.pointer, + showLeading: true, + onTap: () { + RouteUtil.pushCupertinoRoute( + context, const OperationSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.backupSetting, + leading: LucideIcons.cloudUpload, + showLeading: true, + onTap: () { + RouteUtil.pushCupertinoRoute( + context, const BackupSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.safeSetting, + leading: LucideIcons.shieldCheck, + showLeading: true, + onTap: () { + RouteUtil.pushCupertinoRoute( + context, const SafeSettingScreen()); + }, + ), + ], ), const SizedBox(height: 30), ], diff --git a/lib/Screens/Setting/select_font_screen.dart b/lib/Screens/Setting/select_font_screen.dart index 80df04d0..9570160f 100644 --- a/lib/Screens/Setting/select_font_screen.dart +++ b/lib/Screens/Setting/select_font_screen.dart @@ -51,7 +51,7 @@ class _SelectFontScreenState extends BaseDynamicState title: appLocalizations.defaultFontFamily, children: [ Container( - margin: const EdgeInsets.only(top: 10, left: 10, right: 10), + margin: const EdgeInsets.all(10), child: Wrap( runSpacing: 10, spacing: 10, @@ -64,7 +64,7 @@ class _SelectFontScreenState extends BaseDynamicState title: appLocalizations.customFontFamily, children: [ Container( - margin: const EdgeInsets.only(top: 10, left: 10, right: 10), + margin: const EdgeInsets.all(10), child: Wrap( runSpacing: 10, spacing: 10, diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index d5e2f90d..3562f596 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -72,7 +72,7 @@ class _SelectThemeScreenState extends BaseDynamicState Align( alignment: Alignment.centerLeft, child: Container( - margin: const EdgeInsets.only(top: 10), + margin: const EdgeInsets.only(top: 10, bottom: 10), child: SingleChildScrollView( physics: const ClampingScrollPhysics(), scrollDirection: Axis.horizontal, @@ -93,7 +93,7 @@ class _SelectThemeScreenState extends BaseDynamicState Align( alignment: Alignment.centerLeft, child: Container( - margin: const EdgeInsets.only(top: 10), + margin: const EdgeInsets.only(top: 10, bottom: 10), child: SingleChildScrollView( physics: const ClampingScrollPhysics(), scrollDirection: Axis.horizontal, diff --git a/lib/Screens/Setting/setting_backup_screen.dart b/lib/Screens/Setting/setting_backup_screen.dart index bc93ac19..eb2480a9 100644 --- a/lib/Screens/Setting/setting_backup_screen.dart +++ b/lib/Screens/Setting/setting_backup_screen.dart @@ -68,6 +68,7 @@ class _BackupSettingScreenState extends BaseDynamicState bool _useBackupPasswordToExportImport = ChewieHiveUtil.getBool( CloudOTPHiveUtil.useBackupPasswordToExportImportKey); String _autoBackupPath = ""; + String _defaultBackupPath = ""; String _autoBackupPassword = ""; bool _enableCloudBackup = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableCloudBackupKey); @@ -76,12 +77,6 @@ class _BackupSettingScreenState extends BaseDynamicState bool _enableBackupOnLaunch = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBackupOnLaunchKey, defaultValue: false); - bool _enablePeriodicBackup = - ChewieHiveUtil.getBool(CloudOTPHiveUtil.enablePeriodicBackupKey, - defaultValue: false); - int _periodicBackupInterval = ChewieHiveUtil.getInt( - CloudOTPHiveUtil.periodicBackupIntervalKey, - defaultValue: defaultPeriodicBackupIntervalHours * 60); final GlobalKey _setAutoBackupPasswordKey = GlobalKey(); String validConfigs = ""; @@ -99,11 +94,15 @@ class _BackupSettingScreenState extends BaseDynamicState _autoBackupPath = path; }); }); + FileUtil.getBackupDir().then((path) { + _defaultBackupPath = path; + }); loadWebDavConfig(); WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.jumpToAutoBackupPassword) { scrollToSetAutoBackupPassword(); } + _checkBackupMethodsEnabled(); }); } @@ -116,7 +115,20 @@ class _BackupSettingScreenState extends BaseDynamicState } } - @override + void _checkBackupMethodsEnabled() { + if (!_enableLocalBackup && !_enableCloudBackup) { + setState(() { + _enableLocalBackup = true; + ChewieHiveUtil.put( + CloudOTPHiveUtil.enableLocalBackupKey, true); + }); + DialogBuilder.showInfoDialog( + context, + message: appLocalizations.bothBackupDisabledWarning, + ); + } + } + @override Widget build(BuildContext context) { return ItemBuilder.buildSettingScreen( @@ -144,9 +156,7 @@ class _BackupSettingScreenState extends BaseDynamicState bool get canCloudBackup => _autoBackupPassword.isNotEmpty; bool get canImmediateBackup => - canBackup && - ((canLocalBackup && _enableLocalBackup) || - (canCloudBackup && _enableCloudBackup)); + canBackup && (_enableLocalBackup || _enableCloudBackup); loadWebDavConfig() async { List configs = @@ -156,9 +166,8 @@ class _BackupSettingScreenState extends BaseDynamicState }); } - getBackupsCount() async { - int currentLocalBackupsCount = await ExportTokenUtil.getBackupsCount(); - return [currentLocalBackupsCount]; + Future getBackupsCount() async { + return await ExportTokenUtil.getBackupsCount(); } deleteOldBackups(int maxBackupsCount) async { @@ -256,9 +265,8 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - Visibility( - visible: _enableAutoBackup, - child: CheckboxItem( + if (_enableAutoBackup) + CheckboxItem( value: _enableBackupOnLaunch, title: appLocalizations.backupOnLaunch, description: appLocalizations.backupOnLaunchTip, @@ -270,44 +278,8 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - ), - Visibility( - visible: _enableAutoBackup, - child: CheckboxItem( - value: _enablePeriodicBackup, - title: appLocalizations.periodicBackup, - description: appLocalizations.periodicBackupTip, - onTap: () { - setState(() { - _enablePeriodicBackup = !_enablePeriodicBackup; - ChewieHiveUtil.put(CloudOTPHiveUtil.enablePeriodicBackupKey, - _enablePeriodicBackup); - }); - }, - ), - ), - Visibility( - visible: _enableAutoBackup && _enablePeriodicBackup, - child: InlineSelectionItem( - title: appLocalizations.periodicBackupInterval, - hint: appLocalizations.periodicBackupInterval, - selections: BackupIntervalOption.getOptions(), - selected: BackupIntervalOption.fromMinutes( - _periodicBackupInterval), - onChanged: (option) { - if (option == null) return; - setState(() { - _periodicBackupInterval = option.minutes; - ChewieHiveUtil.put( - CloudOTPHiveUtil.periodicBackupIntervalKey, - _periodicBackupInterval); - }); - }, - ), - ), - Visibility( - visible: canImmediateBackup, - child: EntryItem( + if (canImmediateBackup) + EntryItem( title: appLocalizations.immediatelyBackup, description: appLocalizations.immediatelyBackupTip, trailing: LucideIcons.cloudUpload, @@ -316,18 +288,17 @@ class _BackupSettingScreenState extends BaseDynamicState showToast: true, showLoading: true, force: true); }, ), - ), - Visibility( - visible: canImmediateBackup, - child: EntryItem( + if (canImmediateBackup) + EntryItem( title: appLocalizations.maxBackupCount, description: appLocalizations.maxBackupCountTip, tip: _maxBackupsCount.toString(), onTap: () async { CustomLoadingDialog.showLoading( title: appLocalizations.loading); - List counts = await getBackupsCount(); + int currentCount = await getBackupsCount(); CustomLoadingDialog.dismissLoading(); + if (!mounted) return; InputValidateAsyncController validateAsyncController = InputValidateAsyncController( controller: @@ -343,7 +314,7 @@ class _BackupSettingScreenState extends BaseDynamicState title: appLocalizations.maxBackupCount, text: _maxBackupsCount.toString(), message: - '${appLocalizations.maxBackupCountTip}\n${appLocalizations.currentBackupCountTip(counts[0])}', + '${appLocalizations.maxBackupCountTip}\n${appLocalizations.currentBackupCountTip(currentCount)}', hint: appLocalizations.inputMaxBackupCount, inputFormatters: [RegexInputFormatter.onlyNumber], preventPop: true, @@ -376,12 +347,12 @@ class _BackupSettingScreenState extends BaseDynamicState validateAsyncController.doPop?.call(); } - if (count > 0 && (counts[0] > count)) { + if (count > 0 && (currentCount > count)) { DialogBuilder.showConfirmDialog( context, title: appLocalizations.maxBackupCountWarning, message: appLocalizations - .maxBackupCountWarningMessage(counts[0]), + .maxBackupCountWarningMessage(currentCount), onTapConfirm: () { onValid(); }, @@ -398,10 +369,9 @@ class _BackupSettingScreenState extends BaseDynamicState ); }, ), - ), EntryItem( title: appLocalizations.backupLogs, - trailing: Icons.history_rounded, + trailing: LucideIcons.history, onTap: () async { BackupLogScreen.show(context); }, @@ -415,7 +385,9 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _enableLocalBackup : false, title: appLocalizations.enableLocalBackup, description: appLocalizations.enableLocalBackupTip, - disabled: !canLocalBackup, + disabled: !canLocalBackup || + (_enableLocalBackup && + (!_enableCloudBackup || validConfigs.isEmpty)), onTap: () { setState(() { _enableLocalBackup = !_enableLocalBackup; @@ -424,9 +396,8 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - Visibility( - visible: canBackup && _enableLocalBackup, - child: EntryItem( + if (canBackup && _enableLocalBackup) + EntryItem( title: appLocalizations.autoBackupPath, description: _autoBackupPath, trailing: LucideIcons.ellipsis, @@ -444,10 +415,24 @@ class _BackupSettingScreenState extends BaseDynamicState } }, ), - ), - Visibility( - visible: canBackup && _enableLocalBackup, - child: EntryItem( + if (canBackup && + _enableLocalBackup && + _defaultBackupPath.isNotEmpty && + _autoBackupPath != _defaultBackupPath) + EntryItem( + title: appLocalizations.restoreDefaultBackupPath, + description: appLocalizations.restoreDefaultBackupPathTip, + trailing: LucideIcons.rotateCcw, + onTap: () { + setState(() { + _autoBackupPath = _defaultBackupPath; + ChewieHiveUtil.put( + CloudOTPHiveUtil.backupPathKey, ""); + }); + }, + ), + if (canBackup && _enableLocalBackup) + EntryItem( title: appLocalizations.viewLocalBackup, trailing: LucideIcons.squareArrowOutUpRight, onTap: () async { @@ -463,7 +448,6 @@ class _BackupSettingScreenState extends BaseDynamicState ); }, ), - ), ], ), CaptionItem( @@ -473,7 +457,8 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _enableCloudBackup : false, title: appLocalizations.enableCloudBackup, description: appLocalizations.enableCloudBackupTip, - disabled: !canCloudBackup, + disabled: !canCloudBackup || + (_enableCloudBackup && !_enableLocalBackup), onTap: () { setState(() { _enableCloudBackup = !_enableCloudBackup; @@ -483,9 +468,8 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - Visibility( - visible: canBackup && _enableCloudBackup, - child: EntryItem( + if (canBackup && _enableCloudBackup) + EntryItem( title: appLocalizations.cloudBackupServiceSetting, description: validConfigs.isNotEmpty ? appLocalizations.haveSetCloudBackupService(validConfigs) @@ -500,48 +484,8 @@ class _BackupSettingScreenState extends BaseDynamicState ); }, ), - ), ], ), ]; } } - -class BackupIntervalOption implements DropdownMixin { - final String label; - final int minutes; - - const BackupIntervalOption(this.label, this.minutes); - - static List getOptions() { - return const [ - BackupIntervalOption("1h", 60), - BackupIntervalOption("6h", 360), - BackupIntervalOption("12h", 720), - BackupIntervalOption("24h", 1440), - BackupIntervalOption("48h", 2880), - BackupIntervalOption("7d", 10080), - ]; - } - - static BackupIntervalOption? fromMinutes(int minutes) { - return getOptions().firstWhere( - (option) => option.minutes == minutes, - orElse: () => getOptions()[3], - ); - } - - @override - String get display => label; - - @override - String get selection => display; - - @override - bool operator ==(Object other) { - return other is BackupIntervalOption && minutes == other.minutes; - } - - @override - int get hashCode => minutes.hashCode; -} diff --git a/lib/Screens/Setting/setting_general_screen.dart b/lib/Screens/Setting/setting_general_screen.dart index 5528b493..35708c51 100644 --- a/lib/Screens/Setting/setting_general_screen.dart +++ b/lib/Screens/Setting/setting_general_screen.dart @@ -193,8 +193,8 @@ class GeneralSettingScreenState extends BaseDynamicState await FileOutput.clearLogs(); await getLogSize(); IToast.showTop(appLocalizations.clearLogSuccess); - } catch (e, t) { - ILogger.error("Failed to clear logs", e, t); + } catch (e) { + debugPrint("Failed to clear logs: $e"); IToast.showTop(appLocalizations.clearLogFailed); } finally { CustomLoadingDialog.dismissLoading(); diff --git a/lib/Screens/Setting/setting_safe_screen.dart b/lib/Screens/Setting/setting_safe_screen.dart index e0227a0c..144fc018 100644 --- a/lib/Screens/Setting/setting_safe_screen.dart +++ b/lib/Screens/Setting/setting_safe_screen.dart @@ -120,7 +120,6 @@ class _SafeSettingScreenState extends BaseDynamicState return CaptionItem( title: appLocalizations.gestureLockSettings, children: [ - const SizedBox(height: 10), CheckboxItem( disabled: _encryptedAndCustomPassword, value: _enableGuesturePasswd, @@ -128,9 +127,8 @@ class _SafeSettingScreenState extends BaseDynamicState description: appLocalizations.enableGestureLockTip, onTap: onEnablePinTapped, ), - Visibility( - visible: _geusturePasswdAvailable, - child: EntryItem( + if (_geusturePasswdAvailable) + EntryItem( title: _hasGuesturePasswd ? appLocalizations.changeGestureLock : appLocalizations.setGestureLock, @@ -139,10 +137,8 @@ class _SafeSettingScreenState extends BaseDynamicState : appLocalizations.haveToSetGestureLockTip, onTap: onChangePinTapped, ), - ), - Visibility( - visible: _gesturePasswdAvailableAndSet && _biometricAvailable, - child: CheckboxItem( + if (_gesturePasswdAvailableAndSet && _biometricAvailable) + CheckboxItem( value: _allowGuestureBiometric, title: appLocalizations.biometricUnlock, disabled: canAuthenticateResponse?.isSuccess != true, @@ -150,10 +146,8 @@ class _SafeSettingScreenState extends BaseDynamicState appLocalizations.biometricUnlockTip, onTap: onBiometricTapped, ), - ), - Visibility( - visible: _gesturePasswdAvailableAndSet, - child: CheckboxItem( + if (_gesturePasswdAvailableAndSet) + CheckboxItem( value: _hideGestureTrail, title: appLocalizations.hideGestureTrail, description: appLocalizations.hideGestureTrailTip, @@ -165,7 +159,6 @@ class _SafeSettingScreenState extends BaseDynamicState }); }, ), - ), ], ); } @@ -174,9 +167,8 @@ class _SafeSettingScreenState extends BaseDynamicState return CaptionItem( title: appLocalizations.databaseEncryptionSettings, children: [ - Visibility( - visible: DatabaseManager.isDatabaseEncrypted, - child: EntryItem( + if (DatabaseManager.isDatabaseEncrypted) + EntryItem( title: appLocalizations.editEncryptDatabasePassword, description: appLocalizations.encryptDatabaseTip, tip: _encryptDatabaseStatus == EncryptDatabaseStatus.defaultPassword @@ -217,10 +209,8 @@ class _SafeSettingScreenState extends BaseDynamicState ); }, ), - ), - Visibility( - visible: _encryptedAndCustomPassword, - child: EntryItem( + if (_encryptedAndCustomPassword) + EntryItem( title: appLocalizations.clearEncryptDatabasePassword, description: appLocalizations.clearEncryptDatabasePasswordTip, trailing: LucideIcons.refreshCcw, @@ -253,10 +243,8 @@ class _SafeSettingScreenState extends BaseDynamicState ); }, ), - ), - Visibility( - visible: _encryptedAndCustomPassword && _biometricAvailable, - child: CheckboxItem( + if (_encryptedAndCustomPassword && _biometricAvailable) + CheckboxItem( value: _allowDatabaseBiometric, disabled: canAuthenticateResponse?.isSuccess != true, description: canAuthenticateResponseString ?? @@ -284,7 +272,6 @@ class _SafeSettingScreenState extends BaseDynamicState _allowDatabaseBiometric); }, ), - ), ], ); } @@ -299,9 +286,8 @@ class _SafeSettingScreenState extends BaseDynamicState description: appLocalizations.autoLockTip, onTap: onEnableAutoLockTapped, ), - Visibility( - visible: _autoLock, - child: Selector( + if (_autoLock) + Selector( selector: (context, appProvider) => appProvider.autoLockTime, builder: (context, autoLockTime, child) => InlineSelectionItem( @@ -315,7 +301,6 @@ class _SafeSettingScreenState extends BaseDynamicState }, ), ), - ), ], ); } diff --git a/lib/Screens/Token/add_token_screen.dart b/lib/Screens/Token/add_token_screen.dart index 54c9fd23..ccdf22d0 100644 --- a/lib/Screens/Token/add_token_screen.dart +++ b/lib/Screens/Token/add_token_screen.dart @@ -266,9 +266,8 @@ class _AddTokenScreenState extends BaseDynamicState if (!_showAdvancedInfo && !isSteam && !isYandex) _showAdvancedInfoButton(), if (_showAdvancedInfo && !isSteam && !isYandex) _advancedInfo(), - const SizedBox(height: 10), - ..._categoryInfo(), - if (_isEditing) ..._copyTimesInfo(), + _categoryInfo(), + if (_isEditing) _copyTimesInfo(), if (_isEditing) ..._deleteButton(), SizedBox(height: _isEditing ? 0 : 30), ], @@ -428,67 +427,70 @@ class _AddTokenScreenState extends BaseDynamicState ); } - return [ - EntryItem( - tipWidth: 300, - title: appLocalizations.editTokenCategory, - trailing: LucideIcons.shapes, - tipWidget: selectedCategoryUids.isNotEmpty - ? Wrap( - spacing: 5, - runSpacing: 5, - alignment: WrapAlignment.end, - children: selectedCategoryUids - .map( - (e) => RoundIconTextButton( - height: 32, - radius: 6, - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 4), - background: ChewieTheme.primaryColor, - text: categories - .firstWhere((element) => element.uid == e) - .title, - onPressed: selectCategory, - ), - ) - .toList(), - ) - : null, - onTap: selectCategory, - ), - EntryItem( - tipWidth: 120, - title: appLocalizations.autoMatchTokenIcon, - trailing: LucideIcons.refreshCcw, - onTap: () { - setState(() { - customedImage = false; - _otpToken.imagePath = - TokenImageUtil.matchBrandLogo(_otpToken) ?? ""; - }); - }, - ), - EntryItem( - tipWidth: 300, - title: appLocalizations.editTokenIcon, - tip: _otpToken.imagePath.notNullOrEmpty ? _otpToken.imagePath : "", - onTap: () { - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => SelectIconBottomSheet( - token: _otpToken, - onSelected: (path) { - customedImage = true; - _otpToken.imagePath = path; - setState(() {}); - }, - ), - ); - }, - ), - ]; + return CaptionItem( + title: appLocalizations.categoryAndIcon, + children: [ + EntryItem( + tipWidth: 300, + title: appLocalizations.editTokenCategory, + trailing: LucideIcons.shapes, + tipWidget: selectedCategoryUids.isNotEmpty + ? Wrap( + spacing: 5, + runSpacing: 5, + alignment: WrapAlignment.end, + children: selectedCategoryUids + .map( + (e) => RoundIconTextButton( + height: 32, + radius: 6, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + background: ChewieTheme.primaryColor, + text: categories + .firstWhere((element) => element.uid == e) + .title, + onPressed: selectCategory, + ), + ) + .toList(), + ) + : null, + onTap: selectCategory, + ), + EntryItem( + tipWidth: 120, + title: appLocalizations.autoMatchTokenIcon, + trailing: LucideIcons.refreshCcw, + onTap: () { + setState(() { + customedImage = false; + _otpToken.imagePath = + TokenImageUtil.matchBrandLogo(_otpToken) ?? ""; + }); + }, + ), + EntryItem( + tipWidth: 300, + title: appLocalizations.editTokenIcon, + tip: _otpToken.imagePath.notNullOrEmpty ? _otpToken.imagePath : "", + onTap: () { + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => SelectIconBottomSheet( + token: _otpToken, + onSelected: (path) { + customedImage = true; + _otpToken.imagePath = path; + setState(() {}); + }, + ), + ); + }, + ), + ], + ); } _showAdvancedInfoButton() { @@ -635,35 +637,38 @@ class _AddTokenScreenState extends BaseDynamicState } _copyTimesInfo() { - return [ - EntryItem( - tipWidth: 300, - title: appLocalizations.resetCopyTimes, - tip: appLocalizations.currentCopyTimes(_otpToken.copyTimes), - onTap: () { - DialogBuilder.showConfirmDialog( - context, - title: appLocalizations.resetCopyTimesTitle, - message: appLocalizations.resetCopyTimesMessage(_otpToken.title), - onTapConfirm: () async { - await TokenDao.resetSingleTokenCopyTimes(_otpToken); - homeScreenState?.resetCopyTimesSingle(_otpToken); - IToast.showTop(appLocalizations.resetSuccess); - setState(() {}); - }, - onTapCancel: () {}, - ); - }, - ), - EntryItem( - tipWidth: 300, - title: appLocalizations.lastCopyTime, - tip: _otpToken.lastCopyTimeStamp == 0 - ? appLocalizations.neverCopied - : TimeUtil.timestampToDateString(_otpToken.lastCopyTimeStamp), - onTap: () {}, - ), - ]; + return CaptionItem( + title: appLocalizations.copyTimes, + children: [ + EntryItem( + tipWidth: 300, + title: appLocalizations.resetCopyTimes, + tip: appLocalizations.currentCopyTimes(_otpToken.copyTimes), + onTap: () { + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.resetCopyTimesTitle, + message: appLocalizations.resetCopyTimesMessage(_otpToken.title), + onTapConfirm: () async { + await TokenDao.resetSingleTokenCopyTimes(_otpToken); + homeScreenState?.resetCopyTimesSingle(_otpToken); + IToast.showTop(appLocalizations.resetSuccess); + setState(() {}); + }, + onTapCancel: () {}, + ); + }, + ), + EntryItem( + tipWidth: 300, + title: appLocalizations.lastCopyTime, + tip: _otpToken.lastCopyTimeStamp == 0 + ? appLocalizations.neverCopied + : TimeUtil.timestampToDateString(_otpToken.lastCopyTimeStamp), + onTap: () {}, + ), + ], + ); } _deleteButton() { diff --git a/lib/Screens/Token/import_preview_screen.dart b/lib/Screens/Token/import_preview_screen.dart index 9e161b26..bfb912d4 100644 --- a/lib/Screens/Token/import_preview_screen.dart +++ b/lib/Screens/Token/import_preview_screen.dart @@ -250,7 +250,7 @@ class _ImportPreviewScreenState extends BaseDynamicState { Expanded( child: ListView( padding: - const EdgeInsets.only(left: 6, right: 6, bottom: 10), + const EdgeInsets.only(left: 10, right: 10, bottom: 10), children: [ const SizedBox(height: 10), InlineSelectionItem>( @@ -293,78 +293,66 @@ class _ImportPreviewScreenState extends BaseDynamicState { Widget _buildTokenItem(ImportTokenItem item) { final isError = item.status == ImportTokenStatus.error; - return Container( - margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 8), - decoration: BoxDecoration( - color: ChewieTheme.canvasColor, - borderRadius: BorderRadius.circular(8), - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - child: InkWell( - borderRadius: BorderRadius.circular(8), - onTap: isError - ? null - : () { - setState(() { - item.selected = !item.selected; - }); - }, - child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Checkbox( - value: item.selected, - onChanged: isError - ? null - : (value) { - setState(() { - item.selected = value ?? false; - }); - }, - activeColor: ChewieTheme.primaryColor, - ), - const SizedBox(width: 4), - CloudOTPItemBuilder.buildTokenImage(item.token, size: 24), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.token.issuer.isNotEmpty - ? item.token.issuer - : item.token.account, - style: ChewieTheme.titleSmall, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (item.token.account.isNotEmpty && - item.token.account != item.token.issuer) - Text( - item.token.account, - style: ChewieTheme.bodySmall, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (isError && item.errorReason != null) - Text( - item.errorReason!, - style: - ChewieTheme.bodySmall.copyWith(color: Colors.red), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + return InkWell( + onTap: isError + ? null + : () { + setState(() { + item.selected = !item.selected; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: [ + Checkbox( + value: item.selected, + onChanged: isError + ? null + : (value) { + setState(() { + item.selected = value ?? false; + }); + }, + activeColor: ChewieTheme.primaryColor, + ), + const SizedBox(width: 4), + CloudOTPItemBuilder.buildTokenImage(item.token, size: 24), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.token.issuer.isNotEmpty + ? item.token.issuer + : item.token.account, + style: ChewieTheme.titleSmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - const SizedBox(width: 8), - _buildTokenStatusText(item), - ], + if (item.token.account.isNotEmpty && + item.token.account != item.token.issuer) + Text( + item.token.account, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (isError && item.errorReason != null) + Text( + item.errorReason!, + style: ChewieTheme.bodySmall.copyWith(color: Colors.red), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), - ), + const SizedBox(width: 8), + _buildTokenStatusText(item), + const SizedBox(width: 8), + ], ), ), ); @@ -394,68 +382,57 @@ class _ImportPreviewScreenState extends BaseDynamicState { Widget _buildCategoryItem(ImportCategoryItem item) { final bindingText = _buildCategoryBindingText(item); - return Container( - margin: const EdgeInsets.symmetric(vertical: 3, horizontal: 8), - decoration: BoxDecoration( - color: ChewieTheme.canvasColor, - borderRadius: BorderRadius.circular(8), - ), - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - child: InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () { - setState(() { - item.selected = !item.selected; - }); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: Row( - children: [ - Checkbox( - value: item.selected, - onChanged: (value) { - setState(() { - item.selected = value ?? false; - }); - }, - activeColor: ChewieTheme.primaryColor, - ), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.category.title, - style: ChewieTheme.titleSmall, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (bindingText.isNotEmpty) - Text( - bindingText, - style: ChewieTheme.bodySmall, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + return InkWell( + onTap: () { + setState(() { + item.selected = !item.selected; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + children: [ + Checkbox( + value: item.selected, + onChanged: (value) { + setState(() { + item.selected = value ?? false; + }); + }, + activeColor: ChewieTheme.primaryColor, + ), + const SizedBox(width: 4), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.category.title, + style: ChewieTheme.titleSmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - const SizedBox(width: 8), - Text( - item.isNew - ? appLocalizations.importCategoryNew - : (_overwriteExisting - ? appLocalizations.importOverwrite - : appLocalizations.importCategoryExisting), - style: ChewieTheme.bodySmall, - ), - ], + if (bindingText.isNotEmpty) + Text( + bindingText, + style: ChewieTheme.bodySmall, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), - ), + const SizedBox(width: 8), + Text( + item.isNew + ? appLocalizations.importCategoryNew + : (_overwriteExisting + ? appLocalizations.importOverwrite + : appLocalizations.importCategoryExisting), + style: ChewieTheme.bodySmall, + ), + const SizedBox(width: 8), + ], ), ), ); diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index a345f348..f2a09e1c 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -21,16 +21,14 @@ import 'package:cloudotp/Database/category_dao.dart'; import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:cloudotp/Models/opt_token.dart'; import 'package:cloudotp/Screens/Backup/cloud_service_screen.dart'; -import 'package:cloudotp/Screens/Setting/about_setting_screen.dart'; -import 'package:cloudotp/Screens/feature_showcase_screen.dart'; import 'package:cloudotp/Screens/layout_select_screen.dart'; import 'package:cloudotp/Screens/sort_select_screen.dart'; import 'package:cloudotp/Screens/Setting/backup_log_screen.dart'; -import 'package:cloudotp/Screens/Setting/mobile_setting_navigation_screen.dart'; import 'package:cloudotp/Screens/main_screen.dart'; import 'package:cloudotp/Utils/hive_util.dart'; import 'package:cloudotp/Utils/search_query_parser.dart'; import 'package:cloudotp/Widgets/BottomSheet/add_bottom_sheet.dart'; +import 'package:cloudotp/Widgets/BottomSheet/more_bottom_sheet.dart'; import 'package:cloudotp/Widgets/cloudotp/cloudotp_item_builder.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -48,7 +46,6 @@ import '../Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart'; import '../Widgets/BottomSheet/select_token_bottom_sheet.dart'; import '../Widgets/CoachMark/coach_mark_manager.dart'; import '../l10n/l10n.dart'; -import 'Token/category_screen.dart'; import 'Token/token_layout.dart'; class HomeScreen extends StatefulWidget { @@ -870,20 +867,6 @@ class HomeScreenState extends BasePanelScreenState final currentUids = seen; tokenKeyMap.removeWhere((uid, _) => !currentUids.contains(uid)); performSort(); - _maybeShowCoachMark(); - }); - } - - void _maybeShowCoachMark() { - if (!ResponsiveUtil.isMobile()) return; - if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownCoachMarkKey, - defaultValue: false)) return; - if (tokens.isEmpty) return; - if (_multiSelectMode || _shownSearchbarNotifier.value) return; - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - _showCoachMarkInternal(force: false); }); } @@ -1187,55 +1170,12 @@ class HomeScreenState extends BasePanelScreenState context: context, icon: LucideIcons.ellipsisVertical, onPressed: () { - BottomSheetBuilder.showContextMenu( + BottomSheetBuilder.showBottomSheet( context, - FlutterContextMenu( - entries: [ - if (tokens.length > 1) - FlutterContextMenuItem( - appLocalizations.select, - iconData: LucideIcons.listChecks, - onPressed: () { - enterMultiSelectMode(tokens.first.uid); - }, - ), - FlutterContextMenuItem( - appLocalizations.category, - iconData: LucideIcons.shapes, - onPressed: () { - RouteUtil.pushCupertinoRoute( - context, const CategoryScreen()); - }, - ), - FlutterContextMenuItem( - appLocalizations.setting, - iconData: LucideIcons.bolt, - onPressed: () { - RouteUtil.pushCupertinoRoute( - context, const MobileSettingNavigationScreen()); - }, - ), - FlutterContextMenuItem( - appLocalizations.about, - iconData: LucideIcons.info, - onPressed: () { - RouteUtil.pushCupertinoRoute( - context, const AboutSettingScreen()); - }, - ), - FlutterContextMenuItem( - appLocalizations.featureShowcase, - iconData: LucideIcons.telescope, - onPressed: () { - if (ResponsiveUtil.isLandscapeLayout()) { - FeatureShowcaseScreen.showAsDialog(context); - } else { - RouteUtil.pushCupertinoRoute( - context, const FeatureShowcaseScreen()); - } - }, - ), - ], + responsive: true, + (ctx) => MoreBottomSheet( + showSelect: tokens.length > 1, + onSelect: () => enterMultiSelectMode(tokens.first.uid), ), ); }, diff --git a/lib/Screens/layout_select_screen.dart b/lib/Screens/layout_select_screen.dart index 4a3cb017..dc615d77 100644 --- a/lib/Screens/layout_select_screen.dart +++ b/lib/Screens/layout_select_screen.dart @@ -57,13 +57,13 @@ class _LayoutSelectScreenState extends BaseDynamicState { final currentType = homeScreenState?.layoutType ?? LayoutType.Simple; final content = Padding( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeader(), - const SizedBox(height: 14), + const SizedBox(height: 12), _buildGrid(currentType), ], ), @@ -138,7 +138,7 @@ class _LayoutSelectScreenState extends BaseDynamicState { } Widget _buildGrid(LayoutType currentType) { - final values = LayoutType.values; + const values = LayoutType.values; return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -212,14 +212,12 @@ class _LayoutSelectScreenState extends BaseDynamicState { type.title, style: ChewieTheme.labelMedium.copyWith( fontWeight: FontWeight.w600, - color: - selected ? _accent : ChewieTheme.titleLarge.color, + color: selected ? _accent : ChewieTheme.titleLarge.color, ), overflow: TextOverflow.ellipsis, ), ), - if (selected) - Icon(LucideIcons.check, size: 14, color: _accent), + if (selected) Icon(LucideIcons.check, size: 14, color: _accent), ], ), ], @@ -231,70 +229,162 @@ class _LayoutSelectScreenState extends BaseDynamicState { Widget _buildPreview(LayoutType type) { switch (type) { case LayoutType.Simple: - return _gridPreview(crossAxis: 2, rows: 2, itemHeight: 12); + return _buildSimplePreview(); case LayoutType.Compact: - return _gridPreview(crossAxis: 2, rows: 3, itemHeight: 8); + return _buildCompactPreview(); case LayoutType.Spotlight: - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _previewBar(height: 18, widthFactor: 0.85), - const SizedBox(height: 3), - _previewBar(height: 12, widthFactor: 0.85), - ], - ); + return _buildSpotlightPreview(); case LayoutType.List: - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - 4, - (_) => Padding( - padding: const EdgeInsets.symmetric(vertical: 1.5), - child: _previewBar(height: 5, widthFactor: 1), - ), + return _buildListPreview(); + } + } + + Widget _buildSimplePreview() { + Widget card() => Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: _accent.withAlpha(20), + borderRadius: BorderRadius.circular(3), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + _dot(5), + const SizedBox(width: 2), + Expanded(child: _line(height: 3)), + ]), + const SizedBox(height: 3), + Center(child: FractionallySizedBox(widthFactor: 0.7, child: _line(height: 5))), + ], ), ); - } + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row(children: [Expanded(child: card()), const SizedBox(width: 3), Expanded(child: card())]), + const SizedBox(height: 3), + Row(children: [Expanded(child: card()), const SizedBox(width: 3), Expanded(child: card())]), + ], + ); } - Widget _gridPreview({ - required int crossAxis, - required int rows, - required double itemHeight, - }) { + Widget _buildCompactPreview() { + Widget card() => Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: _accent.withAlpha(20), + borderRadius: BorderRadius.circular(3), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Expanded(child: _line(height: 3)), + const SizedBox(width: 2), + _dot(4), + ]), + const SizedBox(height: 2), + FractionallySizedBox(widthFactor: 0.6, child: _line(height: 4)), + ], + ), + ); return Column( mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(rows, (r) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 1.5), + children: [ + Row(children: [Expanded(child: card()), const SizedBox(width: 3), Expanded(child: card())]), + const SizedBox(height: 3), + Row(children: [Expanded(child: card()), const SizedBox(width: 3), Expanded(child: card())]), + const SizedBox(height: 3), + Row(children: [Expanded(child: card()), const SizedBox(width: 3), Expanded(child: card())]), + ], + ); + } + + Widget _buildSpotlightPreview() { + Widget row() => Container( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), + decoration: BoxDecoration( + color: _accent.withAlpha(20), + borderRadius: BorderRadius.circular(3), + ), child: Row( - children: List.generate(crossAxis, (c) { - return Expanded( - child: Container( - margin: EdgeInsets.only(right: c == crossAxis - 1 ? 0 : 3), - height: itemHeight, - decoration: BoxDecoration( - color: _accent.withAlpha(70), - borderRadius: BorderRadius.circular(3), - ), + children: [ + _dot(8), + const SizedBox(width: 4), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FractionallySizedBox(widthFactor: 0.5, child: _line(height: 3)), + const SizedBox(height: 2), + _line(height: 5), + ], ), - ); - }), + ), + const SizedBox(width: 4), + _dot(6), + ], ), ); - }), + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + row(), + const SizedBox(height: 3), + row(), + ], ); } - Widget _previewBar({required double height, required double widthFactor}) { - return FractionallySizedBox( - widthFactor: widthFactor, - child: Container( - height: height, - decoration: BoxDecoration( - color: _accent.withAlpha(70), - borderRadius: BorderRadius.circular(3), - ), + Widget _buildListPreview() { + Widget row() => Container( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 3), + decoration: BoxDecoration( + color: _accent.withAlpha(20), + borderRadius: BorderRadius.circular(3), + ), + child: Row( + children: [ + _dot(5), + const SizedBox(width: 3), + Expanded(child: _line(height: 3)), + const SizedBox(width: 4), + SizedBox(width: 20, child: _line(height: 4)), + ], + ), + ); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + row(), + const SizedBox(height: 3), + row(), + const SizedBox(height: 3), + row(), + const SizedBox(height: 3), + row(), + ], + ); + } + + Widget _dot(double size) { + return Container( + width: size, + height: size, + decoration: BoxDecoration( + color: _accent.withAlpha(90), + shape: BoxShape.circle, + ), + ); + } + + Widget _line({required double height}) { + return Container( + height: height, + decoration: BoxDecoration( + color: _accent.withAlpha(70), + borderRadius: BorderRadius.circular(2), ), ); } diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 7b397a46..95d9587d 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -80,7 +80,6 @@ class MainScreenState extends BaseWindowState ProtocolListener, AutomaticKeepAliveClientMixin { Timer? _timer; - Timer? _periodicBackupTimer; TextEditingController searchController = TextEditingController(); List _menuTokens = []; List _menuCategories = []; @@ -180,7 +179,6 @@ class MainScreenState extends BaseWindowState homeScreenState?.performSearch(searchController.text); }); _startAutoBackupOnLaunch(); - _startPeriodicBackupTimer(); } static const _notifierChannel = MethodChannel('local_notifier'); @@ -1319,22 +1317,6 @@ class MainScreenState extends BaseWindowState } } - void _startPeriodicBackupTimer() { - _periodicBackupTimer?.cancel(); - if (!ChewieHiveUtil.getBool(CloudOTPHiveUtil.enablePeriodicBackupKey)) { - return; - } - final minutes = ChewieHiveUtil.getInt( - CloudOTPHiveUtil.periodicBackupIntervalKey, - defaultValue: defaultPeriodicBackupIntervalHours * 60, - ); - _periodicBackupTimer = Timer.periodic( - Duration(minutes: minutes), - (_) => ExportTokenUtil.autoBackup( - triggerType: AutoBackupTriggerType.scheduled), - ); - } - @override void didChangeAppLifecycleState(AppLifecycleState state) { switch (state) { @@ -1360,7 +1342,6 @@ class MainScreenState extends BaseWindowState @override void dispose() { - _periodicBackupTimer?.cancel(); protocolHandler.removeListener(this); trayManager.removeListener(this); WidgetsBinding.instance.removeObserver(this); diff --git a/lib/Screens/sort_select_screen.dart b/lib/Screens/sort_select_screen.dart index 23379c2e..7a7923be 100644 --- a/lib/Screens/sort_select_screen.dart +++ b/lib/Screens/sort_select_screen.dart @@ -92,7 +92,7 @@ class _SortSelectScreenState extends BaseDynamicState { final groups = _groups(); Widget header = Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: _buildHeader(), ); @@ -112,7 +112,7 @@ class _SortSelectScreenState extends BaseDynamicState { children: [ header, Padding( - padding: const EdgeInsets.fromLTRB(14, 0, 14, 14), + padding: const EdgeInsets.fromLTRB(10, 0, 10, 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -133,10 +133,10 @@ class _SortSelectScreenState extends BaseDynamicState { children: [ for (final g in groups) Padding( - padding: const EdgeInsets.fromLTRB(14, 6, 14, 0), + padding: const EdgeInsets.fromLTRB(12, 6, 12, 0), child: _buildGroup(g), ), - const SizedBox(height: 14), + const SizedBox(height: 12), ], ); @@ -244,8 +244,7 @@ class _SortSelectScreenState extends BaseDynamicState { indent: 44, color: ChewieTheme.borderColor.withAlpha(120), ), - _buildSortItem( - group.types[i], group.types[i] == currentType), + _buildSortItem(group.types[i], group.types[i] == currentType), ], ], ), @@ -288,13 +287,11 @@ class _SortSelectScreenState extends BaseDynamicState { type.title, style: ChewieTheme.bodyMedium.copyWith( fontWeight: selected ? FontWeight.w600 : FontWeight.w500, - color: - selected ? _accent : ChewieTheme.titleLarge.color, + color: selected ? _accent : ChewieTheme.titleLarge.color, ), ), ), - if (selected) - Icon(LucideIcons.check, size: 16, color: _accent), + if (selected) Icon(LucideIcons.check, size: 16, color: _accent), ], ), ), diff --git a/lib/TokenUtils/export_token_util.dart b/lib/TokenUtils/export_token_util.dart index d030491d..f8c32ba7 100644 --- a/lib/TokenUtils/export_token_util.dart +++ b/lib/TokenUtils/export_token_util.dart @@ -501,7 +501,12 @@ class ExportTokenUtil { } static Future getBackupsCount() async { - return (await getLocalBackups()).length; + final lists = await getLocalBackups(); + int total = 0; + for (final list in lists) { + total += list.length; + } + return total; } static Future> getLocalBackupsByPath( diff --git a/lib/Utils/constant.dart b/lib/Utils/constant.dart index e25078b8..08ef1ff4 100644 --- a/lib/Utils/constant.dart +++ b/lib/Utils/constant.dart @@ -21,8 +21,6 @@ const defaultMaxBackupCount = 100; const maxBackupCountThrehold = 500; -const defaultPeriodicBackupIntervalHours = 24; - const maxBytesLength = 1000; const double autoCopyNextCodeProgressThrehold = 0.25; diff --git a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart index 9f34a000..a952b9a3 100644 --- a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart @@ -82,7 +82,7 @@ class AliyunDriveBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -99,8 +99,8 @@ class AliyunDriveBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -111,7 +111,7 @@ class AliyunDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -165,19 +165,17 @@ class AliyunDriveBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.id); setState(() { diff --git a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart index df70c724..47970bd7 100644 --- a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart @@ -81,7 +81,7 @@ class BoxBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -98,8 +98,8 @@ class BoxBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -110,7 +110,7 @@ class BoxBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -164,19 +164,17 @@ class BoxBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.id); setState(() { @@ -184,8 +182,7 @@ class BoxBackupsBottomSheetState }); IToast.showTop(appLocalizations.deleteSuccess); } catch (e, t) { - ILogger.error( - "Failed to delete backup file from box", e, t); + ILogger.error("Failed to delete backup file from box", e, t); IToast.showTop(appLocalizations.deleteFailed); } CustomLoadingDialog.dismissLoading(); diff --git a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart index 3f1b880c..ee7782ba 100644 --- a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart @@ -83,7 +83,7 @@ class DropboxBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -100,8 +100,8 @@ class DropboxBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -112,7 +112,7 @@ class DropboxBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -166,19 +166,17 @@ class DropboxBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.name); setState(() { diff --git a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart index 097245f0..fa207b97 100644 --- a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart @@ -83,7 +83,7 @@ class GoogleDriveBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -100,8 +100,8 @@ class GoogleDriveBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -112,7 +112,7 @@ class GoogleDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -166,19 +166,17 @@ class GoogleDriveBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.id); setState(() { @@ -186,8 +184,7 @@ class GoogleDriveBackupsBottomSheetState }); IToast.showTop(appLocalizations.deleteSuccess); } catch (e, t) { - ILogger.error( - "Failed to delete file from google drive", e, t); + ILogger.error("Failed to delete file from google drive", e, t); IToast.showTop(appLocalizations.deleteFailed); } CustomLoadingDialog.dismissLoading(); diff --git a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart index 86988194..d2e3ecad 100644 --- a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart @@ -82,7 +82,7 @@ class HuaweiCloudBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -99,8 +99,8 @@ class HuaweiCloudBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -111,7 +111,7 @@ class HuaweiCloudBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -165,22 +165,19 @@ class HuaweiCloudBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { - bool success = - await widget.cloudService.deleteFile(file.id); + bool success = await widget.cloudService.deleteFile(file.id); if (success) { setState(() { files.remove(file); @@ -190,8 +187,7 @@ class HuaweiCloudBackupsBottomSheetState IToast.showTop(appLocalizations.deleteFailed); } } catch (e, t) { - ILogger.error( - "Failed to delete file from huawei cloud", e, t); + ILogger.error("Failed to delete file from huawei cloud", e, t); IToast.showTop(appLocalizations.deleteFailed); } CustomLoadingDialog.dismissLoading(); diff --git a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart index 72fc5ce9..d0ef10f5 100644 --- a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart @@ -88,7 +88,7 @@ class LocalBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -123,7 +123,7 @@ class LocalBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem( index < files.length ? files[index] @@ -171,7 +171,7 @@ class LocalBackupsBottomSheetState ), const SizedBox(height: 2), Text( - "$time · $size${isDefaultPath ? " · ${appLocalizations.fromInternalBackupPath}" : ""}", + "$time · $size", style: ChewieTheme.bodySmall.copyWith( color: ChewieTheme.bodyMedium.color?.withAlpha(120), fontSize: 11, @@ -179,24 +179,32 @@ class LocalBackupsBottomSheetState maxLines: 1, overflow: TextOverflow.ellipsis, ), + if (isDefaultPath) + Text( + appLocalizations.fromInternalBackupPath, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(120), + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ], ), ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await file.delete(); setState(() { @@ -204,8 +212,7 @@ class LocalBackupsBottomSheetState }); IToast.showTop(appLocalizations.deleteSuccess); } catch (e, t) { - ILogger.error( - "Failed to delete backup file from local", e, t); + ILogger.error("Failed to delete backup file from local", e, t); IToast.showTop(appLocalizations.deleteFailed); } CustomLoadingDialog.dismissLoading(); diff --git a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart index 8bce72be..286b4146 100644 --- a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart @@ -82,7 +82,7 @@ class OneDriveBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -99,8 +99,8 @@ class OneDriveBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -111,7 +111,7 @@ class OneDriveBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -165,16 +165,15 @@ class OneDriveBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { diff --git a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart index 1f0866f3..a51ba621 100644 --- a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart @@ -83,7 +83,7 @@ class S3CloudBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -100,8 +100,8 @@ class S3CloudBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -112,7 +112,7 @@ class S3CloudBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -166,19 +166,17 @@ class S3CloudBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.path); setState(() { diff --git a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart index c30810d1..a59735e1 100644 --- a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart @@ -82,7 +82,7 @@ class WebDavBackupsBottomSheetState _buildHeader() { return Padding( - padding: const EdgeInsets.fromLTRB(14, 14, 14, 8), + padding: const EdgeInsets.fromLTRB(10, 12, 10, 8), child: Row( children: [ Container( @@ -99,8 +99,8 @@ class WebDavBackupsBottomSheetState Expanded( child: Text( appLocalizations.cloudBackupFiles(widget.files.length), - style: ChewieTheme.titleMedium - .copyWith(fontWeight: FontWeight.bold), + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), ), ), ], @@ -111,7 +111,7 @@ class WebDavBackupsBottomSheetState _buildButtons() { return ListView.builder( shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(14, 4, 14, 14), + padding: const EdgeInsets.fromLTRB(12, 4, 12, 12), itemBuilder: (context, index) => _buildItem(files[index]), itemCount: files.length, ); @@ -167,19 +167,17 @@ class WebDavBackupsBottomSheetState ), const SizedBox(width: 6), CircleIconButton( - icon: Icon(LucideIcons.import, size: 18, - color: ChewieTheme.primaryColor), + icon: Icon(LucideIcons.import, + size: 18, color: ChewieTheme.primaryColor), onTap: () async { Navigator.pop(context); widget.onSelected(file); }, ), CircleIconButton( - icon: - const Icon(LucideIcons.trash2, color: Colors.red, size: 18), + icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 18), onTap: () async { - CustomLoadingDialog.showLoading( - title: appLocalizations.deleting); + CustomLoadingDialog.showLoading(title: appLocalizations.deleting); try { await widget.cloudService.deleteFile(file.path ?? ""); setState(() { diff --git a/lib/Widgets/BottomSheet/more_bottom_sheet.dart b/lib/Widgets/BottomSheet/more_bottom_sheet.dart new file mode 100644 index 00000000..1e2bb205 --- /dev/null +++ b/lib/Widgets/BottomSheet/more_bottom_sheet.dart @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Screens/Setting/about_setting_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_appearance_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_backup_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_general_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_operation_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_safe_screen.dart'; +import 'package:cloudotp/Screens/Token/category_screen.dart'; +import 'package:cloudotp/Screens/feature_showcase_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../../l10n/l10n.dart'; + +class MoreBottomSheet extends StatefulWidget { + const MoreBottomSheet({ + super.key, + this.showSelect = false, + this.onSelect, + }); + + final bool showSelect; + final VoidCallback? onSelect; + + @override + State createState() => _MoreBottomSheetState(); +} + +class _MoreBottomSheetState extends BaseDynamicState { + @override + Widget build(BuildContext context) { + return Wrap( + runAlignment: WrapAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: ChewieDimens.defaultRadius, + bottom: ResponsiveUtil.isWideDevice() + ? ChewieDimens.defaultRadius + : Radius.zero, + ), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeader(), + _buildActionBoxes(), + _buildSettingsGroup(), + const SizedBox(height: 12), + ], + ), + ), + ], + ); + } + + Widget _buildHeader() { + return Padding( + padding: const EdgeInsets.fromLTRB(10, 12, 10, 10), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.ellipsisVertical, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Text( + appLocalizations.more, + style: + ChewieTheme.titleMedium.copyWith(fontWeight: FontWeight.bold), + ), + ], + ), + ); + } + + Widget _buildActionBoxes() { + return Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), + child: Row( + children: [ + if (widget.showSelect) + Expanded( + child: _buildActionBox( + icon: LucideIcons.listChecks, + title: appLocalizations.select, + onTap: () { + Navigator.pop(context); + widget.onSelect?.call(); + }, + ), + ), + if (widget.showSelect) const SizedBox(width: 8), + Expanded( + child: _buildActionBox( + icon: LucideIcons.shapes, + title: appLocalizations.category, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute(context, const CategoryScreen()); + }, + ), + ), + ], + ), + ); + } + + Widget _buildActionBox({ + required IconData icon, + required String title, + required VoidCallback onTap, + }) { + return Material( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: ChewieTheme.borderColor), + ), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Row( + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(7), + ), + child: Icon(icon, size: 15, color: ChewieTheme.primaryColor), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: ChewieTheme.bodyMedium + .copyWith(fontWeight: FontWeight.w500), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSettingsGroup() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CaptionItem( + title: appLocalizations.setting, + initiallyExpanded: true, + children: [ + EntryItem( + title: appLocalizations.generalSetting, + showLeading: true, + leading: LucideIcons.settings2, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, const GeneralSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.appearanceSetting, + showLeading: true, + leading: LucideIcons.paintbrushVertical, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, const AppearanceSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.operationSetting, + showLeading: true, + leading: LucideIcons.pointer, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, const OperationSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.backupSetting, + showLeading: true, + leading: LucideIcons.cloudUpload, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, const BackupSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.safeSetting, + showLeading: true, + leading: LucideIcons.shieldCheck, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute(context, const SafeSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.about, + showLeading: true, + leading: LucideIcons.info, + onTap: () { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute(context, const AboutSettingScreen()); + }, + ), + EntryItem( + title: appLocalizations.featureShowcase, + showLeading: true, + leading: LucideIcons.telescope, + onTap: () { + Navigator.pop(context); + if (ResponsiveUtil.isLandscapeLayout()) { + FeatureShowcaseScreen.showAsDialog(context); + } else { + RouteUtil.pushCupertinoRoute( + context, const FeatureShowcaseScreen()); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 163c5e39..39688aee 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -31,6 +31,7 @@ "reload": "Reload", "loadErrorType": "Error Type: {type}", "loadUnkownError": "Unknown Error", + "more": "More", "setting": "Settings", "generalSetting": "General", "language": "Language", @@ -156,6 +157,8 @@ "autoBackup": "Enable Auto Backup", "autoBackupTip": "Automatically back up to the specified location when tokens or categories change; effective after setting a backup password", "autoBackupPath": "Local Backup Location", + "restoreDefaultBackupPath": "Restore Default Location", + "restoreDefaultBackupPathTip": "Reset backup location to the built-in internal storage path", "backupLogs": "Backup Logs", "backupLogSubtitle": "View automatic backup history", "noBackupLogs": "No Backup Logs Available", @@ -220,6 +223,7 @@ "enableCloudBackupTip": "Once cloud backup is enabled, the backup file will be uploaded to the cloud service during backup", "enableLocalBackup": "Enable Local Backup", "enableLocalBackupTip": "Once local backup is enabled, the backup file will be saved to the specified directory; we recommend enabling local backup even if you have enabled cloud backup", + "bothBackupDisabledWarning": "Both local and cloud backup are disabled. Local backup has been automatically enabled to ensure your data is protected.", "cloudBackupServiceSetting": "Cloud Backup Service Settings", "cloudBackupServiceSettingTip": "Configure cloud backup service", "notCloudBackupService": "Cloud Backup Service Not Configured", @@ -627,6 +631,7 @@ "setCategoryForTokenDetail": "Choose category for token \"{issuer}\"", "setTokenForCategory": "Choose tokens for category \"{category}\"", "category": "Category", + "categoryAndIcon": "Category & Icon", "addCategory": "Add new category", "inputCategory": "Enter category name", "categoryNameCannotBeEmpty": "Category name cannot be empty", diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 894269b6..d2e8ba7e 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -31,6 +31,7 @@ "reload": "再読み込み", "loadErrorType": "エラータイプ:{type}", "loadUnkownError": "不明なエラー", + "more": "もっと見る", "setting": "設定", "generalSetting": "一般", "language": "言語", @@ -156,6 +157,8 @@ "autoBackup": "自動バックアップを有効化", "autoBackupTip": "トークンやカテゴリが変更されたときに指定の場所に自動バックアップします。バックアップパスワード設定後に有効", "autoBackupPath": "ローカルバックアップ位置", + "restoreDefaultBackupPath": "デフォルトに戻す", + "restoreDefaultBackupPathTip": "バックアップ先を内蔵のデフォルトパスにリセットします", "backupLogs": "バックアップログ", "backupLogSubtitle": "自動バックアップの履歴を表示", "noBackupLogs": "バックアップログはありません", @@ -220,6 +223,7 @@ "enableCloudBackupTip": "クラウドバックアップを有効にすると、バックアップファイルがクラウドサービスにアップロードされます", "enableLocalBackup": "ローカルバックアップを有効化", "enableLocalBackupTip": "ローカルバックアップを有効にすると、バックアップファイルが指定のディレクトリに保存されます。クラウドバックアップを有効にしている場合でも、ローカルバックアップを有効にすることをお勧めします", + "bothBackupDisabledWarning": "ローカルバックアップとクラウドバックアップが両方とも無効です。データを保護するため、ローカルバックアップが自動的に有効になりました。", "cloudBackupServiceSetting": "クラウドバックアップサービス設定", "cloudBackupServiceSettingTip": "クラウドバックアップサービスの設定", "notCloudBackupService": "クラウドバックアップサービスが設定されていません", @@ -627,6 +631,7 @@ "setCategoryForTokenDetail": "トークン「{issuer}」のカテゴリを選択", "setTokenForCategory": "「{category}」としてカテゴリを選択したトークン", "category": "カテゴリ", + "categoryAndIcon": "カテゴリとアイコン", "addCategory": "新しいカテゴリを作成", "inputCategory": "カテゴリ名を入力", "categoryNameCannotBeEmpty": "カテゴリ名は空にできません", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 8db03367..1e74a598 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -31,6 +31,7 @@ "reload": "重新加载", "loadErrorType": "错误类型:{type}", "loadUnkownError": "未知错误", + "more": "更多", "setting": "设置", "generalSetting": "通用", "language": "语言", @@ -156,6 +157,8 @@ "autoBackup": "启用自动备份", "autoBackupTip": "当令牌或分类发生更改时自动备份至指定位置;设置备份密码后生效", "autoBackupPath": "本地备份位置", + "restoreDefaultBackupPath": "恢复默认位置", + "restoreDefaultBackupPathTip": "将备份位置重置为内置的默认存储路径", "backupLogs": "备份日志", "backupLogSubtitle": "查看自动备份历史记录", "noBackupLogs": "暂无备份日志", @@ -220,6 +223,7 @@ "enableCloudBackupTip": "启用云备份后,备份时会将备份文件上传至云服务", "enableLocalBackup": "启用本地备份", "enableLocalBackupTip": "启用本地备份后,备份时会将备份文件保存至指定目录;即使您启用了云备份,我们也建议您启用本地备份", + "bothBackupDisabledWarning": "本地备份和云备份均已关闭,已自动启用本地备份以确保您的数据安全。", "cloudBackupServiceSetting": "云备份服务设置", "cloudBackupServiceSettingTip": "设置云备份服务", "notCloudBackupService": "尚未设置云备份服务", @@ -627,6 +631,7 @@ "setCategoryForTokenDetail": "选择令牌「{issuer}」的分类", "setTokenForCategory": "选择分类为「{category}」的令牌", "category": "分类", + "categoryAndIcon": "分类和图标", "addCategory": "新建分类", "inputCategory": "输入分类名称", "categoryNameCannotBeEmpty": "分类名称不能为空", diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index f7557b1e..bf2d2c01 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -31,6 +31,7 @@ "reload": "重新載入", "loadErrorType": "錯誤類型:{type}", "loadUnkownError": "未知錯誤", + "more": "更多", "setting": "設定", "generalSetting": "通用", "language": "語言", @@ -156,6 +157,8 @@ "autoBackup": "啟用自動備份", "autoBackupTip": "當令牌或分類發生變更時自動備份至指定位置;設定備份密碼後生效", "autoBackupPath": "本地備份位置", + "restoreDefaultBackupPath": "恢復預設位置", + "restoreDefaultBackupPathTip": "將備份位置重置為內建的預設儲存路徑", "backupLogs": "備份日誌", "backupLogSubtitle": "查看自動備份歷史記錄", "noBackupLogs": "暫無備份日誌", @@ -220,6 +223,7 @@ "enableCloudBackupTip": "啟用雲端備份後,備份時會將備份檔案上傳至雲端服務", "enableLocalBackup": "啟用本機備份", "enableLocalBackupTip": "啟用本機備份後,備份時會將備份檔案儲存至指定目錄;即使您啟用了雲端備份,我們也建議您啟用本機備份", + "bothBackupDisabledWarning": "本機備份和雲端備份均已關閉,已自動啟用本機備份以確保您的資料安全。", "cloudBackupServiceSetting": "雲端備份服務設定", "cloudBackupServiceSettingTip": "設定雲端備份服務", "notCloudBackupService": "尚未設定雲端備份服務", @@ -627,6 +631,7 @@ "setCategoryForTokenDetail": "選擇令牌「{issuer}」的分類", "setTokenForCategory": "選擇分類為「{category}」的令牌", "category": "分類", + "categoryAndIcon": "分類和圖標", "addCategory": "新建分類", "inputCategory": "輸入分類名稱", "categoryNameCannotBeEmpty": "分類名稱不能為空白", diff --git a/third-party/chewie/lib/src/Utils/System/cache_util.dart b/third-party/chewie/lib/src/Utils/System/cache_util.dart index 53a8e586..0cd1864a 100644 --- a/third-party/chewie/lib/src/Utils/System/cache_util.dart +++ b/third-party/chewie/lib/src/Utils/System/cache_util.dart @@ -12,14 +12,12 @@ class CacheUtil { static Future _getTotalSizeOfFilesInDir( final FileSystemEntity file) async { - if (file is File) { - int length = await file.length(); - return double.parse(length.toString()); + if (file is File && file.existsSync()) { + return (await file.length()).toDouble(); } - if (file is Directory) { - final List children = file.listSync(); + if (file is Directory && file.existsSync()) { double total = 0; - for (final FileSystemEntity child in children) { + for (final FileSystemEntity child in file.listSync()) { total += await _getTotalSizeOfFilesInDir(child); } return total; @@ -28,13 +26,12 @@ class CacheUtil { } static Future delDir(FileSystemEntity file) async { - if (file is Directory) { - final List children = file.listSync(); - for (final FileSystemEntity child in children) { + if (file is Directory && file.existsSync()) { + for (final FileSystemEntity child in file.listSync()) { await delDir(child); } } - await file.delete(); + if (file.existsSync()) await file.delete(); } static String renderSize( diff --git a/third-party/chewie/lib/src/Utils/ilogger.dart b/third-party/chewie/lib/src/Utils/ilogger.dart index 3a62cd9b..a278c0fd 100644 --- a/third-party/chewie/lib/src/Utils/ilogger.dart +++ b/third-party/chewie/lib/src/Utils/ilogger.dart @@ -176,8 +176,10 @@ class FileOutput extends LogOutput { static Future clearLogs() async { List logs = await getLogs(); - for (File file in logs) { - if (file.existsSync()) file.deleteSync(); + for (File logFile in logs) { + try { + if (logFile.existsSync()) logFile.deleteSync(); + } catch (_) {} } } diff --git a/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart b/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart index 577ae364..797f8731 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart @@ -80,30 +80,30 @@ class CheckboxItemState extends SearchableState { if (!shouldShow) return const SizedBox.shrink(); return InkAnimation( borderRadius: _borderRadius, - ink: false, - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: _effectivePadding, - bottom: _effectivePadding, - left: 6, - right: 4, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildRowChildren(), - ), - ), - // _buildBottomDivider(), - ], + ink: true, + color: Colors.transparent, + onTap: widget.disabled ? null : () { + HapticFeedback.lightImpact(); + widget.onTap?.call(); + }, + child: Padding( + padding: EdgeInsets.only( + top: _effectivePadding, + bottom: _effectivePadding, + left: 6, + right: 4, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildRowChildren(), + ), ), ); } List _buildRowChildren() { return [ - if (widget.showLeading) Icon(widget.leading, size: 20), + if (widget.showLeading) _buildLeadingIcon(), const SizedBox(width: 5), Expanded(child: _buildTextContent()), SizedBox(width: widget.trailingLeftMargin), @@ -111,6 +111,19 @@ class CheckboxItemState extends SearchableState { ]; } + Widget _buildLeadingIcon() { + final color = ChewieTheme.primaryColor; + return Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: color.withAlpha(25), + borderRadius: BorderRadius.circular(7), + ), + child: Icon(widget.leading, size: 15, color: color), + ); + } + Widget _buildTextContent() { final titleStyle = ChewieTheme.titleMedium.apply(color: widget.titleColor); final descStyle = ChewieTheme.bodySmall; @@ -170,13 +183,4 @@ class CheckboxItemState extends SearchableState { ); } -// Widget _buildBottomDivider() { -// return Container( -// height: 0, -// margin: const EdgeInsets.symmetric(horizontal: 10), -// decoration: BoxDecoration( -// border: widget.roundBottom ? null : ChewieTheme.bottomDivider, -// ), -// ); -// } } diff --git a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart index 7994ef21..07460e12 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart @@ -94,12 +94,17 @@ class EntryItem extends SearchableStatefulWidget { } class EntryItemState extends SearchableState { - double get _paddingVertical => widget.paddingVertical ?? 12; + double get _paddingVertical => widget.paddingVertical ?? 14; double get _paddingHorizontal => widget.paddingHorizontal ?? 6; - BorderRadius get _borderRadius => - const BorderRadius.vertical(top: Radius.zero, bottom: Radius.zero); + Color get _leadingColor => widget.titleColor ?? ChewieTheme.primaryColor; + + BorderRadius get _borderRadius => BorderRadius.vertical( + top: widget.roundTop ? Radius.circular(widget.radius) : Radius.zero, + bottom: + widget.roundBottom ? Radius.circular(widget.radius) : Radius.zero, + ); @override Widget build(BuildContext context) { @@ -108,50 +113,51 @@ class EntryItemState extends SearchableState { color: Colors.transparent, ink: widget.ink, borderRadius: _borderRadius, - // onTap: widget.onTap, - child: Column( - children: [ - GestureDetector( - onTap: widget.onTap, - child: Container( - decoration: BoxDecoration( - color: widget.backgroundColor ?? Colors.transparent, - borderRadius: _borderRadius, - ), - padding: EdgeInsets.symmetric( - vertical: _paddingVertical, - horizontal: _paddingHorizontal, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildRowChildren(), - ), - ), - ), - // _buildBottomDivider(), - ], + onTap: widget.onTap, + child: Container( + decoration: BoxDecoration( + color: widget.backgroundColor ?? Colors.transparent, + borderRadius: _borderRadius, + ), + padding: EdgeInsets.only( + top: _paddingVertical, + bottom: _paddingVertical, + left: _paddingHorizontal, + right: _paddingHorizontal + 6, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildRowChildren(), + ), ), ); } List _buildRowChildren() { return [ - if (widget.showLeading) - Icon( - widget.leading, - size: 20, - color: widget.titleColor ?? ChewieTheme.iconColor, - ), + if (widget.showLeading) _buildLeadingIcon(), SizedBox(width: widget.showLeading ? 10 : 5), Expanded(child: _buildTextContent()), - const SizedBox(width: 50), - if (widget.tipWidget == null) _buildTipWidget(), + if (widget.tipWidget != null) const SizedBox(width: 10), if (widget.tipWidget != null) _buildCustomTipWidget(), - // if (widget.showTrailing) SizedBox(width: widget.trailingLeftMargin), - // if (widget.showTrailing) _buildTrailingIcon(), + if (widget.tipWidget == null) const SizedBox(width: 10), + if (widget.tipWidget == null) _buildTipWidget(), ]; } + Widget _buildLeadingIcon() { + return Container( + width: 28, + height: 28, + margin: const EdgeInsets.only(left: 4), + decoration: BoxDecoration( + color: _leadingColor.withAlpha(25), + borderRadius: BorderRadius.circular(7), + ), + child: Icon(widget.leading, size: 15, color: _leadingColor), + ); + } + Widget _buildTextContent() { final titleStyle = ChewieTheme.titleMedium.apply(color: widget.titleColor); final descStyle = @@ -192,39 +198,33 @@ class EntryItemState extends SearchableState { } Widget _buildTipWidget() { - return widget.tip.isNotEmpty - ? Container( - constraints: BoxConstraints( - minWidth: widget.minTipWidth, - maxWidth: widget.description.isNotEmpty - ? widget.tipWidth - : widget.tipWidth + 40, - ), - child: RoundIconTextButton( - height: null, - minHeight: 32, - onPressed: widget.onTap, - text: widget.tip, - textStyle: ChewieTheme.bodyMedium - .apply(fontSizeDelta: -1, fontWeightDelta: 2), - padding: const EdgeInsets.symmetric(horizontal: 10), - background: ChewieTheme.canvasColor, - border: ChewieTheme.border, - ), - ) - : RoundIconTextButton( - height: 32, - minHeight: 32, - onPressed: widget.onTap, - icon: Icon( - widget.trailing, - size: 16, - color: ChewieTheme.bodyMedium.color, + if (!widget.showTrailing && widget.tip.isEmpty) { + return const SizedBox.shrink(); + } + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.tip.isNotEmpty) + Flexible( + child: Text( + widget.tip, + style: ChewieTheme.bodyMedium.apply( + color: ChewieTheme.bodySmall.color, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, ), - padding: const EdgeInsets.symmetric(horizontal: 8), - background: ChewieTheme.canvasColor, - border: ChewieTheme.border, - ); + ), + if (widget.tip.isNotEmpty && widget.showTrailing) + const SizedBox(width: 6), + if (widget.showTrailing) + Icon( + widget.trailing, + size: 16, + color: ChewieTheme.bodySmall.color, + ), + ], + ); } Widget _buildCustomTipWidget() { @@ -238,16 +238,6 @@ class EntryItemState extends SearchableState { child: widget.tipWidget!, ); } - -// Widget _buildBottomDivider() { -// return Container( -// height: 0, -// margin: const EdgeInsets.symmetric(horizontal: 10), -// decoration: BoxDecoration( -// border: widget.roundBottom ? null : ChewieTheme.bottomDivider, -// ), -// ); -// } } class SearchableCaptionItem extends SearchableStatefulWidget { @@ -337,64 +327,85 @@ class SearchableCaptionItemState extends SearchableState @override Widget build(BuildContext context) { if (!shouldShow) return const SizedBox.shrink(); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: _toggleExpansion, - child: Container( - color: Colors.transparent, - padding: widget.padding ?? - const EdgeInsets.symmetric(horizontal: 12) - .add(const EdgeInsets.only(top: 20, bottom: 10)), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: ChewieTheme.textDarkGreyColor, - letterSpacing: 0.5, - ), - ), - ), - RotationTransition( - turns: _arrowAnimation, - child: Icon( - LucideIcons.chevronDown, - size: 18, - color: ChewieTheme.textDarkGreyColor, - ), - ), - ], + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Material( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(8), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + ClipRect( + child: SizeTransition( + sizeFactor: _sizeAnimation, + axisAlignment: -1.0, + child: Column(children: _buildChildren()), + ), ), - ), + ], ), - if (widget.showDivider) - Container( - height: 0, - margin: const EdgeInsets.symmetric(horizontal: 10) - .add(const EdgeInsets.only(bottom: 4)), - decoration: BoxDecoration(border: ChewieTheme.bottomDivider), - ), - ClipRect( - child: SizeTransition( - sizeFactor: _sizeAnimation, - axisAlignment: -1.0, - child: Column(children: _buildChildren()), - ), + ), + ); + } + + Widget _buildHeader() { + return GestureDetector( + onTap: _toggleExpansion, + child: Container( + color: Colors.transparent, + padding: widget.padding ?? + const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + widget.title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: ChewieTheme.textDarkGreyColor, + letterSpacing: 0.5, + ), + ), + ), + RotationTransition( + turns: _arrowAnimation, + child: Icon( + LucideIcons.chevronDown, + size: 18, + color: ChewieTheme.textDarkGreyColor, + ), + ), + ], ), - ], + ), ); } List _buildChildren() { - return widget.children - .map((child) => _withUpdatedSearchText(child)) - .toList(); + final children = + widget.children.map((child) => _withUpdatedSearchText(child)).toList(); + final result = [ + Container( + height: 0.5, + margin: const EdgeInsets.symmetric(horizontal: 12), + color: ChewieTheme.dividerColor, + ), + ]; + for (int i = 0; i < children.length; i++) { + if (i > 0) { + result.add(Container( + height: 0.5, + margin: const EdgeInsets.symmetric(horizontal: 12), + color: ChewieTheme.dividerColor, + )); + } + result.add(children[i]); + } + return result; } SearchableStatefulWidget _withUpdatedSearchText( @@ -470,60 +481,85 @@ class CaptionItemState extends BaseDynamicState @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: _toggleExpansion, - child: Container( - color: Colors.transparent, - padding: widget.padding ?? - const EdgeInsets.symmetric(horizontal: 12) - .add(const EdgeInsets.only(top: 20, bottom: 10)), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: ChewieTheme.textDarkGreyColor, - letterSpacing: 0.5, - ), - ), - ), - RotationTransition( - turns: _arrowAnimation, - child: Icon( - LucideIcons.chevronDown, - size: 20, - color: ChewieTheme.textDarkGreyColor, - ), + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Material( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(8), + clipBehavior: Clip.antiAlias, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + ClipRect( + child: SizeTransition( + sizeFactor: _sizeAnimation, + axisAlignment: -1.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildChildren(), ), - ], + ), ), - ), + ], ), - if (widget.showDivider) - Container( - height: 0, - margin: const EdgeInsets.symmetric(horizontal: 10) - .add(const EdgeInsets.only(bottom: 4)), - decoration: BoxDecoration(border: ChewieTheme.bottomDivider), - ), - ClipRect( - child: SizeTransition( - sizeFactor: _sizeAnimation, - axisAlignment: -1.0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: widget.children, + ), + ); + } + + Widget _buildHeader() { + return GestureDetector( + onTap: _toggleExpansion, + child: Container( + color: Colors.transparent, + padding: widget.padding ?? + const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + widget.title, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + color: ChewieTheme.textDarkGreyColor, + letterSpacing: 0.5, + ), + ), ), - ), + RotationTransition( + turns: _arrowAnimation, + child: Icon( + LucideIcons.chevronDown, + size: 20, + color: ChewieTheme.textDarkGreyColor, + ), + ), + ], ), - ], + ), ); } + + List _buildChildren() { + final result = [ + Container( + height: 0.5, + margin: const EdgeInsets.symmetric(horizontal: 12), + color: ChewieTheme.dividerColor, + ), + ]; + for (int i = 0; i < widget.children.length; i++) { + if (i > 0) { + result.add(Container( + height: 0.5, + margin: const EdgeInsets.symmetric(horizontal: 12), + color: ChewieTheme.dividerColor, + )); + } + result.add(widget.children[i]); + } + return result; + } } diff --git a/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart b/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart index 641eb82a..3a1c5aac 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/inline_selection_item.dart @@ -188,7 +188,7 @@ class InlineSelectionItemState child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (widget.showLeading) Icon(widget.leading, size: 20), + if (widget.showLeading) _buildLeadingIcon(), SizedBox(width: widget.showLeading ? 10 : 5), Expanded(child: _buildTitleDescription()), const SizedBox(width: 50), @@ -208,6 +208,19 @@ class InlineSelectionItemState ); } + Widget _buildLeadingIcon() { + final color = ChewieTheme.primaryColor; + return Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: color.withAlpha(25), + borderRadius: BorderRadius.circular(7), + ), + child: Icon(widget.leading, size: 15, color: color), + ); + } + Widget _buildTitleDescription() { final titleStyle = ChewieTheme.titleMedium.apply(color: widget.titleColor); final descStyle = @@ -298,7 +311,7 @@ class InlineSelectionItemState selectedColor: ChewieTheme.hoverColor, selectedIconColor: ChewieTheme.primaryColor, ), - closedFillColor: ChewieTheme.canvasColor, + closedFillColor: ChewieTheme.scaffoldBackgroundColor, expandedFillColor: ChewieTheme.scaffoldBackgroundColor, listItemStyle: ChewieTheme.bodyMedium, closedSuffixIcon: diff --git a/third-party/chewie/lib/src/Widgets/Tile/tip_banner.dart b/third-party/chewie/lib/src/Widgets/Tile/tip_banner.dart index 262d3369..50e9d6aa 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/tip_banner.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/tip_banner.dart @@ -22,7 +22,7 @@ class TipBanner extends StatelessWidget { this.backgroundColor, this.textColor, this.padding = const EdgeInsets.all(12), - this.margin = const EdgeInsets.symmetric(horizontal: 8), + this.margin = const EdgeInsets.symmetric(horizontal: 0), }); factory TipBanner.info( From 9f5839b1ceb5a3423c3d52fd2ed32d8a779216af Mon Sep 17 00:00:00 2001 From: dd
Date: Fri, 22 May 2026 21:33:20 +0800 Subject: [PATCH 27/36] chore: Update version to 4.0.0+400 in pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7a697dbe..c0cd95ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: cloudotp -version: 3.2.0+320 +version: 4.0.0+400 description: An awesome two-factor authenticator which supports cloud storage and multiple platforms. publish_to: none From d2353cadb3f524f8726c2525b0cfabb4325fc731 Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 23 May 2026 19:51:33 +0800 Subject: [PATCH 28/36] feat(localization): update English, Japanese, Chinese, and Traditional Chinese localization files with new welcome messages and coach marks feat(main): add welcome screen logic to main app flow fix(chewie): adjust bottom padding in update log and webview screens for better layout fix(chewie): rename desktopActions to landscapeActions for clarity in responsive app bar and related widgets fix(chewie): ensure proper margin handling in custom dialog widgets to accommodate device padding fix(chewie): improve notification manager to use post frame callback for overlay notifications --- lib/Database/database_manager.dart | 174 +++ lib/Screens/Backup/cloud_service_screen.dart | 62 + lib/Screens/Setting/backup_log_screen.dart | 75 +- .../Setting/setting_appearance_screen.dart | 21 +- .../Setting/setting_backup_screen.dart | 37 +- lib/Screens/Token/add_token_screen.dart | 2 +- lib/Screens/Token/category_screen.dart | 17 +- lib/Screens/Token/import_preview_screen.dart | 2 +- lib/Screens/Token/token_layout.dart | 77 +- lib/Screens/feature_showcase_pages.dart | 825 +++++++++++++ lib/Screens/feature_showcase_screen.dart | 1022 ++--------------- lib/Screens/home_screen.dart | 243 ++-- lib/Screens/layout_select_screen.dart | 6 +- lib/Screens/main_screen.dart | 133 ++- lib/Screens/sort_select_screen.dart | 6 +- lib/Screens/welcome_screen.dart | 461 ++++++++ lib/Utils/app_provider.dart | 5 +- lib/Utils/hive_util.dart | 19 +- .../BottomSheet/more_bottom_sheet.dart | 19 +- lib/Widgets/CoachMark/coach_mark_manager.dart | 496 +++++--- .../CoachMark/desktop_coach_mark_manager.dart | 708 ++++++++++++ lib/l10n/intl_en.arb | 52 +- lib/l10n/intl_ja.arb | 52 +- lib/l10n/intl_zh.arb | 52 +- lib/l10n/intl_zh_TW.arb | 52 +- lib/main.dart | 8 +- .../lib/src/Screens/update_log_screen.dart | 4 +- .../lib/src/Screens/webview_screen.dart | 2 +- .../lib/src/Widgets/Basic/item_builder.dart | 2 +- .../BottomSheet/bottom_sheet_builder.dart | 1 + .../bottom_sheet_wrapper_widget.dart | 14 +- .../widgets/custom_confirm_dialog_widget.dart | 6 +- .../widgets/custom_info_dialog_widget.dart | 6 +- .../Widgets/General/responsive_app_bar.dart | 6 +- .../Notification/notification_manager.dart | 2 +- 35 files changed, 3412 insertions(+), 1257 deletions(-) create mode 100644 lib/Screens/feature_showcase_pages.dart create mode 100644 lib/Screens/welcome_screen.dart create mode 100644 lib/Widgets/CoachMark/desktop_coach_mark_manager.dart diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index 76529874..8c92fb70 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -14,6 +14,7 @@ */ import 'dart:async'; +import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; @@ -48,6 +49,7 @@ class DatabaseManager { }); static DatabaseFactory _currentDbFactory = cipherDbFactory; static bool isDatabaseEncrypted = false; + static bool isNewDatabase = false; static bool get initialized => _database != null; @@ -84,6 +86,7 @@ class DatabaseManager { } } else { isDatabaseEncrypted = true; + isNewDatabase = true; _currentDbFactory = cipherDbFactory; password = await CloudOTPHiveUtil.regeneratePassword(); appProvider.currentDatabasePassword = password; @@ -107,6 +110,69 @@ class DatabaseManager { await ConfigDao.initConfig(); } + static Future clearSampleDataFlag() async { + if (_database == null) return; + final db = _database!; + final tokens = await db.query('otp_token', + where: "remark LIKE '%is_example%'"); + for (final row in tokens) { + final remark = Map.from( + jsonDecode(row['remark'] as String? ?? '{}')); + remark.remove('is_example'); + await db.update( + 'otp_token', + {'remark': jsonEncode(remark)}, + where: 'uid = ?', + whereArgs: [row['uid']], + ); + } + final categories = await db.query('token_category', + where: "remark LIKE '%is_example%'"); + for (final row in categories) { + final remark = Map.from( + jsonDecode(row['remark'] as String? ?? '{}')); + remark.remove('is_example'); + await db.update( + 'token_category', + {'remark': jsonEncode(remark)}, + where: 'uid = ?', + whereArgs: [row['uid']], + ); + } + } + + static Future hasSampleData() async { + if (_database == null) return false; + final db = _database!; + final tokens = await db.query('otp_token', + where: "remark LIKE '%is_example%'"); + return tokens.isNotEmpty; + } + + static Future deleteSampleData() async { + if (_database == null) return; + final db = _database!; + final tokens = await db.query('otp_token', + where: "remark LIKE '%is_example%'"); + for (final row in tokens) { + final uid = row['uid'] as String; + await db.delete('token_category_binding', + where: 'token_uid = ?', whereArgs: [uid]); + await db.delete('otp_token', where: 'uid = ?', whereArgs: [uid]); + } + await db.delete('token_category', where: "remark LIKE '%is_example%'"); + } + + static Future updateSampleCategoryTitle(String title) async { + if (_database == null) return; + final db = _database!; + await db.update( + 'token_category', + {'title': title}, + where: "remark LIKE '%is_example%'", + ); + } + static String _escapeSql(String value) => value.replaceAll("'", "''"); static Future changePassword(String password) async { @@ -159,6 +225,114 @@ class DatabaseManager { await db.execute(Sql.createCloudServiceConfigTable.sql); await db.execute(Sql.createAutoBackupLogTable.sql); await db.execute(Sql.createTokenCategoryBindingTable.sql); + await _insertSampleData(db); + } + + static Future _insertSampleData(Database db) async { + final now = DateTime.now().millisecondsSinceEpoch; + final remark = jsonEncode({'is_example': true}); + + final token1Uid = StringUtil.generateUid(); + final token2Uid = StringUtil.generateUid(); + final token3Uid = StringUtil.generateUid(); + final categoryUid = StringUtil.generateUid(); + + await db.insert('otp_token', { + 'id': 1, + 'uid': token1Uid, + 'seq': 1, + 'issuer': 'Google', + 'secret': 'JBSWY3DPEHPK3PXP', + 'account': 'user@example.com', + 'image_path': 'google.png', + 'token_type': OtpTokenType.TOTP.index, + 'algorithm': 'SHA1', + 'digits': 6, + 'counter': 0, + 'period': 30, + 'pinned': 0, + 'create_timestamp': now, + 'edit_timestamp': now, + 'remark': remark, + 'copy_times': 0, + 'pin': '', + 'last_copy_timestamp': 0, + 'description': '', + 'tags': '', + }); + + await db.insert('otp_token', { + 'id': 2, + 'uid': token2Uid, + 'seq': 2, + 'issuer': 'Steam', + 'secret': 'JBSWY3DPEHPK3PXQ', + 'account': 'gamer', + 'image_path': 'steam.png', + 'token_type': OtpTokenType.Steam.index, + 'algorithm': 'SHA1', + 'digits': 5, + 'counter': 0, + 'period': 30, + 'pinned': 0, + 'create_timestamp': now, + 'edit_timestamp': now, + 'remark': remark, + 'copy_times': 0, + 'pin': '', + 'last_copy_timestamp': 0, + 'description': '', + 'tags': '', + }); + + await db.insert('otp_token', { + 'id': 3, + 'uid': token3Uid, + 'seq': 3, + 'issuer': 'Yandex', + 'secret': 'JBSWY3DPEHPK3PXR', + 'account': 'user@yandex.com', + 'image_path': 'yandex.png', + 'token_type': OtpTokenType.Yandex.index, + 'algorithm': 'SHA256', + 'digits': 8, + 'counter': 0, + 'period': 30, + 'pinned': 0, + 'create_timestamp': now, + 'edit_timestamp': now, + 'remark': remark, + 'copy_times': 0, + 'pin': '1234567890123456', + 'last_copy_timestamp': 0, + 'description': '', + 'tags': '', + }); + + await db.insert('token_category', { + 'id': 1, + 'uid': categoryUid, + 'seq': 1, + 'title': 'Example', + 'description': '', + 'create_timestamp': now, + 'edit_timestamp': now, + 'pinned': 0, + 'remark': remark, + }); + + await db.insert('token_category_binding', { + 'token_uid': token1Uid, + 'category_uid': categoryUid, + }); + await db.insert('token_category_binding', { + 'token_uid': token2Uid, + 'category_uid': categoryUid, + }); + await db.insert('token_category_binding', { + 'token_uid': token3Uid, + 'category_uid': categoryUid, + }); } static Future _onUpgrade( diff --git a/lib/Screens/Backup/cloud_service_screen.dart b/lib/Screens/Backup/cloud_service_screen.dart index a8bae1f2..dc62b7c3 100644 --- a/lib/Screens/Backup/cloud_service_screen.dart +++ b/lib/Screens/Backup/cloud_service_screen.dart @@ -14,6 +14,7 @@ */ import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/config_dao.dart'; import 'package:cloudotp/Models/cloud_service_config.dart'; import 'package:cloudotp/Screens/Backup/aliyundrive_service_screen.dart'; import 'package:cloudotp/Screens/Backup/box_service_screen.dart'; @@ -23,6 +24,8 @@ import 'package:cloudotp/Screens/Backup/huawei_service_screen.dart'; import 'package:cloudotp/Screens/Backup/onedrive_service_screen.dart'; import 'package:cloudotp/Screens/Backup/s3_service_screen.dart'; import 'package:cloudotp/Screens/Backup/webdav_service_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_backup_screen.dart'; +import 'package:cloudotp/Screens/Setting/setting_navigation_screen.dart'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; @@ -50,6 +53,9 @@ class _CloudServiceScreenState extends BaseDynamicState final GroupButtonController _typeController = GroupButtonController(); CloudServiceType _currentType = CloudServiceType.Webdav; late TabController tabController; + String _autoBackupPassword = ""; + + bool get canBackup => _autoBackupPassword.isNotEmpty; @override void initState() { @@ -59,6 +65,11 @@ class _CloudServiceScreenState extends BaseDynamicState vsync: this, ); _typeController.selectIndex(_currentType.index); + ConfigDao.getConfig().then((config) { + setState(() { + _autoBackupPassword = config.backupPassword; + }); + }); } @override @@ -101,6 +112,57 @@ class _CloudServiceScreenState extends BaseDynamicState } _buildBody() { + if (!canBackup) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + LucideIcons.keyRound, + size: 26, + color: ChewieTheme.primaryColor, + ), + ), + const SizedBox(height: 16), + Text( + appLocalizations.haveNotSetBackupPassword, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + RoundIconTextButton( + height: 38, + text: appLocalizations.goToSetBackupPassword, + background: ChewieTheme.primaryColor, + onPressed: () { + if (ResponsiveUtil.isLandscapeLayout()) { + RouteUtil.pushDialogRoute( + context, const SettingNavigationScreen(initPageIndex: 3)); + } else { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, + const BackupSettingScreen( + jumpToAutoBackupPassword: true)); + } + }, + ), + ], + ), + ), + ); + } return Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index ff670e26..e80f1d6f 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -249,34 +249,53 @@ class BackupLogScreenState extends BaseDynamicState { Widget _buildLogList() { if (!canBackup) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - appLocalizations.haveNotSetBackupPassword, - style: ChewieTheme.bodyMedium, - ), - const SizedBox(height: 10), - RoundIconTextButton( - height: 36, - text: appLocalizations.goToSetBackupPassword, - background: _accent, - onPressed: () { - if (widget.isOverlay) { - RouteUtil.pushDialogRoute( - context, const SettingNavigationScreen(initPageIndex: 3)); - } else { - Navigator.pop(context); - RouteUtil.pushCupertinoRoute( - context, - const BackupSettingScreen( - jumpToAutoBackupPassword: true)); - } - }, - ), - ], + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: _accent.withAlpha(20), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + LucideIcons.keyRound, + size: 26, + color: _accent, + ), + ), + const SizedBox(height: 16), + Text( + appLocalizations.haveNotSetBackupPassword, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + RoundIconTextButton( + height: 38, + text: appLocalizations.goToSetBackupPassword, + background: _accent, + onPressed: () { + if (widget.isOverlay) { + RouteUtil.pushDialogRoute( + context, const SettingNavigationScreen(initPageIndex: 3)); + } else { + Navigator.pop(context); + RouteUtil.pushCupertinoRoute( + context, + const BackupSettingScreen( + jumpToAutoBackupPassword: true)); + } + }, + ), + ], + ), ), ); } diff --git a/lib/Screens/Setting/setting_appearance_screen.dart b/lib/Screens/Setting/setting_appearance_screen.dart index e88e36b1..4aaa6f2e 100644 --- a/lib/Screens/Setting/setting_appearance_screen.dart +++ b/lib/Screens/Setting/setting_appearance_screen.dart @@ -50,9 +50,8 @@ class _AppearanceSettingScreenState ChewieHiveUtil.getBool(CloudOTPHiveUtil.showLayoutButtonKey); bool showSortButton = ChewieHiveUtil.getBool(CloudOTPHiveUtil.showSortButtonKey); - bool showBackupLogButton = ChewieHiveUtil.getBool( - CloudOTPHiveUtil.showBackupLogButtonKey, - defaultValue: ResponsiveUtil.isLandscapeLayout()); + bool showBackupLogButton = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.showBackupLogButtonKey); bool showCloudBackupButton = ChewieHiveUtil.getBool( CloudOTPHiveUtil.showCloudBackupButtonKey, defaultValue: true); @@ -182,22 +181,22 @@ class _AppearanceSettingScreenState title: appLocalizations.sideBarSettings, children: [ CheckboxItem( - title: appLocalizations.showBackupLogButton, - value: showBackupLogButton, + title: appLocalizations.showCloudBackupButton, + value: showCloudBackupButton, onTap: () { setState(() { - showBackupLogButton = !showBackupLogButton; - appProvider.showBackupLogButton = showBackupLogButton; + showCloudBackupButton = !showCloudBackupButton; + appProvider.showCloudBackupButton = showCloudBackupButton; }); }, ), CheckboxItem( - title: appLocalizations.showCloudBackupButton, - value: showCloudBackupButton, + title: appLocalizations.showBackupLogButton, + value: showBackupLogButton, onTap: () { setState(() { - showCloudBackupButton = !showCloudBackupButton; - appProvider.showCloudBackupButton = showCloudBackupButton; + showBackupLogButton = !showBackupLogButton; + appProvider.showBackupLogButton = showBackupLogButton; }); }, ), diff --git a/lib/Screens/Setting/setting_backup_screen.dart b/lib/Screens/Setting/setting_backup_screen.dart index eb2480a9..95228d6b 100644 --- a/lib/Screens/Setting/setting_backup_screen.dart +++ b/lib/Screens/Setting/setting_backup_screen.dart @@ -150,14 +150,6 @@ class _BackupSettingScreenState extends BaseDynamicState bool get canBackup => _autoBackupPassword.isNotEmpty; - bool get canLocalBackup => - _autoBackupPath.isNotEmpty && _autoBackupPassword.isNotEmpty; - - bool get canCloudBackup => _autoBackupPassword.isNotEmpty; - - bool get canImmediateBackup => - canBackup && (_enableLocalBackup || _enableCloudBackup); - loadWebDavConfig() async { List configs = await CloudServiceConfigDao.getValidConfigs(); @@ -236,7 +228,7 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _useBackupPasswordToExportImport : false, title: appLocalizations.useBackupPasswordToExportImport, description: appLocalizations.useBackupPasswordToExportImportTip, - disabled: _autoBackupPassword.isEmpty, + disabled: !canBackup, onTap: () { setState(() { _useBackupPasswordToExportImport = @@ -256,7 +248,7 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _enableAutoBackup : false, title: appLocalizations.autoBackup, description: appLocalizations.autoBackupTip, - disabled: !canBackup || !canImmediateBackup, + disabled: !canBackup, onTap: () { setState(() { _enableAutoBackup = !_enableAutoBackup; @@ -265,7 +257,7 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - if (_enableAutoBackup) + if (_enableAutoBackup && canBackup) CheckboxItem( value: _enableBackupOnLaunch, title: appLocalizations.backupOnLaunch, @@ -278,7 +270,7 @@ class _BackupSettingScreenState extends BaseDynamicState }); }, ), - if (canImmediateBackup) + if (canBackup) EntryItem( title: appLocalizations.immediatelyBackup, description: appLocalizations.immediatelyBackupTip, @@ -288,7 +280,7 @@ class _BackupSettingScreenState extends BaseDynamicState showToast: true, showLoading: true, force: true); }, ), - if (canImmediateBackup) + if (canBackup) EntryItem( title: appLocalizations.maxBackupCount, description: appLocalizations.maxBackupCountTip, @@ -369,13 +361,14 @@ class _BackupSettingScreenState extends BaseDynamicState ); }, ), - EntryItem( - title: appLocalizations.backupLogs, - trailing: LucideIcons.history, - onTap: () async { - BackupLogScreen.show(context); - }, - ), + if (canBackup) + EntryItem( + title: appLocalizations.backupLogs, + trailing: LucideIcons.history, + onTap: () async { + BackupLogScreen.show(context); + }, + ), ], ), CaptionItem( @@ -385,7 +378,7 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _enableLocalBackup : false, title: appLocalizations.enableLocalBackup, description: appLocalizations.enableLocalBackupTip, - disabled: !canLocalBackup || + disabled: !canBackup || (_enableLocalBackup && (!_enableCloudBackup || validConfigs.isEmpty)), onTap: () { @@ -457,7 +450,7 @@ class _BackupSettingScreenState extends BaseDynamicState value: canBackup ? _enableCloudBackup : false, title: appLocalizations.enableCloudBackup, description: appLocalizations.enableCloudBackupTip, - disabled: !canCloudBackup || + disabled: !canBackup || (_enableCloudBackup && !_enableLocalBackup), onTap: () { setState(() { diff --git a/lib/Screens/Token/add_token_screen.dart b/lib/Screens/Token/add_token_screen.dart index ccdf22d0..286d4561 100644 --- a/lib/Screens/Token/add_token_screen.dart +++ b/lib/Screens/Token/add_token_screen.dart @@ -173,7 +173,7 @@ class _AddTokenScreenState extends BaseDynamicState _isEditing ? appLocalizations.editToken : appLocalizations.addToken, showBack: !ResponsiveUtil.isLandscapeLayout(), titleLeftMargin: ResponsiveUtil.isLandscapeLayout() ? 15 : 5, - desktopActions: [ + landscapeActions: [ ToolButton( context: context, icon: LucideIcons.check, diff --git a/lib/Screens/Token/category_screen.dart b/lib/Screens/Token/category_screen.dart index 8f150728..09416ff6 100644 --- a/lib/Screens/Token/category_screen.dart +++ b/lib/Screens/Token/category_screen.dart @@ -74,7 +74,7 @@ class _CategoryScreenState extends BaseDynamicState showBorder: true, showBack: !ResponsiveUtil.isLandscapeLayout(), titleLeftMargin: ResponsiveUtil.isLandscapeLayout() ? 15 : 5, - desktopActions: [ + landscapeActions: [ ToolButton( context: context, icon: LucideIcons.plus, @@ -149,7 +149,7 @@ class _CategoryScreenState extends BaseDynamicState Widget _buildGrid() { return EasyRefresh( child: ReorderableGridView.builder( - padding: const EdgeInsets.fromLTRB(12, 6, 12, 30), + padding: const EdgeInsets.fromLTRB(12, 10, 12, 30), gridDelegate: const SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 480, crossAxisSpacing: 10, @@ -159,8 +159,7 @@ class _CategoryScreenState extends BaseDynamicState cacheExtent: 9999, itemCount: categories.length, onReorder: _onReorder, - proxyDecorator: - (Widget child, int index, Animation animation) { + proxyDecorator: (Widget child, int index, Animation animation) { return Container( decoration: BoxDecoration( boxShadow: ChewieTheme.defaultBoxShadow, @@ -274,8 +273,8 @@ class _CategoryScreenState extends BaseDynamicState ), const SizedBox(width: 6), CircleIconButton( - icon: - Icon(LucideIcons.pencil, size: 16, color: ChewieTheme.iconColor), + icon: Icon(LucideIcons.pencil, + size: 16, color: ChewieTheme.iconColor), onTap: () => _editCategory(category), ), CircleIconButton( @@ -299,8 +298,7 @@ class _CategoryScreenState extends BaseDynamicState if (text.isEmpty) { return appLocalizations.categoryNameCannotBeEmpty; } - if (text != category.title && - await CategoryDao.isCategoryExist(text)) { + if (text != category.title && await CategoryDao.isCategoryExist(text)) { return appLocalizations.categoryNameDuplicate; } return null; @@ -350,8 +348,7 @@ class _CategoryScreenState extends BaseDynamicState cancelButtonText: appLocalizations.cancel, onTapConfirm: () async { await CategoryDao.deleteCategory(category); - IToast.showTop( - appLocalizations.deleteCategorySuccess(category.title)); + IToast.showTop(appLocalizations.deleteCategorySuccess(category.title)); categories.remove(category); _categoryTokens.remove(category.uid); setState(() {}); diff --git a/lib/Screens/Token/import_preview_screen.dart b/lib/Screens/Token/import_preview_screen.dart index bfb912d4..b1f1af5b 100644 --- a/lib/Screens/Token/import_preview_screen.dart +++ b/lib/Screens/Token/import_preview_screen.dart @@ -233,7 +233,7 @@ class _ImportPreviewScreenState extends BaseDynamicState { ), const SizedBox(width: 5), ], - desktopActions: [ + landscapeActions: [ if (!_loading) ToolButton( context: context, diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 241bb323..658a370a 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -104,9 +104,14 @@ class TokenLayoutState extends BaseDynamicState TokenLayoutNotifier tokenLayoutNotifier = TokenLayoutNotifier(); + late final AnimationController _entranceController; + late final Animation _entranceAnimation; + AnimationController? _wobbleController; late Animation _wobbleAnimation; + int _lastTimeStep = -1; + SlidableController? _slidableController; Future openEndActionPane() async { @@ -162,6 +167,7 @@ class TokenLayoutState extends BaseDynamicState @override void dispose() { + _entranceController.dispose(); _tickerSubscription?.cancel(); _stopWobble(); final sc = _slidableController; @@ -198,6 +204,15 @@ class TokenLayoutState extends BaseDynamicState @override void initState() { super.initState(); + _entranceController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 450), + ); + _entranceAnimation = CurvedAnimation( + parent: _entranceController, + curve: Curves.easeOutCubic, + ); + _entranceController.forward(); updateCode(); progressNotifier.value = currentProgress; resetTimer(); @@ -224,7 +239,23 @@ class TokenLayoutState extends BaseDynamicState @override Widget build(BuildContext context) { - return _buildContextMenuRegion(); + return AnimatedBuilder( + animation: _entranceAnimation, + builder: (context, child) { + final value = _entranceAnimation.value; + return Transform.translate( + offset: Offset(0, 20 * (1 - value)), + child: Transform.scale( + scale: 0.82 + 0.18 * value, + child: Opacity( + opacity: value, + child: child, + ), + ), + ); + }, + child: RepaintBoundary(child: _buildContextMenuRegion()), + ); } String getCurrentCode() { @@ -335,11 +366,11 @@ class TokenLayoutState extends BaseDynamicState child: Selector( selector: (context, provider) => provider.issuerAndAccountShowOption, builder: (context, issuerAndAccountShowOption, child) { - return PressableAnimation( - child: _buildBody( - issuerAndAccountShowOption: issuerAndAccountShowOption, - ), + final body = _buildBody( + issuerAndAccountShowOption: issuerAndAccountShowOption, ); + if (widget.multiSelectMode) return body; + return PressableAnimation(child: body); }, ), ), @@ -834,20 +865,30 @@ class TokenLayoutState extends BaseDynamicState ); } + bool _showingNextCode = false; + updateCode() { - if (appProvider.autoDisplayNextCode && - currentProgress < autoCopyNextCodeProgressThrehold) { - tokenLayoutNotifier.code = getNextCode(); - } else { - tokenLayoutNotifier.code = getCurrentCode(); + if (!isHOTP && !isYandex && widget.token.period > 0) { + final shouldShowNext = appProvider.autoDisplayNextCode && + currentProgress < autoCopyNextCodeProgressThrehold; + final currentTimeStep = DateTime.now().millisecondsSinceEpoch ~/ + (widget.token.period * 1000); + final timeStepChanged = currentTimeStep != _lastTimeStep; + final nextCodeStateChanged = shouldShowNext != _showingNextCode; + if (!timeStepChanged && !nextCodeStateChanged) return; + _lastTimeStep = currentTimeStep; + _showingNextCode = shouldShowNext; + } + final newCode = (appProvider.autoDisplayNextCode && + currentProgress < autoCopyNextCodeProgressThrehold) + ? getNextCode() + : getCurrentCode(); + if (newCode != tokenLayoutNotifier.code) { + tokenLayoutNotifier.code = newCode; } } _processTap() { - if (widget.multiSelectMode) { - widget.onToggleSelect?.call(); - return; - } if (!appProvider.showEye) { tokenLayoutNotifier.codeVisiable = true; } @@ -890,7 +931,7 @@ class TokenLayoutState extends BaseDynamicState borderRadius: ChewieDimens.defaultBorderRadius), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: _processTap, + onTap: widget.multiSelectMode ? widget.onToggleSelect : _processTap, borderRadius: ChewieDimens.defaultBorderRadius, child: Stack( alignment: Alignment.bottomCenter, @@ -962,7 +1003,7 @@ class TokenLayoutState extends BaseDynamicState borderRadius: ChewieDimens.defaultBorderRadius), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: _processTap, + onTap: widget.multiSelectMode ? widget.onToggleSelect : _processTap, customBorder: const RoundedRectangleBorder( borderRadius: ChewieDimens.defaultBorderRadius), child: Stack( @@ -1066,7 +1107,7 @@ class TokenLayoutState extends BaseDynamicState borderRadius: ChewieDimens.defaultBorderRadius), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: _processTap, + onTap: widget.multiSelectMode ? widget.onToggleSelect : _processTap, borderRadius: ChewieDimens.defaultBorderRadius, child: Container( decoration: const BoxDecoration( @@ -1158,7 +1199,7 @@ class TokenLayoutState extends BaseDynamicState borderRadius: ChewieDimens.defaultBorderRadius), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: _processTap, + onTap: widget.multiSelectMode ? widget.onToggleSelect : _processTap, borderRadius: ChewieDimens.defaultBorderRadius, child: Container( decoration: const BoxDecoration( diff --git a/lib/Screens/feature_showcase_pages.dart b/lib/Screens/feature_showcase_pages.dart new file mode 100644 index 00000000..1f8f5100 --- /dev/null +++ b/lib/Screens/feature_showcase_pages.dart @@ -0,0 +1,825 @@ +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../l10n/l10n.dart'; + +mixin FeatureShowcasePages { + static const Color tokenColor = Color(0xFF4A6CF7); + static const Color cloudColor = Color(0xFF42A5F5); + static const Color iconColor = Color(0xFFFF7043); + static const Color securityColor = Color(0xFF66BB6A); + static const Color platformColor = Color(0xFF26C6DA); + static const Color migrationColor = Color(0xFFAB47BC); + static const Color convenienceColor = Color(0xFFFFA726); + static const Color customizeColor = Color(0xFFEC407A); + + List<({Color color, Widget Function() build})> get featurePages => [ + (color: tokenColor, build: buildTokenPage), + (color: platformColor, build: buildPlatformPage), + (color: cloudColor, build: buildCloudPage), + (color: iconColor, build: buildIconPage), + (color: securityColor, build: buildSecurityPage), + (color: migrationColor, build: buildImportExportPage), + (color: convenienceColor, build: buildConveniencePage), + (color: customizeColor, build: buildCustomizePage), + ]; + + Widget buildHero({ + required IconData icon, + required Color color, + required String title, + required String subtitle, + }) { + return Column( + children: [ + Container( + width: 72, + height: 72, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + color.withAlpha(50), + color.withAlpha(15), + ], + ), + shape: BoxShape.circle, + border: Border.all(color: color.withAlpha(70), width: 1), + ), + child: Icon(icon, size: 34, color: color), + ), + const SizedBox(height: 14), + Text( + title, + style: ChewieTheme.titleLarge.copyWith( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 6), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + subtitle, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(170), + fontSize: 12.5, + height: 1.45, + ), + textAlign: TextAlign.center, + ), + ), + ], + ); + } + + Widget buildChip({ + required String label, + required Color color, + IconData? icon, + }) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 6), + decoration: BoxDecoration( + color: color.withAlpha(25), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withAlpha(60), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 12, color: color), + const SizedBox(width: 4), + ], + Text( + label, + style: ChewieTheme.labelSmall.copyWith( + color: color, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + Widget buildPage({ + required Widget hero, + required Widget content, + }) { + return LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + hero, + const SizedBox(height: 20), + content, + ], + ), + ), + ), + ), + ); + }, + ); + } + + Widget buildSectionLabel(IconData icon, String label, Color color) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 14, color: color), + const SizedBox(width: 5), + Text( + label, + style: ChewieTheme.bodySmall.copyWith( + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ); + } + + // Page 1 — Multi-Token Support + Widget buildTokenPage() { + final types = [ + ('TOTP', appLocalizations.featureTokenTOTPDesc, LucideIcons.clock), + ('HOTP', appLocalizations.featureTokenHOTPDesc, LucideIcons.hash), + ('MOTP', appLocalizations.featureTokenMOTPDesc, LucideIcons.smartphone), + ('Steam', appLocalizations.featureTokenSteamDesc, LucideIcons.gamepad2), + ('Yandex', appLocalizations.featureTokenYandexDesc, LucideIcons.atSign), + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.keyRound, + color: tokenColor, + title: appLocalizations.featureTokenTitle, + subtitle: appLocalizations.featureTokenDescription, + ), + content: Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: + types.map((t) => _buildTokenTypeCard(t.$1, t.$2, t.$3)).toList(), + ), + const SizedBox(height: 18), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + buildChip(label: 'SHA1', color: tokenColor), + buildChip(label: 'SHA256', color: tokenColor), + buildChip(label: 'SHA512', color: tokenColor), + buildChip(label: '5–8 digits', color: tokenColor), + ], + ), + ], + ), + ); + } + + Widget _buildTokenTypeCard(String name, String desc, IconData icon) { + return Container( + width: 92, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 6), + decoration: BoxDecoration( + color: tokenColor.withAlpha(20), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: tokenColor.withAlpha(50), width: 1), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 22, color: tokenColor), + const SizedBox(height: 6), + Text( + name, + style: ChewieTheme.bodySmall.copyWith( + fontWeight: FontWeight.bold, + color: ChewieTheme.titleLarge.color, + ), + ), + const SizedBox(height: 2), + Text( + desc, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 10.5, + color: ChewieTheme.bodyMedium.color?.withAlpha(160), + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + // Page 2 — Platforms + Widget buildPlatformPage() { + final platforms = [ + (LucideIcons.smartphone, 'Android'), + (LucideIcons.smartphone, 'iOS'), + (LucideIcons.monitor, 'Windows'), + (LucideIcons.laptop, 'macOS'), + (LucideIcons.monitor, 'Linux'), + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.layoutPanelTop, + color: platformColor, + title: appLocalizations.featurePlatformTitle, + subtitle: appLocalizations.featurePlatformDescription, + ), + content: Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: + platforms.map((p) => _buildPlatformCard(p.$1, p.$2)).toList(), + ), + const SizedBox(height: 18), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + buildChip( + label: appLocalizations.featurePlatformSync, + color: platformColor, + icon: LucideIcons.refreshCw, + ), + buildChip( + label: appLocalizations.featurePlatformResponsive, + color: platformColor, + icon: LucideIcons.maximize2, + ), + ], + ), + ], + ), + ); + } + + Widget _buildPlatformCard(IconData icon, String name) { + return Container( + width: 78, + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: platformColor.withAlpha(20), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: platformColor.withAlpha(50)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 24, color: platformColor), + const SizedBox(height: 6), + Text( + name, + style: ChewieTheme.labelSmall.copyWith( + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + ), + ], + ), + ); + } + + // Page 3 — Cloud Backup + Widget buildCloudPage() { + final services = [ + 'WebDAV', 'OneDrive', 'Google Drive', 'Dropbox', + 'S3', 'Huawei Cloud', 'Box', 'Aliyun Drive', + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.cloudUpload, + color: cloudColor, + title: appLocalizations.featureCloudTitle, + subtitle: appLocalizations.featureCloudDescription, + ), + content: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: services + .map((s) => buildChip( + label: s, + color: cloudColor, + icon: LucideIcons.cloud, + )) + .toList(), + ), + ), + const SizedBox(height: 18), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + decoration: BoxDecoration( + color: cloudColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: cloudColor.withAlpha(50)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildBadge(LucideIcons.refreshCw, + appLocalizations.featureCloudAuto, cloudColor), + _buildBadge(LucideIcons.lock, + appLocalizations.featureCloudEncrypted, cloudColor), + _buildBadge(LucideIcons.fileText, + appLocalizations.featureCloudLog, cloudColor), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildBadge(IconData icon, String label, Color color) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: color), + const SizedBox(height: 4), + Text( + label, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: color, + ), + ), + ], + ); + } + + // Page 4 — Smart Icons + Widget buildIconPage() { + final brands = [ + 'google.png', 'github.png', 'microsoft.png', 'apple.png', + 'amazon.png', 'discord.png', 'steam.png', 'slack.png', + 'dropbox.png', 'paypal.png', 'netflix.png', 'spotify.png', + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.image, + color: iconColor, + title: appLocalizations.featureIconTitle, + subtitle: appLocalizations.featureIconDescription(2500), + ), + content: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: GridView.count( + crossAxisCount: 4, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 1, + children: brands.map(_buildBrandIcon).toList(), + ), + ), + const SizedBox(height: 18), + buildChip( + label: '2500+', + color: iconColor, + icon: LucideIcons.sparkles, + ), + ], + ), + ); + } + + Widget _buildBrandIcon(String filename) { + return Container( + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: ChewieTheme.borderColor), + ), + padding: const EdgeInsets.all(8), + child: Image.asset( + 'assets/brand/$filename', + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => + Icon(LucideIcons.image, color: iconColor, size: 18), + ), + ); + } + + // Page 5 — Security + Widget buildSecurityPage() { + final features = [ + (LucideIcons.databaseBackup, appLocalizations.featureSecurityEncryption), + (LucideIcons.fingerprint, appLocalizations.featureSecurityBiometric), + (LucideIcons.lockKeyhole, appLocalizations.featureSecurityGesture), + (LucideIcons.timer, appLocalizations.featureSecurityAutoLock), + (LucideIcons.eyeOff, appLocalizations.featureSecuritySafeMode), + (LucideIcons.shieldEllipsis, + appLocalizations.featureSecurityBackupEncrypt), + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.shieldCheck, + color: securityColor, + title: appLocalizations.featureSecurityTitle, + subtitle: appLocalizations.featureSecurityDescription, + ), + content: Column( + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 360), + child: GridView.count( + crossAxisCount: 2, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 3.4, + children: features + .map((f) => _buildSecurityItem(f.$1, f.$2)) + .toList(), + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + securityColor.withAlpha(40), + securityColor.withAlpha(15), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: securityColor.withAlpha(80)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(LucideIcons.github, size: 18, color: securityColor), + const SizedBox(width: 8), + Flexible( + child: Text( + appLocalizations.featureSecurityOpenSource, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 12.5, + fontWeight: FontWeight.bold, + color: securityColor, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + buildChip( + label: 'AES-256-GCM', + color: securityColor, + icon: LucideIcons.lock, + ), + buildChip( + label: 'Argon2', + color: securityColor, + icon: LucideIcons.key, + ), + ], + ), + ], + ), + ); + } + + Widget _buildSecurityItem(IconData icon, String label) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: securityColor.withAlpha(18), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: securityColor.withAlpha(45)), + ), + child: Row( + children: [ + Icon(icon, size: 16, color: securityColor), + const SizedBox(width: 6), + Expanded( + child: Text( + label, + style: ChewieTheme.labelSmall.copyWith( + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + + // Page 6 — Import & Export + Widget buildImportExportPage() { + final imports = [ + 'Google Auth', 'Aegis', '2FAS', 'Bitwarden', 'andOTP', + 'Ente Auth', 'FreeOTP+', 'TOTP Auth', 'WinAuth', + ]; + final exports = [ + appLocalizations.featureExportEncrypted, + appLocalizations.featureExportUri, + appLocalizations.featureExportCloudOTPQR, + appLocalizations.featureExportGoogleQR, + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.arrowLeftRight, + color: migrationColor, + title: appLocalizations.featureImportExportTitle, + subtitle: appLocalizations.featureImportExportDescription, + ), + content: Column( + children: [ + buildSectionLabel( + LucideIcons.download, + appLocalizations.featureImportSection, + migrationColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: imports + .map((s) => buildChip(label: s, color: migrationColor)) + .toList(), + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + buildChip( + label: appLocalizations.featureImportScan, + color: migrationColor, + icon: LucideIcons.scanLine, + ), + buildChip( + label: appLocalizations.featureImportClipboard, + color: migrationColor, + icon: LucideIcons.clipboard, + ), + ], + ), + const SizedBox(height: 16), + buildSectionLabel( + LucideIcons.upload, + appLocalizations.featureExportSection, + migrationColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: exports + .map((s) => buildChip( + label: s, + color: migrationColor, + icon: s.contains('QR') || + s.contains('二维') || + s.contains('二維') || + s.contains('QR') + ? LucideIcons.qrCode + : LucideIcons.fileText, + )) + .toList(), + ), + ], + ), + ); + } + + // Page 7 — Convenience + Widget buildConveniencePage() { + final features = [ + (LucideIcons.search, appLocalizations.featureQuickSearch), + (LucideIcons.arrowUpDown, appLocalizations.featureSort), + (LucideIcons.shapes, appLocalizations.featureCategories), + (LucideIcons.listChecks, appLocalizations.featureMultiSelect), + (LucideIcons.pin, appLocalizations.featurePinTop), + (LucideIcons.arrowLeftRight, appLocalizations.featureSwipe), + (LucideIcons.move, appLocalizations.featureDragReorder), + (LucideIcons.copy, appLocalizations.featureAutoCopy), + (LucideIcons.qrCode, appLocalizations.featureQRScanner), + ]; + return buildPage( + hero: buildHero( + icon: LucideIcons.zap, + color: convenienceColor, + title: appLocalizations.featureConvenienceTitle, + subtitle: appLocalizations.featureConvenienceDescription, + ), + content: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 320), + child: GridView.count( + crossAxisCount: 3, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 1, + children: features + .map((f) => _buildConvenienceCard(f.$1, f.$2)) + .toList(), + ), + ), + ); + } + + Widget _buildConvenienceCard(IconData icon, String label) { + return Container( + decoration: BoxDecoration( + color: convenienceColor.withAlpha(20), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: convenienceColor.withAlpha(50)), + ), + padding: const EdgeInsets.all(6), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 22, color: convenienceColor), + const SizedBox(height: 6), + Text( + label, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 10.5, + fontWeight: FontWeight.w600, + color: ChewieTheme.titleLarge.color, + ), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ], + ), + ); + } + + // Page 8 — Customization + Widget buildCustomizePage() { + const themeColors = [ + Color(0xFF009BFF), Color(0xFF3790A4), Color(0xFFF588A8), + Color(0xFF11B667), Color(0xFF454D66), Color(0xFF272643), + Color(0xFFE74645), Color(0xFF361D32), Color(0xFFF8BE5F), + Color(0xFF0084FF), + ]; + + return buildPage( + hero: buildHero( + icon: LucideIcons.palette, + color: customizeColor, + title: appLocalizations.featureCustomizeTitle, + subtitle: appLocalizations.featureCustomizeDescription, + ), + content: Column( + children: [ + buildSectionLabel( + LucideIcons.droplet, + appLocalizations.featureThemeColors, + customizeColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: themeColors + .map((c) => Container( + width: 22, + height: 22, + decoration: BoxDecoration( + color: c, + shape: BoxShape.circle, + border: Border.all( + color: ChewieTheme.borderColor, width: 1), + ), + )) + .toList(), + ), + const SizedBox(height: 6), + buildChip( + label: appLocalizations.featureCustomThemeEditor, + color: customizeColor, + icon: LucideIcons.pencilLine, + ), + const SizedBox(height: 14), + buildSectionLabel( + LucideIcons.layoutGrid, + appLocalizations.featureLayoutStyles, + customizeColor, + ), + const SizedBox(height: 8), + Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + _buildLayoutPreview( + LucideIcons.layoutGrid, appLocalizations.simpleLayoutType), + _buildLayoutPreview(LucideIcons.layoutDashboard, + appLocalizations.compactLayoutType), + _buildLayoutPreview(LucideIcons.layoutTemplate, + appLocalizations.spotlightLayoutType), + _buildLayoutPreview( + LucideIcons.layoutList, appLocalizations.listLayoutType), + ], + ), + const SizedBox(height: 14), + Wrap( + alignment: WrapAlignment.center, + spacing: 6, + runSpacing: 6, + children: [ + buildChip( + label: appLocalizations.featureGlassEffect, + color: customizeColor, + icon: LucideIcons.sparkles), + buildChip( + label: appLocalizations.featureCustomFont, + color: customizeColor, + icon: LucideIcons.type), + buildChip( + label: appLocalizations.featureMultiLang, + color: customizeColor, + icon: LucideIcons.languages), + ], + ), + ], + ), + ); + } + + Widget _buildLayoutPreview(IconData icon, String label) { + return Container( + width: 58, + padding: const EdgeInsets.symmetric(vertical: 8), + decoration: BoxDecoration( + color: customizeColor.withAlpha(20), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: customizeColor.withAlpha(50)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 18, color: customizeColor), + const SizedBox(height: 4), + Text( + label, + style: ChewieTheme.labelSmall.copyWith( + fontSize: 9.5, + fontWeight: FontWeight.w600, + color: customizeColor, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + } +} diff --git a/lib/Screens/feature_showcase_screen.dart b/lib/Screens/feature_showcase_screen.dart index 5ca7ee2a..1a906207 100644 --- a/lib/Screens/feature_showcase_screen.dart +++ b/lib/Screens/feature_showcase_screen.dart @@ -15,10 +15,10 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import '../Utils/app_provider.dart'; import '../l10n/l10n.dart'; +import 'feature_showcase_pages.dart'; class FeatureShowcaseScreen extends StatefulWidget { final bool isDialog; @@ -27,7 +27,6 @@ class FeatureShowcaseScreen extends StatefulWidget { static const String routeName = "/feature/showcase"; - /// Show as a compact dialog suitable for landscape/desktop layouts. static void showAsDialog(BuildContext context) { DialogBuilder.showPageDialog( context, @@ -42,31 +41,11 @@ class FeatureShowcaseScreen extends StatefulWidget { } class _FeatureShowcaseScreenState - extends BaseDynamicState { + extends BaseDynamicState + with FeatureShowcasePages { final PageController _pageController = PageController(); int _currentPage = 0; - // Page accent colors - static const Color _tokenColor = Color(0xFF4A6CF7); - static const Color _cloudColor = Color(0xFF42A5F5); - static const Color _iconColor = Color(0xFFFF7043); - static const Color _securityColor = Color(0xFF66BB6A); - static const Color _platformColor = Color(0xFF26C6DA); - static const Color _migrationColor = Color(0xFFAB47BC); - static const Color _convenienceColor = Color(0xFFFFA726); - static const Color _customizeColor = Color(0xFFEC407A); - - late final List<({Color color, Widget Function() build})> _pages = [ - (color: _tokenColor, build: _buildTokenPage), - (color: _platformColor, build: _buildPlatformPage), - (color: _cloudColor, build: _buildCloudPage), - (color: _iconColor, build: _buildIconPage), - (color: _securityColor, build: _buildSecurityPage), - (color: _migrationColor, build: _buildImportExportPage), - (color: _convenienceColor, build: _buildConveniencePage), - (color: _customizeColor, build: _buildCustomizePage), - ]; - @override void initState() { super.initState(); @@ -86,27 +65,32 @@ class _FeatureShowcaseScreenState @override Widget build(BuildContext context) { + final pages = featurePages; final body = Column( children: [ Expanded( child: PageView.builder( controller: _pageController, - itemCount: _pages.length, - itemBuilder: (context, index) => _pages[index].build(), + itemCount: pages.length, + itemBuilder: (context, index) => pages[index].build(), ), ), const SizedBox(height: 12), - _buildPageIndicator(_pages.length), + _buildPageIndicator(pages.length), const SizedBox(height: 16), - _buildStartTourButton(), - SizedBox(height: widget.isDialog - ? 16 - : MediaQuery.of(context).padding.bottom + 24), + _buildActionButton(), + SizedBox( + height: widget.isDialog + ? 16 + : MediaQuery.of(context).padding.bottom + 24), ], ); if (widget.isDialog) { - return body; + return Container( + color: ChewieTheme.scaffoldBackgroundColor, + child: body, + ); } return Scaffold( @@ -120,879 +104,8 @@ class _FeatureShowcaseScreenState ); } - // ============================================================ - // Shared components - // ============================================================ - - Widget _buildHero({ - required IconData icon, - required Color color, - required String title, - required String subtitle, - }) { - return Column( - children: [ - Container( - width: 72, - height: 72, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - color.withAlpha(50), - color.withAlpha(15), - ], - ), - shape: BoxShape.circle, - border: Border.all(color: color.withAlpha(70), width: 1), - ), - child: Icon(icon, size: 34, color: color), - ), - const SizedBox(height: 14), - Text( - title, - style: ChewieTheme.titleLarge.copyWith( - fontWeight: FontWeight.bold, - fontSize: 20, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 6), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - subtitle, - style: ChewieTheme.bodyMedium.copyWith( - color: ChewieTheme.bodyMedium.color?.withAlpha(170), - fontSize: 12.5, - height: 1.45, - ), - textAlign: TextAlign.center, - ), - ), - ], - ); - } - - Widget _buildChip({ - required String label, - required Color color, - IconData? icon, - }) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 6), - decoration: BoxDecoration( - color: color.withAlpha(25), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withAlpha(60), width: 1), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) ...[ - Icon(icon, size: 12, color: color), - const SizedBox(width: 4), - ], - Text( - label, - style: TextStyle( - color: color, - fontSize: 11.5, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ); - } - - /// Each page: hero + content centered together as one block. - Widget _buildPage({ - required Widget hero, - required Widget content, - }) { - return LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - hero, - const SizedBox(height: 20), - content, - ], - ), - ), - ), - ), - ); - }, - ); - } - - Widget _buildSectionLabel(IconData icon, String label, Color color) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: 14, color: color), - const SizedBox(width: 5), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: color, - ), - ), - ], - ); - } - - // ============================================================ - // Page 1 — Multi-Token Support - // ============================================================ - - Widget _buildTokenPage() { - final types = [ - ('TOTP', appLocalizations.featureTokenTOTPDesc, LucideIcons.clock), - ('HOTP', appLocalizations.featureTokenHOTPDesc, LucideIcons.hash), - ('MOTP', appLocalizations.featureTokenMOTPDesc, LucideIcons.smartphone), - ('Steam', appLocalizations.featureTokenSteamDesc, LucideIcons.gamepad2), - ('Yandex', appLocalizations.featureTokenYandexDesc, LucideIcons.atSign), - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.keyRound, - color: _tokenColor, - title: appLocalizations.featureTokenTitle, - subtitle: appLocalizations.featureTokenDescription, - ), - content: Column( - children: [ - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - runSpacing: 10, - children: types - .map((t) => _buildTokenTypeCard(t.$1, t.$2, t.$3)) - .toList(), - ), - const SizedBox(height: 18), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: [ - _buildChip(label: 'SHA1', color: _tokenColor), - _buildChip(label: 'SHA256', color: _tokenColor), - _buildChip(label: 'SHA512', color: _tokenColor), - _buildChip(label: '5–8 digits', color: _tokenColor), - ], - ), - ], - ), - ); - } - - Widget _buildTokenTypeCard(String name, String desc, IconData icon) { - return Container( - width: 92, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 6), - decoration: BoxDecoration( - color: _tokenColor.withAlpha(20), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: _tokenColor.withAlpha(50), width: 1), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 22, color: _tokenColor), - const SizedBox(height: 6), - Text( - name, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: ChewieTheme.titleLarge.color, - ), - ), - const SizedBox(height: 2), - Text( - desc, - style: TextStyle( - fontSize: 10.5, - color: ChewieTheme.bodyMedium.color?.withAlpha(160), - ), - textAlign: TextAlign.center, - ), - ], - ), - ); - } - - // ============================================================ - // Page 2 — Platforms - // ============================================================ - - Widget _buildPlatformPage() { - final platforms = [ - (LucideIcons.smartphone, 'Android'), - (LucideIcons.smartphone, 'iOS'), - (LucideIcons.monitor, 'Windows'), - (LucideIcons.laptop, 'macOS'), - (LucideIcons.monitor, 'Linux'), - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.layoutPanelTop, - color: _platformColor, - title: appLocalizations.featurePlatformTitle, - subtitle: appLocalizations.featurePlatformDescription, - ), - content: Column( - children: [ - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - runSpacing: 10, - children: platforms - .map((p) => _buildPlatformCard(p.$1, p.$2)) - .toList(), - ), - const SizedBox(height: 18), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: [ - _buildChip( - label: appLocalizations.featurePlatformSync, - color: _platformColor, - icon: LucideIcons.refreshCw, - ), - _buildChip( - label: appLocalizations.featurePlatformResponsive, - color: _platformColor, - icon: LucideIcons.maximize2, - ), - ], - ), - ], - ), - ); - } - - Widget _buildPlatformCard(IconData icon, String name) { - return Container( - width: 78, - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: _platformColor.withAlpha(20), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: _platformColor.withAlpha(50)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 24, color: _platformColor), - const SizedBox(height: 6), - Text( - name, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: ChewieTheme.titleLarge.color, - ), - ), - ], - ), - ); - } - - // ============================================================ - // Page 3 — Cloud Backup - // ============================================================ - - Widget _buildCloudPage() { - final services = [ - 'WebDAV', - 'OneDrive', - 'Google Drive', - 'Dropbox', - 'S3', - 'Huawei Cloud', - 'Box', - 'Aliyun Drive', - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.cloudUpload, - color: _cloudColor, - title: appLocalizations.featureCloudTitle, - subtitle: appLocalizations.featureCloudDescription, - ), - content: Column( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 360), - child: Wrap( - alignment: WrapAlignment.center, - spacing: 8, - runSpacing: 8, - children: services - .map((s) => _buildChip( - label: s, - color: _cloudColor, - icon: LucideIcons.cloud, - )) - .toList(), - ), - ), - const SizedBox(height: 18), - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 360), - child: Container( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), - decoration: BoxDecoration( - color: _cloudColor.withAlpha(20), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: _cloudColor.withAlpha(50)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildBadge( - LucideIcons.refreshCw, - appLocalizations.featureCloudAuto, - _cloudColor), - _buildBadge( - LucideIcons.lock, - appLocalizations.featureCloudEncrypted, - _cloudColor), - _buildBadge( - LucideIcons.fileText, - appLocalizations.featureCloudLog, - _cloudColor), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildBadge(IconData icon, String label, Color color) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 18, color: color), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 10.5, - fontWeight: FontWeight.w600, - color: color, - ), - ), - ], - ); - } - - // ============================================================ - // Page 4 — Smart Icons - // ============================================================ - - Widget _buildIconPage() { - final brands = [ - 'google.png', - 'github.png', - 'microsoft.png', - 'apple.png', - 'amazon.png', - 'discord.png', - 'steam.png', - 'slack.png', - 'dropbox.png', - 'paypal.png', - 'netflix.png', - 'spotify.png', - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.image, - color: _iconColor, - title: appLocalizations.featureIconTitle, - subtitle: appLocalizations.featureIconDescription(2500), - ), - content: Column( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 280), - child: GridView.count( - crossAxisCount: 4, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - mainAxisSpacing: 10, - crossAxisSpacing: 10, - childAspectRatio: 1, - children: brands.map(_buildBrandIcon).toList(), - ), - ), - const SizedBox(height: 18), - _buildChip( - label: '2500+', - color: _iconColor, - icon: LucideIcons.sparkles, - ), - ], - ), - ); - } - - Widget _buildBrandIcon(String filename) { - return Container( - decoration: BoxDecoration( - color: ChewieTheme.canvasColor, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: ChewieTheme.borderColor), - ), - padding: const EdgeInsets.all(8), - child: Image.asset( - 'assets/brand/$filename', - fit: BoxFit.contain, - errorBuilder: (_, __, ___) => - Icon(LucideIcons.image, color: _iconColor, size: 18), - ), - ); - } - - // ============================================================ - // Page 5 — Security (+ Open Source) - // ============================================================ - - Widget _buildSecurityPage() { - final features = [ - (LucideIcons.databaseBackup, appLocalizations.featureSecurityEncryption), - (LucideIcons.fingerprint, appLocalizations.featureSecurityBiometric), - (LucideIcons.lockKeyhole, appLocalizations.featureSecurityGesture), - (LucideIcons.timer, appLocalizations.featureSecurityAutoLock), - (LucideIcons.eyeOff, appLocalizations.featureSecuritySafeMode), - (LucideIcons.shieldEllipsis, - appLocalizations.featureSecurityBackupEncrypt), - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.shieldCheck, - color: _securityColor, - title: appLocalizations.featureSecurityTitle, - subtitle: appLocalizations.featureSecurityDescription, - ), - content: Column( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 360), - child: GridView.count( - crossAxisCount: 2, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - mainAxisSpacing: 8, - crossAxisSpacing: 8, - childAspectRatio: 3.4, - children: features - .map((f) => _buildSecurityItem(f.$1, f.$2)) - .toList(), - ), - ), - const SizedBox(height: 16), - // Open source highlight - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - _securityColor.withAlpha(40), - _securityColor.withAlpha(15), - ], - ), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: _securityColor.withAlpha(80)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(LucideIcons.github, size: 18, color: _securityColor), - const SizedBox(width: 8), - Flexible( - child: Text( - appLocalizations.featureSecurityOpenSource, - style: TextStyle( - color: _securityColor, - fontSize: 12.5, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: [ - _buildChip( - label: 'AES-256-GCM', - color: _securityColor, - icon: LucideIcons.lock, - ), - _buildChip( - label: 'Argon2', - color: _securityColor, - icon: LucideIcons.key, - ), - ], - ), - ], - ), - ); - } - - Widget _buildSecurityItem(IconData icon, String label) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: _securityColor.withAlpha(18), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: _securityColor.withAlpha(45)), - ), - child: Row( - children: [ - Icon(icon, size: 16, color: _securityColor), - const SizedBox(width: 6), - Expanded( - child: Text( - label, - style: TextStyle( - fontSize: 11.5, - fontWeight: FontWeight.w600, - color: ChewieTheme.titleLarge.color, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ); - } - - // ============================================================ - // Page 6 — Import & Export - // ============================================================ - - Widget _buildImportExportPage() { - final imports = [ - 'Google Auth', - 'Aegis', - '2FAS', - 'Bitwarden', - 'andOTP', - 'Ente Auth', - 'FreeOTP+', - 'TOTP Auth', - 'WinAuth', - ]; - final exports = [ - appLocalizations.featureExportEncrypted, - appLocalizations.featureExportUri, - appLocalizations.featureExportCloudOTPQR, - appLocalizations.featureExportGoogleQR, - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.arrowLeftRight, - color: _migrationColor, - title: appLocalizations.featureImportExportTitle, - subtitle: appLocalizations.featureImportExportDescription, - ), - content: Column( - children: [ - _buildSectionLabel( - LucideIcons.download, - appLocalizations.featureImportSection, - _migrationColor, - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: imports - .map((s) => _buildChip(label: s, color: _migrationColor)) - .toList(), - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: [ - _buildChip( - label: appLocalizations.featureImportScan, - color: _migrationColor, - icon: LucideIcons.scanLine, - ), - _buildChip( - label: appLocalizations.featureImportClipboard, - color: _migrationColor, - icon: LucideIcons.clipboard, - ), - ], - ), - const SizedBox(height: 16), - _buildSectionLabel( - LucideIcons.upload, - appLocalizations.featureExportSection, - _migrationColor, - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: exports - .map((s) => _buildChip( - label: s, - color: _migrationColor, - icon: s.contains('QR') || - s.contains('二维') || - s.contains('二維') || - s.contains('QR') - ? LucideIcons.qrCode - : LucideIcons.fileText, - )) - .toList(), - ), - ], - ), - ); - } - - // ============================================================ - // Page 7 — Convenience - // ============================================================ - - Widget _buildConveniencePage() { - final features = [ - (LucideIcons.search, appLocalizations.featureQuickSearch), - (LucideIcons.arrowUpDown, appLocalizations.featureSort), - (LucideIcons.shapes, appLocalizations.featureCategories), - (LucideIcons.listChecks, appLocalizations.featureMultiSelect), - (LucideIcons.pin, appLocalizations.featurePinTop), - (LucideIcons.arrowLeftRight, appLocalizations.featureSwipe), - (LucideIcons.move, appLocalizations.featureDragReorder), - (LucideIcons.copy, appLocalizations.featureAutoCopy), - (LucideIcons.qrCode, appLocalizations.featureQRScanner), - ]; - return _buildPage( - hero: _buildHero( - icon: LucideIcons.zap, - color: _convenienceColor, - title: appLocalizations.featureConvenienceTitle, - subtitle: appLocalizations.featureConvenienceDescription, - ), - content: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 320), - child: GridView.count( - crossAxisCount: 3, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - mainAxisSpacing: 8, - crossAxisSpacing: 8, - childAspectRatio: 1, - children: features - .map((f) => _buildConvenienceCard(f.$1, f.$2)) - .toList(), - ), - ), - ); - } - - Widget _buildConvenienceCard(IconData icon, String label) { - return Container( - decoration: BoxDecoration( - color: _convenienceColor.withAlpha(20), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: _convenienceColor.withAlpha(50)), - ), - padding: const EdgeInsets.all(6), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: 22, color: _convenienceColor), - const SizedBox(height: 6), - Text( - label, - style: TextStyle( - fontSize: 10.5, - fontWeight: FontWeight.w600, - color: ChewieTheme.titleLarge.color, - ), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - ], - ), - ); - } - - // ============================================================ - // Page 8 — Customization (+ custom themes) - // ============================================================ - - Widget _buildCustomizePage() { - const themeColors = [ - Color(0xFF009BFF), - Color(0xFF3790A4), - Color(0xFFF588A8), - Color(0xFF11B667), - Color(0xFF454D66), - Color(0xFF272643), - Color(0xFFE74645), - Color(0xFF361D32), - Color(0xFFF8BE5F), - Color(0xFF0084FF), - ]; - - return _buildPage( - hero: _buildHero( - icon: LucideIcons.palette, - color: _customizeColor, - title: appLocalizations.featureCustomizeTitle, - subtitle: appLocalizations.featureCustomizeDescription, - ), - content: Column( - children: [ - _buildSectionLabel( - LucideIcons.droplet, - appLocalizations.featureThemeColors, - _customizeColor, - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: themeColors - .map((c) => Container( - width: 22, - height: 22, - decoration: BoxDecoration( - color: c, - shape: BoxShape.circle, - border: Border.all( - color: ChewieTheme.borderColor, width: 1), - ), - )) - .toList(), - ), - const SizedBox(height: 6), - _buildChip( - label: appLocalizations.featureCustomThemeEditor, - color: _customizeColor, - icon: LucideIcons.pencilLine, - ), - const SizedBox(height: 14), - _buildSectionLabel( - LucideIcons.layoutGrid, - appLocalizations.featureLayoutStyles, - _customizeColor, - ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, - spacing: 8, - runSpacing: 8, - children: [ - _buildLayoutPreview( - LucideIcons.layoutGrid, appLocalizations.simpleLayoutType), - _buildLayoutPreview(LucideIcons.layoutDashboard, - appLocalizations.compactLayoutType), - _buildLayoutPreview(LucideIcons.layoutTemplate, - appLocalizations.spotlightLayoutType), - _buildLayoutPreview( - LucideIcons.layoutList, appLocalizations.listLayoutType), - ], - ), - const SizedBox(height: 14), - Wrap( - alignment: WrapAlignment.center, - spacing: 6, - runSpacing: 6, - children: [ - _buildChip( - label: appLocalizations.featureGlassEffect, - color: _customizeColor, - icon: LucideIcons.sparkles), - _buildChip( - label: appLocalizations.featureCustomFont, - color: _customizeColor, - icon: LucideIcons.type), - _buildChip( - label: appLocalizations.featureMultiLang, - color: _customizeColor, - icon: LucideIcons.languages), - ], - ), - ], - ), - ); - } - - Widget _buildLayoutPreview(IconData icon, String label) { - return Container( - width: 58, - padding: const EdgeInsets.symmetric(vertical: 8), - decoration: BoxDecoration( - color: _customizeColor.withAlpha(20), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: _customizeColor.withAlpha(50)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, size: 18, color: _customizeColor), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 9.5, - fontWeight: FontWeight.w600, - color: _customizeColor, - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - } - - // ============================================================ - // Indicators & Buttons - // ============================================================ - Widget _buildPageIndicator(int total) { - final activeColor = _pages[_currentPage].color; + final activeColor = featurePages[_currentPage].color; return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(total, (i) { @@ -1003,9 +116,7 @@ class _FeatureShowcaseScreenState height: 6, margin: const EdgeInsets.symmetric(horizontal: 3), decoration: BoxDecoration( - color: isActive - ? activeColor - : activeColor.withAlpha(50), + color: isActive ? activeColor : activeColor.withAlpha(50), borderRadius: BorderRadius.circular(3), ), ); @@ -1013,34 +124,77 @@ class _FeatureShowcaseScreenState ); } - Widget _buildStartTourButton() { - if (!ResponsiveUtil.isMobile() || widget.isDialog) { - return const SizedBox.shrink(); + Widget _buildActionButton() { + final pages = featurePages; + final isLast = _currentPage == pages.length - 1; + final color = pages[_currentPage].color; + + final String label; + final VoidCallback onPressed; + + if (!isLast) { + label = appLocalizations.welcomeNextButton; + onPressed = () { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }; + } else if (ResponsiveUtil.isMobile() && !widget.isDialog) { + label = appLocalizations.welcomeStartGuide; + onPressed = () { + Navigator.of(context).pop(); + WidgetsBinding.instance.addPostFrameCallback((_) { + homeScreenState?.showCoachMark(); + }); + }; + } else if (ResponsiveUtil.isDesktop() || + ResponsiveUtil.isLandscapeTablet()) { + label = appLocalizations.welcomeStartGuide; + onPressed = () { + if (widget.isDialog) { + DialogNavigatorHelper.popPage(); + } else { + Navigator.of(context).pop(); + } + Future.delayed(const Duration(milliseconds: 400), () { + mainScreenState?.showDesktopCoachMark(force: true); + }); + }; + } else { + label = appLocalizations.welcomeGetStarted; + onPressed = () { + if (widget.isDialog) { + DialogNavigatorHelper.popPage(); + } else { + Navigator.of(context).pop(); + } + }; } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: SizedBox( - width: double.infinity, - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - WidgetsBinding.instance.addPostFrameCallback((_) { - homeScreenState?.showCoachMark(); - }); - }, - style: TextButton.styleFrom( - backgroundColor: _pages[_currentPage].color, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - appLocalizations.featureShowcaseStartTour, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w600, + + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + backgroundColor: color, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + label, + style: ChewieTheme.titleMedium.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), ), ), ), diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index f2a09e1c..cdbc1f01 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -17,6 +17,7 @@ import 'dart:async'; import 'dart:ui' as ui; import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/database_manager.dart'; import 'package:cloudotp/Database/category_dao.dart'; import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:cloudotp/Models/opt_token.dart'; @@ -45,6 +46,7 @@ import '../Utils/app_provider.dart'; import '../Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart'; import '../Widgets/BottomSheet/select_token_bottom_sheet.dart'; import '../Widgets/CoachMark/coach_mark_manager.dart'; +import 'Token/add_token_screen.dart'; import '../l10n/l10n.dart'; import 'Token/token_layout.dart'; @@ -64,6 +66,7 @@ class HomeScreenState extends BasePanelScreenState LayoutType layoutType = CloudOTPHiveUtil.getLayoutType(); OrderType orderType = CloudOTPHiveUtil.getOrderType(); List tokens = []; + int _allTokenCount = 0; List categories = []; List tabList = []; @@ -92,6 +95,12 @@ class HomeScreenState extends BasePanelScreenState final GlobalKey _appBarTitleKey = GlobalKey(); final GlobalKey _moreButtonKey = GlobalKey(); final GlobalKey _firstCategoryTabKey = GlobalKey(); + final GlobalKey _sortButtonKey = GlobalKey(); + final GlobalKey _layoutButtonKey = GlobalKey(); + final GlobalKey _fabKey = GlobalKey(); + final GlobalKey _cloudBackupKey = GlobalKey(); + final GlobalKey _backupLogKey = GlobalKey(); + final GlobalKey desktopSearchBarKey = GlobalKey(); bool get hasSearchFocus => appProvider.searchFocusNode.hasFocus; @@ -697,8 +706,21 @@ class HomeScreenState extends BasePanelScreenState @override void initState() { super.initState(); + if (DatabaseManager.isNewDatabase) { + DatabaseManager.updateSampleCategoryTitle( + appLocalizations.sampleCategoryName); + } initTab(true); - refresh(true); + refresh(true).then((_) { + if (ResponsiveUtil.isMobile() && + !ResponsiveUtil.isLandscapeTablet() && + !ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownCoachMarkKey, + defaultValue: false)) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) _showCoachMarkInternal(force: true); + }); + } + }); _searchController.addListener(() { performSearch(_searchController.text); }); @@ -868,6 +890,13 @@ class HomeScreenState extends BasePanelScreenState tokenKeyMap.removeWhere((uid, _) => !currentUids.contains(uid)); performSort(); }); + + if (currentCategoryUid.isEmpty && _searchKey.isEmpty) { + _allTokenCount = tokens.length; + } else { + final allTokens = await TokenDao.listTokens(); + _allTokenCount = allTokens.length; + } } void showCoachMark() { @@ -875,68 +904,27 @@ class HomeScreenState extends BasePanelScreenState } void _showCoachMarkInternal({required bool force}) { + final provider = context.read(); CoachMarkManager( context: context, appBarTitleKey: _appBarTitleKey, firstTokenKey: tokenKeyMap.isNotEmpty ? tokenKeyMap.values.first : null, categoryTabKey: categories.isNotEmpty ? _firstCategoryTabKey : null, moreButtonKey: _moreButtonKey, + sortButtonKey: provider.showSortButton ? _sortButtonKey : null, + layoutButtonKey: provider.showLayoutButton ? _layoutButtonKey : null, + fabKey: _fabKey, + cloudBackupKey: + provider.showCloudBackupButton ? _cloudBackupKey : null, + backupLogKey: + provider.showBackupLogButton ? _backupLogKey : null, layoutType: layoutType, tokenCount: tokens.length, categoryCount: categories.length, - onDemoAction: _performCoachDemo, - onUndoDemoAction: _undoCoachDemo, + onDeleteSampleData: () => refresh(true), ).show(force: force); } - Future _performCoachDemo(String identify) async { - switch (identify) { - case 'search_title': - changeSearchBar(true); - break; - case 'swipe_token': - final key = tokenKeyMap.values.firstOrNull; - await key?.currentState?.openEndActionPane(); - break; - case 'category_tab': - if (categories.isNotEmpty) { - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (ctx) => SelectTokenBottomSheet(category: categories.first), - ); - } - break; - case 'more_menu': - if (tokens.isNotEmpty) { - setState(() { - _multiSelectMode = true; - _selectedTokenUids.clear(); - _selectedTokenUids.add(tokens.first.uid); - }); - } - break; - } - } - - Future _undoCoachDemo(String identify) async { - switch (identify) { - case 'search_title': - changeSearchBar(false); - break; - case 'swipe_token': - final key = tokenKeyMap.values.firstOrNull; - await key?.currentState?.closeSlidable(); - break; - case 'category_tab': - if (Navigator.of(context).canPop()) Navigator.of(context).pop(); - break; - case 'more_menu': - exitMultiSelectMode(); - break; - } - } - getCategories([bool isInit = false]) async { String oldUid = currentCategoryUid; await CategoryDao.listCategories().then((value) async { @@ -984,6 +972,7 @@ class HomeScreenState extends BasePanelScreenState : ResponsiveAppBar( titleLeftMargin: 10, titleWidget: Container( + key: desktopSearchBarKey, constraints: const BoxConstraints( maxWidth: 300, minWidth: 200, maxHeight: 36), child: MySearchBar( @@ -1079,6 +1068,7 @@ class HomeScreenState extends BasePanelScreenState _buildFloatingActionButton() { var button = MyFloatingActionButton( + key: _fabKey, heroTag: "Hero-${categories.length}", onPressed: () { BottomSheetBuilder.showBottomSheet( @@ -1111,12 +1101,14 @@ class HomeScreenState extends BasePanelScreenState getActions(AppProvider provider) { return [ - if (provider.canShowCloudBackupButton && provider.showCloudBackupButton) + if (provider.showCloudBackupButton) Container( margin: const EdgeInsets.only(right: 5), child: ToolButton( + key: _cloudBackupKey, context: context, tooltip: appLocalizations.cloudBackupServiceSetting, + tooltipPosition: TooltipPosition.bottom, icon: LucideIcons.cloud, onPressed: () { RouteUtil.pushCupertinoRoute(context, const CloudServiceScreen()); @@ -1126,28 +1118,33 @@ class HomeScreenState extends BasePanelScreenState if (provider.showBackupLogButton) Container( margin: const EdgeInsets.only(right: 5), - child: CircleIconButton( - tooltip: appLocalizations.backupLogs, - padding: EdgeInsets.zero, - icon: Selector( - selector: (context, appProvider) => - appProvider.autoBackupLoadingStatus, - builder: (context, autoBackupLoadingStatus, child) => LoadingIcon( + child: Selector( + selector: (context, appProvider) => + appProvider.autoBackupLoadingStatus, + builder: (context, autoBackupLoadingStatus, child) => ToolButton( + key: _backupLogKey, + context: context, + tooltip: appLocalizations.backupLogs, + tooltipPosition: TooltipPosition.bottom, + iconBuilder: (buttonContext) => LoadingIcon( status: autoBackupLoadingStatus, - normalIcon: - Icon(Icons.history_rounded, color: ChewieTheme.iconColor), + normalIcon: Icon(Icons.history_rounded, + color: buttonContext.iconColor), ), + onPressed: () { + BackupLogScreen.show(context); + }, ), - onTap: () { - BackupLogScreen.show(context); - }, ), ), if (provider.showLayoutButton) Container( margin: const EdgeInsets.only(right: 5), child: ToolButton( + key: _layoutButtonKey, context: context, + tooltip: appLocalizations.layoutType, + tooltipPosition: TooltipPosition.bottom, icon: layoutType.icon, onPressed: () { LayoutSelectScreen.show(context); @@ -1158,7 +1155,10 @@ class HomeScreenState extends BasePanelScreenState Container( margin: const EdgeInsets.only(right: 5), child: ToolButton( + key: _sortButtonKey, context: context, + tooltip: appLocalizations.sortType, + tooltipPosition: TooltipPosition.bottom, icon: orderType.icon, onPressed: () { SortSelectScreen.show(context); @@ -1168,6 +1168,8 @@ class HomeScreenState extends BasePanelScreenState ToolButton( key: _moreButtonKey, context: context, + tooltip: appLocalizations.more, + tooltipPosition: TooltipPosition.bottom, icon: LucideIcons.ellipsisVertical, onPressed: () { BottomSheetBuilder.showBottomSheet( @@ -1191,6 +1193,7 @@ class HomeScreenState extends BasePanelScreenState pinned: true, elevation: 0, scrolledUnderElevation: 0, + titleSpacing: 0, backgroundColor: ChewieTheme.scaffoldBackgroundColor, leading: IconButton( icon: Icon(LucideIcons.x, color: ChewieTheme.iconColor), @@ -1382,7 +1385,6 @@ class HomeScreenState extends BasePanelScreenState preferredHeight: layoutType.getHeight(settings.hideProgress), ), dragToReorder: _multiSelectMode ? false : settings.dragToReorder, - cacheExtent: 9999, onReorderStart: (_) { _fabScrollToHideController.hide(); _bottombarScrollToHideController.hide(); @@ -1441,20 +1443,111 @@ class HomeScreenState extends BasePanelScreenState }, ); Widget body = tokens.isEmpty - ? ListView( - padding: const EdgeInsets.symmetric(vertical: 50), - children: [ - EmptyPlaceholder( - text: _searchKey.isEmpty - ? appLocalizations.noToken - : appLocalizations.noTokenContainingSearchKey(_searchKey), - ), - ], - ) + ? _buildEmptyPlaceholder() : gridView; return SlidableAutoCloseBehavior(child: body); } + Widget _buildEmptyPlaceholder() { + if (_searchKey.isNotEmpty) { + return ListView( + padding: const EdgeInsets.symmetric(vertical: 50), + children: [ + EmptyPlaceholder( + text: appLocalizations.noTokenContainingSearchKey(_searchKey), + ), + ], + ); + } + + final inCategory = currentCategoryUid.isNotEmpty; + final hasGlobalTokens = _allTokenCount > 0; + + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + LucideIcons.inbox, + size: 48, + color: ChewieTheme.labelLarge.color?.withAlpha(120), + ), + const SizedBox(height: 16), + Text( + inCategory + ? appLocalizations.noTokenInCategory + : appLocalizations.noToken, + style: ChewieTheme.labelLarge.copyWith(fontSize: 15), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + FilledButton.icon( + onPressed: () { + if (ResponsiveUtil.isMobile()) { + BottomSheetBuilder.showBottomSheet( + context, + enableDrag: false, + responsive: true, + (context) => AddBottomSheet( + onlyShowScanner: ResponsiveUtil.isLandscapeTablet(), + ), + ); + } else { + DialogBuilder.showPageDialog(context, + child: const AddTokenScreen()); + } + }, + icon: const Icon(LucideIcons.plus, size: 18), + label: Text(appLocalizations.addToken), + style: FilledButton.styleFrom( + backgroundColor: ChewieTheme.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + if (inCategory && hasGlobalTokens) ...[ + const SizedBox(width: 12), + OutlinedButton.icon( + onPressed: () { + final category = categories[_currentTabIndex - 1]; + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => + SelectTokenBottomSheet(category: category), + ); + }, + icon: const Icon(LucideIcons.listPlus, size: 18), + label: Text(appLocalizations.addExistingToken), + style: OutlinedButton.styleFrom( + foregroundColor: ChewieTheme.primaryColor, + side: BorderSide( + color: ChewieTheme.primaryColor.withAlpha(120)), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + ], + ], + ), + ], + ), + ), + ); + } + _buildTabBar([EdgeInsetsGeometry? padding]) { return TabBar( controller: _tabController, diff --git a/lib/Screens/layout_select_screen.dart b/lib/Screens/layout_select_screen.dart index dc615d77..24787f9c 100644 --- a/lib/Screens/layout_select_screen.dart +++ b/lib/Screens/layout_select_screen.dart @@ -169,7 +169,11 @@ class _LayoutSelectScreenState extends BaseDynamicState { return GestureDetector( onTap: () { homeScreenState?.changeLayoutType(type); - setState(() {}); + if (!widget.isOverlay) { + Navigator.pop(context); + } else { + setState(() {}); + } }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), diff --git a/lib/Screens/main_screen.dart b/lib/Screens/main_screen.dart index 95d9587d..ca59a7f5 100644 --- a/lib/Screens/main_screen.dart +++ b/lib/Screens/main_screen.dart @@ -53,11 +53,15 @@ import '../Utils/utils.dart'; import '../Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart'; import '../l10n/l10n.dart'; import 'Backup/cloud_service_screen.dart'; +import 'package:flutter/foundation.dart'; + import 'Lock/database_decrypt_screen.dart'; import 'Lock/pin_verify_screen.dart'; import 'Setting/backup_log_screen.dart'; import 'Setting/setting_navigation_screen.dart'; import 'Token/category_screen.dart'; +import 'feature_showcase_screen.dart'; +import '../Widgets/CoachMark/desktop_coach_mark_manager.dart'; const borderColor = Color(0xFF805306); const backgroundStartColor = Color(0xFFFFD500); @@ -84,6 +88,20 @@ class MainScreenState extends BaseWindowState List _menuTokens = []; List _menuCategories = []; + final GlobalKey _sidebarLogoKey = GlobalKey(); + final GlobalKey _sidebarAddKey = GlobalKey(); + final GlobalKey _sidebarCategoryKey = GlobalKey(); + final GlobalKey _sidebarImportKey = GlobalKey(); + final GlobalKey _sidebarImportThirdKey = GlobalKey(); + final GlobalKey _sidebarCloudBackupKey = GlobalKey(); + final GlobalKey _sidebarBackupLogKey = GlobalKey(); + final GlobalKey _sidebarFeatureShowcaseKey = GlobalKey(); + final GlobalKey _sidebarSettingKey = GlobalKey(); + final GlobalKey _sidebarQrScanKey = GlobalKey(); + final GlobalKey _sidebarSortKey = GlobalKey(); + final GlobalKey _sidebarLayoutKey = GlobalKey(); + final GlobalKey _searchBarKey = GlobalKey(); + @override void onWindowMinimize() { setTimer(); @@ -179,6 +197,46 @@ class MainScreenState extends BaseWindowState homeScreenState?.performSearch(searchController.text); }); _startAutoBackupOnLaunch(); + _scheduleDesktopCoachMark(); + } + + void _scheduleDesktopCoachMark() { + if (!ResponsiveUtil.isDesktop() && !ResponsiveUtil.isLandscapeTablet()) { + return; + } + if (ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownDesktopCoachMarkKey, + defaultValue: false)) return; + Future.delayed(const Duration(milliseconds: 800), () { + if (!mounted) return; + showDesktopCoachMark(); + }); + } + + void showDesktopCoachMark({bool force = false}) { + final provider = context.read(); + final searchKey = ResponsiveUtil.isMacOS() + ? _searchBarKey + : (homeScreenState?.desktopSearchBarKey ?? _searchBarKey); + DesktopCoachMarkManager( + context: context, + searchBarKey: searchKey, + addTokenKey: _sidebarAddKey, + categoryKey: _sidebarCategoryKey, + qrScanKey: ResponsiveUtil.isLandscapeTablet() ? null : _sidebarQrScanKey, + importExportKey: _sidebarImportKey, + importThirdPartyKey: _sidebarImportThirdKey, + cloudBackupKey: + provider.showCloudBackupButton ? _sidebarCloudBackupKey : null, + backupLogKey: + provider.showBackupLogButton ? _sidebarBackupLogKey : null, + sortButtonKey: provider.showSortButton ? _sidebarSortKey : null, + layoutButtonKey: provider.showLayoutButton ? _sidebarLayoutKey : null, + featureShowcaseKey: _sidebarFeatureShowcaseKey, + settingKey: _sidebarSettingKey, + logoKey: _sidebarLogoKey, + firstTokenKey: homeScreenState?.tokenKeyMap.values.firstOrNull, + onDeleteSampleData: () => homeScreenState?.refresh(true), + ).show(force: force); } static const _notifierChannel = MethodChannel('local_notifier'); @@ -1048,6 +1106,7 @@ class MainScreenState extends BaseWindowState _buildLogo(), const SizedBox(height: 8), ToolButton( + key: _sidebarAddKey, context: context, tooltip: appLocalizations.addToken, tooltipPosition: TooltipPosition.right, @@ -1061,6 +1120,7 @@ class MainScreenState extends BaseWindowState ), const SizedBox(height: 4), ToolButton( + key: _sidebarCategoryKey, context: context, tooltip: appLocalizations.category, tooltipPosition: TooltipPosition.right, @@ -1076,6 +1136,7 @@ class MainScreenState extends BaseWindowState const SizedBox(height: 4), if (!ResponsiveUtil.isLandscapeTablet()) ToolButton( + key: _sidebarQrScanKey, context: context, tooltip: appLocalizations.scanToken, tooltipPosition: TooltipPosition.right, @@ -1089,6 +1150,7 @@ class MainScreenState extends BaseWindowState ), const SizedBox(height: 4), ToolButton( + key: _sidebarImportKey, context: context, tooltip: appLocalizations.exportImport, tooltipPosition: TooltipPosition.right, @@ -1104,6 +1166,7 @@ class MainScreenState extends BaseWindowState ), const SizedBox(height: 4), ToolButton( + key: _sidebarImportThirdKey, context: context, tooltip: appLocalizations.importFromThirdParty, icon: LucideIcons.waypoints, @@ -1118,9 +1181,9 @@ class MainScreenState extends BaseWindowState }, ), const SizedBox(height: 4), - if (provider.canShowCloudBackupButton && - provider.showCloudBackupButton) + if (provider.showCloudBackupButton) ToolButton( + key: _sidebarCloudBackupKey, context: context, tooltip: appLocalizations.cloudBackupServiceSetting, icon: LucideIcons.cloudUpload, @@ -1132,9 +1195,27 @@ class MainScreenState extends BaseWindowState child: const CloudServiceScreen(showBack: false)); }, ), + if (ResponsiveUtil.isLandscapeTablet()) + const SizedBox(height: 4), + if (ResponsiveUtil.isLandscapeTablet()) + ToolButton( + context: context, + tooltip: appLocalizations.select, + tooltipPosition: TooltipPosition.right, + icon: LucideIcons.listChecks, + padding: const EdgeInsets.all(8), + iconSize: 22, + onPressed: () { + final tokens = homeScreenState?.tokens; + if (tokens != null && tokens.isNotEmpty) { + homeScreenState?.enterMultiSelectMode(tokens.first.uid); + } + }, + ), const Spacer(), if (provider.showBackupLogButton) ...[ ToolButton( + key: _sidebarBackupLogKey, context: context, tooltip: appLocalizations.backupLogs, tooltipPosition: TooltipPosition.right, @@ -1156,6 +1237,7 @@ class MainScreenState extends BaseWindowState ], if (provider.showSortButton) ...[ ToolButton( + key: _sidebarSortKey, context: context, icon: homeScreenState?.orderType.icon ?? LucideIcons.arrowUpNarrowWide, @@ -1171,6 +1253,7 @@ class MainScreenState extends BaseWindowState ], if (provider.showLayoutButton) ...[ ToolButton( + key: _sidebarLayoutKey, context: context, icon: homeScreenState?.layoutType.icon ?? LucideIcons.layoutDashboard, @@ -1184,6 +1267,19 @@ class MainScreenState extends BaseWindowState ), const SizedBox(height: 4), ], + ToolButton( + key: _sidebarFeatureShowcaseKey, + context: context, + tooltip: appLocalizations.featureShowcase, + tooltipPosition: TooltipPosition.right, + icon: LucideIcons.telescope, + padding: const EdgeInsets.all(8), + iconSize: 22, + onPressed: () { + FeatureShowcaseScreen.showAsDialog(context); + }, + ), + const SizedBox(height: 4), ToolButton.dynamicButton( tooltip: appLocalizations.themeMode, iconBuilder: (context, isDark) => @@ -1193,8 +1289,29 @@ class MainScreenState extends BaseWindowState onChangemode: (context, themeMode, child) {}, iconSize: 22, ), + if (kDebugMode) + const SizedBox(height: 4), + if (kDebugMode) + ToolButton( + context: context, + tooltip: "Reset Welcome", + tooltipPosition: TooltipPosition.right, + icon: LucideIcons.rotateCcw, + padding: const EdgeInsets.all(8), + iconSize: 22, + onPressed: () { + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownWelcome4Key, false); + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownCoachMarkKey, false); + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownDesktopCoachMarkKey, false); + IToast.showTop("Welcome screen reset"); + }, + ), const SizedBox(height: 4), ToolButton( + key: _sidebarSettingKey, context: context, tooltip: appLocalizations.setting, tooltipPosition: TooltipPosition.right, @@ -1218,8 +1335,14 @@ class MainScreenState extends BaseWindowState Widget _buildLogo({ double size = 32, }) { - return IgnorePointer( - child: ClipRRect( + return ToolButton( + key: _sidebarLogoKey, + context: context, + tooltip: appLocalizations.shortcut, + tooltipPosition: TooltipPosition.right, + padding: const EdgeInsets.all(4), + iconSize: size, + iconBuilder: (_) => ClipRRect( borderRadius: ChewieDimens.borderRadius8, clipBehavior: Clip.antiAlias, child: Container( @@ -1233,6 +1356,7 @@ class MainScreenState extends BaseWindowState ), ), ), + onPressed: () => ShortcutsUtil.showShortcutHelp(context), ); } @@ -1273,6 +1397,7 @@ class MainScreenState extends BaseWindowState leftWidgets: [ const SizedBox(width: macosTitleBarLeftMargin), Container( + key: _searchBarKey, constraints: const BoxConstraints( maxWidth: 300, minWidth: 200, maxHeight: 36), child: MySearchBar( diff --git a/lib/Screens/sort_select_screen.dart b/lib/Screens/sort_select_screen.dart index 7a7923be..b74caff5 100644 --- a/lib/Screens/sort_select_screen.dart +++ b/lib/Screens/sort_select_screen.dart @@ -257,7 +257,11 @@ class _SortSelectScreenState extends BaseDynamicState { return GestureDetector( onTap: () { homeScreenState?.changeOrderType(type: type); - setState(() {}); + if (!widget.isOverlay) { + Navigator.pop(context); + } else { + setState(() {}); + } }, behavior: HitTestBehavior.opaque, child: Padding( diff --git a/lib/Screens/welcome_screen.dart b/lib/Screens/welcome_screen.dart new file mode 100644 index 00000000..b2561ddd --- /dev/null +++ b/lib/Screens/welcome_screen.dart @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2024 Robert-Stackflow. + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Utils/hive_util.dart'; +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; + +import '../Utils/constant.dart'; +import '../Utils/shortcuts_util.dart'; +import '../l10n/l10n.dart'; +import 'feature_showcase_pages.dart'; + +class WelcomeScreen extends StatefulWidget { + const WelcomeScreen({super.key}); + + @override + State createState() => _WelcomeScreenState(); +} + +class _WelcomeScreenState extends State + with TickerProviderStateMixin, FeatureShowcasePages { + late final AnimationController _splashController; + late final AnimationController _transitionController; + + late final Animation _iconFade; + late final Animation _iconScale; + late final Animation _titleFade; + late final Animation _subtitleFade; + late final Animation _buttonFade; + + late final Animation _heroProgress; + late final Animation _contentFade; + + final PageController _pageController = PageController(); + int _currentPage = 0; + bool _splashReady = false; + bool _transitioning = false; + bool _transitionDone = false; + + @override + void initState() { + super.initState(); + + _splashController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 1500), + ); + _transitionController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 700), + ); + + _iconFade = CurvedAnimation( + parent: _splashController, + curve: const Interval(0.0, 0.4, curve: Curves.easeOut), + ); + _iconScale = Tween(begin: 0.6, end: 1.0).animate( + CurvedAnimation( + parent: _splashController, + curve: const Interval(0.0, 0.4, curve: Curves.easeOutBack), + ), + ); + _titleFade = CurvedAnimation( + parent: _splashController, + curve: const Interval(0.2, 0.6, curve: Curves.easeOut), + ); + _subtitleFade = CurvedAnimation( + parent: _splashController, + curve: const Interval(0.4, 0.8, curve: Curves.easeOut), + ); + _buttonFade = CurvedAnimation( + parent: _splashController, + curve: const Interval(0.6, 1.0, curve: Curves.easeOut), + ); + + _heroProgress = CurvedAnimation( + parent: _transitionController, + curve: const Interval(0.0, 0.7, curve: Curves.easeInOut), + ); + _contentFade = CurvedAnimation( + parent: _transitionController, + curve: const Interval(0.4, 1.0, curve: Curves.easeOut), + ); + + _splashController.forward(); + _splashController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + setState(() => _splashReady = true); + } + }); + + _transitionController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + setState(() => _transitionDone = true); + } + }); + + _pageController.addListener(() { + final page = _pageController.page?.round() ?? 0; + if (page != _currentPage) { + setState(() => _currentPage = page); + } + }); + } + + @override + void dispose() { + _splashController.dispose(); + _transitionController.dispose(); + _pageController.dispose(); + super.dispose(); + } + + void _startExploring() { + setState(() => _transitioning = true); + _transitionController.forward(); + } + + void _skip() { + ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownWelcome4Key, true); + ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownCoachMarkKey, true); + ShortcutsUtil.jumpToMain(); + } + + void _finishWithGuide() { + ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownWelcome4Key, true); + ShortcutsUtil.jumpToMain(); + } + + void _nextPage() { + final pages = featurePages; + if (_currentPage < pages.length - 1) { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } else { + if (ResponsiveUtil.isMobile()) { + _finishWithGuide(); + } else { + _skip(); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: ChewieTheme.scaffoldBackgroundColor, + body: SafeArea( + child: Stack( + children: [ + _buildFeatureContent(), + if (!_transitionDone) _buildHeroTransition(), + _buildSkipButton(), + ], + ), + ), + ); + } + + Widget _buildHeroTransition() { + return AnimatedBuilder( + animation: Listenable.merge([_splashController, _transitionController]), + builder: (context, child) { + final t = _heroProgress.value; + final screenHeight = MediaQuery.of(context).size.height; + + final bgOpacity = (1.0 - t).clamp(0.0, 1.0); + + // Elements fade out during transition + final iconOpacity = _iconFade.value * + (1.0 - Curves.easeIn.transform((t / 0.5).clamp(0.0, 1.0))); + final subtitleOpacity = _subtitleFade.value * + (1.0 - Curves.easeIn.transform((t / 0.3).clamp(0.0, 1.0))); + final buttonOpacity = _buttonFade.value * + (1.0 - Curves.easeIn.transform((t / 0.3).clamp(0.0, 1.0))); + + // Title hero: moves from center to top-left, font shrinks + final titleTopStart = screenHeight * 0.42; + final titleTopEnd = ResponsiveUtil.isMacOS() ? 15.0 : 12.0; + final titleTop = titleTopStart + (titleTopEnd - titleTopStart) * t; + final titleFontSize = 28.0 + (16.0 - 28.0) * t; + final titleAlignment = + Alignment.lerp(Alignment.center, Alignment.centerLeft, t)!; + + return Stack( + children: [ + if (bgOpacity > 0) + Positioned.fill( + child: Container( + color: ChewieTheme.scaffoldBackgroundColor + .withAlpha((255 * bgOpacity).round()), + ), + ), + // Icon + if (iconOpacity > 0.01) + Positioned.fill( + child: Opacity( + opacity: iconOpacity.clamp(0.0, 1.0), + child: FadeTransition( + opacity: _iconFade, + child: ScaleTransition( + scale: _iconScale, + child: Align( + alignment: const Alignment(0, -0.35), + child: Container( + width: 88, + height: 88, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + ChewieTheme.primaryColor.withAlpha(60), + ChewieTheme.primaryColor.withAlpha(20), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: ChewieTheme.primaryColor.withAlpha(80), + width: 1.5, + ), + ), + child: Icon( + LucideIcons.shieldCheck, + size: 40, + color: ChewieTheme.primaryColor, + ), + ), + ), + ), + ), + ), + ), + // Title (hero animation: center → top-left) + Positioned( + left: ResponsiveUtil.isMacOS() + ? 20 + (macosTitleBarLeftMargin - 20) * t + : 20, + right: 20, + top: titleTop, + child: Opacity( + opacity: _titleFade.value.clamp(0.0, 1.0), + child: Align( + alignment: titleAlignment, + child: Text( + t < 0.5 ? appLocalizations.welcomeTitle : 'CloudOTP 4.0', + textAlign: t < 0.5 ? TextAlign.center : TextAlign.left, + style: ChewieTheme.titleLarge.copyWith( + fontSize: titleFontSize, + fontWeight: FontWeight.bold, + height: 1.3, + ), + ), + ), + ), + ), + // Subtitle + if (subtitleOpacity > 0.01) + Positioned( + left: 48, + right: 48, + top: screenHeight * 0.54, + child: Opacity( + opacity: subtitleOpacity.clamp(0.0, 1.0), + child: Text( + appLocalizations.welcomeSubtitle, + textAlign: TextAlign.center, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + fontSize: 14, + ), + ), + ), + ), + // Button + if (buttonOpacity > 0.01) + Positioned( + left: 48, + right: 48, + top: screenHeight * 0.62, + child: Opacity( + opacity: buttonOpacity.clamp(0.0, 1.0), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: _splashReady && !_transitioning + ? _startExploring + : null, + style: TextButton.styleFrom( + backgroundColor: ChewieTheme.primaryColor, + overlayColor: Colors.white.withAlpha(30), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + appLocalizations.welcomeStartExploring, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ), + ), + ], + ); + }, + ); + } + + Widget _buildFeatureContent() { + final pages = featurePages; + return FadeTransition( + opacity: _contentFade, + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB( + ResponsiveUtil.isMacOS() ? macosTitleBarLeftMargin : 20, + ResponsiveUtil.isMacOS() ? 15 : 12, + 20, + 0, + ), + child: Row( + children: [ + Opacity( + opacity: _transitionDone ? 1.0 : 0.0, + child: Text( + 'CloudOTP 4.0', + style: ChewieTheme.titleLarge.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + height: 1.3, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 8), + Expanded( + child: PageView.builder( + controller: _pageController, + itemCount: pages.length, + itemBuilder: (context, index) => pages[index].build(), + ), + ), + const SizedBox(height: 12), + _buildPageIndicator(pages.length), + const SizedBox(height: 16), + _buildActionButton(pages.length), + SizedBox(height: MediaQuery.of(context).padding.bottom + 24), + ], + ), + ); + } + + Widget _buildPageIndicator(int total) { + final activeColor = featurePages[_currentPage].color; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(total, (i) { + final isActive = i == _currentPage; + return AnimatedContainer( + duration: const Duration(milliseconds: 250), + width: isActive ? 18 : 6, + height: 6, + margin: const EdgeInsets.symmetric(horizontal: 3), + decoration: BoxDecoration( + color: isActive ? activeColor : activeColor.withAlpha(50), + borderRadius: BorderRadius.circular(3), + ), + ); + }), + ); + } + + Widget _buildActionButton(int totalPages) { + final isLast = _currentPage == totalPages - 1; + final color = featurePages[_currentPage].color; + final label = isLast + ? (ResponsiveUtil.isMobile() + ? appLocalizations.welcomeStartGuide + : appLocalizations.welcomeGetStarted) + : appLocalizations.welcomeNextButton; + + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: _nextPage, + style: TextButton.styleFrom( + backgroundColor: color, + overlayColor: Colors.white.withAlpha(30), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildSkipButton() { + return Positioned( + top: 8, + right: ResponsiveUtil.isMacOS() ? 8 : 16, + child: TextButton( + onPressed: _skip, + style: TextButton.styleFrom( + overlayColor: ChewieTheme.primaryColor.withAlpha(30), + ), + child: Text( + appLocalizations.welcomeSkip, + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.primaryColor, + ), + ), + ), + ); + } +} diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index 1784cb28..18e87446 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -369,9 +369,8 @@ class AppProvider with ChangeNotifier { notifyListeners(); } - bool _showBackupLogButton = ChewieHiveUtil.getBool( - CloudOTPHiveUtil.showBackupLogButtonKey, - defaultValue: ResponsiveUtil.isLandscapeLayout(false)); + bool _showBackupLogButton = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.showBackupLogButtonKey); bool get showBackupLogButton => _showBackupLogButton; diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index 6145cb7b..a26d8c8e 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -14,6 +14,7 @@ */ import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -99,10 +100,12 @@ class CloudOTPHiveUtil { static const String oldVersionKey = "oldVersion"; static const String haveShowQAuthDialogKey = "haveShowQAuthDialog"; static const String haveShownCoachMarkKey = "haveShownCoachMark"; + static const String haveShownDesktopCoachMarkKey = "haveShownDesktopCoachMark"; + static const String haveShownWelcome4Key = "haveShownWelcome4"; static initConfig() async { await ChewieHiveUtil.put( - CloudOTPHiveUtil.layoutTypeKey, LayoutType.Compact.index); + CloudOTPHiveUtil.layoutTypeKey, LayoutType.List.index); await ChewieHiveUtil.put(CloudOTPHiveUtil.autoFocusSearchBarKey, false); await ChewieHiveUtil.put( CloudOTPHiveUtil.maxBackupsCountKey, defaultMaxBackupCount); @@ -112,6 +115,8 @@ class CloudOTPHiveUtil { await ChewieHiveUtil.put( CloudOTPHiveUtil.autoMinimizeAfterClickToCopyKey, false); await ChewieHiveUtil.put(CloudOTPHiveUtil.hideGestureTrailKey, false); + await ChewieHiveUtil.put(CloudOTPHiveUtil.showSortButtonKey, true); + await ChewieHiveUtil.put(CloudOTPHiveUtil.showLayoutButtonKey, true); } static bool canLock() => canGuestureLock() || canDatabaseLock(); @@ -238,6 +243,18 @@ class CloudOTPHiveUtil { } catch (_) {} return hivePassword; } + if (securePassword == null && Platform.isWindows) { + try { + await _secureStorage.delete(key: _secureDbPasswordKey); + securePassword = + await _secureStorage.read(key: _secureDbPasswordKey); + if (securePassword != null && securePassword.isNotEmpty) { + await ChewieHiveUtil.put( + CloudOTPHiveUtil.defaultDatabasePasswordKey, securePassword); + return securePassword; + } + } catch (_) {} + } return ''; } diff --git a/lib/Widgets/BottomSheet/more_bottom_sheet.dart b/lib/Widgets/BottomSheet/more_bottom_sheet.dart index 1e2bb205..93ff45ea 100644 --- a/lib/Widgets/BottomSheet/more_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/more_bottom_sheet.dart @@ -14,6 +14,7 @@ */ import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Utils/hive_util.dart'; import 'package:cloudotp/Screens/Setting/about_setting_screen.dart'; import 'package:cloudotp/Screens/Setting/setting_appearance_screen.dart'; import 'package:cloudotp/Screens/Setting/setting_backup_screen.dart'; @@ -22,6 +23,7 @@ import 'package:cloudotp/Screens/Setting/setting_operation_screen.dart'; import 'package:cloudotp/Screens/Setting/setting_safe_screen.dart'; import 'package:cloudotp/Screens/Token/category_screen.dart'; import 'package:cloudotp/Screens/feature_showcase_screen.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; @@ -145,7 +147,6 @@ class _MoreBottomSheetState extends BaseDynamicState { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - border: Border.all(color: ChewieTheme.borderColor), ), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: Row( @@ -255,6 +256,22 @@ class _MoreBottomSheetState extends BaseDynamicState { } }, ), + if (kDebugMode) + EntryItem( + title: "Reset Welcome", + showLeading: true, + leading: LucideIcons.rotateCcw, + onTap: () { + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownWelcome4Key, false); + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownCoachMarkKey, false); + ChewieHiveUtil.put( + CloudOTPHiveUtil.haveShownDesktopCoachMarkKey, false); + Navigator.pop(context); + IToast.showTop("Welcome screen reset"); + }, + ), ], ), ); diff --git a/lib/Widgets/CoachMark/coach_mark_manager.dart b/lib/Widgets/CoachMark/coach_mark_manager.dart index 3292feda..7b26d41a 100644 --- a/lib/Widgets/CoachMark/coach_mark_manager.dart +++ b/lib/Widgets/CoachMark/coach_mark_manager.dart @@ -13,13 +13,12 @@ * If not, see . */ -import 'dart:async'; import 'dart:ui' as ui; import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/database_manager.dart'; import 'package:cloudotp/Utils/hive_util.dart'; import 'package:flutter/material.dart'; -import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; import '../../Screens/home_screen.dart'; import '../../l10n/l10n.dart'; @@ -30,11 +29,15 @@ class CoachMarkManager { final GlobalKey? firstTokenKey; final GlobalKey? categoryTabKey; final GlobalKey moreButtonKey; + final GlobalKey? sortButtonKey; + final GlobalKey? layoutButtonKey; + final GlobalKey? fabKey; + final GlobalKey? cloudBackupKey; + final GlobalKey? backupLogKey; final LayoutType layoutType; final int tokenCount; final int categoryCount; - final Future Function(String identify)? onDemoAction; - final Future Function(String identify)? onUndoDemoAction; + final VoidCallback? onDeleteSampleData; CoachMarkManager({ required this.context, @@ -42,15 +45,19 @@ class CoachMarkManager { this.firstTokenKey, this.categoryTabKey, required this.moreButtonKey, + this.sortButtonKey, + this.layoutButtonKey, + this.fabKey, + this.cloudBackupKey, + this.backupLogKey, required this.layoutType, required this.tokenCount, required this.categoryCount, - this.onDemoAction, - this.onUndoDemoAction, + this.onDeleteSampleData, }); Future show({bool force = false}) async { - if (!ResponsiveUtil.isMobile()) return; + if (!ResponsiveUtil.isMobile() || ResponsiveUtil.isLandscapeTablet()) return; if (!force && ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownCoachMarkKey, defaultValue: false)) { @@ -60,55 +67,43 @@ class CoachMarkManager { List<_CoachStep> steps = _buildSteps(); if (steps.isEmpty) return; - bool skipped = false; - - for (int i = 0; i < steps.length; i++) { - if (skipped || !context.mounted) break; - final step = steps[i]; - final completer = Completer(); - - final tcm = TutorialCoachMark( - targets: [_buildTarget(step, i, steps.length, completer)], - colorShadow: Colors.black, - opacityShadow: 0.75, - paddingFocus: 8, - pulseEnable: true, - focusAnimationDuration: const Duration(milliseconds: 300), - unFocusAnimationDuration: const Duration(milliseconds: 300), - showSkipInLastTarget: true, - hideSkip: true, + if (!context.mounted) return; + + final overlay = Overlay.of(context); + late OverlayEntry entry; + entry = OverlayEntry( + builder: (_) => _CoachMarkOverlay( + steps: steps, onFinish: () { - if (!completer.isCompleted) completer.complete(true); - }, - onSkip: () { - if (!completer.isCompleted) completer.complete(false); - return true; + entry.remove(); + _markShown(); }, - ); - - tcm.show(context: context); - final proceed = await completer.future; - - if (!proceed) { - skipped = true; - break; - } - - if (onDemoAction != null && context.mounted) { - await Future.delayed(const Duration(milliseconds: 200)); - await onDemoAction!(step.identify); - await Future.delayed(const Duration(milliseconds: 1200)); - if (context.mounted) { - await onUndoDemoAction?.call(step.identify); - } - await Future.delayed(const Duration(milliseconds: 400)); - } - } - _markShown(); + ), + ); + overlay.insert(entry); } void _markShown() { ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownCoachMarkKey, true); + _promptDeleteSampleData(); + } + + Future _promptDeleteSampleData() async { + if (!context.mounted) return; + final hasSample = await DatabaseManager.hasSampleData(); + if (!hasSample || !context.mounted) return; + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.coachMarkDeleteSampleTitle, + message: appLocalizations.coachMarkDeleteSampleMessage, + onTapConfirm: () async { + await DatabaseManager.deleteSampleData(); + onDeleteSampleData?.call(); + }, + onTapCancel: () { + DatabaseManager.clearSampleDataFlag(); + }, + ); } List<_CoachStep> _buildSteps() { @@ -116,114 +111,317 @@ class CoachMarkManager { steps.add(_CoachStep( key: appBarTitleKey, - identify: "search_title", - shape: ShapeLightFocus.RRect, - radius: 8, title: appLocalizations.coachMarkSearchTitle, description: appLocalizations.coachMarkSearchDescription, )); + if (cloudBackupKey != null) { + steps.add(_CoachStep( + key: cloudBackupKey!, + title: appLocalizations.coachMarkCloudBackupTitle, + description: appLocalizations.coachMarkCloudBackupDescription, + )); + } + + if (backupLogKey != null) { + steps.add(_CoachStep( + key: backupLogKey!, + title: appLocalizations.coachMarkBackupLogTitle, + description: appLocalizations.coachMarkBackupLogDescription, + )); + } + + if (layoutButtonKey != null) { + steps.add(_CoachStep( + key: layoutButtonKey!, + title: appLocalizations.coachMarkLayoutTitle, + description: appLocalizations.coachMarkLayoutDescription, + )); + } + + if (sortButtonKey != null) { + steps.add(_CoachStep( + key: sortButtonKey!, + title: appLocalizations.coachMarkSortTitle, + description: appLocalizations.coachMarkSortDescription, + )); + } + + if (tokenCount > 1) { + steps.add(_CoachStep( + key: moreButtonKey, + title: appLocalizations.coachMarkMultiSelectTitle, + description: appLocalizations.coachMarkMultiSelectDescription, + )); + } + bool swipeApplicable = tokenCount >= 1 && firstTokenKey != null && (layoutType == LayoutType.List || layoutType == LayoutType.Spotlight); if (swipeApplicable) { steps.add(_CoachStep( key: firstTokenKey!, - identify: "swipe_token", - shape: ShapeLightFocus.RRect, - radius: 12, title: appLocalizations.coachMarkSwipeTitle, description: appLocalizations.coachMarkSwipeDescription, + radius: 12, )); } if (categoryCount >= 1 && categoryTabKey != null) { steps.add(_CoachStep( key: categoryTabKey!, - identify: "category_tab", - shape: ShapeLightFocus.RRect, - radius: 8, title: appLocalizations.coachMarkCategoryTitle, description: appLocalizations.coachMarkCategoryDescription, )); } - if (tokenCount > 1) { + if (fabKey != null) { steps.add(_CoachStep( - key: moreButtonKey, - identify: "more_menu", - shape: ShapeLightFocus.Circle, - radius: 0, - title: appLocalizations.coachMarkMultiSelectTitle, - description: appLocalizations.coachMarkMultiSelectDescription, + key: fabKey!, + title: appLocalizations.coachMarkScanTitle, + description: appLocalizations.coachMarkScanDescription, + radius: 12, )); } return steps; } +} - TargetFocus _buildTarget( - _CoachStep step, int index, int total, Completer completer) { - return TargetFocus( - identify: step.identify, - keyTarget: step.key, - alignSkip: Alignment.topRight, - shape: step.shape, - radius: step.radius, - enableOverlayTab: false, - enableTargetTab: false, - contents: [ - TargetContent( - align: _contentAlign(step.key), - builder: (context, controller) { - return _buildContent( - title: step.title, - description: step.description, - stepIndex: index, - totalSteps: total, - isLast: index == total - 1, - onNext: () { - if (!completer.isCompleted) { - controller.next(); - } - }, - onSkip: () { - if (!completer.isCompleted) { - completer.complete(false); - controller.skip(); +class _CoachMarkOverlay extends StatefulWidget { + final List<_CoachStep> steps; + final VoidCallback onFinish; + + const _CoachMarkOverlay({ + required this.steps, + required this.onFinish, + }); + + @override + State<_CoachMarkOverlay> createState() => _CoachMarkOverlayState(); +} + +class _CoachMarkOverlayState extends State<_CoachMarkOverlay> + with TickerProviderStateMixin { + static const _shadowOpacity = 0.75; + static const _padding = 4.0; + static const _animDuration = Duration(milliseconds: 350); + + int _currentIndex = 0; + late AnimationController _holeController; + Animation? _holeAnimation; + late AnimationController _contentController; + late AnimationController _pulseController; + bool _isTransitioning = false; + + Rect _currentTargetRect = Rect.zero; + + @override + void initState() { + super.initState(); + _holeController = AnimationController(vsync: this, duration: _animDuration); + _contentController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + + _holeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + _currentTargetRect = _holeAnimation?.value ?? _currentTargetRect; + _isTransitioning = false; + _contentController.forward(); + _pulseController.repeat(reverse: true); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _initFirstTarget(); + }); + } + + Rect _expandedRect(Rect target, Size screenSize) { + final expandFactor = screenSize.longestSide; + return Rect.fromCenter( + center: target.center, + width: target.width + expandFactor, + height: target.height + expandFactor, + ); + } + + void _initFirstTarget() { + final rect = _getTargetRect(widget.steps[0]); + if (rect == null) { + widget.onFinish(); + return; + } + final screenSize = MediaQuery.of(context).size; + final startRect = _expandedRect(rect, screenSize); + _currentTargetRect = rect; + _holeAnimation = RectTween(begin: startRect, end: rect) + .animate(CurvedAnimation(parent: _holeController, curve: Curves.easeOutCubic)); + _holeController.forward(from: 0).then((_) { + _contentController.forward(); + }); + setState(() {}); + } + + Rect? _getTargetRect(_CoachStep step) { + final renderBox = + step.key.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null || !renderBox.attached) return null; + final pos = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + return Rect.fromLTWH( + pos.dx - _padding, + pos.dy - _padding, + size.width + _padding * 2, + size.height + _padding * 2, + ); + } + + void _goToNext() { + if (_isTransitioning) return; + final nextIndex = _currentIndex + 1; + if (nextIndex >= widget.steps.length) { + _dismiss(); + return; + } + _transitionTo(nextIndex); + } + + void _dismiss() { + _pulseController.stop(); + _contentController.reverse().then((_) { + widget.onFinish(); + }); + } + + void _transitionTo(int index) { + final nextRect = _getTargetRect(widget.steps[index]); + if (nextRect == null) { + if (index + 1 < widget.steps.length) { + _transitionTo(index + 1); + } else { + _dismiss(); + } + return; + } + + _isTransitioning = true; + _pulseController.stop(); + _contentController.reverse().then((_) { + setState(() { + _currentIndex = index; + }); + _holeAnimation = RectTween(begin: _currentTargetRect, end: nextRect) + .animate(CurvedAnimation(parent: _holeController, curve: Curves.easeInOut)); + _holeController.forward(from: 0); + }); + } + + @override + void dispose() { + _holeController.dispose(); + _contentController.dispose(); + _pulseController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_holeAnimation == null) return const SizedBox.shrink(); + final step = widget.steps[_currentIndex]; + return Material( + type: MaterialType.transparency, + child: Stack( + children: [ + Positioned.fill( + child: AnimatedBuilder( + animation: Listenable.merge([_holeController, _pulseController]), + builder: (context, _) { + Rect rect = _holeAnimation?.value ?? _currentTargetRect; + if (!_isTransitioning && _pulseController.isAnimating) { + final pulseAmount = 3.0 * _pulseController.value; + rect = rect.inflate(pulseAmount); } + return CustomPaint( + painter: _HolePainter( + holeRect: rect, + radius: step.radius, + shadowColor: Colors.black, + shadowOpacity: _shadowOpacity, + ), + child: const SizedBox.expand(), + ); }, - ); - }, - ), - ], + ), + ), + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _goToNext, + child: const SizedBox.expand(), + ), + ), + _buildContentPositioned(step), + ], + ), ); } - ContentAlign _contentAlign(GlobalKey key) { - try { - final renderBox = - key.currentContext?.findRenderObject() as RenderBox?; - if (renderBox == null) return ContentAlign.bottom; - final pos = renderBox.localToGlobal(Offset.zero); - final screenHeight = MediaQuery.of(context).size.height; - return pos.dy < screenHeight / 2 - ? ContentAlign.bottom - : ContentAlign.top; - } catch (_) { - return ContentAlign.bottom; + Widget _buildContentPositioned(_CoachStep step) { + final screenSize = MediaQuery.of(context).size; + final targetRect = _getTargetRect(step) ?? _currentTargetRect; + final contentAlign = _computeContentAlign(targetRect, screenSize); + + double? top, bottom, left, right; + + switch (contentAlign) { + case _ContentAlign.bottom: + top = targetRect.bottom + 12; + left = 0; + right = 0; + break; + case _ContentAlign.top: + bottom = screenSize.height - targetRect.top + 12; + left = 0; + right = 0; + break; } + + return Positioned( + top: top, + bottom: bottom, + left: left, + right: right, + child: FadeTransition( + opacity: _contentController, + child: _buildContent(step: step), + ), + ); } - Widget _buildContent({ - required String title, - required String description, - required int stepIndex, - required int totalSteps, - required bool isLast, - required VoidCallback onNext, - required VoidCallback onSkip, - }) { + _ContentAlign _computeContentAlign(Rect targetRect, Size screenSize) { + const contentHeight = 200.0; + + final targetBottom = targetRect.bottom; + if (targetBottom + contentHeight > screenSize.height - 40) { + return _ContentAlign.top; + } + if (targetRect.top - contentHeight < 40) { + return _ContentAlign.bottom; + } + return targetRect.top < screenSize.height / 2 + ? _ContentAlign.bottom + : _ContentAlign.top; + } + + Widget _buildContent({required _CoachStep step}) { + final totalSteps = widget.steps.length; + final isLast = _currentIndex == totalSteps - 1; + return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: ClipRRect( @@ -241,10 +439,10 @@ class CoachMarkManager { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildProgressDots(stepIndex, totalSteps), + _buildProgressDots(_currentIndex, totalSteps), const SizedBox(height: 16), Text( - title, + step.title, style: const TextStyle( color: Colors.white, fontSize: 18, @@ -253,7 +451,7 @@ class CoachMarkManager { ), const SizedBox(height: 8), Text( - description, + step.description, style: TextStyle( color: Colors.white.withAlpha(200), fontSize: 14, @@ -265,8 +463,9 @@ class CoachMarkManager { mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: onSkip, + onPressed: _dismiss, style: TextButton.styleFrom( + overlayColor: Colors.white.withAlpha(40), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10), shape: RoundedRectangleBorder( @@ -283,9 +482,11 @@ class CoachMarkManager { ), const SizedBox(width: 8), TextButton( - onPressed: onNext, + onPressed: _goToNext, style: TextButton.styleFrom( backgroundColor: Colors.white, + overlayColor: + ChewieTheme.primaryColor.withAlpha(40), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 10), shape: RoundedRectangleBorder( @@ -331,20 +532,53 @@ class CoachMarkManager { } } +class _HolePainter extends CustomPainter { + final Rect holeRect; + final double radius; + final Color shadowColor; + final double shadowOpacity; + + _HolePainter({ + required this.holeRect, + required this.radius, + required this.shadowColor, + required this.shadowOpacity, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = shadowColor.withAlpha((shadowOpacity * 255).round()) + ..style = PaintingStyle.fill; + + final fullPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); + final holePath = Path() + ..addRRect(RRect.fromRectAndRadius(holeRect, Radius.circular(radius))); + + final combined = Path.combine(PathOperation.difference, fullPath, holePath); + canvas.drawPath(combined, paint); + } + + @override + bool shouldRepaint(_HolePainter oldDelegate) { + return oldDelegate.holeRect != holeRect || + oldDelegate.radius != radius || + oldDelegate.shadowOpacity != shadowOpacity; + } +} + +enum _ContentAlign { top, bottom } + class _CoachStep { final GlobalKey key; - final String identify; - final ShapeLightFocus shape; - final double radius; final String title; final String description; + final double radius; _CoachStep({ required this.key, - required this.identify, - required this.shape, - required this.radius, required this.title, required this.description, + this.radius = 8, }); } diff --git a/lib/Widgets/CoachMark/desktop_coach_mark_manager.dart b/lib/Widgets/CoachMark/desktop_coach_mark_manager.dart new file mode 100644 index 00000000..b55749d1 --- /dev/null +++ b/lib/Widgets/CoachMark/desktop_coach_mark_manager.dart @@ -0,0 +1,708 @@ +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:cloudotp/Database/database_manager.dart'; +import 'package:cloudotp/Utils/hive_util.dart'; +import 'package:cloudotp/Utils/shortcuts_util.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../l10n/l10n.dart'; + +class DesktopCoachMarkManager { + final BuildContext context; + final GlobalKey searchBarKey; + final GlobalKey addTokenKey; + final GlobalKey categoryKey; + final GlobalKey? qrScanKey; + final GlobalKey importExportKey; + final GlobalKey importThirdPartyKey; + final GlobalKey? cloudBackupKey; + final GlobalKey? backupLogKey; + final GlobalKey? sortButtonKey; + final GlobalKey? layoutButtonKey; + final GlobalKey featureShowcaseKey; + final GlobalKey settingKey; + final GlobalKey logoKey; + final GlobalKey? firstTokenKey; + final VoidCallback? onDeleteSampleData; + + DesktopCoachMarkManager({ + required this.context, + required this.searchBarKey, + required this.addTokenKey, + required this.categoryKey, + this.qrScanKey, + required this.importExportKey, + required this.importThirdPartyKey, + this.cloudBackupKey, + this.backupLogKey, + this.sortButtonKey, + this.layoutButtonKey, + required this.featureShowcaseKey, + required this.settingKey, + required this.logoKey, + this.firstTokenKey, + this.onDeleteSampleData, + }); + + Future show({bool force = false}) async { + if (!ResponsiveUtil.isDesktop() && !ResponsiveUtil.isLandscapeTablet()) { + return; + } + if (!force && + ChewieHiveUtil.getBool(CloudOTPHiveUtil.haveShownDesktopCoachMarkKey, + defaultValue: false)) { + return; + } + + List<_CoachStep> steps = _buildSteps(); + if (steps.isEmpty) return; + + if (!context.mounted) return; + + final overlay = Overlay.of(context); + late OverlayEntry entry; + entry = OverlayEntry( + builder: (_) => _CoachMarkOverlay( + steps: steps, + onFinish: () { + entry.remove(); + _markShown(); + }, + ), + ); + overlay.insert(entry); + } + + void _markShown() { + ChewieHiveUtil.put(CloudOTPHiveUtil.haveShownDesktopCoachMarkKey, true); + _promptDeleteSampleData(); + } + + Future _promptDeleteSampleData() async { + if (!context.mounted) return; + final hasSample = await DatabaseManager.hasSampleData(); + if (!hasSample || !context.mounted) return; + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.coachMarkDeleteSampleTitle, + message: appLocalizations.coachMarkDeleteSampleMessage, + onTapConfirm: () async { + await DatabaseManager.deleteSampleData(); + onDeleteSampleData?.call(); + }, + onTapCancel: () { + DatabaseManager.clearSampleDataFlag(); + }, + ); + } + + CloudOTPShortcut? _findShortcut() { + if (ResponsiveUtil.isLandscapeTablet()) return null; + try { + return ShortcutsUtil.shortcuts.firstWhere((s) => s.intent is T); + } catch (_) { + return null; + } + } + + List<_CoachStep> _buildSteps() { + List<_CoachStep> steps = []; + + steps.add(_CoachStep( + key: searchBarKey, + title: appLocalizations.desktopCoachSearchTitle, + description: appLocalizations.desktopCoachSearchDescription, + shortcut: _findShortcut(), + )); + + steps.add(_CoachStep( + key: logoKey, + title: appLocalizations.desktopCoachLogoShortcutTitle, + description: appLocalizations.desktopCoachLogoShortcutDescription, + shortcut: _findShortcut(), + )); + + steps.add(_CoachStep( + key: addTokenKey, + title: appLocalizations.desktopCoachAddTokenTitle, + description: appLocalizations.desktopCoachAddTokenDescription, + shortcut: _findShortcut(), + )); + + steps.add(_CoachStep( + key: categoryKey, + title: appLocalizations.desktopCoachCategoryTitle, + description: appLocalizations.desktopCoachCategoryDescription, + shortcut: _findShortcut(), + )); + + if (qrScanKey != null) { + steps.add(_CoachStep( + key: qrScanKey!, + title: appLocalizations.desktopCoachQrScanTitle, + description: appLocalizations.desktopCoachQrScanDescription, + )); + } + + steps.add(_CoachStep( + key: importExportKey, + title: appLocalizations.desktopCoachImportTitle, + description: appLocalizations.desktopCoachImportDescription, + shortcut: _findShortcut(), + )); + + steps.add(_CoachStep( + key: importThirdPartyKey, + title: appLocalizations.desktopCoachImportThirdTitle, + description: appLocalizations.desktopCoachImportThirdDescription, + )); + + if (cloudBackupKey != null) { + steps.add(_CoachStep( + key: cloudBackupKey!, + title: appLocalizations.desktopCoachCloudBackupTitle, + description: appLocalizations.desktopCoachCloudBackupDescription, + )); + } + + if (backupLogKey != null) { + steps.add(_CoachStep( + key: backupLogKey!, + title: appLocalizations.desktopCoachBackupLogTitle, + description: appLocalizations.desktopCoachBackupLogDescription, + )); + } + + if (sortButtonKey != null) { + steps.add(_CoachStep( + key: sortButtonKey!, + title: appLocalizations.desktopCoachSortTitle, + description: appLocalizations.desktopCoachSortDescription, + )); + } + + if (layoutButtonKey != null) { + steps.add(_CoachStep( + key: layoutButtonKey!, + title: appLocalizations.desktopCoachLayoutTitle, + description: appLocalizations.desktopCoachLayoutDescription, + shortcut: _findShortcut(), + )); + } + + steps.add(_CoachStep( + key: featureShowcaseKey, + title: appLocalizations.desktopCoachFeatureShowcaseTitle, + description: appLocalizations.desktopCoachFeatureShowcaseDescription, + )); + + steps.add(_CoachStep( + key: settingKey, + title: appLocalizations.desktopCoachSettingTitle, + description: appLocalizations.desktopCoachSettingDescription, + shortcut: _findShortcut(), + )); + + if (firstTokenKey != null) { + steps.add(_CoachStep( + key: firstTokenKey!, + title: appLocalizations.desktopCoachRightClickTitle, + description: appLocalizations.desktopCoachRightClickDescription, + radius: 12, + )); + } + + return steps; + } +} + +class _CoachMarkOverlay extends StatefulWidget { + final List<_CoachStep> steps; + final VoidCallback onFinish; + + const _CoachMarkOverlay({ + required this.steps, + required this.onFinish, + }); + + @override + State<_CoachMarkOverlay> createState() => _CoachMarkOverlayState(); +} + +class _CoachMarkOverlayState extends State<_CoachMarkOverlay> + with TickerProviderStateMixin { + static const _shadowOpacity = 0.75; + static const _padding = 4.0; + static const _animDuration = Duration(milliseconds: 350); + + int _currentIndex = 0; + late AnimationController _holeController; + Animation? _holeAnimation; + late AnimationController _contentController; + late AnimationController _pulseController; + bool _isTransitioning = false; + + Rect _currentTargetRect = Rect.zero; + + @override + void initState() { + super.initState(); + _holeController = AnimationController(vsync: this, duration: _animDuration); + _contentController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + _pulseController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + + _holeController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + _currentTargetRect = _holeAnimation?.value ?? _currentTargetRect; + _isTransitioning = false; + _contentController.forward(); + _pulseController.repeat(reverse: true); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + _initFirstTarget(); + }); + } + + Rect _expandedRect(Rect target, Size screenSize) { + final expandFactor = screenSize.longestSide; + return Rect.fromCenter( + center: target.center, + width: target.width + expandFactor, + height: target.height + expandFactor, + ); + } + + void _initFirstTarget() { + final rect = _getTargetRect(widget.steps[0]); + if (rect == null) { + widget.onFinish(); + return; + } + final screenSize = MediaQuery.of(context).size; + final startRect = _expandedRect(rect, screenSize); + _currentTargetRect = rect; + _holeAnimation = RectTween(begin: startRect, end: rect) + .animate(CurvedAnimation(parent: _holeController, curve: Curves.easeOutCubic)); + _holeController.forward(from: 0).then((_) { + _contentController.forward(); + }); + setState(() {}); + } + + Rect? _getTargetRect(_CoachStep step) { + final renderBox = + step.key.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null || !renderBox.attached) return null; + final pos = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + return Rect.fromLTWH( + pos.dx - _padding, + pos.dy - _padding, + size.width + _padding * 2, + size.height + _padding * 2, + ); + } + + void _goToNext() { + if (_isTransitioning) return; + final nextIndex = _currentIndex + 1; + if (nextIndex >= widget.steps.length) { + _dismiss(); + return; + } + _transitionTo(nextIndex); + } + + void _dismiss() { + _contentController.reverse().then((_) { + widget.onFinish(); + }); + } + + void _transitionTo(int index) { + final nextRect = _getTargetRect(widget.steps[index]); + if (nextRect == null) { + if (index + 1 < widget.steps.length) { + _transitionTo(index + 1); + } else { + _dismiss(); + } + return; + } + + _isTransitioning = true; + _pulseController.stop(); + _contentController.reverse().then((_) { + setState(() { + _currentIndex = index; + }); + _holeAnimation = RectTween(begin: _currentTargetRect, end: nextRect) + .animate(CurvedAnimation(parent: _holeController, curve: Curves.easeInOut)); + _holeController.forward(from: 0); + }); + } + + @override + void dispose() { + _holeController.dispose(); + _contentController.dispose(); + _pulseController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_holeAnimation == null) return const SizedBox.shrink(); + final step = widget.steps[_currentIndex]; + return Material( + type: MaterialType.transparency, + child: Stack( + children: [ + Positioned.fill( + child: AnimatedBuilder( + animation: Listenable.merge([_holeController, _pulseController]), + builder: (context, _) { + Rect rect = _holeAnimation?.value ?? _currentTargetRect; + if (!_isTransitioning && _pulseController.isAnimating) { + final pulseAmount = 3.0 * _pulseController.value; + rect = rect.inflate(pulseAmount); + } + return CustomPaint( + painter: _HolePainter( + holeRect: rect, + radius: step.radius, + shadowColor: Colors.black, + shadowOpacity: _shadowOpacity, + ), + child: const SizedBox.expand(), + ); + }, + ), + ), + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _goToNext, + child: const SizedBox.expand(), + ), + ), + _buildContentPositioned(step), + ], + ), + ); + } + + Widget _buildContentPositioned(_CoachStep step) { + final screenSize = MediaQuery.of(context).size; + final targetRect = _getTargetRect(step) ?? _currentTargetRect; + final contentAlign = _computeContentAlign(targetRect, screenSize); + const maxWidth = 360.0; + const gap = 10.0; + + double? top, bottom, left, right; + + switch (contentAlign) { + case _ContentAlign.bottom: + top = targetRect.bottom + gap; + left = (targetRect.left).clamp(12.0, screenSize.width - maxWidth - 12); + break; + case _ContentAlign.top: + bottom = screenSize.height - targetRect.top + gap; + left = (targetRect.left).clamp(12.0, screenSize.width - maxWidth - 12); + break; + case _ContentAlign.right: + left = targetRect.right + gap; + top = (targetRect.top).clamp(12.0, screenSize.height - 240); + break; + case _ContentAlign.left: + right = screenSize.width - targetRect.left + gap; + top = (targetRect.top).clamp(12.0, screenSize.height - 240); + break; + } + + return Positioned( + top: top, + bottom: bottom, + left: left, + right: right, + child: FadeTransition( + opacity: _contentController, + child: _buildContent( + step: step, + maxWidth: maxWidth, + ), + ), + ); + } + + _ContentAlign _computeContentAlign(Rect targetRect, Size screenSize) { + const contentHeight = 220.0; + const contentWidth = 360.0; + const gap = 10.0; + + final spaceRight = screenSize.width - targetRect.right - gap; + final spaceLeft = targetRect.left - gap; + final spaceBottom = screenSize.height - targetRect.bottom - gap; + final spaceTop = targetRect.top - gap; + + if (targetRect.left < screenSize.width * 0.25) { + if (spaceRight >= contentWidth) return _ContentAlign.right; + if (spaceBottom >= contentHeight) return _ContentAlign.bottom; + if (spaceTop >= contentHeight) return _ContentAlign.top; + return _ContentAlign.right; + } + + if (targetRect.right > screenSize.width * 0.75) { + if (spaceLeft >= contentWidth) return _ContentAlign.left; + if (spaceBottom >= contentHeight) return _ContentAlign.bottom; + if (spaceTop >= contentHeight) return _ContentAlign.top; + return _ContentAlign.left; + } + + if (spaceBottom >= contentHeight) return _ContentAlign.bottom; + if (spaceTop >= contentHeight) return _ContentAlign.top; + if (spaceRight >= contentWidth) return _ContentAlign.right; + return _ContentAlign.bottom; + } + + Widget _buildContent({ + required _CoachStep step, + required double maxWidth, + }) { + final totalSteps = widget.steps.length; + final isLast = _currentIndex == totalSteps - 1; + + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: maxWidth), + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.black.withAlpha(100), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white.withAlpha(25)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildProgressDots(_currentIndex, totalSteps), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Text( + step.title, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + if (step.shortcut != null) ...[ + const SizedBox(width: 10), + _buildShortcutBadge(step.shortcut!), + ], + ], + ), + const SizedBox(height: 8), + Text( + step.description, + style: TextStyle( + color: Colors.white.withAlpha(200), + fontSize: 14, + height: 1.5, + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: _dismiss, + style: TextButton.styleFrom( + overlayColor: Colors.white.withAlpha(40), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + appLocalizations.coachMarkSkip, + style: TextStyle( + color: Colors.white.withAlpha(180), + fontSize: 14, + ), + ), + ), + const SizedBox(width: 8), + TextButton( + onPressed: _goToNext, + style: TextButton.styleFrom( + backgroundColor: Colors.white, + overlayColor: + ChewieTheme.primaryColor.withAlpha(40), + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text( + isLast + ? appLocalizations.coachMarkGotIt + : appLocalizations.coachMarkNext, + style: const TextStyle( + color: Colors.black87, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildProgressDots(int current, int total) { + return Row( + children: List.generate( + total, + (i) => Container( + width: i == current ? 16 : 6, + height: 6, + margin: const EdgeInsets.only(right: 4), + decoration: BoxDecoration( + color: i == current ? Colors.white : Colors.white.withAlpha(80), + borderRadius: BorderRadius.circular(3), + ), + ), + ), + ); + } + + Widget _buildShortcutBadge(CloudOTPShortcut shortcut) { + final modifiers = _getModifierKeys(shortcut); + final triggerLabel = shortcut.triggerLabel; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + ...modifiers.map((m) => Padding( + padding: const EdgeInsets.only(right: 4), + child: _buildKeyCap(m), + )), + _buildKeyCap(triggerLabel), + ], + ); + } + + Widget _buildKeyCap(String text) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: Colors.white.withAlpha(20), + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.white.withAlpha(80)), + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ); + } + + List _getModifierKeys(CloudOTPShortcut shortcut) { + List keys = []; + final isMac = !kIsWeb && Platform.isMacOS; + if (shortcut.isMetaPressed) { + keys.add(isMac ? '⌘' : 'Meta'); + } + if (shortcut.isControlPressed) { + keys.add(isMac ? '⌃' : 'Ctrl'); + } + if (shortcut.isShiftPressed) { + keys.add(isMac ? '⇧' : 'Shift'); + } + if (shortcut.isAltPressed) { + keys.add(isMac ? '⌥' : 'Alt'); + } + return keys; + } +} + +class _HolePainter extends CustomPainter { + final Rect holeRect; + final double radius; + final Color shadowColor; + final double shadowOpacity; + + _HolePainter({ + required this.holeRect, + required this.radius, + required this.shadowColor, + required this.shadowOpacity, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = shadowColor.withAlpha((shadowOpacity * 255).round()) + ..style = PaintingStyle.fill; + + final fullPath = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); + final holePath = Path() + ..addRRect(RRect.fromRectAndRadius(holeRect, Radius.circular(radius))); + + final combined = Path.combine(PathOperation.difference, fullPath, holePath); + canvas.drawPath(combined, paint); + } + + @override + bool shouldRepaint(_HolePainter oldDelegate) { + return oldDelegate.holeRect != holeRect || + oldDelegate.radius != radius || + oldDelegate.shadowOpacity != shadowOpacity; + } +} + +enum _ContentAlign { top, bottom, right, left } + +class _CoachStep { + final GlobalKey key; + final String title; + final String description; + final CloudOTPShortcut? shortcut; + final double radius; + + _CoachStep({ + required this.key, + required this.title, + required this.description, + this.shortcut, + this.radius = 8, + }); +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 39688aee..88d065e1 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -701,7 +701,9 @@ "listLayoutType": "List", "allTokens": "All", "noToken": "No tokens available", + "noTokenInCategory": "No tokens in this category", "noTokenContainingSearchKey": "No tokens found containing search keyword \"{key}\"", + "addExistingToken": "Add existing token", "copyTokenCode": "Copy code", "copyNextTokenCode": "Copy next code", "editToken": "Edit details", @@ -897,6 +899,8 @@ "showGuidedTourDescription": "Show the feature guide tour again", "coachMarkNext": "Next", "coachMarkGotIt": "Got it", + "coachMarkDeleteSampleTitle": "Delete Sample Data", + "coachMarkDeleteSampleMessage": "The guide is complete. Would you like to delete the sample tokens and categories?", "alreadyPinnedSelectedTokens": "Pinned {count} tokens", "@alreadyPinnedSelectedTokens": { "placeholders": { @@ -978,5 +982,51 @@ "featureMultiLang": "4 Languages", "featureThemeColors": "Theme Colors", "featureLayoutStyles": "Layout Styles", - "featureCustomThemeEditor": "Custom Theme Editor" + "featureCustomThemeEditor": "Custom Theme Editor", + "welcomeTitle": "Welcome to\nCloudOTP 4.0", + "welcomeSubtitle": "Your tokens, secured everywhere", + "welcomeNextButton": "Next", + "welcomeStartGuide": "Start Feature Guide", + "welcomeGetStarted": "Get Started", + "welcomeSkip": "Skip", + "welcomeStartExploring": "Start Exploring", + "sampleCategoryName": "Example", + "coachMarkSortTitle": "Sort Your Tokens", + "coachMarkSortDescription": "Tap the sort button to organize your tokens by name, creation date, copy frequency, and more.", + "coachMarkLayoutTitle": "Switch Layout", + "coachMarkLayoutDescription": "Tap the layout button to switch between Simple, Compact, List, and Spotlight views.", + "coachMarkScanTitle": "Add New Token", + "coachMarkScanDescription": "Tap the QR code button to scan a QR code and quickly add a new token.", + "coachMarkCloudBackupTitle": "Cloud Backup", + "coachMarkCloudBackupDescription": "Tap the cloud backup button to quickly back up your tokens to the cloud service.", + "coachMarkBackupLogTitle": "Backup Logs", + "coachMarkBackupLogDescription": "Tap the backup log button to view backup history and check the status of each backup.", + "desktopCoachSearchTitle": "Search Tokens", + "desktopCoachSearchDescription": "Use the search bar to quickly find tokens.", + "desktopCoachAddTokenTitle": "Add Token", + "desktopCoachAddTokenDescription": "Click to add a new token.", + "desktopCoachCategoryTitle": "Manage Categories", + "desktopCoachCategoryDescription": "Click to manage token categories for better organization.", + "desktopCoachQrScanTitle": "Scan QR Code", + "desktopCoachQrScanDescription": "Click to scan QR codes from screen or image files to add tokens.", + "desktopCoachImportTitle": "Import & Export", + "desktopCoachImportDescription": "Click to import or export your tokens.", + "desktopCoachImportThirdTitle": "Import from Third Party", + "desktopCoachImportThirdDescription": "Click to import tokens from other authenticator apps like Google Authenticator, Aegis, 2FAS, etc.", + "desktopCoachCloudBackupTitle": "Cloud Backup", + "desktopCoachCloudBackupDescription": "Click to configure and manage your cloud backup services.", + "desktopCoachBackupLogTitle": "Backup Logs", + "desktopCoachBackupLogDescription": "Click to view the history of your automatic and manual backup operations.", + "desktopCoachSortTitle": "Sort Tokens", + "desktopCoachSortDescription": "Click to change the sort order of your tokens.", + "desktopCoachLayoutTitle": "Switch Layout", + "desktopCoachLayoutDescription": "Click to switch between Simple, Compact, List, and Spotlight views.", + "desktopCoachFeatureShowcaseTitle": "Feature Showcase", + "desktopCoachFeatureShowcaseDescription": "Click to review all the features of CloudOTP 4.0 at any time.", + "desktopCoachSettingTitle": "Settings", + "desktopCoachSettingDescription": "Click to open the settings panel.", + "desktopCoachLogoShortcutTitle": "Keyboard Shortcuts", + "desktopCoachLogoShortcutDescription": "Click the logo to view all keyboard shortcuts.", + "desktopCoachRightClickTitle": "Right-Click Menu", + "desktopCoachRightClickDescription": "Right-click on any token to access actions like edit, pin, view QR code, delete, or enter multi-select mode." } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index d2e8ba7e..9f872151 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -701,7 +701,9 @@ "listLayoutType": "リスト", "allTokens": "すべて", "noToken": "トークンはありません", + "noTokenInCategory": "このカテゴリにトークンはありません", "noTokenContainingSearchKey": "検索語「{key}」を含むトークンはありません", + "addExistingToken": "既存のトークンを追加", "copyTokenCode": "コードをコピー", "copyNextTokenCode": "次のコードをコピー", "editToken": "詳細を編集", @@ -897,6 +899,8 @@ "showGuidedTourDescription": "機能ガイドツアーを再表示する", "coachMarkNext": "次へ", "coachMarkGotIt": "わかった", + "coachMarkDeleteSampleTitle": "サンプルデータを削除", + "coachMarkDeleteSampleMessage": "ガイドが完了しました。サンプルトークンとカテゴリを削除しますか?", "alreadyPinnedSelectedTokens": "{count} 件のトークンをピン留めしました", "@alreadyPinnedSelectedTokens": { "placeholders": { @@ -978,5 +982,51 @@ "featureMultiLang": "4言語対応", "featureThemeColors": "テーマカラー", "featureLayoutStyles": "レイアウト", - "featureCustomThemeEditor": "カスタムテーマエディタ" + "featureCustomThemeEditor": "カスタムテーマエディタ", + "welcomeTitle": "ようこそ\nCloudOTP 4.0", + "welcomeSubtitle": "あなたのトークンをどこでも安全に", + "welcomeNextButton": "次へ", + "welcomeStartGuide": "機能ガイドを開始", + "welcomeGetStarted": "始める", + "welcomeSkip": "スキップ", + "welcomeStartExploring": "探索を始める", + "sampleCategoryName": "サンプル", + "coachMarkSortTitle": "トークンの並べ替え", + "coachMarkSortDescription": "並べ替えボタンをタップして、名前、作成日、コピー回数などでトークンを整理できます。", + "coachMarkLayoutTitle": "レイアウトの切り替え", + "coachMarkLayoutDescription": "レイアウトボタンをタップして、シンプル、コンパクト、リスト、スポットライトの各表示に切り替えられます。", + "coachMarkScanTitle": "新しいトークンを追加", + "coachMarkScanDescription": "QRコードボタンをタップしてQRコードをスキャンし、新しいトークンを素早く追加できます。", + "coachMarkCloudBackupTitle": "クラウドバックアップ", + "coachMarkCloudBackupDescription": "クラウドバックアップボタンをタップして、トークンをクラウドサービスに素早くバックアップできます。", + "coachMarkBackupLogTitle": "バックアップログ", + "coachMarkBackupLogDescription": "バックアップログボタンをタップして、バックアップ履歴と各バックアップのステータスを確認できます。", + "desktopCoachSearchTitle": "トークンを検索", + "desktopCoachSearchDescription": "検索バーを使ってトークンをすばやく見つけられます。", + "desktopCoachAddTokenTitle": "トークンを追加", + "desktopCoachAddTokenDescription": "クリックして新しいトークンを追加します。", + "desktopCoachCategoryTitle": "カテゴリ管理", + "desktopCoachCategoryDescription": "クリックしてトークンのカテゴリを管理し、整理できます。", + "desktopCoachQrScanTitle": "QRコードスキャン", + "desktopCoachQrScanDescription": "クリックしてスクリーンショットや画像ファイルからQRコードをスキャンしてトークンを追加します。", + "desktopCoachImportTitle": "インポートとエクスポート", + "desktopCoachImportDescription": "クリックしてトークンデータをインポートまたはエクスポートします。", + "desktopCoachImportThirdTitle": "サードパーティからインポート", + "desktopCoachImportThirdDescription": "クリックして他の認証アプリからトークンをインポートします。Google Authenticator、Aegis、2FAS などに対応。", + "desktopCoachCloudBackupTitle": "クラウドバックアップ", + "desktopCoachCloudBackupDescription": "クリックしてクラウドバックアップサービスを設定・管理します。", + "desktopCoachBackupLogTitle": "バックアップログ", + "desktopCoachBackupLogDescription": "クリックして自動バックアップと手動バックアップの履歴を確認します。", + "desktopCoachSortTitle": "トークンを並べ替え", + "desktopCoachSortDescription": "クリックしてトークンの並べ替え方法を変更します。", + "desktopCoachLayoutTitle": "レイアウトを切替", + "desktopCoachLayoutDescription": "クリックしてシンプル、コンパクト、リスト、スポットライト表示を切り替えます。", + "desktopCoachFeatureShowcaseTitle": "機能紹介", + "desktopCoachFeatureShowcaseDescription": "クリックしていつでも CloudOTP 4.0 のすべての機能を確認できます。", + "desktopCoachSettingTitle": "設定", + "desktopCoachSettingDescription": "クリックして設定パネルを開きます。", + "desktopCoachLogoShortcutTitle": "キーボードショートカット", + "desktopCoachLogoShortcutDescription": "ロゴをクリックしてすべてのショートカットを表示できます。", + "desktopCoachRightClickTitle": "右クリックメニュー", + "desktopCoachRightClickDescription": "トークンを右クリックすると、編集、ピン留め、QRコード表示、削除、複数選択モードなどの操作にアクセスできます。" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 1e74a598..19a42959 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -701,7 +701,9 @@ "listLayoutType": "列表", "allTokens": "全部", "noToken": "暂无令牌", + "noTokenInCategory": "该分类下暂无令牌", "noTokenContainingSearchKey": "暂无包含搜索词\"{key}\"的令牌", + "addExistingToken": "添加已有令牌", "copyTokenCode": "复制代码", "copyNextTokenCode": "复制下一个代码", "editToken": "编辑详情", @@ -897,6 +899,8 @@ "showGuidedTourDescription": "重新显示功能引导教程", "coachMarkNext": "下一步", "coachMarkGotIt": "知道了", + "coachMarkDeleteSampleTitle": "删除示例数据", + "coachMarkDeleteSampleMessage": "引导已完成。是否删除示例令牌和分类?", "alreadyPinnedSelectedTokens": "已置顶 {count} 个令牌", "@alreadyPinnedSelectedTokens": { "placeholders": { @@ -978,5 +982,51 @@ "featureMultiLang": "4 种语言", "featureThemeColors": "主题色", "featureLayoutStyles": "布局样式", - "featureCustomThemeEditor": "自定义主题编辑器" + "featureCustomThemeEditor": "自定义主题编辑器", + "welcomeTitle": "欢迎使用\nCloudOTP 4.0", + "welcomeSubtitle": "你的令牌,随处安全", + "welcomeNextButton": "下一步", + "welcomeStartGuide": "开始功能引导", + "welcomeGetStarted": "开始使用", + "welcomeSkip": "跳过", + "welcomeStartExploring": "开始探索", + "sampleCategoryName": "示例", + "coachMarkSortTitle": "排序令牌", + "coachMarkSortDescription": "点击排序按钮,按名称、创建时间、复制次数等方式对令牌进行排序。", + "coachMarkLayoutTitle": "切换布局", + "coachMarkLayoutDescription": "点击布局按钮,在简洁、紧凑、列表和聚焦四种视图之间切换。", + "coachMarkScanTitle": "添加新令牌", + "coachMarkScanDescription": "点击二维码按钮扫描二维码,快速添加新令牌。", + "coachMarkCloudBackupTitle": "云备份", + "coachMarkCloudBackupDescription": "点击云备份按钮,将令牌快速备份到云服务。", + "coachMarkBackupLogTitle": "备份日志", + "coachMarkBackupLogDescription": "点击备份日志按钮,查看备份历史和每次备份的状态。", + "desktopCoachSearchTitle": "搜索令牌", + "desktopCoachSearchDescription": "使用搜索栏快速查找令牌。", + "desktopCoachAddTokenTitle": "添加令牌", + "desktopCoachAddTokenDescription": "点击添加新令牌。", + "desktopCoachCategoryTitle": "管理分类", + "desktopCoachCategoryDescription": "点击管理令牌分类,便于整理归类。", + "desktopCoachQrScanTitle": "扫描二维码", + "desktopCoachQrScanDescription": "点击从屏幕截图或图片文件中扫描二维码以添加令牌。", + "desktopCoachImportTitle": "导入与导出", + "desktopCoachImportDescription": "点击导入或导出你的令牌数据。", + "desktopCoachImportThirdTitle": "从第三方导入", + "desktopCoachImportThirdDescription": "点击从其他验证器应用导入令牌,支持 Google Authenticator、Aegis、2FAS 等。", + "desktopCoachCloudBackupTitle": "云备份", + "desktopCoachCloudBackupDescription": "点击配置和管理你的云备份服务。", + "desktopCoachBackupLogTitle": "备份日志", + "desktopCoachBackupLogDescription": "点击查看自动备份和手动备份的历史记录。", + "desktopCoachSortTitle": "排序令牌", + "desktopCoachSortDescription": "点击更改令牌的排序方式。", + "desktopCoachLayoutTitle": "切换布局", + "desktopCoachLayoutDescription": "点击在简洁、紧凑、列表和聚焦视图之间切换。", + "desktopCoachFeatureShowcaseTitle": "功能特色", + "desktopCoachFeatureShowcaseDescription": "点击随时查看 CloudOTP 4.0 的所有功能特色。", + "desktopCoachSettingTitle": "设置", + "desktopCoachSettingDescription": "点击打开设置面板。", + "desktopCoachLogoShortcutTitle": "快捷键帮助", + "desktopCoachLogoShortcutDescription": "点击 Logo 查看所有快捷键。", + "desktopCoachRightClickTitle": "右键菜单", + "desktopCoachRightClickDescription": "右键点击任何令牌可快速访问编辑、置顶、查看二维码、删除或进入多选模式等操作。" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index bf2d2c01..4c11028e 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -701,7 +701,9 @@ "listLayoutType": "列表", "allTokens": "全部", "noToken": "暫無令牌", + "noTokenInCategory": "該分類下暫無令牌", "noTokenContainingSearchKey": "暫無包含搜尋字\"{key}\"的令牌", + "addExistingToken": "新增已有令牌", "copyTokenCode": "複製程式碼", "copyNextTokenCode": "複製下一個程式碼", "editToken": "編輯詳情", @@ -897,6 +899,8 @@ "showGuidedTourDescription": "重新顯示功能引導教學", "coachMarkNext": "下一步", "coachMarkGotIt": "知道了", + "coachMarkDeleteSampleTitle": "刪除範例資料", + "coachMarkDeleteSampleMessage": "引導已完成。是否刪除範例令牌和分類?", "alreadyPinnedSelectedTokens": "已置頂 {count} 個令牌", "@alreadyPinnedSelectedTokens": { "placeholders": { @@ -978,5 +982,51 @@ "featureMultiLang": "4 種語言", "featureThemeColors": "主題色", "featureLayoutStyles": "佈局樣式", - "featureCustomThemeEditor": "自訂主題編輯器" + "featureCustomThemeEditor": "自訂主題編輯器", + "welcomeTitle": "歡迎使用\nCloudOTP 4.0", + "welcomeSubtitle": "你的令牌,隨處安全", + "welcomeNextButton": "下一步", + "welcomeStartGuide": "開始功能引導", + "welcomeGetStarted": "開始使用", + "welcomeSkip": "跳過", + "welcomeStartExploring": "開始探索", + "sampleCategoryName": "範例", + "coachMarkSortTitle": "排序令牌", + "coachMarkSortDescription": "點擊排序按鈕,按名稱、建立時間、複製次數等方式對令牌進行排序。", + "coachMarkLayoutTitle": "切換佈局", + "coachMarkLayoutDescription": "點擊佈局按鈕,在簡潔、緊湊、列表和聚焦四種檢視之間切換。", + "coachMarkScanTitle": "新增令牌", + "coachMarkScanDescription": "點擊 QR Code 按鈕掃描 QR Code,快速新增令牌。", + "coachMarkCloudBackupTitle": "雲端備份", + "coachMarkCloudBackupDescription": "點擊雲端備份按鈕,將令牌快速備份到雲端服務。", + "coachMarkBackupLogTitle": "備份日誌", + "coachMarkBackupLogDescription": "點擊備份日誌按鈕,查看備份歷史和每次備份的狀態。", + "desktopCoachSearchTitle": "搜尋令牌", + "desktopCoachSearchDescription": "使用搜尋列快速查找令牌。", + "desktopCoachAddTokenTitle": "新增令牌", + "desktopCoachAddTokenDescription": "點擊新增令牌。", + "desktopCoachCategoryTitle": "管理分類", + "desktopCoachCategoryDescription": "點擊管理令牌分類,便於整理歸類。", + "desktopCoachQrScanTitle": "掃描 QR Code", + "desktopCoachQrScanDescription": "點擊從螢幕截圖或圖片檔案中掃描 QR Code 以新增令牌。", + "desktopCoachImportTitle": "匯入與匯出", + "desktopCoachImportDescription": "點擊匯入或匯出你的令牌資料。", + "desktopCoachImportThirdTitle": "從第三方匯入", + "desktopCoachImportThirdDescription": "點擊從其他驗證器應用匯入令牌,支援 Google Authenticator、Aegis、2FAS 等。", + "desktopCoachCloudBackupTitle": "雲端備份", + "desktopCoachCloudBackupDescription": "點擊設定和管理你的雲端備份服務。", + "desktopCoachBackupLogTitle": "備份日誌", + "desktopCoachBackupLogDescription": "點擊查看自動備份和手動備份的歷史紀錄。", + "desktopCoachSortTitle": "排序令牌", + "desktopCoachSortDescription": "點擊更改令牌的排序方式。", + "desktopCoachLayoutTitle": "切換佈局", + "desktopCoachLayoutDescription": "點擊在簡潔、緊湊、列表和聚焦檢視之間切換。", + "desktopCoachFeatureShowcaseTitle": "功能特色", + "desktopCoachFeatureShowcaseDescription": "點擊隨時查看 CloudOTP 4.0 的所有功能特色。", + "desktopCoachSettingTitle": "設定", + "desktopCoachSettingDescription": "點擊開啟設定面板。", + "desktopCoachLogoShortcutTitle": "快捷鍵說明", + "desktopCoachLogoShortcutDescription": "點擊 Logo 查看所有快捷鍵。", + "desktopCoachRightClickTitle": "右鍵選單", + "desktopCoachRightClickDescription": "右鍵點擊任何令牌可快速存取編輯、置頂、查看 QR Code、刪除或進入多選模式等操作。" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f4bd7752..3796b9fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,6 +42,7 @@ import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; import 'Screens/main_screen.dart'; +import 'Screens/welcome_screen.dart'; import 'TokenUtils/token_image_util.dart'; import 'Utils/utils.dart'; import 'Widgets/Shortcuts/app_shortcuts.dart'; @@ -79,6 +80,10 @@ Widget getRootPage([bool isMain = false]) { jumpToMain: true, showWindowTitle: true, ); + } else if (!ChewieHiveUtil.getBool( + CloudOTPHiveUtil.haveShownWelcome4Key, + defaultValue: false)) { + home = const WelcomeScreen(); } else { home = AppShortcuts(child: MainScreen(key: mainScreenKey)); } @@ -125,7 +130,8 @@ Future initHive() async { try { await DatabaseManager.initDataBase( await CloudOTPHiveUtil.getDatabasePassword()); - } catch (e) { + } catch (e, t) { + ILogger.error("Failed to init database", e, t); await DatabaseManager.resetDatabase(); if (DatabaseManager.lib != null) { CloudOTPHiveUtil.setEncryptDatabaseStatus( diff --git a/third-party/chewie/lib/src/Screens/update_log_screen.dart b/third-party/chewie/lib/src/Screens/update_log_screen.dart index 0cbcabe7..488ef39a 100644 --- a/third-party/chewie/lib/src/Screens/update_log_screen.dart +++ b/third-party/chewie/lib/src/Screens/update_log_screen.dart @@ -106,7 +106,9 @@ class _UpdateLogScreenState extends BaseDynamicState }, child: ListView.builder( padding: widget.padding - .add(const EdgeInsets.symmetric(horizontal: 4, vertical: 10)), + .add(const EdgeInsets.symmetric(horizontal: 4, vertical: 10)) + .add(EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom)), itemBuilder: (context, index) => _buildItem( releaseItems[index], index, diff --git a/third-party/chewie/lib/src/Screens/webview_screen.dart b/third-party/chewie/lib/src/Screens/webview_screen.dart index 98a40b35..256285dc 100644 --- a/third-party/chewie/lib/src/Screens/webview_screen.dart +++ b/third-party/chewie/lib/src/Screens/webview_screen.dart @@ -118,7 +118,7 @@ class _WebviewScreenState extends BaseDynamicState }, ), ], - desktopActions: [ + landscapeActions: [ ToolButton( context: context, icon: LucideIcons.ellipsisVertical, diff --git a/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart b/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart index bde5c424..e13eb020 100644 --- a/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart +++ b/third-party/chewie/lib/src/Widgets/Basic/item_builder.dart @@ -45,7 +45,7 @@ class ItemBuilder { showBorder: showBorder, onTapBack: onTapBack, actions: actions, - desktopActions: desktopActions, + landscapeActions: desktopActions, ) : null, body: overrideBody ?? diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart index e1e08c24..953a1d26 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart @@ -46,6 +46,7 @@ class BottomSheetBuilder { animation: animation, child: BottomSheetWrapperWidget( preferMinWidth: preferMinWidth, + useVerticalMargin: true, child: builder(context), ), ); diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart index cfb36d9b..21e7510a 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart @@ -30,6 +30,8 @@ class BottomSheetWrapperWidget extends StatelessWidget { : 0; double preferVerticalMargin = height > preferHeight ? (height - preferHeight) / 2 : 0; + double bottomPadding = + isLandScape ? 0 : MediaQuery.of(context).viewPadding.bottom; return BackdropFilter( filter: ResponsiveUtil.isDesktop() ? ImageFilter.blur(sigmaX: 2, sigmaY: 2) @@ -45,7 +47,17 @@ class BottomSheetWrapperWidget extends StatelessWidget { : 100, bottom: useVerticalMargin ? preferVerticalMargin : 0, ), - child: child, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: child), + if (bottomPadding > 0) + Container( + height: bottomPadding, + color: ChewieTheme.scaffoldBackgroundColor, + ), + ], + ), ), ); } diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart index 0bffc299..318ebfef 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart @@ -66,7 +66,11 @@ class _CustomConfirmDialogWidgetState constraints: ResponsiveUtil.isWideDevice() ? const BoxConstraints(maxWidth: 400) : null, - margin: widget.margin ?? const EdgeInsets.all(16), + margin: widget.margin ?? + EdgeInsets.all(16).copyWith( + bottom: widget.align == Alignment.bottomCenter + ? 16 + MediaQuery.of(context).padding.bottom + : 16), padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 24, vertical: 30), decoration: BoxDecoration( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart index efd8604d..f428cf8a 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart @@ -65,7 +65,11 @@ class _CustomInfoDialogWidgetState constraints: ResponsiveUtil.isWideDevice() ? const BoxConstraints(maxWidth: 400) : null, - margin: widget.margin ?? const EdgeInsets.all(16), + margin: widget.margin ?? + EdgeInsets.all(16).copyWith( + bottom: widget.align == Alignment.bottomCenter + ? 16 + MediaQuery.of(context).padding.bottom + : 16), padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 24, vertical: 30), decoration: BoxDecoration( diff --git a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart index 9f449bc4..b45775df 100644 --- a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart +++ b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart @@ -15,7 +15,7 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { final double titleLeftMargin; final double rightSpacing; final List actions; - final List desktopActions; + final List landscapeActions; final double height; final double? borderWidth; @@ -32,7 +32,7 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { this.centerTitle = false, this.titleLeftMargin = 5, this.rightSpacing = 8, - this.desktopActions = const [], + this.landscapeActions = const [], this.actions = const [], this.height = 48, this.borderWidth, @@ -81,7 +81,7 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { titleContent, const Spacer(), ...[ - ...desktopActions, + ...landscapeActions, const SizedBox(width: 44), ], ], diff --git a/third-party/chewie/lib/src/Widgets/Notification/notification_manager.dart b/third-party/chewie/lib/src/Widgets/Notification/notification_manager.dart index c63373ec..b0fb5422 100644 --- a/third-party/chewie/lib/src/Widgets/Notification/notification_manager.dart +++ b/third-party/chewie/lib/src/Widgets/Notification/notification_manager.dart @@ -45,7 +45,7 @@ class NotificationManager { ); _queue.add(entry); - Future.delayed(Duration.zero, () { + WidgetsBinding.instance.addPostFrameCallback((_) { _overlayState?.addNotification(entry); }); } From 043214389dbcac8d644ced734854b43c9379f187 Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 23 May 2026 21:24:25 +0800 Subject: [PATCH 29/36] feat: Enhance export functionality and UI improvements across various components --- lib/Database/database_manager.dart | 6 +- lib/Screens/home_screen.dart | 152 ++++++++++++++++-- lib/TokenUtils/export_token_util.dart | 37 +++++ .../color_picker_bottom_sheet.dart | 10 +- .../chewie/lib/src/Utils/System/uri_util.dart | 4 +- .../bottom_sheet_wrapper_widget.dart | 7 +- .../Dialog/widgets/dialog_wrapper_widget.dart | 11 +- 7 files changed, 199 insertions(+), 28 deletions(-) diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index 8c92fb70..627f8c6f 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -381,8 +381,10 @@ class DatabaseManager { } } if (oldVersion < 7) { - await db.execute( - "alter table otp_token add column tags TEXT NOT NULL DEFAULT ''"); + if (!(await isColumnExist("otp_token", "tags", overrideDb: db))) { + await db.execute( + "alter table otp_token add column tags TEXT NOT NULL DEFAULT ''"); + } } if (oldVersion < 8) { await db.execute(''' diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index cdbc1f01..0cc0c72b 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -14,6 +14,9 @@ */ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -31,6 +34,7 @@ import 'package:cloudotp/Utils/search_query_parser.dart'; import 'package:cloudotp/Widgets/BottomSheet/add_bottom_sheet.dart'; import 'package:cloudotp/Widgets/BottomSheet/more_bottom_sheet.dart'; import 'package:cloudotp/Widgets/cloudotp/cloudotp_item_builder.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -38,6 +42,9 @@ import 'package:lucide_icons/lucide_icons.dart'; import 'package:move_to_background/move_to_background.dart'; import 'package:provider/provider.dart'; +import 'package:file_picker/file_picker.dart'; + +import '../Database/config_dao.dart'; import '../Database/token_dao.dart'; import '../Models/token_category.dart'; import '../TokenUtils/export_token_util.dart'; @@ -271,12 +278,38 @@ class HomeScreenState extends BasePanelScreenState FlutterContextMenu( entries: [ FlutterContextMenuItem( - appLocalizations.exportToFile, + appLocalizations.exportUriFile, iconData: LucideIcons.fileOutput, onPressed: () { - ExportTokenUtil.exportSelectedTokensUri(selected); + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.exportUriClearWarningTitle, + message: appLocalizations.exportUriClearWarningTip, + onTapConfirm: () async { + if (ResponsiveUtil.isDesktop()) { + String? result = await FileUtil.saveFile( + dialogTitle: appLocalizations.exportUriFileTitle, + fileName: ExportTokenUtil.getExportFileName("txt"), + type: FileType.custom, + allowedExtensions: ['txt'], + lockParentWindow: true, + ); + if (result != null) { + _exportSelectedTokensUriToFile(selected, result); + } + } else { + _exportSelectedTokensUriToMobile(selected); + } + }, + onTapCancel: () {}, + ); }, ), + FlutterContextMenuItem( + appLocalizations.exportEncryptFile, + iconData: LucideIcons.fileLock2, + onPressed: () => _exportSelectedEncrypted(selected), + ), FlutterContextMenuItem( appLocalizations.exportQrcode, iconData: LucideIcons.qrCode, @@ -330,6 +363,100 @@ class HomeScreenState extends BasePanelScreenState ); } + Future _exportSelectedEncrypted(List selected) async { + if (await CloudOTPHiveUtil.canImportOrExportUseBackupPassword()) { + _doExportSelectedEncrypted(selected, await ConfigDao.getBackupPassword()); + } else { + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => InputBottomSheet( + title: appLocalizations.setExportPasswordTitle, + message: appLocalizations.setExportPasswordTip, + hint: appLocalizations.setExportPasswordHint, + tailingConfig: InputItemLeadingTailingConfig( + type: InputItemLeadingTailingType.password, + ), + inputFormatters: [ + RegexInputFormatter.onlyNumberAndLetterAndSymbol, + ], + validator: (value) { + if (value.isEmpty) { + return appLocalizations.encryptDatabasePasswordCannotBeEmpty; + } + return null; + }, + onValidConfirm: (password) async { + _doExportSelectedEncrypted(selected, password); + return null; + }, + ), + ); + } + } + + Future _doExportSelectedEncrypted( + List selected, String password) async { + if (ResponsiveUtil.isDesktop()) { + String? result = await FileUtil.saveFile( + dialogTitle: appLocalizations.exportEncryptFileTitle, + fileName: ExportTokenUtil.getExportFileName("bin"), + type: FileType.custom, + allowedExtensions: ['bin'], + lockParentWindow: true, + ); + if (result != null) { + final data = await ExportTokenUtil.getUint8ListForTokens( + tokens: selected, password: password); + if (data != null) { + ExportTokenUtil.exportEncryptFile(result, password, + encryptedData: data); + } + } + } else { + final data = await ExportTokenUtil.getUint8ListForTokens( + tokens: selected, password: password); + if (data != null) { + ExportTokenUtil.exportEncryptToMobileDirectory( + encryptedData: data, password: password); + } + } + } + + Future _exportSelectedTokensUriToFile( + List tokens, String filePath) async { + CustomLoadingDialog.showLoading(title: appLocalizations.exporting); + await compute((_) async { + List uris = + tokens.map((e) => OtpTokenParser.toUri(e).toString()).toList(); + String content = uris.join("\n"); + File(filePath).writeAsStringSync(content); + }, null); + CustomLoadingDialog.dismissLoading(); + IToast.showTop(appLocalizations.exportSuccess); + } + + Future _exportSelectedTokensUriToMobile(List tokens) async { + CustomLoadingDialog.showLoading(title: appLocalizations.exporting); + Uint8List res = await compute((_) async { + List uris = + tokens.map((e) => OtpTokenParser.toUri(e).toString()).toList(); + String content = uris.join("\n"); + return utf8.encode(content); + }, null); + String? filePath = await FileUtil.saveFile( + dialogTitle: appLocalizations.exportUriFileTitle, + fileName: ExportTokenUtil.getExportFileName("txt"), + type: FileType.custom, + allowedExtensions: ['txt'], + bytes: res, + ); + CustomLoadingDialog.dismissLoading(); + if (filePath != null) { + IToast.showTop(appLocalizations.exportSuccess); + } + } + Future _processMultiPin() async { List selected = selectedTokens; if (selected.isEmpty) return; @@ -914,10 +1041,8 @@ class HomeScreenState extends BasePanelScreenState sortButtonKey: provider.showSortButton ? _sortButtonKey : null, layoutButtonKey: provider.showLayoutButton ? _layoutButtonKey : null, fabKey: _fabKey, - cloudBackupKey: - provider.showCloudBackupButton ? _cloudBackupKey : null, - backupLogKey: - provider.showBackupLogButton ? _backupLogKey : null, + cloudBackupKey: provider.showCloudBackupButton ? _cloudBackupKey : null, + backupLogKey: provider.showBackupLogButton ? _backupLogKey : null, layoutType: layoutType, tokenCount: tokens.length, categoryCount: categories.length, @@ -1128,8 +1253,8 @@ class HomeScreenState extends BasePanelScreenState tooltipPosition: TooltipPosition.bottom, iconBuilder: (buttonContext) => LoadingIcon( status: autoBackupLoadingStatus, - normalIcon: Icon(Icons.history_rounded, - color: buttonContext.iconColor), + normalIcon: + Icon(Icons.history_rounded, color: buttonContext.iconColor), ), onPressed: () { BackupLogScreen.show(context); @@ -1363,7 +1488,7 @@ class HomeScreenState extends BasePanelScreenState hideProgress: provider.hideProgressBar, ), builder: (context, settings, child) { - double bottomPadding = MediaQuery.of(context).viewPadding.bottom; + double bottomPadding = MediaQuery.of(context).padding.bottom; return ReorderableGridView.builder( // controller: _scrollController, gridItemsNotifier: gridItemsNotifier, @@ -1374,7 +1499,7 @@ class HomeScreenState extends BasePanelScreenState right: 10, top: 10, bottom: _multiSelectMode - ? 80 + bottomPadding + ? 88 + bottomPadding : settings.hideBottombar || categories.isEmpty ? 10 + bottomPadding : 85 + bottomPadding), @@ -1442,9 +1567,7 @@ class HomeScreenState extends BasePanelScreenState ); }, ); - Widget body = tokens.isEmpty - ? _buildEmptyPlaceholder() - : gridView; + Widget body = tokens.isEmpty ? _buildEmptyPlaceholder() : gridView; return SlidableAutoCloseBehavior(child: body); } @@ -1522,8 +1645,7 @@ class HomeScreenState extends BasePanelScreenState BottomSheetBuilder.showBottomSheet( context, responsive: true, - (context) => - SelectTokenBottomSheet(category: category), + (context) => SelectTokenBottomSheet(category: category), ); }, icon: const Icon(LucideIcons.listPlus, size: 18), diff --git a/lib/TokenUtils/export_token_util.dart b/lib/TokenUtils/export_token_util.dart index f8c32ba7..74711877 100644 --- a/lib/TokenUtils/export_token_util.dart +++ b/lib/TokenUtils/export_token_util.dart @@ -167,6 +167,43 @@ class ExportTokenUtil { } } + static Future getUint8ListForTokens({ + required List tokens, + String? password, + }) async { + try { + String tmpPassword = password ?? await ConfigDao.getBackupPassword(); + List categories = await CategoryDao.listCategories(); + for (TokenCategory category in categories) { + category.bindings = await BindingDao.getTokenUids(category.uid); + } + Set tokenUids = tokens.map((t) => t.uid).toSet(); + categories = categories.where((c) { + return c.bindings.any((uid) => tokenUids.contains(uid)); + }).toList(); + for (var c in categories) { + c.bindings = + c.bindings.where((uid) => tokenUids.contains(uid)).toList(); + } + return await compute((_) async { + Backup backup = Backup( + tokens: tokens, + categories: categories, + ); + BackupEncryptionV1 backupEncryption = BackupEncryptionV1(); + Uint8List encryptedData = + await backupEncryption.encrypt(backup, tmpPassword); + return encryptedData; + }, null); + } catch (e, t) { + ILogger.error("Failed to export selected tokens to Uint8List", e, t); + if (e is BackupBaseException) { + IToast.showTop(e.intlMessage); + } + return null; + } + } + static exportEncryptFile( String filePath, String password, { diff --git a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart index 1d1010dd..f09add81 100644 --- a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart @@ -84,10 +84,11 @@ class _ColorPickerBottomSheetState extends State { ), child: Material( type: MaterialType.transparency, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ Container( padding: const EdgeInsets.symmetric(vertical: 12), alignment: Alignment.center, @@ -166,6 +167,7 @@ class _ColorPickerBottomSheetState extends State { ], ), ), + ), ), ], ), diff --git a/third-party/chewie/lib/src/Utils/System/uri_util.dart b/third-party/chewie/lib/src/Utils/System/uri_util.dart index 3899101b..9d5c39c0 100644 --- a/third-party/chewie/lib/src/Utils/System/uri_util.dart +++ b/third-party/chewie/lib/src/Utils/System/uri_util.dart @@ -254,14 +254,14 @@ class UriUtil { static Future openExternal(String url) async { await launchUrl( Uri.parse(url), - mode: LaunchMode.externalNonBrowserApplication, + mode: LaunchMode.externalApplication, ); } static Future openExternalUri(WebUri uri) async { await launchUrl( uri, - mode: LaunchMode.externalNonBrowserApplication, + mode: LaunchMode.externalApplication, ); } } diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart index 21e7510a..f0911455 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_wrapper_widget.dart @@ -30,8 +30,11 @@ class BottomSheetWrapperWidget extends StatelessWidget { : 0; double preferVerticalMargin = height > preferHeight ? (height - preferHeight) / 2 : 0; - double bottomPadding = - isLandScape ? 0 : MediaQuery.of(context).viewPadding.bottom; + double bottomPadding = isLandScape + ? 0 + : MediaQuery.of(context).viewInsets.bottom > 0 + ? 0 + : MediaQuery.of(context).viewPadding.bottom; return BackdropFilter( filter: ResponsiveUtil.isDesktop() ? ImageFilter.blur(sigmaX: 2, sigmaY: 2) diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/dialog_wrapper_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/dialog_wrapper_widget.dart index e3e181c5..c857599f 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/dialog_wrapper_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/dialog_wrapper_widget.dart @@ -85,6 +85,9 @@ class DialogWrapperWidgetState extends State height > preferHeight ? (height - preferHeight) / 2 : 0; preferHorizontalMargin = max(preferHorizontalMargin, 20); preferVerticalMargin = max(preferVerticalMargin, 80); + final keyboardHeight = MediaQuery.of(context).viewInsets.bottom; + final adjustedBottomMargin = + max(preferVerticalMargin - keyboardHeight, 20.0); return BackdropFilter( filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), child: GestureDetector( @@ -110,9 +113,11 @@ class DialogWrapperWidgetState extends State color: widget.barrierDismissible ? null : Colors.transparent, padding: widget.fullScreen ? EdgeInsets.zero - : EdgeInsets.symmetric( - horizontal: preferHorizontalMargin, - vertical: preferVerticalMargin, + : EdgeInsets.only( + left: preferHorizontalMargin, + right: preferHorizontalMargin, + top: preferVerticalMargin, + bottom: adjustedBottomMargin, ), child: GestureDetector( onTap: () {}, From efcb624035ba4ae87979df6781d9fec15f158e08 Mon Sep 17 00:00:00 2001 From: dd
Date: Sat, 23 May 2026 23:01:18 +0800 Subject: [PATCH 30/36] feat: Update UI components for improved user experience and functionality --- lib/Screens/Backup/webdav_service_screen.dart | 2 +- lib/Screens/Lock/database_decrypt_screen.dart | 3 +- lib/Screens/Lock/pin_verify_screen.dart | 3 +- lib/Screens/Setting/select_theme_screen.dart | 37 ++++++ lib/Screens/Setting/theme_editor_screen.dart | 55 +++----- lib/Screens/welcome_screen.dart | 1 + .../color_picker_bottom_sheet.dart | 124 +++++++++--------- lib/main.dart | 24 +++- .../src/Widgets/States/base_window_state.dart | 4 +- .../lib/src/Widgets/Tile/entry_item.dart | 14 +- 10 files changed, 155 insertions(+), 112 deletions(-) diff --git a/lib/Screens/Backup/webdav_service_screen.dart b/lib/Screens/Backup/webdav_service_screen.dart index ad9aa220..da70413c 100644 --- a/lib/Screens/Backup/webdav_service_screen.dart +++ b/lib/Screens/Backup/webdav_service_screen.dart @@ -155,7 +155,7 @@ class _WebDavServiceScreenState extends BaseDynamicState } _buildBody() { - return Column( + return ListView( children: [ if (_configInitialized) _enableInfo(), if (_configInitialized) _accountInfo(), diff --git a/lib/Screens/Lock/database_decrypt_screen.dart b/lib/Screens/Lock/database_decrypt_screen.dart index 14ef3aaf..3dcc817d 100644 --- a/lib/Screens/Lock/database_decrypt_screen.dart +++ b/lib/Screens/Lock/database_decrypt_screen.dart @@ -13,6 +13,7 @@ * If not, see . */ +import 'dart:io'; import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -45,7 +46,7 @@ class DatabaseDecryptScreenState extends BaseWindowState @override Future onWindowClose() async { - await windowManager.destroy(); + exit(0); } late InputValidateAsyncController validateAsyncController; diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 2a8c87ce..2fe1337a 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -14,6 +14,7 @@ */ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -57,7 +58,7 @@ class PinVerifyScreenState extends BaseWindowState @override Future onWindowClose() async { - await windowManager.destroy(); + exit(0); } static const int _lockoutDurationSeconds = 30; diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index 3562f596..7345a35f 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -322,6 +322,8 @@ class _SelectThemeScreenState extends BaseDynamicState ...allThemes.map((theme) => EntryItem( paddingHorizontal: 20, title: theme.i18nName, + showLeading: true, + leadingWidget: _buildThemePreviewDots(theme), showTrailing: false, onTap: () { Navigator.of(ctx).pop(); @@ -331,6 +333,8 @@ class _SelectThemeScreenState extends BaseDynamicState ...customThemes.map((theme) => EntryItem( paddingHorizontal: 20, title: theme.name, + showLeading: true, + leadingWidget: _buildThemePreviewDots(theme), showTrailing: false, onTap: () { Navigator.of(ctx).pop(); @@ -365,6 +369,39 @@ class _SelectThemeScreenState extends BaseDynamicState ); } + Widget _buildThemePreviewDots(ChewieThemeColorData theme) { + final colors = [ + theme.primaryColor, + theme.scaffoldBackgroundColor, + theme.textColor, + ]; + return SizedBox( + width: 28, + height: 28, + child: Stack( + clipBehavior: Clip.none, + children: List.generate(colors.length, (i) { + return Positioned( + left: i * 8.0, + top: (28 - 18) / 2, + child: Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: colors[i], + shape: BoxShape.circle, + border: Border.all( + color: ChewieTheme.dividerColor, + width: 1, + ), + ), + ), + ); + }), + ), + ); + } + void _navigateToEditor(ChewieThemeColorData baseTheme, bool isDark, {int? editIndex}) { RouteUtil.pushDialogRoute( diff --git a/lib/Screens/Setting/theme_editor_screen.dart b/lib/Screens/Setting/theme_editor_screen.dart index 73d399ee..f767836e 100644 --- a/lib/Screens/Setting/theme_editor_screen.dart +++ b/lib/Screens/Setting/theme_editor_screen.dart @@ -258,24 +258,26 @@ class _ThemeEditorScreenState extends BaseDynamicState return CaptionItem( title: title, initiallyExpanded: initiallyExpanded, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 8, - right: 8, - top: 3, - bottom: 3, - ), - child: Column(children: children), - ), - ], + children: children, ); } Widget _buildColorRow(String field, String label) { final color = _getColor(field); - return InkWell( - borderRadius: BorderRadius.circular(8), + return EntryItem( + title: label, + tip: '#${color.toHex().substring(3).toUpperCase()}', + showLeading: true, + leadingWidget: Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: ChewieTheme.dividerColor, width: 1), + ), + ), + showTrailing: false, onTap: () { ColorPickerBottomSheet.show( context, @@ -284,33 +286,6 @@ class _ThemeEditorScreenState extends BaseDynamicState onColorChanged: (newColor) => _updateColor(field, newColor), ); }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: ChewieTheme.dividerColor, width: 1), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Text(label, style: ChewieTheme.bodyMedium), - ), - Text( - '#${color.toHex().substring(3).toUpperCase()}', - style: ChewieTheme.bodySmall, - ), - ], - ), - ), ); } diff --git a/lib/Screens/welcome_screen.dart b/lib/Screens/welcome_screen.dart index b2561ddd..ecbd7d7d 100644 --- a/lib/Screens/welcome_screen.dart +++ b/lib/Screens/welcome_screen.dart @@ -166,6 +166,7 @@ class _WelcomeScreenState extends State _buildFeatureContent(), if (!_transitionDone) _buildHeroTransition(), _buildSkipButton(), + if (ResponsiveUtil.isDesktop()) const WindowMoveHandle(), ], ), ), diff --git a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart index f09add81..c18e6b6e 100644 --- a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart @@ -69,32 +69,29 @@ class _ColorPickerBottomSheetState extends State { return AnimatedPadding( padding: MediaQuery.of(context).viewInsets, duration: const Duration(milliseconds: 100), - child: Wrap( - runAlignment: WrapAlignment.center, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), - decoration: BoxDecoration( - borderRadius: BorderRadius.vertical( - top: radius, - bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), - color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, - boxShadow: ChewieTheme.defaultBoxShadow, - ), - child: Material( - type: MaterialType.transparency, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 12), - alignment: Alignment.center, - child: Text(widget.title, style: ChewieTheme.titleLarge), - ), - ColorPicker( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: radius, + bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), + color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.border, + boxShadow: ChewieTheme.defaultBoxShadow, + ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 12), + alignment: Alignment.center, + child: Text(widget.title, style: ChewieTheme.titleLarge), + ), + Flexible( + child: SingleChildScrollView( + child: ColorPicker( color: _currentColor, onColorChanged: (Color color) { setState(() => _currentColor = color); @@ -127,49 +124,48 @@ class _ColorPickerBottomSheetState extends State { wheelWidth: 20, wheelDiameter: 220, ), - Container( - padding: - const EdgeInsets.symmetric(vertical: 16, horizontal: 0), - alignment: Alignment.center, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: SizedBox( - height: 45, - child: RoundIconTextButton( - text: appLocalizations.cancel, - onPressed: () => Navigator.of(context).pop(), - fontSizeDelta: 2, - ), - ), + ), + ), + Container( + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 0), + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), + fontSizeDelta: 2, ), - const SizedBox(width: 10), - Expanded( - child: SizedBox( - height: 45, - child: RoundIconTextButton( - background: ChewieTheme.primaryColor, - color: Colors.white, - text: appLocalizations.confirm, - onPressed: () { - widget.onColorChanged(_currentColor); - Navigator.of(context).pop(); - }, - fontSizeDelta: 2, - ), - ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: appLocalizations.confirm, + onPressed: () { + widget.onColorChanged(_currentColor); + Navigator.of(context).pop(); + }, + fontSizeDelta: 2, ), - ], + ), ), - ), - ], + ], + ), ), - ), - ), + ], ), - ], + ), ), ); } diff --git a/lib/main.dart b/lib/main.dart index 3796b9fc..8a388967 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -145,10 +145,12 @@ Future initHive() async { Future initAndroid() async { await initDisplayMode(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, systemNavigationBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, systemNavigationBarIconBrightness: Brightness.dark); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); } @@ -197,17 +199,20 @@ Future initDesktop() async { Future initWindow() async { await windowManager.ensureInitialized(); Offset position = ChewieHiveUtil.getWindowPosition(); + bool shouldCenter = position == Offset.zero; WindowOptions windowOptions = WindowOptions( size: ChewieHiveUtil.getWindowSize(), minimumSize: ChewieProvider.minimumWindowSize, - center: position == Offset.zero, + center: shouldCenter, backgroundColor: Colors.transparent, skipTaskbar: false, titleBarStyle: TitleBarStyle.hidden, ); await windowManager.waitUntilReadyToShow(windowOptions, () async { await windowManager.setPreventClose(true); - await windowManager.setPosition(position); + if (!shouldCenter) { + await windowManager.setPosition(position); + } await windowManager.show(); await windowManager.focus(); }); @@ -329,6 +334,21 @@ class MyApp extends StatelessWidget { home: home, builder: (context, widget) { chewieProvider.initRootContext(context); + if (ResponsiveUtil.isAndroid()) { + final brightness = Theme.of(context).brightness; + final overlayStyle = SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, + systemNavigationBarIconBrightness: brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + ); + SystemChrome.setSystemUIOverlayStyle(overlayStyle); + } return Overlay( initialEntries: [ if (widget != null) ...[ diff --git a/third-party/chewie/lib/src/Widgets/States/base_window_state.dart b/third-party/chewie/lib/src/Widgets/States/base_window_state.dart index 4831f3b1..4b540024 100644 --- a/third-party/chewie/lib/src/Widgets/States/base_window_state.dart +++ b/third-party/chewie/lib/src/Widgets/States/base_window_state.dart @@ -13,6 +13,8 @@ * If not, see . */ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:awesome_chewie/awesome_chewie.dart'; @@ -70,7 +72,7 @@ abstract class BaseWindowState ChewieHiveUtil.getBool(ChewieHiveUtil.enableCloseToTrayKey)) { await windowManager.hide(); } else { - await windowManager.destroy(); + exit(0); } } } diff --git a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart index 07460e12..6f97a38a 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/entry_item.dart @@ -13,6 +13,7 @@ class EntryItem extends SearchableStatefulWidget { final Color? descriptionColor; final CrossAxisAlignment crossAxisAlignment; final IconData leading; + final Widget? leadingWidget; final String tip; final Function()? onTap; final double? paddingVertical; @@ -37,6 +38,7 @@ class EntryItem extends SearchableStatefulWidget { this.descriptionColor, this.crossAxisAlignment = CrossAxisAlignment.start, this.leading = LucideIcons.house, + this.leadingWidget, this.tip = "", this.onTap, this.paddingVertical, @@ -80,6 +82,7 @@ class EntryItem extends SearchableStatefulWidget { descriptionColor: descriptionColor, crossAxisAlignment: crossAxisAlignment, leading: leading, + leadingWidget: leadingWidget, tip: tip, onTap: onTap, paddingVertical: paddingVertical, @@ -134,9 +137,10 @@ class EntryItemState extends SearchableState { } List _buildRowChildren() { + final hasLeading = widget.showLeading || widget.leadingWidget != null; return [ - if (widget.showLeading) _buildLeadingIcon(), - SizedBox(width: widget.showLeading ? 10 : 5), + if (hasLeading) _buildLeadingIcon(), + SizedBox(width: hasLeading ? 10 : 5), Expanded(child: _buildTextContent()), if (widget.tipWidget != null) const SizedBox(width: 10), if (widget.tipWidget != null) _buildCustomTipWidget(), @@ -146,6 +150,12 @@ class EntryItemState extends SearchableState { } Widget _buildLeadingIcon() { + if (widget.leadingWidget != null) { + return Container( + margin: const EdgeInsets.only(left: 4), + child: widget.leadingWidget!, + ); + } return Container( width: 28, height: 28, From 9caeb533368173289d0cc10f401166f6a4f07535 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 23 May 2026 23:37:28 +0800 Subject: [PATCH 31/36] feat: Update border styling to use responsive borders across various components --- lib/Screens/Setting/backup_log_screen.dart | 2 +- lib/Screens/Setting/select_theme_screen.dart | 2 +- lib/Screens/Setting/theme_editor_screen.dart | 5 +++-- lib/Screens/home_screen.dart | 1 + lib/Screens/layout_select_screen.dart | 2 +- lib/Screens/sort_select_screen.dart | 2 +- .../Backups/aliyundrive_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/box_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/dropbox_backups_bottom_sheet.dart | 2 +- .../Backups/googledrive_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/huawei_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/local_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/onedrive_backups_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart | 2 +- .../BottomSheet/Backups/webdav_backups_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/add_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/input_password_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/more_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/select_category_bottom_sheet.dart | 2 +- .../BottomSheet/select_category_for_tokens_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/select_icon_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/select_token_bottom_sheet.dart | 2 +- lib/Widgets/BottomSheet/token_option_bottom_sheet.dart | 2 +- lib/Widgets/cloudotp/qrcodes_dialog_widget.dart | 1 + third-party/chewie/lib/src/Resources/theme.dart | 4 +++- .../src/Widgets/BottomSheet/context_menu_bottom_sheet.dart | 2 +- .../lib/src/Widgets/BottomSheet/input_bottom_sheet.dart | 2 +- .../lib/src/Widgets/BottomSheet/star_bottom_sheet.dart | 2 +- .../Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart | 4 ++-- .../Widgets/Dialog/widgets/custom_info_dialog_widget.dart | 4 ++-- .../src/Widgets/Dialog/widgets/loading_dialog_widget.dart | 1 + .../src/Widgets/Dialog/widgets/progress_dialog_widget.dart | 1 + 33 files changed, 39 insertions(+), 32 deletions(-) diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index e80f1d6f..3792c2b2 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -173,7 +173,7 @@ class BackupLogScreenState extends BaseDynamicState { bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, ), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index 7345a35f..06d34e26 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -693,7 +693,7 @@ class _MenuListSheet extends StatelessWidget { bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Screens/Setting/theme_editor_screen.dart b/lib/Screens/Setting/theme_editor_screen.dart index f767836e..4eea6e2f 100644 --- a/lib/Screens/Setting/theme_editor_screen.dart +++ b/lib/Screens/Setting/theme_editor_screen.dart @@ -43,6 +43,7 @@ class _ThemeEditorScreenState extends BaseDynamicState late ChewieThemeColorData _theme; late TextEditingController _nameController; final FocusNode _nameFocusNode = FocusNode(); + bool get _isEditing => widget.editIndex != null; @override @@ -317,7 +318,7 @@ class _ThemeEditorScreenState extends BaseDynamicState ], children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 10), child: InputItem( controller: _nameController, focusNode: _nameFocusNode, @@ -327,7 +328,7 @@ class _ThemeEditorScreenState extends BaseDynamicState ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 10), child: _ThemePreview(theme: _theme), ), _buildColorGroup( diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 0cc0c72b..3742e77c 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -1664,6 +1664,7 @@ class HomeScreenState extends BasePanelScreenState ], ], ), + const SizedBox(height: 100), ], ), ), diff --git a/lib/Screens/layout_select_screen.dart b/lib/Screens/layout_select_screen.dart index 24787f9c..225409e1 100644 --- a/lib/Screens/layout_select_screen.dart +++ b/lib/Screens/layout_select_screen.dart @@ -92,7 +92,7 @@ class _LayoutSelectScreenState extends BaseDynamicState { bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, ), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: content, diff --git a/lib/Screens/sort_select_screen.dart b/lib/Screens/sort_select_screen.dart index b74caff5..67ef5ddc 100644 --- a/lib/Screens/sort_select_screen.dart +++ b/lib/Screens/sort_select_screen.dart @@ -150,7 +150,7 @@ class _SortSelectScreenState extends BaseDynamicState { bottom: ResponsiveUtil.isWideDevice() ? _radius : Radius.zero, ), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart index a952b9a3..4b388146 100644 --- a/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/aliyundrive_backups_bottom_sheet.dart @@ -62,7 +62,7 @@ class AliyunDriveBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart index 47970bd7..8290b6c9 100644 --- a/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/box_backups_bottom_sheet.dart @@ -61,7 +61,7 @@ class BoxBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart index ee7782ba..e1930a30 100644 --- a/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/dropbox_backups_bottom_sheet.dart @@ -63,7 +63,7 @@ class DropboxBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart index fa207b97..1f294abb 100644 --- a/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/googledrive_backups_bottom_sheet.dart @@ -63,7 +63,7 @@ class GoogleDriveBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart index d2e3ecad..5b7c6edd 100644 --- a/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/huawei_backups_bottom_sheet.dart @@ -62,7 +62,7 @@ class HuaweiCloudBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart index d0ef10f5..fba7d7e3 100644 --- a/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/local_backups_bottom_sheet.dart @@ -68,7 +68,7 @@ class LocalBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart index 286b4146..82b23c6f 100644 --- a/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/onedrive_backups_bottom_sheet.dart @@ -62,7 +62,7 @@ class OneDriveBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart index a51ba621..3c87d73b 100644 --- a/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/s3_backups_bottom_sheet.dart @@ -63,7 +63,7 @@ class S3CloudBackupsBottomSheetState bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero, ), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart index a59735e1..e1248bb1 100644 --- a/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/Backups/webdav_backups_bottom_sheet.dart @@ -62,7 +62,7 @@ class WebDavBackupsBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/add_bottom_sheet.dart b/lib/Widgets/BottomSheet/add_bottom_sheet.dart index 7dad1b81..95395ebf 100644 --- a/lib/Widgets/BottomSheet/add_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/add_bottom_sheet.dart @@ -135,7 +135,7 @@ class AddBottomSheetState extends BaseDynamicState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: SingleChildScrollView( diff --git a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart index c18e6b6e..978334f7 100644 --- a/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/color_picker_bottom_sheet.dart @@ -76,7 +76,7 @@ class _ColorPickerBottomSheetState extends State { top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Material( diff --git a/lib/Widgets/BottomSheet/input_password_bottom_sheet.dart b/lib/Widgets/BottomSheet/input_password_bottom_sheet.dart index ac538a1d..5088f2a2 100644 --- a/lib/Widgets/BottomSheet/input_password_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/input_password_bottom_sheet.dart @@ -71,7 +71,7 @@ class InputPasswordBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Form( diff --git a/lib/Widgets/BottomSheet/more_bottom_sheet.dart b/lib/Widgets/BottomSheet/more_bottom_sheet.dart index 93ff45ea..82b4b6b5 100644 --- a/lib/Widgets/BottomSheet/more_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/more_bottom_sheet.dart @@ -58,7 +58,7 @@ class _MoreBottomSheetState extends BaseDynamicState { : Radius.zero, ), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart index 3607a563..f984078c 100644 --- a/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart @@ -91,7 +91,7 @@ class SelectCategoryBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart index 68a9a67b..9c49e4ab 100644 --- a/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart @@ -71,7 +71,7 @@ class _SelectCategoryForTokensBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/select_icon_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_icon_bottom_sheet.dart index 96a85af0..f0b4176e 100644 --- a/lib/Widgets/BottomSheet/select_icon_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_icon_bottom_sheet.dart @@ -83,7 +83,7 @@ class SelectIconBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart index 162bc172..4395b957 100644 --- a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart @@ -79,7 +79,7 @@ class SelectTokenBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart index 984495ce..b1443851 100644 --- a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart @@ -127,7 +127,7 @@ class TokenOptionBottomSheetState top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: ListView( diff --git a/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart b/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart index 52da2b40..6160dc49 100644 --- a/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart +++ b/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart @@ -76,6 +76,7 @@ class QrcodesDialogWidgetState extends BaseDynamicState { : EdgeInsets.zero, decoration: ChewieTheme.defaultDecoration.copyWith( color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.responsiveBorder, ), child: ListView( padding: const EdgeInsets.symmetric(vertical: 24), diff --git a/third-party/chewie/lib/src/Resources/theme.dart b/third-party/chewie/lib/src/Resources/theme.dart index 76bdaf68..f545a9ae 100644 --- a/third-party/chewie/lib/src/Resources/theme.dart +++ b/third-party/chewie/lib/src/Resources/theme.dart @@ -138,7 +138,7 @@ class ChewieTheme { static BoxDecoration get defaultDecoration { return BoxDecoration( color: ChewieTheme.canvasColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, borderRadius: ChewieDimens.defaultBorderRadius, ); @@ -174,6 +174,8 @@ class ChewieTheme { static Border get border => borderWithWidth(0.8); + static Border? get responsiveBorder => ResponsiveUtil.isLandscapeLayout() ? ChewieTheme.border : null; + static Border get divider => dividerWithWidth(1); static Border get topBorder => Border(top: borderSide); diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart index 2cac8e0a..c3f8c0e3 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/context_menu_bottom_sheet.dart @@ -49,7 +49,7 @@ class ContextMenuBottomSheetState extends State { top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero, ), - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/input_bottom_sheet.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/input_bottom_sheet.dart index c53e6168..37d33e4c 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/input_bottom_sheet.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/input_bottom_sheet.dart @@ -101,7 +101,7 @@ class InputBottomSheetState extends State { top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), color: ChewieTheme.scaffoldBackgroundColor, - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/star_bottom_sheet.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/star_bottom_sheet.dart index 8cd00551..17548621 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/star_bottom_sheet.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/star_bottom_sheet.dart @@ -33,7 +33,7 @@ class StarBottomSheetState extends State { borderRadius: BorderRadius.vertical( top: radius, bottom: ResponsiveUtil.isWideDevice() ? radius : Radius.zero), - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart index 318ebfef..837c448f 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_confirm_dialog_widget.dart @@ -67,7 +67,7 @@ class _CustomConfirmDialogWidgetState ? const BoxConstraints(maxWidth: 400) : null, margin: widget.margin ?? - EdgeInsets.all(16).copyWith( + const EdgeInsets.all(16).copyWith( bottom: widget.align == Alignment.bottomCenter ? 16 + MediaQuery.of(context).padding.bottom : 16), @@ -78,7 +78,7 @@ class _CustomConfirmDialogWidgetState widget.backgroundColor ?? ChewieTheme.scaffoldBackgroundColor, borderRadius: BorderRadius.circular( widget.radiusDimen ?? ChewieDimens.dimen16), - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart index f428cf8a..469470d9 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/custom_info_dialog_widget.dart @@ -66,7 +66,7 @@ class _CustomInfoDialogWidgetState ? const BoxConstraints(maxWidth: 400) : null, margin: widget.margin ?? - EdgeInsets.all(16).copyWith( + const EdgeInsets.all(16).copyWith( bottom: widget.align == Alignment.bottomCenter ? 16 + MediaQuery.of(context).padding.bottom : 16), @@ -81,7 +81,7 @@ class _CustomInfoDialogWidgetState top: Radius.circular(widget.roundTop ? ChewieDimens.dimen16 : 0), ), - border: ChewieTheme.border, + border: ChewieTheme.responsiveBorder, boxShadow: ChewieTheme.defaultBoxShadow, ), child: Column( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart index 06033a68..fe9c4768 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/loading_dialog_widget.dart @@ -31,6 +31,7 @@ class LoadingDialogWidgetState extends State { child: Container( decoration: ChewieTheme.defaultDecoration.copyWith( color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.responsiveBorder, ), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30), child: ValueListenableBuilder( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart b/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart index 8ddf02cd..5467154c 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/widgets/progress_dialog_widget.dart @@ -144,6 +144,7 @@ class ProgressDialog { child: Container( decoration: ChewieTheme.defaultDecoration.copyWith( color: ChewieTheme.scaffoldBackgroundColor, + border: ChewieTheme.responsiveBorder, ), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30), From 89844d90a874acee78ba46a32f3ff483c9232ac9 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 23 May 2026 23:57:18 +0800 Subject: [PATCH 32/36] feat: Add ink parameter to CheckboxItem and update related screens --- .../Backup/aliyundrive_service_screen.dart | 1 + lib/Screens/Backup/box_service_screen.dart | 1 + lib/Screens/Backup/dropbox_service_screen.dart | 1 + .../Backup/googledrive_service_screen.dart | 1 + lib/Screens/Backup/huawei_service_screen.dart | 1 + lib/Screens/Backup/onedrive_service_screen.dart | 1 + lib/Screens/Backup/s3_service_screen.dart | 1 + lib/Screens/Backup/webdav_service_screen.dart | 1 + lib/Screens/layout_select_screen.dart | 2 +- lib/Screens/sort_select_screen.dart | 1 - lib/Screens/welcome_screen.dart | 2 +- .../lib/src/Widgets/Tile/checkbox_item.dart | 16 ++++++++++------ 12 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/Screens/Backup/aliyundrive_service_screen.dart b/lib/Screens/Backup/aliyundrive_service_screen.dart index 5c2bb1e9..74efc757 100644 --- a/lib/Screens/Backup/aliyundrive_service_screen.dart +++ b/lib/Screens/Backup/aliyundrive_service_screen.dart @@ -194,6 +194,7 @@ class _AliyunDriveServiceScreenState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeAliyunDrive, description: appLocalizations.cloudOAuthSafeTip( CloudService.serverEndpoint, appLocalizations.cloudTypeAliyunDrive), diff --git a/lib/Screens/Backup/box_service_screen.dart b/lib/Screens/Backup/box_service_screen.dart index 187f84fa..8867d10a 100644 --- a/lib/Screens/Backup/box_service_screen.dart +++ b/lib/Screens/Backup/box_service_screen.dart @@ -202,6 +202,7 @@ class _BoxServiceScreenState extends BaseDynamicState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeBox, description: appLocalizations.cloudOAuthSafeTip( CloudService.serverEndpoint, appLocalizations.cloudTypeBox), diff --git a/lib/Screens/Backup/dropbox_service_screen.dart b/lib/Screens/Backup/dropbox_service_screen.dart index 0ecb0285..9092199b 100644 --- a/lib/Screens/Backup/dropbox_service_screen.dart +++ b/lib/Screens/Backup/dropbox_service_screen.dart @@ -191,6 +191,7 @@ class _DropboxServiceScreenState extends BaseDynamicState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeDropbox, value: _dropboxCloudServiceConfig?.enabled ?? false, onTap: () { diff --git a/lib/Screens/Backup/googledrive_service_screen.dart b/lib/Screens/Backup/googledrive_service_screen.dart index c5e3555d..5167c8d6 100644 --- a/lib/Screens/Backup/googledrive_service_screen.dart +++ b/lib/Screens/Backup/googledrive_service_screen.dart @@ -187,6 +187,7 @@ class _GoogleDriveServiceScreenState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeGoogleDrive, description: appLocalizations.cloudOAuthSafeTip( CloudService.serverEndpoint, appLocalizations.cloudTypeGoogleDrive), diff --git a/lib/Screens/Backup/huawei_service_screen.dart b/lib/Screens/Backup/huawei_service_screen.dart index a962f7ec..7d3c039e 100644 --- a/lib/Screens/Backup/huawei_service_screen.dart +++ b/lib/Screens/Backup/huawei_service_screen.dart @@ -203,6 +203,7 @@ class _HuaweiCloudServiceScreenState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeHuaweiCloud, description: appLocalizations.cloudOAuthSafeTip( CloudService.serverEndpoint, appLocalizations.cloudTypeHuaweiCloud), diff --git a/lib/Screens/Backup/onedrive_service_screen.dart b/lib/Screens/Backup/onedrive_service_screen.dart index b5e60fea..cbcbe098 100644 --- a/lib/Screens/Backup/onedrive_service_screen.dart +++ b/lib/Screens/Backup/onedrive_service_screen.dart @@ -199,6 +199,7 @@ class _OneDriveServiceScreenState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeOneDrive, description: appLocalizations.cloudTypeOneDriveTip, value: _oneDriveCloudServiceConfig?.enabled ?? false, diff --git a/lib/Screens/Backup/s3_service_screen.dart b/lib/Screens/Backup/s3_service_screen.dart index bb8e6982..1846b337 100644 --- a/lib/Screens/Backup/s3_service_screen.dart +++ b/lib/Screens/Backup/s3_service_screen.dart @@ -178,6 +178,7 @@ class _S3CloudServiceScreenState extends BaseDynamicState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeS3Cloud, value: _s3CloudServiceConfig?.enabled ?? false, onTap: () { diff --git a/lib/Screens/Backup/webdav_service_screen.dart b/lib/Screens/Backup/webdav_service_screen.dart index da70413c..170654d8 100644 --- a/lib/Screens/Backup/webdav_service_screen.dart +++ b/lib/Screens/Backup/webdav_service_screen.dart @@ -170,6 +170,7 @@ class _WebDavServiceScreenState extends BaseDynamicState return Container( padding: const EdgeInsets.symmetric(horizontal: 6), child: CheckboxItem( + ink: false, title: appLocalizations.enable + appLocalizations.cloudTypeWebDav, value: _webDavCloudServiceConfig?.enabled ?? false, onTap: () { diff --git a/lib/Screens/layout_select_screen.dart b/lib/Screens/layout_select_screen.dart index 225409e1..71e58ae4 100644 --- a/lib/Screens/layout_select_screen.dart +++ b/lib/Screens/layout_select_screen.dart @@ -182,7 +182,7 @@ class _LayoutSelectScreenState extends BaseDynamicState { borderRadius: BorderRadius.circular(12), border: Border.all( color: selected ? _accent : ChewieTheme.borderColor, - width: selected ? 1.5 : 1, + width: selected ? 1.5 : 0, ), ), padding: const EdgeInsets.all(10), diff --git a/lib/Screens/sort_select_screen.dart b/lib/Screens/sort_select_screen.dart index 67ef5ddc..d863a2d0 100644 --- a/lib/Screens/sort_select_screen.dart +++ b/lib/Screens/sort_select_screen.dart @@ -232,7 +232,6 @@ class _SortSelectScreenState extends BaseDynamicState { decoration: BoxDecoration( color: ChewieTheme.canvasColor, borderRadius: BorderRadius.circular(12), - border: Border.all(color: ChewieTheme.borderColor), ), child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/Screens/welcome_screen.dart b/lib/Screens/welcome_screen.dart index ecbd7d7d..b5717465 100644 --- a/lib/Screens/welcome_screen.dart +++ b/lib/Screens/welcome_screen.dart @@ -444,7 +444,7 @@ class _WelcomeScreenState extends State Widget _buildSkipButton() { return Positioned( top: 8, - right: ResponsiveUtil.isMacOS() ? 8 : 16, + right: 8, child: TextButton( onPressed: _skip, style: TextButton.styleFrom( diff --git a/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart b/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart index 797f8731..8ab5778d 100644 --- a/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart +++ b/third-party/chewie/lib/src/Widgets/Tile/checkbox_item.dart @@ -16,9 +16,11 @@ class CheckboxItem extends SearchableStatefulWidget { final double trailingLeftMargin; final double padding; final bool disabled; + final bool ink; const CheckboxItem({ super.key, + this.ink = true, this.radius = 8, this.roundTop = false, this.roundBottom = false, @@ -57,6 +59,7 @@ class CheckboxItem extends SearchableStatefulWidget { trailingLeftMargin: trailingLeftMargin, padding: padding, disabled: disabled, + ink: ink, ); } @@ -68,10 +71,11 @@ class CheckboxItemState extends SearchableState { double get _effectivePadding => widget.description.isNotEmpty ? widget.padding : widget.padding - 3; - BorderRadius get _borderRadius => BorderRadius.vertical( + BorderRadius get _borderRadius => + BorderRadius.vertical( top: widget.roundTop ? Radius.circular(widget.radius) : Radius.zero, bottom: - widget.roundBottom ? Radius.circular(widget.radius) : Radius.zero, + widget.roundBottom ? Radius.circular(widget.radius) : Radius.zero, ); @override @@ -80,7 +84,7 @@ class CheckboxItemState extends SearchableState { if (!shouldShow) return const SizedBox.shrink(); return InkAnimation( borderRadius: _borderRadius, - ink: true, + ink: widget.ink, color: Colors.transparent, onTap: widget.disabled ? null : () { HapticFeedback.lightImpact(); @@ -175,9 +179,9 @@ class CheckboxItemState extends SearchableState { onChanged: widget.disabled ? null : (_) { - HapticFeedback.lightImpact(); - widget.onTap?.call(); - }, + HapticFeedback.lightImpact(); + widget.onTap?.call(); + }, ), ), ); From 09933134a659e6e4c444f59d179f6a9fee44832c Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 24 May 2026 12:33:06 +0800 Subject: [PATCH 33/36] feat: Enhance Slidable actions with auto-trigger functionality and improve UI components --- lib/Screens/Backup/cloud_service_screen.dart | 15 +- lib/Screens/Token/token_layout.dart | 219 ++++++++++++------ lib/Screens/home_screen.dart | 87 +++---- .../token_option_bottom_sheet.dart | 18 +- .../lib/src/Utils/System/route_util.dart | 7 +- .../FlutterSlidable/src/action_pane.dart | 118 +++++++++- .../Module/FlutterSlidable/src/actions.dart | 48 +++- .../Module/FlutterSlidable/src/slidable.dart | 3 + .../Scaffold/custom_cupertino_route.dart | 123 +++++++++- 9 files changed, 491 insertions(+), 147 deletions(-) diff --git a/lib/Screens/Backup/cloud_service_screen.dart b/lib/Screens/Backup/cloud_service_screen.dart index dc62b7c3..f73e4ae1 100644 --- a/lib/Screens/Backup/cloud_service_screen.dart +++ b/lib/Screens/Backup/cloud_service_screen.dart @@ -150,11 +150,18 @@ class _CloudServiceScreenState extends BaseDynamicState RouteUtil.pushDialogRoute( context, const SettingNavigationScreen(initPageIndex: 3)); } else { - Navigator.pop(context); RouteUtil.pushCupertinoRoute( - context, - const BackupSettingScreen( - jumpToAutoBackupPassword: true)); + context, + const BackupSettingScreen( + jumpToAutoBackupPassword: true), + onThen: (_) { + ConfigDao.getConfig().then((config) { + setState(() { + _autoBackupPassword = config.backupPassword; + }); + }); + }, + ); } }, ), diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 658a370a..5e8efc9d 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -391,17 +391,23 @@ class TokenLayoutState extends BaseDynamicState startActionPane: ActionPane( extentRatio: startExtentRatio, motion: const ScrollMotion(), + onAutoTrigger: () => _processPin(), children: [ SlidableAction( onPressed: (context) => _processPin(), backgroundColor: widget.token.pinned ? ChewieTheme.primaryColor : ChewieTheme.cardColor, + autoTriggerBackgroundColor: ChewieTheme.primaryColor, + autoTriggerIconAndTextColor: Colors.white, borderRadius: const BorderRadius.all(Radius.circular(12)), foregroundColor: ChewieTheme.primaryColor, icon: widget.token.pinned ? Icons.push_pin_rounded : Icons.push_pin_outlined, + autoTriggerIcon: widget.token.pinned + ? Icons.push_pin_outlined + : Icons.push_pin_rounded, label: widget.token.pinned ? appLocalizations.unPinTokenShort : appLocalizations.pinTokenShort, @@ -416,62 +422,146 @@ class TokenLayoutState extends BaseDynamicState endActionPane: ActionPane( extentRatio: endExtentRatio, motion: const ScrollMotion(), - children: [ - const SizedBox(width: 6), - SlidableAction( - onPressed: (context) => _processViewQrCode(), - backgroundColor: ChewieTheme.cardColor, - borderRadius: const BorderRadius.all(Radius.circular(12)), - foregroundColor: ChewieTheme.primaryColor, - icon: LucideIcons.qrCode, - label: appLocalizations.viewTokenQrCodeShort, - spacing: 8, - simple: simple, - padding: const EdgeInsets.symmetric(horizontal: 4), - ), - const SizedBox(width: 6), - SlidableAction( - onPressed: (context) => _processEdit(), - backgroundColor: ChewieTheme.cardColor, - borderRadius: const BorderRadius.all(Radius.circular(12)), - foregroundColor: ChewieTheme.primaryColor, - icon: LucideIcons.pencilLine, - padding: const EdgeInsets.symmetric(horizontal: 4), - label: appLocalizations.editTokenShort, - simple: simple, - spacing: 8, - ), - const SizedBox(width: 6), - SlidableAction( - onPressed: (context) => showContextMenu(), - backgroundColor: ChewieTheme.cardColor, - borderRadius: const BorderRadius.all(Radius.circular(12)), - foregroundColor: ChewieTheme.primaryColor, - icon: LucideIcons.ellipsisVertical, - padding: const EdgeInsets.symmetric(horizontal: 4), - label: appLocalizations.moreOptionShort, - simple: simple, - spacing: 8, - ), - const SizedBox(width: 6), - SlidableAction( - onPressed: (context) => _processDelete(), - backgroundColor: Colors.red, - borderRadius: const BorderRadius.all(Radius.circular(12)), - foregroundColor: ChewieTheme.primaryColor, - icon: LucideIcons.trash2, - simple: simple, - label: appLocalizations.deleteTokenShort, - padding: const EdgeInsets.symmetric(horizontal: 4), - spacing: 8, - iconAndTextColor: Colors.white, - ), - ], + children: simple + ? [ + const SizedBox(width: 6), + SlidableAction( + onPressed: (context) => _processViewQrCode(), + backgroundColor: ChewieTheme.cardColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + foregroundColor: ChewieTheme.primaryColor, + icon: LucideIcons.qrCode, + simple: true, + padding: const EdgeInsets.symmetric(horizontal: 4), + ), + const SizedBox(width: 6), + SlidableAction( + onPressed: (context) => _processEdit(), + backgroundColor: ChewieTheme.cardColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + foregroundColor: ChewieTheme.primaryColor, + icon: LucideIcons.pencilLine, + simple: true, + padding: const EdgeInsets.symmetric(horizontal: 4), + ), + const SizedBox(width: 6), + SlidableAction( + onPressed: (context) => showContextMenu(), + backgroundColor: ChewieTheme.cardColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + foregroundColor: ChewieTheme.primaryColor, + icon: LucideIcons.ellipsisVertical, + simple: true, + padding: const EdgeInsets.symmetric(horizontal: 4), + ), + // const SizedBox(width: 6), + // SlidableAction( + // onPressed: (context) => widget.onEnterMultiSelect?.call(), + // backgroundColor: ChewieTheme.cardColor, + // borderRadius: const BorderRadius.all(Radius.circular(12)), + // foregroundColor: ChewieTheme.primaryColor, + // icon: LucideIcons.listChecks, + // simple: true, + // padding: const EdgeInsets.symmetric(horizontal: 4), + // ), + const SizedBox(width: 6), + SlidableAction( + onPressed: (context) => _processDelete(), + backgroundColor: Colors.red, + borderRadius: const BorderRadius.all(Radius.circular(12)), + foregroundColor: ChewieTheme.primaryColor, + icon: LucideIcons.trash2, + simple: true, + padding: const EdgeInsets.symmetric(horizontal: 4), + iconAndTextColor: Colors.white, + ), + ] + : [ + const SizedBox(width: 6), + Expanded( + child: Column( + children: [ + Expanded( + child: _buildGridActionButton( + onPressed: _processViewQrCode, + icon: LucideIcons.qrCode, + ), + ), + const SizedBox(height: 6), + Expanded( + child: _buildGridActionButton( + onPressed: () => widget.onEnterMultiSelect?.call(), + icon: LucideIcons.listChecks, + ), + ), + ], + ), + ), + const SizedBox(width: 6), + Expanded( + child: Column( + children: [ + Expanded( + child: _buildGridActionButton( + onPressed: _processEdit, + icon: LucideIcons.pencilLine, + ), + ), + const SizedBox(height: 6), + Expanded( + child: _buildGridActionButton( + onPressed: showContextMenu, + icon: LucideIcons.ellipsisVertical, + ), + ), + ], + ), + ), + const SizedBox(width: 6), + Expanded( + child: _buildGridActionButton( + onPressed: _processDelete, + icon: LucideIcons.trash2, + backgroundColor: Colors.red, + iconAndTextColor: Colors.white, + ), + ), + const SizedBox(width: 6), + ], ), child: child, ); } + Widget _buildGridActionButton({ + required VoidCallback onPressed, + required IconData icon, + Color? backgroundColor, + Color? iconAndTextColor, + }) { + final bg = backgroundColor ?? ChewieTheme.cardColor; + final fgColor = iconAndTextColor ?? Theme.of(context).iconTheme.color; + return SizedBox.expand( + child: OutlinedButton( + onPressed: () { + onPressed(); + Slidable.of(context)?.close(); + }, + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 4), + backgroundColor: bg, + foregroundColor: fgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + side: BorderSide.none, + ), + child: + Icon(icon, color: fgColor, size: Theme.of(context).iconTheme.size), + ), + ); + } + _buildBody({ required IssuerAndAccountShowOption issuerAndAccountShowOption, }) { @@ -501,7 +591,7 @@ class TokenLayoutState extends BaseDynamicState case LayoutType.Spotlight: body = _buildSlidable( startExtentRatio: 0.21, - endExtentRatio: 0.8, + endExtentRatio: 0.50, child: _buildSpotlightLayout( issuerAndAccountShowOption: issuerAndAccountShowOption, ), @@ -555,19 +645,14 @@ class TokenLayoutState extends BaseDynamicState } showContextMenu() { - if (ResponsiveUtil.isLandscapeLayout()) { - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => TokenOptionBottomSheet(token: widget.token), - ); - } else { - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => TokenOptionBottomSheet(token: widget.token), - ); - } + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => TokenOptionBottomSheet( + token: widget.token, + onEnterMultiSelect: widget.onEnterMultiSelect, + ), + ); } _processCopyCode() { @@ -871,8 +956,8 @@ class TokenLayoutState extends BaseDynamicState if (!isHOTP && !isYandex && widget.token.period > 0) { final shouldShowNext = appProvider.autoDisplayNextCode && currentProgress < autoCopyNextCodeProgressThrehold; - final currentTimeStep = DateTime.now().millisecondsSinceEpoch ~/ - (widget.token.period * 1000); + final currentTimeStep = + DateTime.now().millisecondsSinceEpoch ~/ (widget.token.period * 1000); final timeStepChanged = currentTimeStep != _lastTimeStep; final nextCodeStateChanged = shouldShowNext != _showingNextCode; if (!timeStepChanged && !nextCodeStateChanged) return; diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 3742e77c..523c809c 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -150,6 +150,9 @@ class HomeScreenState extends BasePanelScreenState token = tokens.firstWhere((t) => t.uid == tokenUid); } + for (final key in tokenKeyMap.values) { + key.currentState?.closeSlidable(); + } setState(() { _multiSelectMode = true; _selectedTokenUids.clear(); @@ -1592,24 +1595,58 @@ class HomeScreenState extends BasePanelScreenState child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( - LucideIcons.inbox, - size: 48, - color: ChewieTheme.labelLarge.color?.withAlpha(120), + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(20), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + LucideIcons.inbox, + size: 26, + color: ChewieTheme.primaryColor, + ), ), const SizedBox(height: 16), Text( inCategory ? appLocalizations.noTokenInCategory : appLocalizations.noToken, - style: ChewieTheme.labelLarge.copyWith(fontSize: 15), + style: ChewieTheme.bodyMedium.copyWith( + color: ChewieTheme.bodyMedium.color?.withAlpha(150), + ), textAlign: TextAlign.center, ), - const SizedBox(height: 24), + const SizedBox(height: 16), Row( mainAxisSize: MainAxisSize.min, children: [ - FilledButton.icon( + if (inCategory && hasGlobalTokens) ...[ + RoundIconButton( + icon: Icon( + LucideIcons.listPlus, + size: 18, + color: ChewieTheme.primaryColor, + ), + background: ChewieTheme.primaryColor.withAlpha(20), + padding: const EdgeInsets.all(10), + onPressed: () { + final category = categories[_currentTabIndex - 1]; + BottomSheetBuilder.showBottomSheet( + context, + responsive: true, + (context) => + SelectTokenBottomSheet(category: category), + ); + }, + ), + const SizedBox(width: 12), + ], + RoundIconTextButton( + height: 38, + text: appLocalizations.addToken, + background: ChewieTheme.primaryColor, onPressed: () { if (ResponsiveUtil.isMobile()) { BottomSheetBuilder.showBottomSheet( @@ -1625,43 +1662,7 @@ class HomeScreenState extends BasePanelScreenState child: const AddTokenScreen()); } }, - icon: const Icon(LucideIcons.plus, size: 18), - label: Text(appLocalizations.addToken), - style: FilledButton.styleFrom( - backgroundColor: ChewieTheme.primaryColor, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), ), - if (inCategory && hasGlobalTokens) ...[ - const SizedBox(width: 12), - OutlinedButton.icon( - onPressed: () { - final category = categories[_currentTabIndex - 1]; - BottomSheetBuilder.showBottomSheet( - context, - responsive: true, - (context) => SelectTokenBottomSheet(category: category), - ); - }, - icon: const Icon(LucideIcons.listPlus, size: 18), - label: Text(appLocalizations.addExistingToken), - style: OutlinedButton.styleFrom( - foregroundColor: ChewieTheme.primaryColor, - side: BorderSide( - color: ChewieTheme.primaryColor.withAlpha(120)), - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - ], ], ), const SizedBox(height: 100), diff --git a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart index b1443851..dcd0f8be 100644 --- a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart @@ -41,10 +41,12 @@ class TokenOptionBottomSheet extends StatefulWidget { super.key, required this.token, this.isNewToken, + this.onEnterMultiSelect, }); final OtpToken token; final bool? isNewToken; + final VoidCallback? onEnterMultiSelect; @override TokenOptionBottomSheetState createState() => TokenOptionBottomSheetState(); @@ -389,6 +391,15 @@ class TokenOptionBottomSheetState ); }, ), + if (widget.onEnterMultiSelect != null) + _buildItem( + leading: LucideIcons.listChecks, + title: appLocalizations.select, + onTap: () { + Navigator.pop(context); + widget.onEnterMultiSelect!(); + }, + ), if (widget.token.tokenType == OtpTokenType.HOTP) _buildItem( leading: Icons.plus_one_rounded, @@ -398,13 +409,6 @@ class TokenOptionBottomSheetState _buildItem( leading: LucideIcons.squarePercent, title: appLocalizations.currentCopyTimes(widget.token.copyTimes), - onTap: () {}, - ), - _buildItem( - leading: LucideIcons.rotateCcw, - title: appLocalizations.resetCopyTimes, - titleColor: Colors.red, - leadingColor: Colors.red, onTap: () { DialogBuilder.showConfirmDialog( context, diff --git a/third-party/chewie/lib/src/Utils/System/route_util.dart b/third-party/chewie/lib/src/Utils/System/route_util.dart index 5043917f..7982db55 100644 --- a/third-party/chewie/lib/src/Utils/System/route_util.dart +++ b/third-party/chewie/lib/src/Utils/System/route_util.dart @@ -31,11 +31,14 @@ class RouteUtil { if (popAll) { Navigator.pushAndRemoveUntil( context, - CustomCupertinoPageRoute(builder: (context) => page), + CustomCupertinoPageRoute( + builder: (context) => page, fullscreenDialog: true), (_) => false).then(onThen ?? (_) => {}); } else { Navigator.push( - context, CustomCupertinoPageRoute(builder: (context) => page)) + context, + CustomCupertinoPageRoute( + builder: (context) => page, fullscreenDialog: true)) .then(onThen ?? (_) => {}); } } diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart index 2feee333..7aede5e2 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart @@ -12,6 +12,7 @@ class ActionPaneData { required this.direction, required this.fromStart, required this.children, + this.autoTriggerProgress = 0.0, }); /// The total extent of this [ActionPane] relatively to the enclosing @@ -31,6 +32,9 @@ class ActionPaneData { /// The actions for this pane. final List children; + + /// Progress toward auto-trigger threshold (0.0 = at extentRatio, 1.0 = at threshold). + final double autoTriggerProgress; } /// An action pane. @@ -52,6 +56,8 @@ class ActionPane extends StatefulWidget { this.dragDismissible = true, this.openThreshold, this.closeThreshold, + this.onAutoTrigger, + this.autoTriggerThreshold, required this.children, }) : assert(extentRatio > 0 && extentRatio <= 1), assert( @@ -92,6 +98,14 @@ class ActionPane extends StatefulWidget { /// By default this value is half the [extentRatio]. final double? closeThreshold; + /// Callback invoked when the user drags past the [autoTriggerThreshold] + /// and releases. + final VoidCallback? onAutoTrigger; + + /// The ratio threshold beyond which [onAutoTrigger] is called. + /// Defaults to `extentRatio * 2.0` if [onAutoTrigger] is set. + final double? autoTriggerThreshold; + /// The actions for this pane. final List children; @@ -112,6 +126,7 @@ class _ActionPaneState extends State implements RatioConfigurator { late double openThreshold; late double closeThreshold; bool showMotion = true; + bool _hasTriggeredAutoTriggerHaptic = false; @override double get extentRatio => widget.extentRatio; @@ -164,6 +179,27 @@ class _ActionPaneState extends State implements RatioConfigurator { return ratio; } + if (widget.onAutoTrigger != null) { + final effectiveThreshold = + widget.autoTriggerThreshold ?? widget.extentRatio * 2.0; + final absoluteRatio = ratio.abs(); + + if (absoluteRatio <= widget.extentRatio) { + _checkAutoTriggerThreshold(absoluteRatio, effectiveThreshold); + return ratio < 0 ? -absoluteRatio : absoluteRatio; + } else { + final overExtent = absoluteRatio - widget.extentRatio; + final maxOverExtent = effectiveThreshold - widget.extentRatio; + final dampedOverExtent = maxOverExtent * + (1 - + math.pow(math.e, -overExtent / maxOverExtent * 2).toDouble()); + final clampedResult = + (widget.extentRatio + dampedOverExtent).clamp(0.0, effectiveThreshold); + _checkAutoTriggerThreshold(clampedResult, effectiveThreshold); + return ratio < 0 ? -clampedResult : clampedResult; + } + } + final absoluteRatio = ratio.abs().clamp(0.0, widget.extentRatio); if (ratio < 0) { return -absoluteRatio; @@ -171,6 +207,20 @@ class _ActionPaneState extends State implements RatioConfigurator { return absoluteRatio; } + void _checkAutoTriggerThreshold(double currentRatio, double threshold) { + final hapticThreshold = + widget.extentRatio + (threshold - widget.extentRatio) * 0.7; + + if (currentRatio >= hapticThreshold && !_hasTriggeredAutoTriggerHaptic) { + _hasTriggeredAutoTriggerHaptic = true; + HapticFeedback.mediumImpact(); + } else if (currentRatio < hapticThreshold && + _hasTriggeredAutoTriggerHaptic) { + _hasTriggeredAutoTriggerHaptic = false; + HapticFeedback.lightImpact(); + } + } + @override void handleEndGestureChanged() { final gesture = controller!.endGesture.value; @@ -182,13 +232,21 @@ class _ActionPaneState extends State implements RatioConfigurator { if (controller!.isDismissibleReady) { controller!.dismissGesture.value = DismissGesture(gesture); } else { - // If the dismissible is not ready, the animation will stop. - // So we prefere to open the action pane instead. controller!.openCurrentActionPane(); } + _hasTriggeredAutoTriggerHaptic = false; return; } + if (widget.onAutoTrigger != null && _hasTriggeredAutoTriggerHaptic) { + HapticFeedback.heavyImpact(); + widget.onAutoTrigger!(); + controller!.close(); + _hasTriggeredAutoTriggerHaptic = false; + return; + } + _hasTriggeredAutoTriggerHaptic = false; + if ((gesture is OpeningGesture && openThreshold <= extentRatio) || gesture is StillGesture && ((gesture.opening && position >= openThreshold) || @@ -218,17 +276,59 @@ class _ActionPaneState extends State implements RatioConfigurator { Widget? child; if (showMotion) { - final factor = widget.extentRatio; - child = FractionallySizedBox( - alignment: config.alignment, - widthFactor: config.direction == Axis.horizontal ? factor : null, - heightFactor: config.direction == Axis.horizontal ? null : factor, - child: widget.motion, - ); + if (widget.onAutoTrigger != null) { + child = AnimatedBuilder( + animation: controller!.animation, + builder: (context, _) { + final currentRatio = controller!.animation.value; + final factor = currentRatio > widget.extentRatio + ? currentRatio + : widget.extentRatio; + final effectiveThreshold = + widget.autoTriggerThreshold ?? widget.extentRatio * 2.0; + final overRange = effectiveThreshold - widget.extentRatio; + final progress = overRange > 0 + ? ((currentRatio - widget.extentRatio) / overRange) + .clamp(0.0, 1.0) + : 0.0; + + return _ActionPaneScope( + actionPaneData: ActionPaneData( + alignment: config.alignment, + direction: config.direction, + fromStart: config.isStartActionPane, + extentRatio: widget.extentRatio, + children: widget.children, + autoTriggerProgress: progress, + ), + child: FractionallySizedBox( + alignment: config.alignment, + widthFactor: + config.direction == Axis.horizontal ? factor : null, + heightFactor: + config.direction == Axis.horizontal ? null : factor, + child: widget.motion, + ), + ); + }, + ); + } else { + final factor = widget.extentRatio; + child = FractionallySizedBox( + alignment: config.alignment, + widthFactor: config.direction == Axis.horizontal ? factor : null, + heightFactor: config.direction == Axis.horizontal ? null : factor, + child: widget.motion, + ); + } } else { child = widget.dismissible; } + if (widget.onAutoTrigger != null && showMotion) { + return child!; + } + return _ActionPaneScope( actionPaneData: ActionPaneData( alignment: config.alignment, diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/actions.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/actions.dart index e2f7e8b1..52350531 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/actions.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/actions.dart @@ -144,6 +144,10 @@ class SlidableAction extends StatelessWidget { this.simple = false, this.borderRadius = BorderRadius.zero, this.padding, + this.autoTriggerBackgroundColor, + this.autoTriggerIcon, + this.autoTriggerLabel, + this.autoTriggerIconAndTextColor, }) : assert(flex > 0), assert(icon != null || label != null); @@ -185,21 +189,51 @@ class SlidableAction extends StatelessWidget { /// Padding of the OutlinedButton final EdgeInsets? padding; + /// Background color when auto-trigger threshold is reached. + final Color? autoTriggerBackgroundColor; + + /// Icon when auto-trigger threshold is reached. + final IconData? autoTriggerIcon; + + /// Label when auto-trigger threshold is reached. + final String? autoTriggerLabel; + + /// Icon and text color when auto-trigger threshold is reached. + final Color? autoTriggerIconAndTextColor; + @override Widget build(BuildContext context) { + final actionPaneData = ActionPane.of(context); + final progress = actionPaneData?.autoTriggerProgress ?? 0.0; + + final effectiveBg = autoTriggerBackgroundColor != null && progress > 0 + ? Color.lerp(backgroundColor, autoTriggerBackgroundColor!, progress)! + : backgroundColor; + final effectiveIcon = + autoTriggerIcon != null && progress >= 0.7 ? autoTriggerIcon! : icon; + final effectiveLabel = + autoTriggerLabel != null && progress >= 0.7 ? autoTriggerLabel! : label; + final effectiveIconAndTextColor = + autoTriggerIconAndTextColor != null && progress > 0 + ? Color.lerp( + iconAndTextColor ?? Theme.of(context).iconTheme.color, + autoTriggerIconAndTextColor!, + progress)! + : iconAndTextColor; + final children = []; - if (icon != null) { + if (effectiveIcon != null) { children.add( Icon( - icon, - color: iconAndTextColor ?? Theme.of(context).iconTheme.color, + effectiveIcon, + color: effectiveIconAndTextColor ?? Theme.of(context).iconTheme.color, size: Theme.of(context).iconTheme.size, ), ); } - if (!simple && label != null) { + if (!simple && effectiveLabel != null) { if (children.isNotEmpty) { children.add( SizedBox(height: spacing), @@ -208,10 +242,10 @@ class SlidableAction extends StatelessWidget { children.add( Text( - label!, + effectiveLabel, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodyMedium?.apply( - color: iconAndTextColor, + color: effectiveIconAndTextColor, ), ), ); @@ -235,7 +269,7 @@ class SlidableAction extends StatelessWidget { padding: padding, onPressed: onPressed, autoClose: autoClose, - backgroundColor: backgroundColor, + backgroundColor: effectiveBg, foregroundColor: foregroundColor, flex: flex, child: child, diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/slidable.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/slidable.dart index 7464d104..8250b406 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/slidable.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/slidable.dart @@ -1,4 +1,7 @@ +import 'dart:math' as math; + import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import './auto_close_behavior.dart'; diff --git a/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart b/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart index 0a229080..497a19f8 100644 --- a/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart +++ b/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart @@ -19,6 +19,7 @@ import 'package:flutter/rendering.dart'; const double _kBackGestureWidth = 20.0; const double _kMinFlingVelocity = 1.0; // Screen widths per second. +const double _kDownDismissGestureHeight = 150.0; // Top drag area height. // An eyeballed value for the maximum time it takes for a page to animate forward // if the user releases a page mid swipe. @@ -220,7 +221,11 @@ mixin CupertinoRouteTransitionMixin on PageRoute { primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, - child: child, + child: _CupertinoDownDismissGestureDetector( + enabledCallback: () => route.popGestureEnabled, + onStartPopGesture: () => _startPopGesture(route), + child: child, + ), ); } else { return CupertinoPageTransition( @@ -599,13 +604,13 @@ class _CupertinoFullscreenDialogTransitionState } void _setupAnimation() { - _primaryPositionAnimation = (_primaryPositionCurve = CurvedAnimation( - parent: widget.primaryRouteAnimation, - curve: Curves.linearToEaseOut, - // The curve must be flipped so that the reverse animation doesn't play - // an ease-in curve, which iOS does not use. - reverseCurve: Curves.linearToEaseOut.flipped, - )) + _primaryPositionAnimation = (widget.linearTransition + ? widget.primaryRouteAnimation + : _primaryPositionCurve = CurvedAnimation( + parent: widget.primaryRouteAnimation, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.linearToEaseOut.flipped, + )) .drive(_kBottomUpTween); _secondaryPositionAnimation = (widget.linearTransition ? widget.secondaryRouteAnimation @@ -765,6 +770,108 @@ class _CupertinoBackGestureDetectorState } } +class _CupertinoDownDismissGestureDetector extends StatefulWidget { + const _CupertinoDownDismissGestureDetector({ + super.key, + required this.enabledCallback, + required this.onStartPopGesture, + required this.child, + }); + + final Widget child; + final ValueGetter enabledCallback; + final ValueGetter<_CupertinoBackGestureController> onStartPopGesture; + + @override + _CupertinoDownDismissGestureDetectorState createState() => + _CupertinoDownDismissGestureDetectorState(); +} + +class _CupertinoDownDismissGestureDetectorState + extends State<_CupertinoDownDismissGestureDetector> { + _CupertinoBackGestureController? _backGestureController; + + late VerticalDragGestureRecognizer _recognizer; + + @override + void initState() { + super.initState(); + _recognizer = VerticalDragGestureRecognizer(debugOwner: this) + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _handleDragCancel; + } + + @override + void dispose() { + _recognizer.dispose(); + if (_backGestureController != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_backGestureController?.navigator.mounted ?? false) { + _backGestureController?.navigator.didStopUserGesture(); + } + _backGestureController = null; + }); + } + super.dispose(); + } + + void _handleDragStart(DragStartDetails details) { + assert(mounted); + assert(_backGestureController == null); + _backGestureController = widget.onStartPopGesture(); + } + + void _handleDragUpdate(DragUpdateDetails details) { + assert(mounted); + assert(_backGestureController != null); + _backGestureController! + .dragUpdate(details.primaryDelta! / context.size!.height); + } + + void _handleDragEnd(DragEndDetails details) { + assert(mounted); + assert(_backGestureController != null); + _backGestureController!.dragEnd( + details.velocity.pixelsPerSecond.dy / context.size!.height); + _backGestureController = null; + } + + void _handleDragCancel() { + assert(mounted); + _backGestureController?.dragEnd(0.0); + _backGestureController = null; + } + + void _handlePointerDown(PointerDownEvent event) { + if (widget.enabledCallback()) { + _recognizer.addPointer(event); + } + } + + @override + Widget build(BuildContext context) { + final double topPadding = MediaQuery.paddingOf(context).top; + return Stack( + fit: StackFit.passthrough, + children: [ + widget.child, + Positioned( + left: 0.0, + right: 0.0, + top: 0.0, + height: max(topPadding, _kDownDismissGestureHeight), + child: Listener( + onPointerDown: _handlePointerDown, + behavior: HitTestBehavior.translucent, + ), + ), + ], + ); + } +} + /// A controller for an iOS-style back gesture. /// /// This is created by a [CustomCupertinoPageRoute] in response from a gesture caught From d6f2a6f7377f64e34d542f752b9340a5dfc611be Mon Sep 17 00:00:00 2001 From: dd
Date: Sun, 24 May 2026 17:13:27 +0800 Subject: [PATCH 34/36] feat: Implement modal sheet functionality and enhance UI components across various screens - Added support for modal sheets in navigation and dialogs, allowing pages to extend to the status bar. - Updated the appearance settings to include an option for enabling/disabling modal sheets. - Enhanced the Pin Change and Verify screens with step indicators for better user guidance. - Improved the backup log screen with confirmation dialogs for clearing logs. - Refined the UI components in the auto backup log and token layout screens for a more cohesive experience. - Adjusted animations and transitions for smoother interactions across the app. --- lib/Models/auto_backup_log.dart | 14 +- lib/Screens/Lock/pin_change_screen.dart | 62 +++++++- lib/Screens/Lock/pin_verify_screen.dart | 8 +- lib/Screens/Setting/about_setting_screen.dart | 2 +- lib/Screens/Setting/backup_log_screen.dart | 34 +++-- .../Setting/setting_appearance_screen.dart | 13 ++ lib/Screens/Token/token_layout.dart | 26 ++-- lib/Screens/home_screen.dart | 14 +- lib/Utils/app_provider.dart | 11 ++ lib/Utils/hive_util.dart | 1 + .../select_category_bottom_sheet.dart | 135 +++++++++++------- ...lect_category_for_tokens_bottom_sheet.dart | 106 +++++++++----- .../select_token_bottom_sheet.dart | 116 +++++++++------ .../cloudotp/cloudotp_item_builder.dart | 2 + lib/l10n/intl_en.arb | 6 +- lib/l10n/intl_ja.arb | 6 +- lib/l10n/intl_zh.arb | 6 +- lib/l10n/intl_zh_TW.arb | 6 +- .../chewie/lib/src/Resources/theme.dart | 4 +- .../lib/src/Utils/System/route_util.dart | 17 ++- .../BottomSheet/bottom_sheet_builder.dart | 5 +- .../Widgets/Dialog/components/animations.dart | 2 +- .../lib/src/Widgets/Dialog/custom_dialog.dart | 4 +- .../src/Widgets/Dialog/dialog_builder.dart | 2 +- .../Widgets/General/responsive_app_bar.dart | 6 +- .../FlutterSlidable/src/action_pane.dart | 49 +++++-- .../FlutterSlidable/src/gesture_detector.dart | 6 +- .../WaterfallFlow/reorderable_grid.dart | 1 + .../Scaffold/custom_cupertino_route.dart | 125 ++++++++++------ 29 files changed, 545 insertions(+), 244 deletions(-) diff --git a/lib/Models/auto_backup_log.dart b/lib/Models/auto_backup_log.dart index d1a7a97f..292355f8 100644 --- a/lib/Models/auto_backup_log.dart +++ b/lib/Models/auto_backup_log.dart @@ -57,25 +57,25 @@ enum AutoBackupStatus { case AutoBackupStatus.pending: return Colors.grey; case AutoBackupStatus.encrypting: - return ChewieTheme.primaryColor; + return ChewieTheme.successColor; case AutoBackupStatus.encryptFailed: return Colors.red; case AutoBackupStatus.encrpytSuccess: - return Colors.green; + return ChewieTheme.successColor; case AutoBackupStatus.saving: - return ChewieTheme.primaryColor; + return ChewieTheme.successColor; case AutoBackupStatus.saveFailed: return Colors.red; case AutoBackupStatus.saveSuccess: - return Colors.green; + return ChewieTheme.successColor; case AutoBackupStatus.uploading: - return ChewieTheme.primaryColor; + return ChewieTheme.successColor; case AutoBackupStatus.uploadFailed: return Colors.red; case AutoBackupStatus.uploadSuccess: - return Colors.green; + return ChewieTheme.successColor; case AutoBackupStatus.complete: - return Colors.green; + return ChewieTheme.primaryColor; case AutoBackupStatus.failed: return Colors.red; } diff --git a/lib/Screens/Lock/pin_change_screen.dart b/lib/Screens/Lock/pin_change_screen.dart index a13be5a1..1ae67383 100644 --- a/lib/Screens/Lock/pin_change_screen.dart +++ b/lib/Screens/Lock/pin_change_screen.dart @@ -45,6 +45,8 @@ class PinChangeScreenState extends BaseDynamicState { bool _isEditMode = ChewieHiveUtil.getString(CloudOTPHiveUtil.guesturePasswdKey) .notNullOrEmpty; + late final int _totalSteps = _isEditMode ? 3 : 2; + int _currentStep = 1; late final bool _enableBiometric = ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableBiometricKey); final bool _hideGestureTrail = ChewieHiveUtil.getBool( @@ -75,8 +77,7 @@ class PinChangeScreenState extends BaseDynamicState { _verifyLockedOut = true; _verifyLockoutRemaining = _verifyLockoutSeconds; _verifyLockoutTimer?.cancel(); - _verifyLockoutTimer = - Timer.periodic(const Duration(seconds: 1), (timer) { + _verifyLockoutTimer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { _verifyLockoutRemaining--; if (_verifyLockoutRemaining <= 0) { @@ -129,14 +130,68 @@ class PinChangeScreenState extends BaseDynamicState { gestureText: appLocalizations.drawNewGestureLock, ); _isEditMode = false; + _currentStep = 2; }); _gestureUnlockView.currentState?.updateStatus(UnlockStatus.normal); }); } + Widget _buildStepIndicator() { + final primaryColor = ChewieTheme.primaryColor; + final inactiveColor = Colors.grey.withOpacity(0.3); + return Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(_totalSteps * 2 - 1, (i) { + if (i.isOdd) { + final stepBefore = (i ~/ 2) + 1; + final isCompleted = _currentStep > stepBefore; + return Container( + width: 32, + height: 2, + color: isCompleted ? primaryColor : inactiveColor, + ); + } + final step = (i ~/ 2) + 1; + final isActive = step == _currentStep; + final isCompleted = step < _currentStep; + return Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isActive || isCompleted ? primaryColor : inactiveColor, + ), + child: Center( + child: isCompleted + ? const Icon(Icons.check, size: 14, color: Colors.white) + : Text( + '$step', + style: TextStyle( + color: isActive ? Colors.white : Colors.grey, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ); + }), + ); + } + @override Widget build(BuildContext context) { return Scaffold( + appBar: ResponsiveAppBar( + showBack: true, + onTapBack: () => Navigator.pop(context), + titleWidget: _buildStepIndicator(), + centerTitle: true, + showBorder: false, + actions: const [ + BlankIconButton(), + SizedBox(width: 5), + ], + ), body: SafeArea( right: false, child: Center( @@ -145,7 +200,6 @@ class PinChangeScreenState extends BaseDynamicState { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 50), Text( _notifier.gestureText, style: ChewieTheme.titleMedium, @@ -210,6 +264,7 @@ class PinChangeScreenState extends BaseDynamicState { status: GestureStatus.verify, gestureText: appLocalizations.drawGestureLockAgain, ); + _currentStep = _totalSteps; }); _gesturePassword = GestureUnlockView.selectedToString(selected); _gestureUnlockView.currentState?.updateStatus(UnlockStatus.success); @@ -251,6 +306,7 @@ class PinChangeScreenState extends BaseDynamicState { gestureText: appLocalizations.drawNewGestureLock, ); _isEditMode = false; + _currentStep = 2; }); _gestureUnlockView.currentState?.updateStatus(UnlockStatus.normal); } else { diff --git a/lib/Screens/Lock/pin_verify_screen.dart b/lib/Screens/Lock/pin_verify_screen.dart index 2fe1337a..58cd0c1c 100644 --- a/lib/Screens/Lock/pin_verify_screen.dart +++ b/lib/Screens/Lock/pin_verify_screen.dart @@ -193,7 +193,13 @@ class PinVerifyScreenState extends BaseWindowState BlankIconButton(), ], ) - : null, + : !widget.isModal + ? ResponsiveAppBar( + showBack: true, + onTapBack: () => Navigator.pop(context), + showBorder: false, + ) + : null, bottomNavigationBar: widget.showWindowTitle ? Container( height: 86, diff --git a/lib/Screens/Setting/about_setting_screen.dart b/lib/Screens/Setting/about_setting_screen.dart index dbe7455e..6dc06287 100644 --- a/lib/Screens/Setting/about_setting_screen.dart +++ b/lib/Screens/Setting/about_setting_screen.dart @@ -80,7 +80,7 @@ class _AboutSettingScreenState extends BaseDynamicState diaplayCelebrate() { restore(); - RouteUtil.pushFadeRoute(context, const EggScreen()); + RouteUtil.pushDialogRoute(context, const EggScreen()); setState(() {}); } diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 3792c2b2..2d115140 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -230,21 +230,29 @@ class BackupLogScreenState extends BaseDynamicState { } clear() async { - final completedIds = _mergedLogs - .where((log) => log.lastStatus.isCompleted && log.id >= 0) - .map((log) => log.id) - .toList(); - - appProvider.clearAutoBackupLogs(); - appProvider.autoBackupLoadingStatus = LoadingStatus.none; + DialogBuilder.showConfirmDialog( + context, + title: appLocalizations.clearBackupLogs, + message: appLocalizations.clearBackupLogsConfirm, + onTapConfirm: () async { + final completedIds = _mergedLogs + .where((log) => log.lastStatus.isCompleted && log.id >= 0) + .map((log) => log.id) + .toList(); + + appProvider.clearAutoBackupLogs(); + appProvider.autoBackupLoadingStatus = LoadingStatus.none; - if (completedIds.isNotEmpty) { - await AutoBackupLogDao.deleteCompletedLogs(completedIds); - } + if (completedIds.isNotEmpty) { + await AutoBackupLogDao.deleteCompletedLogs(completedIds); + } - final remainingDbLogs = await AutoBackupLogDao.getLogs(limit: 50); - _mergeLogs(remainingDbLogs); - setState(() {}); + final remainingDbLogs = await AutoBackupLogDao.getLogs(limit: 50); + _mergeLogs(remainingDbLogs); + setState(() {}); + }, + onTapCancel: () {}, + ); } Widget _buildLogList() { diff --git a/lib/Screens/Setting/setting_appearance_screen.dart b/lib/Screens/Setting/setting_appearance_screen.dart index 4aaa6f2e..f7b24181 100644 --- a/lib/Screens/Setting/setting_appearance_screen.dart +++ b/lib/Screens/Setting/setting_appearance_screen.dart @@ -63,6 +63,8 @@ class _AppearanceSettingScreenState ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideAppbarWhenScrollingKey); bool hideBottombarWhenScrolling = ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideBottombarWhenScrollingKey); + bool enableModalSheet = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableModalSheetKey, defaultValue: false); final GlobalKey _setAutoBackupPasswordKey = GlobalKey(); bool hideProgressBar = ChewieHiveUtil.getBool(CloudOTPHiveUtil.hideProgressBarKey); @@ -310,6 +312,17 @@ class _AppearanceSettingScreenState }); }, ), + CheckboxItem( + value: enableModalSheet, + title: appLocalizations.enableModalSheet, + description: appLocalizations.enableModalSheetTip, + onTap: () { + setState(() { + enableModalSheet = !enableModalSheet; + appProvider.enableModalSheet = enableModalSheet; + }); + }, + ), ], ); } diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 5e8efc9d..55927bbc 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -122,6 +122,11 @@ class TokenLayoutState extends BaseDynamicState await _slidableController?.close(); } + void replayEntrance() { + _entranceController.reset(); + _entranceController.forward(); + } + void _startWobble() { if (_wobbleController != null) return; _wobbleController = AnimationController( @@ -170,13 +175,8 @@ class TokenLayoutState extends BaseDynamicState _entranceController.dispose(); _tickerSubscription?.cancel(); _stopWobble(); - final sc = _slidableController; + _slidableController?.dispose(); _slidableController = null; - if (sc != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - sc.dispose(); - }); - } tokenLayoutNotifier.dispose(); super.dispose(); } @@ -189,6 +189,9 @@ class TokenLayoutState extends BaseDynamicState } else if (!widget.multiSelectMode && oldWidget.multiSelectMode) { _stopWobble(); } + if (widget.layoutType != oldWidget.layoutType) { + _slidableController?.close(); + } } updateInfo({ @@ -206,11 +209,11 @@ class TokenLayoutState extends BaseDynamicState super.initState(); _entranceController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 450), + duration: const Duration(milliseconds: 500), ); _entranceAnimation = CurvedAnimation( parent: _entranceController, - curve: Curves.easeOutCubic, + curve: Curves.easeOutBack, ); _entranceController.forward(); updateCode(); @@ -244,11 +247,11 @@ class TokenLayoutState extends BaseDynamicState builder: (context, child) { final value = _entranceAnimation.value; return Transform.translate( - offset: Offset(0, 20 * (1 - value)), + offset: Offset(0, 40 * (1 - value)), child: Transform.scale( - scale: 0.82 + 0.18 * value, + scale: 0.75 + 0.25 * value, child: Opacity( - opacity: value, + opacity: value.clamp(0.0, 1.0), child: child, ), ), @@ -392,6 +395,7 @@ class TokenLayoutState extends BaseDynamicState extentRatio: startExtentRatio, motion: const ScrollMotion(), onAutoTrigger: () => _processPin(), + autoTriggerThreshold: startExtentRatio * 3.0, children: [ SlidableAction( onPressed: (context) => _processPin(), diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 523c809c..611d4c15 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -28,6 +28,7 @@ import 'package:cloudotp/Screens/Backup/cloud_service_screen.dart'; import 'package:cloudotp/Screens/layout_select_screen.dart'; import 'package:cloudotp/Screens/sort_select_screen.dart'; import 'package:cloudotp/Screens/Setting/backup_log_screen.dart'; +import 'package:cloudotp/Screens/Token/category_screen.dart'; import 'package:cloudotp/Screens/main_screen.dart'; import 'package:cloudotp/Utils/hive_util.dart'; import 'package:cloudotp/Utils/search_query_parser.dart'; @@ -1493,6 +1494,7 @@ class HomeScreenState extends BasePanelScreenState builder: (context, settings, child) { double bottomPadding = MediaQuery.of(context).padding.bottom; return ReorderableGridView.builder( + cacheExtent: 1000, // controller: _scrollController, gridItemsNotifier: gridItemsNotifier, autoScroll: true, @@ -1636,8 +1638,7 @@ class HomeScreenState extends BasePanelScreenState BottomSheetBuilder.showBottomSheet( context, responsive: true, - (context) => - SelectTokenBottomSheet(category: category), + (context) => SelectTokenBottomSheet(category: category), ); }, ), @@ -1696,6 +1697,11 @@ class HomeScreenState extends BasePanelScreenState _currentTabIndex = index; getTokens(); CloudOTPHiveUtil.setSelectedCategoryUid(currentCategoryUid); + WidgetsBinding.instance.addPostFrameCallback((_) { + for (final key in tokenKeyMap.values) { + key.currentState?.replayEntrance(); + } + }); }, ); } @@ -1709,13 +1715,15 @@ class HomeScreenState extends BasePanelScreenState ? _firstCategoryTabKey : null, onLongPress: () { + HapticFeedback.lightImpact(); if (category != null) { - HapticFeedback.lightImpact(); BottomSheetBuilder.showBottomSheet( context, responsive: true, (context) => SelectTokenBottomSheet(category: category), ); + } else { + RouteUtil.pushDialogRoute(context, const CategoryScreen()); } }, child: Text(category?.title ?? (() => appLocalizations.allTokens)()), diff --git a/lib/Utils/app_provider.dart b/lib/Utils/app_provider.dart index 18e87446..2da3a4e6 100644 --- a/lib/Utils/app_provider.dart +++ b/lib/Utils/app_provider.dart @@ -426,6 +426,17 @@ class AppProvider with ChangeNotifier { notifyListeners(); } + bool _enableModalSheet = + ChewieHiveUtil.getBool(CloudOTPHiveUtil.enableModalSheetKey, defaultValue: false); + + bool get enableModalSheet => _enableModalSheet; + + set enableModalSheet(bool value) { + _enableModalSheet = value; + ChewieHiveUtil.put(CloudOTPHiveUtil.enableModalSheetKey, value); + notifyListeners(); + } + // Map> _dynamicShortcuts = // KeyboardHandlerState.mainScreenShortcuts; diff --git a/lib/Utils/hive_util.dart b/lib/Utils/hive_util.dart index a26d8c8e..58f523d1 100644 --- a/lib/Utils/hive_util.dart +++ b/lib/Utils/hive_util.dart @@ -67,6 +67,7 @@ class CloudOTPHiveUtil { static const String hideBottombarWhenScrollingKey = "hideBottombarWhenScrolling"; static const String enableLandscapeInTabletKey = "enableLandscapeInTablet"; + static const String enableModalSheetKey = "enableModalSheet"; //Backup static const String enableAutoBackupKey = "enableAutoBackup"; diff --git a/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart index f984078c..8f90b899 100644 --- a/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_category_bottom_sheet.dart @@ -17,6 +17,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:cloudotp/Screens/home_screen.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../Database/category_dao.dart'; import '../../Models/opt_token.dart'; @@ -100,8 +101,10 @@ class SelectCategoryBottomSheetState mainAxisAlignment: MainAxisAlignment.center, children: [ _buildHeader(), - const SizedBox(height: 10), - _buildButtons(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: _buildButtons(), + ), _buildFooter(), ], ), @@ -111,17 +114,34 @@ class SelectCategoryBottomSheetState } _buildHeader() { - return Container( - padding: const EdgeInsets.symmetric(vertical: 20), - alignment: Alignment.center, - decoration: - BoxDecoration(borderRadius: BorderRadius.vertical(top: radius)), - child: Text( - textAlign: TextAlign.center, - widget.token.issuer.isNotEmpty - ? appLocalizations.setCategoryForTokenDetail(widget.token.issuer) - : appLocalizations.setCategoryForToken, - style: ChewieTheme.titleLarge, + return Padding( + padding: const EdgeInsets.fromLTRB(0, 12, 0, 10), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.tag, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + widget.token.issuer.isNotEmpty + ? appLocalizations + .setCategoryForTokenDetail(widget.token.issuer) + : appLocalizations.setCategoryForToken, + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ); } @@ -155,51 +175,64 @@ class SelectCategoryBottomSheetState _buildFooter() { return Container( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12), + padding: const EdgeInsets.symmetric(vertical: 16), alignment: Alignment.center, child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, children: [ - const Expanded(flex: 2, child: SizedBox(height: 50)), - if (categories.isNotEmpty) - Expanded( - flex: 1, + Expanded( + child: SizedBox( + height: 45, child: RoundIconTextButton( - background: ChewieTheme.primaryColor, - text: widget.isEditingToken - ? appLocalizations.confirm - : appLocalizations.save, - onPressed: () async { - List selectedIndexes = - controller.selectedIndexes.toList(); - List allSelectedCategoryUids = - selectedIndexes.map((e) => categories[e].uid).toList(); - List unselectedCategoryUids = oldCategoryUids - .where((element) => - !allSelectedCategoryUids.contains(element)) - .toList(); - List newSelectedCategoryUids = allSelectedCategoryUids - .where((element) => !oldCategoryUids.contains(element)) - .toList(); - Navigator.of(context).pop(); - widget.onCategoryChanged?.call(allSelectedCategoryUids); - if (!widget.isEditingToken) { - await BindingDao.bingdingsForToken( - widget.token.uid, newSelectedCategoryUids); - await BindingDao.unBingdingsForToken( - widget.token.uid, unselectedCategoryUids); - homeScreenState?.changeCategoriesForToken( - widget.token, - unselectedCategoryUids, - newSelectedCategoryUids, - ); - IToast.showTop(appLocalizations.saveSuccess); - } - }, + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), fontSizeDelta: 2, ), ), + ), + if (categories.isNotEmpty) const SizedBox(width: 10), + if (categories.isNotEmpty) + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: widget.isEditingToken + ? appLocalizations.confirm + : appLocalizations.save, + onPressed: () async { + List selectedIndexes = + controller.selectedIndexes.toList(); + List allSelectedCategoryUids = + selectedIndexes.map((e) => categories[e].uid).toList(); + List unselectedCategoryUids = oldCategoryUids + .where((element) => + !allSelectedCategoryUids.contains(element)) + .toList(); + List newSelectedCategoryUids = + allSelectedCategoryUids + .where( + (element) => !oldCategoryUids.contains(element)) + .toList(); + Navigator.of(context).pop(); + widget.onCategoryChanged?.call(allSelectedCategoryUids); + if (!widget.isEditingToken) { + await BindingDao.bingdingsForToken( + widget.token.uid, newSelectedCategoryUids); + await BindingDao.unBingdingsForToken( + widget.token.uid, unselectedCategoryUids); + homeScreenState?.changeCategoriesForToken( + widget.token, + unselectedCategoryUids, + newSelectedCategoryUids, + ); + IToast.showTop(appLocalizations.saveSuccess); + } + }, + fontSizeDelta: 2, + ), + ), + ), ], ), ); diff --git a/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart index 9c49e4ab..24accf9b 100644 --- a/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_category_for_tokens_bottom_sheet.dart @@ -17,6 +17,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:cloudotp/Screens/home_screen.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../Database/category_dao.dart'; import '../../Models/opt_token.dart'; @@ -80,8 +81,10 @@ class _SelectCategoryForTokensBottomSheetState mainAxisAlignment: MainAxisAlignment.center, children: [ _buildHeader(), - const SizedBox(height: 10), - _buildButtons(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: _buildButtons(), + ), _buildFooter(), ], ), @@ -91,15 +94,31 @@ class _SelectCategoryForTokensBottomSheetState } _buildHeader() { - return Container( - padding: const EdgeInsets.symmetric(vertical: 20), - alignment: Alignment.center, - decoration: - BoxDecoration(borderRadius: BorderRadius.vertical(top: radius)), - child: Text( - textAlign: TextAlign.center, - appLocalizations.setCategoryForTokens(widget.tokens.length), - style: ChewieTheme.titleLarge, + return Padding( + padding: const EdgeInsets.fromLTRB(0, 12, 0, 10), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.tags, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.setCategoryForTokens(widget.tokens.length), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ); } @@ -131,39 +150,50 @@ class _SelectCategoryForTokensBottomSheetState _buildFooter() { return Container( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12), + padding: const EdgeInsets.symmetric(vertical: 16), alignment: Alignment.center, child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, children: [ - const Expanded(flex: 2, child: SizedBox(height: 50)), + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), + fontSizeDelta: 2, + ), + ), + ), + if (categories.isNotEmpty) const SizedBox(width: 10), if (categories.isNotEmpty) Expanded( - flex: 1, - child: RoundIconTextButton( - background: ChewieTheme.primaryColor, - text: appLocalizations.save, - onPressed: () async { - List selectedIndexes = - controller.selectedIndexes.toList(); - List selectedCategoryUids = - selectedIndexes.map((e) => categories[e].uid).toList(); - Navigator.of(context).pop(); - for (OtpToken token in widget.tokens) { - List existingUids = - await BindingDao.getCategoryUids(token.uid); - List newUids = selectedCategoryUids - .where((uid) => !existingUids.contains(uid)) - .toList(); - if (newUids.isNotEmpty) { - await BindingDao.bingdingsForToken(token.uid, newUids); + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: appLocalizations.save, + onPressed: () async { + List selectedIndexes = + controller.selectedIndexes.toList(); + List selectedCategoryUids = + selectedIndexes.map((e) => categories[e].uid).toList(); + Navigator.of(context).pop(); + for (OtpToken token in widget.tokens) { + List existingUids = + await BindingDao.getCategoryUids(token.uid); + List newUids = selectedCategoryUids + .where((uid) => !existingUids.contains(uid)) + .toList(); + if (newUids.isNotEmpty) { + await BindingDao.bingdingsForToken(token.uid, newUids); + } } - } - IToast.showTop(appLocalizations.saveSuccess); - widget.onCompleted(); - }, - fontSizeDelta: 2, + IToast.showTop(appLocalizations.saveSuccess); + widget.onCompleted(); + }, + fontSizeDelta: 2, + ), ), ), ], diff --git a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart index 4395b957..f82e1c11 100644 --- a/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/select_token_bottom_sheet.dart @@ -16,6 +16,7 @@ import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:cloudotp/Database/token_category_binding_dao.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import '../../Database/category_dao.dart'; import '../../Database/token_dao.dart'; @@ -88,10 +89,13 @@ class SelectTokenBottomSheetState mainAxisAlignment: MainAxisAlignment.center, children: [ _buildHeader(), - Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height - 320), - child: _buildButtons(), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height - 320), + child: _buildButtons(), + ), ), _buildFooter(), ], @@ -103,15 +107,31 @@ class SelectTokenBottomSheetState } _buildHeader() { - return Container( - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: - BoxDecoration(borderRadius: BorderRadius.vertical(top: radius)), - alignment: Alignment.center, - child: Text( - textAlign: TextAlign.center, - appLocalizations.setTokenForCategory(widget.category.title), - style: ChewieTheme.titleLarge, + return Padding( + padding: const EdgeInsets.fromLTRB(0, 12, 0, 10), + child: Row( + children: [ + Container( + width: 34, + height: 34, + decoration: BoxDecoration( + color: ChewieTheme.primaryColor.withAlpha(30), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(LucideIcons.keyRound, + color: ChewieTheme.primaryColor, size: 17), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + appLocalizations.setTokenForCategory(widget.category.title), + style: ChewieTheme.titleMedium + .copyWith(fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ); } @@ -130,39 +150,51 @@ class SelectTokenBottomSheetState _buildFooter() { return Container( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12), + padding: const EdgeInsets.symmetric(vertical: 16), alignment: Alignment.center, child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, children: [ - const Expanded(flex: 2, child: SizedBox(height: 50)), Expanded( - flex: 1, - child: RoundIconTextButton( - background: ChewieTheme.primaryColor, - text: appLocalizations.save, - onPressed: () async { - List selectedIndexes = controller.selectedIndexes.toList(); - List tokenUids = - selectedIndexes.map((e) => tokens[e].uid).toList(); - List unSelectedUids = oldSelectedUids - .where((element) => !tokenUids.contains(element)) - .toList(); - List newSelectedUids = tokenUids - .where((element) => !oldSelectedUids.contains(element)) - .toList(); - await BindingDao.bingdingsForCategory( - widget.category.uid, newSelectedUids); - await BindingDao.unBingdingsForCategory( - widget.category.uid, unSelectedUids); - await CategoryDao.updateCategory(widget.category); - homeScreenState?.changeTokensForCategory(widget.category); - widget.onChanged?.call(); - IToast.showTop(appLocalizations.saveSuccess); - Navigator.of(context).pop(); - }, - fontSizeDelta: 2, + child: SizedBox( + height: 45, + child: RoundIconTextButton( + text: appLocalizations.cancel, + onPressed: () => Navigator.of(context).pop(), + fontSizeDelta: 2, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: SizedBox( + height: 45, + child: RoundIconTextButton( + background: ChewieTheme.primaryColor, + color: Colors.white, + text: appLocalizations.save, + onPressed: () async { + List selectedIndexes = + controller.selectedIndexes.toList(); + List tokenUids = + selectedIndexes.map((e) => tokens[e].uid).toList(); + List unSelectedUids = oldSelectedUids + .where((element) => !tokenUids.contains(element)) + .toList(); + List newSelectedUids = tokenUids + .where((element) => !oldSelectedUids.contains(element)) + .toList(); + await BindingDao.bingdingsForCategory( + widget.category.uid, newSelectedUids); + await BindingDao.unBingdingsForCategory( + widget.category.uid, unSelectedUids); + await CategoryDao.updateCategory(widget.category); + homeScreenState?.changeTokensForCategory(widget.category); + widget.onChanged?.call(); + IToast.showTop(appLocalizations.saveSuccess); + if (mounted) Navigator.of(context).pop(); + }, + fontSizeDelta: 2, + ), ), ), ], diff --git a/lib/Widgets/cloudotp/cloudotp_item_builder.dart b/lib/Widgets/cloudotp/cloudotp_item_builder.dart index 9786bc53..c59fcb66 100644 --- a/lib/Widgets/cloudotp/cloudotp_item_builder.dart +++ b/lib/Widgets/cloudotp/cloudotp_item_builder.dart @@ -305,6 +305,8 @@ class QrcodeDialog { context: context, elevation: 0, enableDrag: true, + barrierColor: ChewieTheme.barrierColor, + duration: ChewieTheme.animationDuration, backgroundColor: ChewieTheme.canvasColor, builder: (context) => QrcodesDialogWidget( title: title, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 88d065e1..5367ec4e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1028,5 +1028,9 @@ "desktopCoachLogoShortcutTitle": "Keyboard Shortcuts", "desktopCoachLogoShortcutDescription": "Click the logo to view all keyboard shortcuts.", "desktopCoachRightClickTitle": "Right-Click Menu", - "desktopCoachRightClickDescription": "Right-click on any token to access actions like edit, pin, view QR code, delete, or enter multi-select mode." + "desktopCoachRightClickDescription": "Right-click on any token to access actions like edit, pin, view QR code, delete, or enter multi-select mode.", + "enableModalSheet": "Extend Page to Status Bar", + "enableModalSheetTip": "When disabled, pages extend behind the status bar instead of stopping below it", + "clearBackupLogs": "Clear Backup Logs", + "clearBackupLogsConfirm": "Are you sure you want to clear all backup logs?" } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 9f872151..ebfcb587 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1028,5 +1028,9 @@ "desktopCoachLogoShortcutTitle": "キーボードショートカット", "desktopCoachLogoShortcutDescription": "ロゴをクリックしてすべてのショートカットを表示できます。", "desktopCoachRightClickTitle": "右クリックメニュー", - "desktopCoachRightClickDescription": "トークンを右クリックすると、編集、ピン留め、QRコード表示、削除、複数選択モードなどの操作にアクセスできます。" + "desktopCoachRightClickDescription": "トークンを右クリックすると、編集、ピン留め、QRコード表示、削除、複数選択モードなどの操作にアクセスできます。", + "enableModalSheet": "ページをステータスバーまで延長", + "enableModalSheetTip": "無効にすると、ページがステータスバーの背後まで延長されます", + "clearBackupLogs": "バックアップログを消去", + "clearBackupLogsConfirm": "すべてのバックアップログを消去しますか?" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 19a42959..ad6ee0f4 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1028,5 +1028,9 @@ "desktopCoachLogoShortcutTitle": "快捷键帮助", "desktopCoachLogoShortcutDescription": "点击 Logo 查看所有快捷键。", "desktopCoachRightClickTitle": "右键菜单", - "desktopCoachRightClickDescription": "右键点击任何令牌可快速访问编辑、置顶、查看二维码、删除或进入多选模式等操作。" + "desktopCoachRightClickDescription": "右键点击任何令牌可快速访问编辑、置顶、查看二维码、删除或进入多选模式等操作。", + "enableModalSheet": "页面延伸至状态栏", + "enableModalSheetTip": "关闭后,页面将延伸至状态栏后方而非停留在状态栏下方", + "clearBackupLogs": "清除备份日志", + "clearBackupLogsConfirm": "确定要清除所有备份日志吗?" } \ No newline at end of file diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index 4c11028e..7e2b1260 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -1028,5 +1028,9 @@ "desktopCoachLogoShortcutTitle": "快捷鍵說明", "desktopCoachLogoShortcutDescription": "點擊 Logo 查看所有快捷鍵。", "desktopCoachRightClickTitle": "右鍵選單", - "desktopCoachRightClickDescription": "右鍵點擊任何令牌可快速存取編輯、置頂、查看 QR Code、刪除或進入多選模式等操作。" + "desktopCoachRightClickDescription": "右鍵點擊任何令牌可快速存取編輯、置頂、查看 QR Code、刪除或進入多選模式等操作。", + "enableModalSheet": "頁面延伸至狀態列", + "enableModalSheetTip": "關閉後,頁面將延伸至狀態列後方而非停留在狀態列下方", + "clearBackupLogs": "清除備份日誌", + "clearBackupLogsConfirm": "確定要清除所有備份日誌嗎?" } \ No newline at end of file diff --git a/third-party/chewie/lib/src/Resources/theme.dart b/third-party/chewie/lib/src/Resources/theme.dart index f545a9ae..c6604998 100644 --- a/third-party/chewie/lib/src/Resources/theme.dart +++ b/third-party/chewie/lib/src/Resources/theme.dart @@ -295,7 +295,9 @@ class ChewieTheme { static Color get barrierColor => ResponsiveUtil.isLandscapeLayout() ? ChewieTheme.scaffoldBackgroundColor.withValues(alpha: 0.7) - : Colors.black54; + : const Color(0x59000000); + + static const Duration animationDuration = Duration(milliseconds: 450); static Color get iconColor => Theme.of(chewieProvider.rootContext).iconTheme.color!; diff --git a/third-party/chewie/lib/src/Utils/System/route_util.dart b/third-party/chewie/lib/src/Utils/System/route_util.dart index 7982db55..69076186 100644 --- a/third-party/chewie/lib/src/Utils/System/route_util.dart +++ b/third-party/chewie/lib/src/Utils/System/route_util.dart @@ -23,7 +23,10 @@ class RouteUtil { Widget page, { Function(dynamic)? onThen, bool popAll = false, + bool? modalSheet, }) { + final bool effectiveModalSheet = + modalSheet ?? !ChewieHiveUtil.getBool("enableModalSheet", defaultValue: false); WidgetsBinding.instance.addPostFrameCallback((_) { if (ResponsiveUtil.isLandscapeLayout()) { pushFadeRoute(context, page, onThen: onThen); @@ -32,13 +35,17 @@ class RouteUtil { Navigator.pushAndRemoveUntil( context, CustomCupertinoPageRoute( - builder: (context) => page, fullscreenDialog: true), + builder: (context) => page, + fullscreenDialog: true, + modalSheet: effectiveModalSheet), (_) => false).then(onThen ?? (_) => {}); } else { Navigator.push( context, CustomCupertinoPageRoute( - builder: (context) => page, fullscreenDialog: true)) + builder: (context) => page, + fullscreenDialog: true, + modalSheet: effectiveModalSheet)) .then(onThen ?? (_) => {}); } } @@ -91,7 +98,10 @@ class RouteUtil { Function(dynamic)? onThen, bool useFade = false, bool popAll = false, + bool? modalSheet, }) { + final bool effectiveModalSheet = + modalSheet ?? !ChewieHiveUtil.getBool("enableModalSheet", defaultValue: false); if (ResponsiveUtil.isLandscapeLayout()) { if (DialogNavigatorHelper.isMounted()) { DialogNavigatorHelper.pushPage(page); @@ -111,7 +121,8 @@ class RouteUtil { if (useFade) { pushFadeRoute(context, page, onThen: onThen, popAll: popAll); } else { - pushCupertinoRoute(context, page, onThen: onThen, popAll: popAll); + pushCupertinoRoute(context, page, + onThen: onThen, popAll: popAll, modalSheet: effectiveModalSheet); } } } diff --git a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart index 953a1d26..cf7b212a 100644 --- a/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart +++ b/third-party/chewie/lib/src/Widgets/BottomSheet/bottom_sheet_builder.dart @@ -39,7 +39,7 @@ class BottomSheetBuilder { barrierColor: ChewieTheme.barrierColor, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, - transitionDuration: const Duration(milliseconds: 300), + transitionDuration: ChewieTheme.animationDuration, pageBuilder: (context, anim1, anim2) => const SizedBox.shrink(), transitionBuilder: (context, animation, secondaryAnimation, child) { return DialogAnimation( @@ -58,9 +58,10 @@ class BottomSheetBuilder { elevation: 0, enableDrag: enableDrag, barrierColor: ChewieTheme.barrierColor, + duration: ChewieTheme.animationDuration, backgroundColor: backgroundColor ?? ChewieTheme.canvasColor, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + borderRadius: BorderRadius.vertical(top: ChewieDimens.defaultRadius), ), builder: builder, containerWidget: (_, animation, child) => BottomSheetWrapperWidget( diff --git a/third-party/chewie/lib/src/Widgets/Dialog/components/animations.dart b/third-party/chewie/lib/src/Widgets/Dialog/components/animations.dart index 99a4d8a5..5c54c736 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/components/animations.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/components/animations.dart @@ -70,7 +70,7 @@ class CustomDialogAnimations { ).animate( CurvedAnimation( parent: animation, - curve: Curves.easeInOut, + curve: Curves.easeOutCubic, ), ), child: child, diff --git a/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart b/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart index 39278e1b..efeedf49 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/custom_dialog.dart @@ -81,7 +81,7 @@ class CustomInfoDialog { context: context, barrierLabel: '', barrierColor: ChewieTheme.barrierColor, - transitionDuration: const Duration(milliseconds: 400), + transitionDuration: ChewieTheme.animationDuration, transitionBuilder: (context, animation, secondaryAnimation, child) { return CustomDialogAnimations.fromBottom( animation, @@ -188,7 +188,7 @@ class CustomConfirmDialog { context: context, barrierLabel: '', barrierColor: ChewieTheme.barrierColor, - transitionDuration: const Duration(milliseconds: 400), + transitionDuration: ChewieTheme.animationDuration, transitionBuilder: (context, animation, secondaryAnimation, child) { return CustomDialogAnimations.fromBottom( animation, diff --git a/third-party/chewie/lib/src/Widgets/Dialog/dialog_builder.dart b/third-party/chewie/lib/src/Widgets/Dialog/dialog_builder.dart index 11bdde65..65770b01 100644 --- a/third-party/chewie/lib/src/Widgets/Dialog/dialog_builder.dart +++ b/third-party/chewie/lib/src/Widgets/Dialog/dialog_builder.dart @@ -150,7 +150,7 @@ class DialogBuilder { context: context, barrierLabel: '', barrierColor: ChewieTheme.barrierColor, - transitionDuration: const Duration(milliseconds: 300), + transitionDuration: ChewieTheme.animationDuration, transitionBuilder: (context, animation, secondaryAnimation, _) { return DialogAnimation( animation: animation, diff --git a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart index b45775df..48b4a4d1 100644 --- a/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart +++ b/third-party/chewie/lib/src/Widgets/General/responsive_app_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:awesome_chewie/awesome_chewie.dart'; +import 'package:lucide_icons/lucide_icons.dart'; class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { final String title; @@ -73,9 +74,8 @@ class ResponsiveAppBar extends StatelessWidget implements PreferredSizeWidget { onPressed: onTapBack ?? () => chewieProvider.panelScreenState ?.popPage(), - iconBuilder: (_) => const Icon( - Icons.arrow_back_rounded, - size: 22), + buttonSize: const Size(32, 32), + icon: LucideIcons.arrowLeft, ), ), titleContent, diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart index 7aede5e2..11bbd6ed 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/action_pane.dart @@ -200,11 +200,16 @@ class _ActionPaneState extends State implements RatioConfigurator { } } - final absoluteRatio = ratio.abs().clamp(0.0, widget.extentRatio); - if (ratio < 0) { - return -absoluteRatio; + final absoluteRatio = ratio.abs(); + if (absoluteRatio <= widget.extentRatio) { + return ratio < 0 ? -absoluteRatio : absoluteRatio; } - return absoluteRatio; + final overExtent = absoluteRatio - widget.extentRatio; + final maxOverExtent = widget.extentRatio * 0.2; + final dampedOverExtent = maxOverExtent * + (1 - math.pow(math.e, -overExtent / maxOverExtent * 2).toDouble()); + final result = widget.extentRatio + dampedOverExtent; + return ratio < 0 ? -result : result; } void _checkAutoTriggerThreshold(double currentRatio, double threshold) { @@ -313,12 +318,36 @@ class _ActionPaneState extends State implements RatioConfigurator { }, ); } else { - final factor = widget.extentRatio; - child = FractionallySizedBox( - alignment: config.alignment, - widthFactor: config.direction == Axis.horizontal ? factor : null, - heightFactor: config.direction == Axis.horizontal ? null : factor, - child: widget.motion, + child = AnimatedBuilder( + animation: controller!.animation, + builder: (context, _) { + final currentRatio = controller!.animation.value; + final factor = widget.extentRatio; + Alignment alignment = config.alignment; + if (currentRatio > widget.extentRatio && widget.extentRatio < 1.0) { + final overRatio = (currentRatio - widget.extentRatio) / + (1 - widget.extentRatio); + if (config.direction == Axis.horizontal) { + alignment = Alignment( + config.alignment.x * (1 - 2 * overRatio), + config.alignment.y, + ); + } else { + alignment = Alignment( + config.alignment.x, + config.alignment.y * (1 - 2 * overRatio), + ); + } + } + return FractionallySizedBox( + alignment: alignment, + widthFactor: + config.direction == Axis.horizontal ? factor : null, + heightFactor: + config.direction == Axis.horizontal ? null : factor, + child: widget.motion, + ); + }, ); } } else { diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart index 8a827129..0da6d04a 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart @@ -6,6 +6,8 @@ import 'controller.dart'; // INTERNAL USE // ignore_for_file: public_member_api_docs +const double _kDragDampingFactor = 0.6; + class SlidableGestureDetector extends StatefulWidget { const SlidableGestureDetector({ super.key, @@ -84,7 +86,7 @@ class _SlidableGestureDetectorState extends State { } void handleDragUpdate(DragUpdateDetails details) { - final delta = details.primaryDelta!; + final delta = details.primaryDelta! * _kDragDampingFactor; dragExtent += delta; lastPosition = details.localPosition; widget.controller.ratio = dragExtent / overallDragAxisExtent; @@ -97,7 +99,7 @@ class _SlidableGestureDetectorState extends State { primaryDelta >= 0 ? GestureDirection.opening : GestureDirection.closing; widget.controller.dispatchEndGesture( - details.primaryVelocity, + (details.primaryVelocity ?? 0) * _kDragDampingFactor, gestureDirection, ); } diff --git a/third-party/chewie/lib/src/Widgets/Module/WaterfallFlow/reorderable_grid.dart b/third-party/chewie/lib/src/Widgets/Module/WaterfallFlow/reorderable_grid.dart index 18d65dda..ab1bc2c2 100644 --- a/third-party/chewie/lib/src/Widgets/Module/WaterfallFlow/reorderable_grid.dart +++ b/third-party/chewie/lib/src/Widgets/Module/WaterfallFlow/reorderable_grid.dart @@ -857,6 +857,7 @@ class SliverReorderableGridState extends State SliverChildBuilderDelegate( _itemBuilder, childCount: widget.itemCount, + addAutomaticKeepAlives: false, ); return SliverWaterfallFlow( delegate: childrenDelegate, diff --git a/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart b/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart index 497a19f8..92d684a1 100644 --- a/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart +++ b/third-party/chewie/lib/src/Widgets/Scaffold/custom_cupertino_route.dart @@ -15,8 +15,11 @@ import 'dart:ui' show ImageFilter, lerpDouble; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' show Theme; import 'package:flutter/rendering.dart'; +import 'package:awesome_chewie/awesome_chewie.dart' show ChewieTheme; + const double _kBackGestureWidth = 20.0; const double _kMinFlingVelocity = 1.0; // Screen widths per second. const double _kDownDismissGestureHeight = 150.0; // Top drag area height. @@ -101,6 +104,8 @@ mixin CupertinoRouteTransitionMixin on PageRoute { /// {@endtemplate} String? get title; + bool get modalSheet => false; + ValueNotifier? _previousTitle; /// The title string of the previous [CustomCupertinoPageRoute]. @@ -146,12 +151,26 @@ mixin CupertinoRouteTransitionMixin on PageRoute { } @override - // A relatively rigorous eyeball estimation. - Duration get transitionDuration => const Duration(milliseconds: 500); + bool get opaque => !fullscreenDialog; @override - Color? get barrierColor => - fullscreenDialog ? null : _kCupertinoPageTransitionBarrierColor; + bool get popGestureEnabled { + if (isFirst) return false; + if (willHandlePopInternally) return false; + if (popDisposition == RoutePopDisposition.doNotPop) { + return false; + } + if (!animation!.isCompleted) return false; + return true; + } + + @override + Duration get transitionDuration => ChewieTheme.animationDuration; + + @override + Color? get barrierColor => fullscreenDialog + ? const Color(0x59000000) + : _kCupertinoPageTransitionBarrierColor; @override String? get barrierLabel => null; @@ -217,10 +236,13 @@ mixin CupertinoRouteTransitionMixin on PageRoute { // match finger motions. final bool linearTransition = route.popGestureInProgress; if (route.fullscreenDialog) { + final bool modalSheet = route is CupertinoRouteTransitionMixin && + (route as CupertinoRouteTransitionMixin).modalSheet; return CupertinoFullscreenDialogTransition( primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: linearTransition, + modalSheet: modalSheet, child: _CupertinoDownDismissGestureDetector( enabledCallback: () => route.popGestureEnabled, onStartPopGesture: () => _startPopGesture(route), @@ -284,14 +306,13 @@ class CustomCupertinoPageRoute extends PageRoute CustomCupertinoPageRoute({ required this.builder, this.title, + this.modalSheet = false, super.settings, this.maintainState = true, super.fullscreenDialog, super.allowSnapshotting = true, super.barrierDismissible = false, - }) { - assert(opaque); - } + }); /// Builds the primary contents of the route. final WidgetBuilder builder; @@ -302,6 +323,9 @@ class CustomCupertinoPageRoute extends PageRoute @override final String? title; + @override + final bool modalSheet; + @override final bool maintainState; @@ -318,9 +342,7 @@ class _PageBasedCustomCupertinoPageRoute extends PageRoute _PageBasedCustomCupertinoPageRoute({ required CupertinoPage page, super.allowSnapshotting = true, - }) : super(settings: page) { - assert(opaque); - } + }) : super(settings: page); CupertinoPage get _page => settings as CupertinoPage; @@ -330,6 +352,9 @@ class _PageBasedCustomCupertinoPageRoute extends PageRoute @override String? get title => _page.title; + @override + bool get modalSheet => _page.modalSheet; + @override bool get maintainState => _page.maintainState; @@ -363,6 +388,7 @@ class CupertinoPage extends Page { this.maintainState = true, this.title, this.fullscreenDialog = false, + this.modalSheet = false, this.allowSnapshotting = true, super.canPop, super.onPopInvoked, @@ -384,6 +410,8 @@ class CupertinoPage extends Page { /// {@macro flutter.widgets.PageRoute.fullscreenDialog} final bool fullscreenDialog; + final bool modalSheet; + /// {@macro flutter.widgets.TransitionRoute.allowSnapshotting} final bool allowSnapshotting; @@ -536,6 +564,7 @@ class CupertinoFullscreenDialogTransition extends StatefulWidget { required this.secondaryRouteAnimation, required this.child, required this.linearTransition, + this.modalSheet = false, }); /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 @@ -550,6 +579,8 @@ class CupertinoFullscreenDialogTransition extends StatefulWidget { /// Used to precisely track back gesture drags. final bool linearTransition; + final bool modalSheet; + /// The widget below this widget in the tree. final Widget child; @@ -563,15 +594,9 @@ class _CupertinoFullscreenDialogTransitionState /// When this page is coming in to cover another page. late Animation _primaryPositionAnimation; - /// When this page is becoming covered by another page. - late Animation _secondaryPositionAnimation; - /// Curve of primary page which is coming in to cover another page. CurvedAnimation? _primaryPositionCurve; - /// Curve of secondary page which is becoming covered by another page. - CurvedAnimation? _secondaryPositionCurve; - @override void initState() { super.initState(); @@ -598,42 +623,52 @@ class _CupertinoFullscreenDialogTransitionState void _disposeCurve() { _primaryPositionCurve?.dispose(); - _secondaryPositionCurve?.dispose(); _primaryPositionCurve = null; - _secondaryPositionCurve = null; } void _setupAnimation() { - _primaryPositionAnimation = (widget.linearTransition - ? widget.primaryRouteAnimation - : _primaryPositionCurve = CurvedAnimation( - parent: widget.primaryRouteAnimation, - curve: Curves.linearToEaseOut, - reverseCurve: Curves.linearToEaseOut.flipped, - )) - .drive(_kBottomUpTween); - _secondaryPositionAnimation = (widget.linearTransition - ? widget.secondaryRouteAnimation - : _secondaryPositionCurve = CurvedAnimation( - parent: widget.secondaryRouteAnimation, - curve: Curves.linearToEaseOut, - reverseCurve: Curves.easeInToLinear, - )) - .drive(_kMiddleLeftTween); + final primaryCurved = widget.linearTransition + ? widget.primaryRouteAnimation + : _primaryPositionCurve = CurvedAnimation( + parent: widget.primaryRouteAnimation, + curve: Curves.easeOutCubic, + reverseCurve: Curves.easeInCubic, + ); + _primaryPositionAnimation = primaryCurved.drive(_kBottomUpTween); } @override Widget build(BuildContext context) { - assert(debugCheckHasDirectionality(context)); - final TextDirection textDirection = Directionality.of(context); + Widget content = widget.child; + const borderRadius = BorderRadius.vertical( + top: Radius.circular(20.0), + ); + if (widget.modalSheet) { + final topPadding = MediaQuery.of(context).padding.top; + content = Padding( + padding: EdgeInsets.only(top: topPadding), + child: ClipRRect( + borderRadius: borderRadius, + child: Container( + color: Theme.of(context).scaffoldBackgroundColor, + padding: const EdgeInsets.only(top: 6), + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: content, + ), + ), + ), + ); + } else { + content = ClipRRect( + borderRadius: borderRadius, + child: content, + ); + } return SlideTransition( - position: _secondaryPositionAnimation, - textDirection: textDirection, - transformHitTests: false, - child: SlideTransition( - position: _primaryPositionAnimation, - child: widget.child, - ), + position: _primaryPositionAnimation, + child: content, ); } } @@ -833,8 +868,8 @@ class _CupertinoDownDismissGestureDetectorState void _handleDragEnd(DragEndDetails details) { assert(mounted); assert(_backGestureController != null); - _backGestureController!.dragEnd( - details.velocity.pixelsPerSecond.dy / context.size!.height); + _backGestureController! + .dragEnd(details.velocity.pixelsPerSecond.dy / context.size!.height); _backGestureController = null; } From 9cf3a45316a256111cc4a840527f2251057e5287 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 May 2026 17:58:26 +0800 Subject: [PATCH 35/36] feat: Update icons and enhance UI components across various screens --- lib/Screens/Setting/about_setting_screen.dart | 2 +- lib/Screens/Setting/backup_log_screen.dart | 8 +- lib/Screens/Setting/select_theme_screen.dart | 2 +- lib/Screens/Token/add_token_screen.dart | 8 +- lib/Screens/Token/token_layout.dart | 16 +- lib/Screens/home_screen.dart | 8 +- .../import_from_third_party_bottom_sheet.dart | 9 +- .../token_option_bottom_sheet.dart | 6 +- .../cloudotp/qrcodes_dialog_widget.dart | 3 +- .../chewie/lib/src/Resources/theme.dart | 2 +- .../lib/src/Screens/update_log_screen.dart | 223 +++++++++--------- .../FlutterSlidable/src/gesture_detector.dart | 2 +- 12 files changed, 145 insertions(+), 144 deletions(-) diff --git a/lib/Screens/Setting/about_setting_screen.dart b/lib/Screens/Setting/about_setting_screen.dart index 6dc06287..ec56a221 100644 --- a/lib/Screens/Setting/about_setting_screen.dart +++ b/lib/Screens/Setting/about_setting_screen.dart @@ -304,7 +304,7 @@ class _AboutSettingScreenState extends BaseDynamicState UriUtil.openExternal(telegramLink); }, showLeading: true, - leading: LucideIcons.telescope, + leading: LucideIcons.component, trailing: LucideIcons.externalLink, ), ], diff --git a/lib/Screens/Setting/backup_log_screen.dart b/lib/Screens/Setting/backup_log_screen.dart index 2d115140..65bf89b0 100644 --- a/lib/Screens/Setting/backup_log_screen.dart +++ b/lib/Screens/Setting/backup_log_screen.dart @@ -291,8 +291,8 @@ class BackupLogScreenState extends BaseDynamicState { background: _accent, onPressed: () { if (widget.isOverlay) { - RouteUtil.pushDialogRoute( - context, const SettingNavigationScreen(initPageIndex: 3)); + RouteUtil.pushDialogRoute(context, + const SettingNavigationScreen(initPageIndex: 3)); } else { Navigator.pop(context); RouteUtil.pushCupertinoRoute( @@ -443,8 +443,8 @@ class BackupLogItemState extends BaseDynamicState { padding: const EdgeInsets.all(4), icon: Icon( expanded - ? Icons.keyboard_arrow_up_rounded - : Icons.keyboard_arrow_down_rounded, + ? LucideIcons.chevronUp + : LucideIcons.chevronDown, size: 16, color: ChewieTheme.labelSmall.color), onTap: () { diff --git a/lib/Screens/Setting/select_theme_screen.dart b/lib/Screens/Setting/select_theme_screen.dart index 06d34e26..8bb2325f 100644 --- a/lib/Screens/Setting/select_theme_screen.dart +++ b/lib/Screens/Setting/select_theme_screen.dart @@ -597,7 +597,7 @@ class _SelectThemeScreenState extends BaseDynamicState ), _buildAccentCircle( color: null, - icon: Icons.colorize, + icon: LucideIcons.paintBucket, label: appLocalizations.customColor, isSelected: selectedIndex == _presetAccentColors.length + 1, onTap: () { diff --git a/lib/Screens/Token/add_token_screen.dart b/lib/Screens/Token/add_token_screen.dart index 286d4561..cee2b413 100644 --- a/lib/Screens/Token/add_token_screen.dart +++ b/lib/Screens/Token/add_token_screen.dart @@ -502,8 +502,8 @@ class _AddTokenScreenState extends BaseDynamicState radius: 12, text: appLocalizations.showAdvancedInfo, icon: Icon( - Icons.keyboard_arrow_down_rounded, - color: ChewieTheme.bodySmall?.color, + LucideIcons.chevronDown, + color: ChewieTheme.bodySmall.color, ), textStyle: ChewieTheme.titleMedium, fontSizeDelta: 2, @@ -619,8 +619,8 @@ class _AddTokenScreenState extends BaseDynamicState radius: 12, text: appLocalizations.hideAdvancedInfo, icon: Icon( - Icons.keyboard_arrow_up_rounded, - color: ChewieTheme.bodySmall?.color, + LucideIcons.chevronUp, + color: ChewieTheme.bodySmall.color, ), textStyle: ChewieTheme.titleMedium, fontSizeDelta: 2, diff --git a/lib/Screens/Token/token_layout.dart b/lib/Screens/Token/token_layout.dart index 55927bbc..375d3148 100644 --- a/lib/Screens/Token/token_layout.dart +++ b/lib/Screens/Token/token_layout.dart @@ -395,7 +395,7 @@ class TokenLayoutState extends BaseDynamicState extentRatio: startExtentRatio, motion: const ScrollMotion(), onAutoTrigger: () => _processPin(), - autoTriggerThreshold: startExtentRatio * 3.0, + autoTriggerThreshold: startExtentRatio * 2.5, children: [ SlidableAction( onPressed: (context) => _processPin(), @@ -407,15 +407,15 @@ class TokenLayoutState extends BaseDynamicState borderRadius: const BorderRadius.all(Radius.circular(12)), foregroundColor: ChewieTheme.primaryColor, icon: widget.token.pinned - ? Icons.push_pin_rounded - : Icons.push_pin_outlined, + ? LucideIcons.pin + : LucideIcons.pinOff, autoTriggerIcon: widget.token.pinned - ? Icons.push_pin_outlined - : Icons.push_pin_rounded, + ? LucideIcons.pinOff + : LucideIcons.pin, label: widget.token.pinned ? appLocalizations.unPinTokenShort : appLocalizations.pinTokenShort, - simple: simple, + simple: true, spacing: 8, padding: const EdgeInsets.symmetric(horizontal: 4), iconAndTextColor: widget.token.pinned ? Colors.white : null, @@ -624,7 +624,7 @@ class TokenLayoutState extends BaseDynamicState ), padding: const EdgeInsets.all(2), child: Icon( - Icons.check, + LucideIcons.check, size: 14, color: widget.isSelected ? Colors.white : Colors.transparent, ), @@ -839,7 +839,7 @@ class TokenLayoutState extends BaseDynamicState }, padding: EdgeInsets.all(padding), icon: Icon( - Icons.refresh_rounded, + LucideIcons.rotateCw, size: 20, color: color ?? ChewieTheme.labelMedium.color, ), diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 611d4c15..36796cfe 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -500,7 +500,7 @@ class HomeScreenState extends BasePanelScreenState _buildDockItem( icon: _selectedTokenUids.length == tokens.length ? Icons.deselect - : Icons.select_all, + : LucideIcons.checkCheck, label: _selectedTokenUids.length == tokens.length ? appLocalizations.deselectAll : appLocalizations.selectAll, @@ -1202,7 +1202,7 @@ class HomeScreenState extends BasePanelScreenState onPressed: () { BottomSheetBuilder.showBottomSheet( context, - enableDrag: false, + enableDrag: true, responsive: true, (context) => AddBottomSheet( onlyShowScanner: ResponsiveUtil.isLandscapeTablet(), @@ -1378,7 +1378,7 @@ class HomeScreenState extends BasePanelScreenState CircleIconButton( tooltip: appLocalizations.cancel, icon: Icon( - Icons.arrow_back_rounded, + LucideIcons.arrowLeft, color: ChewieTheme.iconColor, ), onTap: () { @@ -1652,7 +1652,7 @@ class HomeScreenState extends BasePanelScreenState if (ResponsiveUtil.isMobile()) { BottomSheetBuilder.showBottomSheet( context, - enableDrag: false, + enableDrag: true, responsive: true, (context) => AddBottomSheet( onlyShowScanner: ResponsiveUtil.isLandscapeTablet(), diff --git a/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart b/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart index 8a3ac726..c992f57f 100644 --- a/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/import_from_third_party_bottom_sheet.dart @@ -59,8 +59,11 @@ class ImportFromThirdPartyBottomSheetState const SizedBox(width: 5), ], ), - body: EasyRefresh( - child: _buildBody(), + body: SafeArea( + top: false, + child: EasyRefresh( + child: _buildBody(), + ), ), ); } @@ -87,7 +90,7 @@ class ImportFromThirdPartyBottomSheetState if (ResponsiveUtil.isMobile()) { BottomSheetBuilder.showBottomSheet( context, - enableDrag: false, + enableDrag: true, responsive: true, (context) => const AddBottomSheet(onlyShowScanner: true), ); diff --git a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart index dcd0f8be..0666b6e7 100644 --- a/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart +++ b/lib/Widgets/BottomSheet/token_option_bottom_sheet.dart @@ -206,7 +206,7 @@ class TokenOptionBottomSheetState setState(() {}); }, icon: Icon( - Icons.refresh_rounded, + LucideIcons.refreshCcw, size: 20, color: ChewieTheme.bodyMedium.color, ), @@ -297,9 +297,7 @@ class TokenOptionBottomSheetState }, ), _buildItem( - leading: widget.token.pinned - ? Icons.push_pin_rounded - : Icons.push_pin_outlined, + leading: widget.token.pinned ? LucideIcons.pinOff : LucideIcons.pin, title: widget.token.pinned ? appLocalizations.unPinToken : appLocalizations.pinToken, diff --git a/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart b/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart index 6160dc49..97625ab5 100644 --- a/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart +++ b/lib/Widgets/cloudotp/qrcodes_dialog_widget.dart @@ -18,6 +18,7 @@ import 'dart:ui'; import 'package:awesome_chewie/awesome_chewie.dart'; import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; import 'package:pretty_qr_code/pretty_qr_code.dart'; import '../../../l10n/l10n.dart'; @@ -189,7 +190,7 @@ class QrcodesDialogWidgetState extends BaseDynamicState { fontSizeDelta: 2, disabled: currentPage <= 0, icon: Icon( - Icons.arrow_back_rounded, + LucideIcons.arrowLeft, color: currentPage <= 0 ? Colors.grey : null, ), padding: const EdgeInsets.symmetric( diff --git a/third-party/chewie/lib/src/Resources/theme.dart b/third-party/chewie/lib/src/Resources/theme.dart index c6604998..3472b998 100644 --- a/third-party/chewie/lib/src/Resources/theme.dart +++ b/third-party/chewie/lib/src/Resources/theme.dart @@ -297,7 +297,7 @@ class ChewieTheme { ? ChewieTheme.scaffoldBackgroundColor.withValues(alpha: 0.7) : const Color(0x59000000); - static const Duration animationDuration = Duration(milliseconds: 450); + static const Duration animationDuration = Duration(milliseconds: 500); static Color get iconColor => Theme.of(chewieProvider.rootContext).iconTheme.color!; diff --git a/third-party/chewie/lib/src/Screens/update_log_screen.dart b/third-party/chewie/lib/src/Screens/update_log_screen.dart index 488ef39a..4945d394 100644 --- a/third-party/chewie/lib/src/Screens/update_log_screen.dart +++ b/third-party/chewie/lib/src/Screens/update_log_screen.dart @@ -98,23 +98,24 @@ class _UpdateLogScreenState extends BaseDynamicState : ChewieTheme.scaffoldBackgroundColor, ) : null, - body: EasyRefresh( - controller: _refreshController, - refreshOnStart: true, - onRefresh: () async { - await fetchReleases(); - }, - child: ListView.builder( - padding: widget.padding - .add(const EdgeInsets.symmetric(horizontal: 4, vertical: 10)) - .add(EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom)), - itemBuilder: (context, index) => _buildItem( - releaseItems[index], - index, - index == releaseItems.length - 1, + body: SafeArea( + top: false, + child: EasyRefresh( + controller: _refreshController, + refreshOnStart: true, + onRefresh: () async { + await fetchReleases(); + }, + child: ListView.builder( + padding: widget.padding + .add(const EdgeInsets.symmetric(horizontal: 4, vertical: 10)), + itemBuilder: (context, index) => _buildItem( + releaseItems[index], + index, + index == releaseItems.length - 1, + ), + itemCount: releaseItems.length, ), - itemCount: releaseItems.length, ), ), ); @@ -165,117 +166,115 @@ class _UpdateLogScreenState extends BaseDynamicState const SizedBox(width: 13), Expanded( child: Container( - margin: const EdgeInsets.only(bottom: 10), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: ChewieTheme.canvasColor, - borderRadius: ChewieDimens.borderRadius12, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: accent.withAlpha(30), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - isLatest - ? LucideIcons.sparkles - : LucideIcons.tag, - size: 15, - color: accent, + margin: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: ChewieTheme.canvasColor, + borderRadius: ChewieDimens.borderRadius12, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: accent.withAlpha(30), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + isLatest ? LucideIcons.sparkles : LucideIcons.tag, + size: 15, + color: accent, + ), ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - item.tagName, - style: ChewieTheme.bodyMedium.copyWith( - fontWeight: FontWeight.w600, - ), - ), - if (isCurrent) ...[ - const SizedBox(width: 6), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 1), - decoration: BoxDecoration( - color: accent.withAlpha(25), - borderRadius: BorderRadius.circular(10), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + item.tagName, + style: ChewieTheme.bodyMedium.copyWith( + fontWeight: FontWeight.w600, ), - child: Text( - chewieLocalizations.currentVersion, - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w600, - color: accent, + ), + if (isCurrent) ...[ + const SizedBox(width: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 1), + decoration: BoxDecoration( + color: accent.withAlpha(25), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + chewieLocalizations.currentVersion, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: accent, + ), ), ), - ), + ], ], - ], - ), - if (releaseDate.isNotEmpty) ...[ - const SizedBox(height: 2), - Text( - releaseDate, - style: ChewieTheme.bodySmall.copyWith( - color: ChewieTheme.bodyMedium.color - ?.withAlpha(120), - fontSize: 11, - ), ), + if (releaseDate.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + releaseDate, + style: ChewieTheme.bodySmall.copyWith( + color: ChewieTheme.bodyMedium.color + ?.withAlpha(120), + fontSize: 11, + ), + ), + ], ], - ], - ), - ), - CircleIconButton( - icon: Icon( - LucideIcons.externalLink, - size: 14, - color: ChewieTheme.iconColor, + ), ), - onTap: () { - UriUtil.launchUrlUri(context, item.htmlUrl); - }, - ), - ], - ), - if ((item.body ?? "").isNotEmpty) ...[ - Padding( - padding: const EdgeInsets.only(top: 10), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: ChewieTheme.scaffoldBackgroundColor, - borderRadius: ChewieDimens.borderRadius8, + CircleIconButton( + icon: Icon( + LucideIcons.externalLink, + size: 14, + color: ChewieTheme.iconColor, + ), + onTap: () { + UriUtil.launchUrlUri(context, item.htmlUrl); + }, ), - child: SelectableAreaWrapper( - focusNode: FocusNode(), - child: CustomMarkdownWidget( - item.body ?? "", - baseStyle: ChewieTheme.bodySmall, + ], + ), + if ((item.body ?? "").isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: ChewieTheme.scaffoldBackgroundColor, + borderRadius: ChewieDimens.borderRadius8, + ), + child: SelectableAreaWrapper( + focusNode: FocusNode(), + child: CustomMarkdownWidget( + item.body ?? "", + baseStyle: ChewieTheme.bodySmall, + ), ), ), ), - ), + ], ], - ], + ), ), ), - ), ], ), ], diff --git a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart index 0da6d04a..25ab2ed4 100644 --- a/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart +++ b/third-party/chewie/lib/src/Widgets/Module/FlutterSlidable/src/gesture_detector.dart @@ -6,7 +6,7 @@ import 'controller.dart'; // INTERNAL USE // ignore_for_file: public_member_api_docs -const double _kDragDampingFactor = 0.6; +const double _kDragDampingFactor = 0.8; class SlidableGestureDetector extends StatefulWidget { const SlidableGestureDetector({ From 971a217b291063dfc7dd5fdafe2faa009b15e963 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 May 2026 18:00:01 +0800 Subject: [PATCH 36/36] feat: Comment out Linux-arm build steps in release workflow --- .github/workflows/release.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73718559..0baceeaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,12 +37,12 @@ jobs: artifact_path: | build/linux/*.deb build/linux/*.tar.gz - - target: Linux-arm - os: ubuntu-22.04-arm - artifact_name: release-Linux-arm - artifact_path: | - build/linux/*.deb - build/linux/*.tar.gz + # - target: Linux-arm + # os: ubuntu-22.04-arm + # artifact_name: release-Linux-arm + # artifact_path: | + # build/linux/*.deb + # build/linux/*.tar.gz - target: iOS os: macos-latest cache_pod_key: ios-pods @@ -267,8 +267,8 @@ jobs: mv /tmp/artifacts/release-Windows/*.exe /tmp/artifacts/final/ mv /tmp/artifacts/release-Linux/*.deb /tmp/artifacts/final/ mv /tmp/artifacts/release-Linux/*.tar.gz /tmp/artifacts/final/ - mv /tmp/artifacts/release-Linux-arm/*.deb /tmp/artifacts/final/ - mv /tmp/artifacts/release-Linux-arm/*.tar.gz /tmp/artifacts/final/ + # mv /tmp/artifacts/release-Linux-arm/*.deb /tmp/artifacts/final/ + # mv /tmp/artifacts/release-Linux-arm/*.tar.gz /tmp/artifacts/final/ mv /tmp/artifacts/release-iOS/*.ipa /tmp/artifacts/final/ 2>/dev/null || true mv /tmp/artifacts/release-macOS/*.dmg /tmp/artifacts/final/ 2>/dev/null || true