diff --git a/lib/Database/database_manager.dart b/lib/Database/database_manager.dart index 66f6aa95..c4b16a67 100644 --- a/lib/Database/database_manager.dart +++ b/lib/Database/database_manager.dart @@ -74,7 +74,7 @@ class DatabaseManager { _currentDbFactory = cipherDbFactory; password = await HiveUtil.regeneratePassword(); appProvider.currentDatabasePassword = password; - ILogger.info("Database not exist and new password is $password"); + ILogger.info("Database not exist and new password is generated"); await HiveUtil.setEncryptDatabaseStatus( EncryptDatabaseStatus.defaultPassword); } diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart index 85990078..221f357c 100644 --- a/lib/Screens/home_screen.dart +++ b/lib/Screens/home_screen.dart @@ -327,6 +327,7 @@ class HomeScreenState extends State with TickerProviderStateMixin { floatingActionButton: ResponsiveUtil.isDesktop() ? null : _buildFloatingActionButton(), floatingActionButtonLocation: FloatingActionButtonLocation.endContained, + extendBody: true, ); } @@ -675,79 +676,87 @@ class HomeScreenState extends State with TickerProviderStateMixin { Widget gridView = Selector( selector: (context, provider) => provider.dragToReorder, builder: (context, dragToReorder, child) => Selector( - selector: (context, provider) => provider.hideProgressBar, - builder: (context, hideProgressBar, child) => - ReorderableGridView.builder( - // controller: _scrollController, - gridItemsNotifier: gridItemsNotifier, - autoScroll: true, - physics: const AlwaysScrollableScrollPhysics(), - padding: - const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10), - gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: layoutType.maxCrossAxisExtent, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - preferredHeight: layoutType.getHeight(hideProgressBar), + 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: Theme.of(rootContext).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, + ); + }, ), - 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: Theme.of(rootContext).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, - ); - }, ), ), ); diff --git a/lib/TokenUtils/export_token_util.dart b/lib/TokenUtils/export_token_util.dart index 4b593417..e15f0ab0 100644 --- a/lib/TokenUtils/export_token_util.dart +++ b/lib/TokenUtils/export_token_util.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'package:cloudotp/Database/auto_backup_log_dao.dart'; import 'package:cloudotp/Database/category_dao.dart'; @@ -484,6 +485,7 @@ class ExportTokenUtil { } List tokenQrcodes = []; int passCount = 0; + List payloads = []; try { List tokens = await TokenDao.listTokens(); OtpMigrationPayload payload = OtpMigrationPayload.create(); @@ -496,14 +498,22 @@ class ExportTokenUtil { payload.otpParameters.add(token.toOtpMigrationParameters()); String currentRes = base64Encode(payload.writeToBuffer()); if (currentRes.bytesLength > maxBytesLength) { - tokenQrcodes.add(preRes); preRes = currentRes = ""; + payloads.add(payload); payload = OtpMigrationPayload.create(); } else { preRes = currentRes; } } - if (preRes.isNotEmpty) tokenQrcodes.add(preRes); + if (preRes.isNotEmpty) payloads.add(payload); + int batchId = Random().nextInt(1000000000) * -1; + for (OtpMigrationPayload payload in payloads) { + payload.batchSize = payloads.length; + payload.batchIndex = payloads.indexOf(payload); + payload.batchId = batchId; + payload.version = 1; + tokenQrcodes.add(base64Encode(payload.writeToBuffer())); + } tokenQrcodes = tokenQrcodes .map((e) => "otpauth-migration://offline?data=${Uri.encodeComponent(e)}") @@ -526,9 +536,12 @@ class ExportTokenUtil { if (showLoading) { CustomLoadingDialog.showLoading(title: S.current.exporting); } - List tokenQrcodes = []; - List categoryQrcodes = []; + List qrcodes = []; + List payloads = []; + List categoryPayloads = []; + int batchId = Random().nextInt(1000000000) * -1; try { + //Tokens List tokens = await TokenDao.listTokens(); CloudOtpTokenPayload payload = CloudOtpTokenPayload.create(); String preRes = ""; @@ -536,18 +549,15 @@ class ExportTokenUtil { payload.tokenParameters.add(token.toCloudOtpTokenParameters()); String currentRes = base64Encode(payload.writeToBuffer()); if (currentRes.bytesLength > maxBytesLength) { - tokenQrcodes.add(preRes); + payloads.add(payload); preRes = currentRes = ""; payload = CloudOtpTokenPayload.create(); } else { preRes = currentRes; } } - if (preRes.isNotEmpty) tokenQrcodes.add(preRes); - tokenQrcodes = tokenQrcodes - .map((e) => - "cloudotpauth-migration://offline?tokens=${Uri.encodeComponent(e)}") - .toList(); + if (preRes.isNotEmpty) payloads.add(payload); + //Categories List categories = await CategoryDao.listCategories(); TokenCategoryPayload categoryPayload = TokenCategoryPayload.create(); preRes = ""; @@ -557,20 +567,32 @@ class ExportTokenUtil { categoryPayload.categoryParameters.add(parameters); String currentRes = base64Encode(categoryPayload.writeToBuffer()); if (currentRes.bytesLength > maxBytesLength) { - categoryQrcodes.add(preRes); + categoryPayloads.add(categoryPayload); preRes = currentRes = ""; categoryPayload = TokenCategoryPayload.create(); } else { preRes = currentRes; } } - if (preRes.isNotEmpty) categoryQrcodes.add(preRes); - categoryQrcodes = categoryQrcodes - .map((e) => - "cloudotpauth-migration://offline?categories=${Uri.encodeComponent(e)}") - .toList(); - tokenQrcodes.addAll(categoryQrcodes); - return tokenQrcodes; + if (preRes.isNotEmpty) categoryPayloads.add(categoryPayload); + for (CloudOtpTokenPayload payload in payloads) { + payload.version = 1; + payload.batchSize = payloads.length + categoryPayloads.length; + payload.batchIndex = payloads.indexOf(payload); + payload.batchId = batchId; + qrcodes.add( + "cloudotpauth-migration://offline?tokens=${Uri.encodeComponent(base64Encode(payload.writeToBuffer()))}"); + } + for (TokenCategoryPayload payload in categoryPayloads) { + payload.version = 1; + payload.batchSize = payloads.length + categoryPayloads.length; + payload.batchIndex = + payloads.length + categoryPayloads.indexOf(payload); + payload.batchId = batchId; + qrcodes.add( + "cloudotpauth-migration://offline?categories=${Uri.encodeComponent(base64Encode(payload.writeToBuffer()))}"); + } + return qrcodes; } catch (e, t) { ILogger.error("Failed to export data to qrcodes", e, t); return null; diff --git a/lib/TokenUtils/otp_token_parser.dart b/lib/TokenUtils/otp_token_parser.dart index a62e3c5e..1f8a49a7 100644 --- a/lib/TokenUtils/otp_token_parser.dart +++ b/lib/TokenUtils/otp_token_parser.dart @@ -23,14 +23,16 @@ class OtpTokenParser { labelAndIssuer = token.account; } String uriText = - "otpauth://${token.tokenType.authority}/$labelAndIssuer?secret=${token.secret}&algorithm=${token.algorithm.label}&digits=${token.digits.digit}&period=${token.period}"; + "otpauth://${token.tokenType.authority}/$labelAndIssuer?secret=${token + .secret}&algorithm=${token.algorithm.label}&digits=${token.digits + .digit}&period=${token.period}"; switch (token.tokenType) { case OtpTokenType.HOTP: uriText += "&counter=${token.counter + 1}"; break; case OtpTokenType.MOTP: uriText += - "motp://$labelAndIssuer?secret=${token.secret}&pin=${token.pin}"; + "motp://$labelAndIssuer?secret=${token.secret}&pin=${token.pin}"; case OtpTokenType.Yandex: uriText += "&pin=${token.pin}"; case OtpTokenType.TOTP: @@ -215,7 +217,7 @@ class OtpTokenParser { rawData = rawData.padRight(nextFactor, '='); } OtpMigrationPayload payload = - OtpMigrationPayload.fromBuffer(base64Decode(rawData)); + OtpMigrationPayload.fromBuffer(base64Decode(rawData)); List tokens = []; for (var param in payload.otpParameters) { OtpToken? token = OtpToken.fromOtpMigrationParameters(param); @@ -237,7 +239,7 @@ class OtpTokenParser { rawData = rawData.padRight(nextFactor, '='); } CloudOtpTokenPayload payload = - CloudOtpTokenPayload.fromBuffer(base64Decode(rawData)); + CloudOtpTokenPayload.fromBuffer(base64Decode(rawData)); List tokens = []; for (var param in payload.tokenParameters) { OtpToken token = OtpToken.fromCloudOtpParameters(param); @@ -259,7 +261,7 @@ class OtpTokenParser { rawData = rawData.padRight(nextFactor, '='); } TokenCategoryPayload payload = - TokenCategoryPayload.fromBuffer(base64Decode(rawData)); + TokenCategoryPayload.fromBuffer(base64Decode(rawData)); List categories = []; for (var param in payload.categoryParameters) { TokenCategory category = TokenCategory.fromCategoryParameters(param); @@ -305,9 +307,9 @@ class OtpAuthMigrationData { String username = reader.readString(); String issuer = reader.readString(); OtpAuthMigrationDataAlgorithm algorithm = - OtpAuthMigrationDataAlgorithm.values[reader.readInt32()]; + OtpAuthMigrationDataAlgorithm.values[reader.readInt32()]; OtpAuthMigrationDataType type = - OtpAuthMigrationDataType.values[reader.readInt32()]; + OtpAuthMigrationDataType.values[reader.readInt32()]; int counter = reader.readInt32(); return OtpAuthMigrationData( secret: secret, diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index eb34d02a..812790a8 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -361,7 +361,7 @@ class Utils { if (showLoading) { CustomLoadingDialog.showLoading(title: S.current.checkingUpdates); } - String currentVersion ="0.0.0"?? (await PackageInfo.fromPlatform()).version; + String currentVersion = (await PackageInfo.fromPlatform()).version; onGetCurrentVersion?.call(currentVersion); String latestVersion = "0.0.0"; await GithubApi.getReleases("Robert-Stackflow", "CloudOTP") diff --git a/pubspec.lock b/pubspec.lock index baacb2a8..2bfe3a4f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1161,10 +1161,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: b8c0e9afcfd52534f85ec666f3d52156f560b5e6c25b1e3d4fe2087763607926 + sha256: "82c9beb863705831a779e02e80398e61a86a48d1fcfdf4241ebd4292605acd9b" url: "https://pub.flutter-io.cn" source: hosted - version: "5.1.1" + version: "5.2.2" modal_bottom_sheet: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 09bf086d..29664583 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: cloudotp -version: 2.4.1+241 +version: 2.4.2+242 description: An awesome two-factor authenticator which supports cloud storage and multiple platforms. publish_to: none @@ -31,7 +31,7 @@ dependencies: # 二维码 image: ^4.2.0 # 图片 zxing2: ^0.2.3 # 二维码 - mobile_scanner: ^5.1.1 # 扫码 + mobile_scanner: ^5.2.2 # 扫码 pretty_qr_code: ^3.3.0 # 二维码 screen_capturer: path: third-party/screen_capturer_lib/screen_capturer diff --git a/tools/CloudOTP.iss b/tools/CloudOTP.iss index 89798852..f7f5e821 100644 --- a/tools/CloudOTP.iss +++ b/tools/CloudOTP.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "CloudOTP" -#define MyAppVersion "2.4.1" +#define MyAppVersion "2.4.2" #define MyAppPublisher "Cloudchewie" #define MyAppURL "https://apps.cloudchewie.com/cloudotp" #define MyAppExeName "CloudOTP.exe"