diff --git a/.gitignore b/.gitignore
index 0470ce32..e8d80147 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,5 @@ app.*.map.json
cloudotp.db
/assets/fonts/**
+
+/releases/**
diff --git a/README.md b/README.md
index 8ab8d079..4a314c4f 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,8 @@ This is an awesome two-factor authenticator based on Flutter for Android and Win
## Screenshots
-
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/README_CN.md b/README_CN.md
index 58a17b3f..65b95fe2 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -15,8 +15,8 @@
## Screenshots
-
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5813fb20..7483464f 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -75,4 +75,5 @@ dependencies {
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'
}
diff --git a/assets/brand/WPS.png b/assets/brand/WPS.png
new file mode 100644
index 00000000..20960451
Binary files /dev/null and b/assets/brand/WPS.png differ
diff --git a/assets/brand/kingsoft.png b/assets/brand/kingsoft.png
new file mode 100644
index 00000000..20960451
Binary files /dev/null and b/assets/brand/kingsoft.png differ
diff --git "a/assets/brand/\345\215\216\344\270\272.png" "b/assets/brand/\345\215\216\344\270\272.png"
new file mode 100644
index 00000000..d5675f9a
Binary files /dev/null and "b/assets/brand/\345\215\216\344\270\272.png" differ
diff --git "a/assets/brand/\350\205\276\350\256\257\344\272\221.png" "b/assets/brand/\350\205\276\350\256\257\344\272\221.png"
new file mode 100644
index 00000000..fc831156
Binary files /dev/null and "b/assets/brand/\350\205\276\350\256\257\344\272\221.png" differ
diff --git "a/assets/brand/\351\207\221\345\261\261.png" "b/assets/brand/\351\207\221\345\261\261.png"
new file mode 100644
index 00000000..20960451
Binary files /dev/null and "b/assets/brand/\351\207\221\345\261\261.png" differ
diff --git a/devtools_options.yaml b/devtools_options.yaml
deleted file mode 100644
index 7e7e7f67..00000000
--- a/devtools_options.yaml
+++ /dev/null
@@ -1 +0,0 @@
-extensions:
diff --git a/lib/Models/cloud_service_config.dart b/lib/Models/cloud_service_config.dart
index f6c672a5..c78f0d89 100644
--- a/lib/Models/cloud_service_config.dart
+++ b/lib/Models/cloud_service_config.dart
@@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:cloudotp/TokenUtils/Cloud/cloud_service.dart';
import 'package:cloudotp/TokenUtils/Cloud/googledrive_cloud_service.dart';
-import 'package:cloudotp/Utils/app_provider.dart';
import 'package:cloudotp/Utils/cache_util.dart';
import '../TokenUtils/Cloud/dropbox_cloud_service.dart';
@@ -18,7 +17,8 @@ enum CloudServiceType {
OneDrive,
GoogleDrive,
Dropbox,
- S3Cloud,HuaweiCloud;
+ S3Cloud,
+ HuaweiCloud;
String get label {
switch (this) {
@@ -40,6 +40,15 @@ enum CloudServiceType {
static List toStrings() {
return CloudServiceType.values.map((e) => e.label).toList();
}
+
+ static List toEnableStrings() {
+ return [
+ CloudServiceType.OneDrive.label,
+ CloudServiceType.Dropbox.label,
+ CloudServiceType.Webdav.label,
+ CloudServiceType.S3Cloud.label,
+ ];
+ }
}
extension CloudServiceTypeExtensionOnint on int {
@@ -107,13 +116,13 @@ class CloudServiceConfig {
case CloudServiceType.Webdav:
return WebDavCloudService(this);
case CloudServiceType.GoogleDrive:
- return GoogleDriveCloudService(rootContext, this);
+ return GoogleDriveCloudService(this);
case CloudServiceType.OneDrive:
- return OneDriveCloudService(rootContext, this);
+ return OneDriveCloudService(this);
case CloudServiceType.Dropbox:
- return DropboxCloudService(rootContext, this);
+ return DropboxCloudService(this);
case CloudServiceType.HuaweiCloud:
- return HuaweiCloudService(rootContext, this);
+ return HuaweiCloudService(this);
case CloudServiceType.S3Cloud:
return S3CloudService(this);
}
diff --git a/lib/Screens/Backup/cloud_service_screen.dart b/lib/Screens/Backup/cloud_service_screen.dart
index a5c36997..454200ac 100644
--- a/lib/Screens/Backup/cloud_service_screen.dart
+++ b/lib/Screens/Backup/cloud_service_screen.dart
@@ -80,12 +80,10 @@ class _CloudServiceScreenState extends State
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
children: const [
- WebDavServiceScreen(),
OneDriveServiceScreen(),
- GoogleDriveServiceScreen(),
DropboxServiceScreen(),
+ WebDavServiceScreen(),
S3CloudServiceScreen(),
- HuaweiCloudServiceScreen(),
],
),
),
@@ -101,14 +99,13 @@ class _CloudServiceScreenState extends State
context: context,
topRadius: true,
bottomRadius: true,
- padding: const EdgeInsets.only(right: 10),
child: Column(
children: [
ItemBuilder.buildGroupTile(
context: context,
controller: _typeController,
constraintWidth: false,
- buttons: CloudServiceType.toStrings(),
+ buttons: CloudServiceType.toEnableStrings(),
onSelected: (value, index, isSelected) {
setState(() {
_currentType = index.toCloudServiceType;
diff --git a/lib/Screens/Backup/dropbox_service_screen.dart b/lib/Screens/Backup/dropbox_service_screen.dart
index ffab3218..de41e0fa 100644
--- a/lib/Screens/Backup/dropbox_service_screen.dart
+++ b/lib/Screens/Backup/dropbox_service_screen.dart
@@ -61,7 +61,6 @@ class _DropboxServiceScreenState extends State
_accountController.text = _dropboxCloudServiceConfig!.account ?? "";
_emailController.text = _dropboxCloudServiceConfig!.email ?? "";
_dropboxCloudService = DropboxCloudService(
- context,
_dropboxCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -70,7 +69,6 @@ class _DropboxServiceScreenState extends State
CloudServiceConfig.init(type: CloudServiceType.Dropbox);
await CloudServiceConfigDao.insertConfig(_dropboxCloudServiceConfig!);
_dropboxCloudService = DropboxCloudService(
- context,
_dropboxCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -87,7 +85,7 @@ class _DropboxServiceScreenState extends State
updateConfig(_dropboxCloudServiceConfig!);
}
inited = true;
- setState(() {});
+ if (mounted) setState(() {});
}
updateConfig(CloudServiceConfig config) {
@@ -322,6 +320,8 @@ class _DropboxServiceScreenState extends State
text: S.current.webDavLogout,
fontSizeDelta: 2,
onTap: () async {
+ CustomLoadingDialog.showLoading(
+ title: S.current.webDavLoggingOut);
await _dropboxCloudService!.signOut();
setState(() {
_dropboxCloudServiceConfig!.connected = false;
@@ -332,6 +332,8 @@ class _DropboxServiceScreenState extends State
_dropboxCloudServiceConfig!.usedSize = -1;
updateConfig(_dropboxCloudServiceConfig!);
});
+ CustomLoadingDialog.dismissLoading();
+ IToast.show(S.current.webDavLogoutSuccess);
},
),
),
diff --git a/lib/Screens/Backup/googledrive_service_screen.dart b/lib/Screens/Backup/googledrive_service_screen.dart
index 8ee5ee6a..4e431102 100644
--- a/lib/Screens/Backup/googledrive_service_screen.dart
+++ b/lib/Screens/Backup/googledrive_service_screen.dart
@@ -63,7 +63,6 @@ class _GoogleDriveServiceScreenState extends State
_accountController.text = _googledriveCloudServiceConfig!.account ?? "";
_emailController.text = _googledriveCloudServiceConfig!.email ?? "";
_googledriveCloudService = GoogleDriveCloudService(
- context,
_googledriveCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -72,7 +71,6 @@ class _GoogleDriveServiceScreenState extends State
CloudServiceConfig.init(type: CloudServiceType.GoogleDrive);
await CloudServiceConfigDao.insertConfig(_googledriveCloudServiceConfig!);
_googledriveCloudService = GoogleDriveCloudService(
- context,
_googledriveCloudServiceConfig!,
onConfigChanged: updateConfig,
);
diff --git a/lib/Screens/Backup/huawei_service_screen.dart b/lib/Screens/Backup/huawei_service_screen.dart
index f573a94c..a089d52c 100644
--- a/lib/Screens/Backup/huawei_service_screen.dart
+++ b/lib/Screens/Backup/huawei_service_screen.dart
@@ -59,7 +59,6 @@ class _HuaweiCloudServiceScreenState extends State
_sizeController.text = _huaweiCloudCloudServiceConfig!.size;
_accountController.text = _huaweiCloudCloudServiceConfig!.account ?? "";
_huaweiCloudCloudService = HuaweiCloudService(
- context,
_huaweiCloudCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -68,7 +67,6 @@ class _HuaweiCloudServiceScreenState extends State
CloudServiceConfig.init(type: CloudServiceType.HuaweiCloud);
await CloudServiceConfigDao.insertConfig(_huaweiCloudCloudServiceConfig!);
_huaweiCloudCloudService = HuaweiCloudService(
- context,
_huaweiCloudCloudServiceConfig!,
onConfigChanged: updateConfig,
);
diff --git a/lib/Screens/Backup/onedrive_service_screen.dart b/lib/Screens/Backup/onedrive_service_screen.dart
index 1424af61..066c9329 100644
--- a/lib/Screens/Backup/onedrive_service_screen.dart
+++ b/lib/Screens/Backup/onedrive_service_screen.dart
@@ -62,7 +62,6 @@ class _OneDriveServiceScreenState extends State
_accountController.text = _oneDriveCloudServiceConfig!.account ?? "";
_emailController.text = _oneDriveCloudServiceConfig!.email ?? "";
_oneDriveCloudService = OneDriveCloudService(
- context,
_oneDriveCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -71,7 +70,6 @@ class _OneDriveServiceScreenState extends State
CloudServiceConfig.init(type: CloudServiceType.OneDrive);
await CloudServiceConfigDao.insertConfig(_oneDriveCloudServiceConfig!);
_oneDriveCloudService = OneDriveCloudService(
- context,
_oneDriveCloudServiceConfig!,
onConfigChanged: updateConfig,
);
@@ -335,6 +333,7 @@ class _OneDriveServiceScreenState extends State
text: S.current.webDavLogout,
fontSizeDelta: 2,
onTap: () async {
+ CustomLoadingDialog.showLoading(title: S.current.webDavLoggingOut);
await _oneDriveCloudService!.signOut();
setState(() {
_oneDriveCloudServiceConfig!.connected = false;
@@ -345,6 +344,8 @@ class _OneDriveServiceScreenState extends State
_oneDriveCloudServiceConfig!.usedSize = -1;
updateConfig(_oneDriveCloudServiceConfig!);
});
+ CustomLoadingDialog.dismissLoading();
+ IToast.show(S.current.webDavLogoutSuccess);
},
),
),
diff --git a/lib/Screens/Setting/about_setting_screen.dart b/lib/Screens/Setting/about_setting_screen.dart
index 8bd41ff4..753c3f86 100644
--- a/lib/Screens/Setting/about_setting_screen.dart
+++ b/lib/Screens/Setting/about_setting_screen.dart
@@ -203,9 +203,31 @@ class _AboutSettingScreenState extends State
UriUtil.launchUrlUri(context, repoUrl);
},
showLeading: true,
- bottomRadius: true,
leading: Icons.commit_outlined,
),
+ ItemBuilder.buildEntryItem(
+ context: context,
+ title: S.current.privacyPolicy,
+ onTap: () {
+ Locale locale = Localizations.localeOf(context);
+ UriUtil.launchUrlUri(
+ context, privacyPolicyUrl + locale.languageCode);
+ },
+ showLeading: true,
+ leading: Icons.privacy_tip_outlined,
+ ),
+ ItemBuilder.buildEntryItem(
+ context: context,
+ title: S.current.serviceTerm,
+ onTap: () {
+ Locale locale = Localizations.localeOf(context);
+ UriUtil.launchUrlUri(
+ context, serviceTermUrl + locale.languageCode);
+ },
+ showLeading: true,
+ bottomRadius: true,
+ leading: Icons.topic_outlined,
+ ),
const SizedBox(height: 10),
ItemBuilder.buildEntryItem(
topRadius: true,
diff --git a/lib/Screens/home_screen.dart b/lib/Screens/home_screen.dart
index b627fdc5..f438cc08 100644
--- a/lib/Screens/home_screen.dart
+++ b/lib/Screens/home_screen.dart
@@ -90,8 +90,8 @@ class HomeScreenState extends State with TickerProviderStateMixin {
@override
void initState() {
super.initState();
- initAppName();
initTab(true);
+ initAppName();
refresh(true);
_searchController.addListener(() {
performSearch(_searchController.text);
@@ -741,6 +741,19 @@ class HomeScreenState extends State with TickerProviderStateMixin {
}
_buildTab(TokenCategory? category) {
+ // {
+ // bool normalUserBold = false,
+ // bool sameFontSize = false,
+ // double fontSizeDelta = 0,
+ // }) {
+ // TextStyle normalStyle = Theme.of(context).textTheme.titleLarge!.apply(
+ // color: Colors.grey,
+ // fontSizeDelta: fontSizeDelta - (sameFontSize ? 0 : 1),
+ // fontWeightDelta: normalUserBold ? 0 : -2,
+ // );
+ // TextStyle selectedStyle = Theme.of(context).textTheme.titleLarge!.apply(
+ // fontSizeDelta: fontSizeDelta + (sameFontSize ? 0 : 1),
+ // );
return Tab(
child: ContextMenuRegion(
behavior: ResponsiveUtil.isDesktop()
@@ -757,8 +770,19 @@ class HomeScreenState extends State with TickerProviderStateMixin {
);
}
},
+ // child: AnimatedDefaultTextStyle(
+ // style: (category == null
+ // ? _currentTabIndex == 0
+ // : currentCategoryId == category.id)
+ // ? selectedStyle
+ // : normalStyle,
+ // duration: const Duration(milliseconds: 100),
+ // child: Container(
+ // alignment: Alignment.center,
child: Text(category?.title ?? S.current.allTokens),
),
+ // ),
+ // ),
),
);
}
diff --git a/lib/TokenUtils/Cloud/dropbox_cloud_service.dart b/lib/TokenUtils/Cloud/dropbox_cloud_service.dart
index 0701e83d..9c8d2977 100644
--- a/lib/TokenUtils/Cloud/dropbox_cloud_service.dart
+++ b/lib/TokenUtils/Cloud/dropbox_cloud_service.dart
@@ -1,13 +1,11 @@
import 'dart:typed_data';
-import 'package:flutter/cupertino.dart';
import 'package:flutter_cloud/dropbox.dart';
import 'package:flutter_cloud/dropbox_response.dart';
import 'package:path/path.dart';
import '../../Models/cloud_service_config.dart';
import '../../Utils/hive_util.dart';
-import '../../generated/l10n.dart';
import '../export_token_util.dart';
import 'cloud_service.dart';
@@ -22,11 +20,9 @@ class DropboxCloudService extends CloudService {
static const String _dropboxPath = '/';
final CloudServiceConfig _config;
late Dropbox dropbox;
- late BuildContext context;
Function(CloudServiceConfig)? onConfigChanged;
DropboxCloudService(
- this.context,
this._config, {
this.onConfigChanged,
}) {
@@ -46,10 +42,7 @@ class DropboxCloudService extends CloudService {
Future authenticate() async {
bool isAuthorized = await dropbox.isConnected();
if (!isAuthorized) {
- isAuthorized = await dropbox.connect(
- context,
- windowName: S.current.cloudTypeDropboxAuthenticateWindowName,
- );
+ isAuthorized = await dropbox.connect();
}
if (isAuthorized) {
DropboxUserInfo? info = await fetchInfo();
diff --git a/lib/TokenUtils/Cloud/googledrive_cloud_service.dart b/lib/TokenUtils/Cloud/googledrive_cloud_service.dart
index 830eb586..585830ed 100644
--- a/lib/TokenUtils/Cloud/googledrive_cloud_service.dart
+++ b/lib/TokenUtils/Cloud/googledrive_cloud_service.dart
@@ -1,12 +1,10 @@
import 'dart:typed_data';
-import 'package:flutter/cupertino.dart';
import 'package:flutter_cloud/googledrive.dart';
import 'package:flutter_cloud/googledrive_response.dart';
import '../../Models/cloud_service_config.dart';
import '../../Utils/hive_util.dart';
-import '../../generated/l10n.dart';
import '../export_token_util.dart';
import 'cloud_service.dart';
@@ -17,19 +15,17 @@ class GoogleDriveCloudService extends CloudService {
'https://apps.cloudchewie.com/oauth/cloudotp/googledrive/callback';
static const String _callbackUrl = 'cloudotp://auth/googledrive/callback';
static const String _clientId =
- '631913875304-g9qfaoauakvbsuhqiqu85thlbg1ddep5.apps.googleusercontent.com';
- static const String _clientSecret = "XXXXXXXXXXXXXXXXXXXX";
+ '547353482361-fi716v2qnfvh3aj515ok1r4cdqqhdqbh.apps.googleusercontent.com';
static const String _googledrivePath = '/CloudOTP';
static const String _googledrivePathName = 'CloudOTP';
final CloudServiceConfig _config;
late GoogleDrive googledrive;
- late BuildContext context;
Function(CloudServiceConfig)? onConfigChanged;
- GoogleDriveCloudService(this.context,
- this._config, {
- this.onConfigChanged,
- }) {
+ GoogleDriveCloudService(
+ this._config, {
+ this.onConfigChanged,
+ }) {
init();
}
@@ -38,7 +34,6 @@ class GoogleDriveCloudService extends CloudService {
googledrive = GoogleDrive(
redirectUrl: _callbackUrl,
callbackUrl: _callbackUrl,
- clientSecret:_clientSecret,
clientId: _clientId,
);
}
@@ -47,10 +42,7 @@ class GoogleDriveCloudService extends CloudService {
Future authenticate() async {
bool isAuthorized = await googledrive.isConnected();
if (!isAuthorized) {
- isAuthorized = await googledrive.connect(
- context,
- windowName: S.current.cloudTypeGoogleDriveAuthenticateWindowName,
- );
+ isAuthorized = await googledrive.connect();
}
if (isAuthorized) {
await fetchInfo();
@@ -104,7 +96,8 @@ class GoogleDriveCloudService extends CloudService {
}
@override
- Future downloadFile(String path, {
+ Future downloadFile(
+ String path, {
Function(int p1, int p2)? onProgress,
}) async {
GoogleDriveResponse response = await googledrive.pullById(path);
@@ -140,10 +133,11 @@ class GoogleDriveCloudService extends CloudService {
}
@override
- Future uploadFile(String fileName,
- Uint8List fileData, {
- Function(int p1, int p2)? onProgress,
- }) async {
+ Future uploadFile(
+ String fileName,
+ Uint8List fileData, {
+ Function(int p1, int p2)? onProgress,
+ }) async {
GoogleDriveResponse response = await googledrive.push(
fileData,
fileName,
diff --git a/lib/TokenUtils/Cloud/huawei_cloud_service.dart b/lib/TokenUtils/Cloud/huawei_cloud_service.dart
index 46116f9a..0b0d666d 100644
--- a/lib/TokenUtils/Cloud/huawei_cloud_service.dart
+++ b/lib/TokenUtils/Cloud/huawei_cloud_service.dart
@@ -1,12 +1,10 @@
import 'dart:typed_data';
-import 'package:flutter/cupertino.dart';
import 'package:flutter_cloud/huaweicloud.dart';
import 'package:flutter_cloud/huaweicloud_response.dart';
import '../../Models/cloud_service_config.dart';
import '../../Utils/hive_util.dart';
-import '../../generated/l10n.dart';
import '../export_token_util.dart';
import 'cloud_service.dart';
@@ -17,16 +15,13 @@ class HuaweiCloudService extends CloudService {
'https://apps.cloudchewie.com/oauth/cloudotp/huaweicloud/callback';
static const String _callbackUrl = "cloudotp://auth/huaweicloud/callback";
static const String _clientId = '111829035';
- static const String _clientSecret = 'XXXXXXXXXXXXXXXXXXXXX';
static const String _huaweiCloudEmptyPath = '';
static const String _huaweiCloudPath = 'CloudOTP';
final CloudServiceConfig _config;
late HuaweiCloud huaweiCloud;
- late BuildContext context;
Function(CloudServiceConfig)? onConfigChanged;
HuaweiCloudService(
- this.context,
this._config, {
this.onConfigChanged,
}) {
@@ -39,7 +34,6 @@ class HuaweiCloudService extends CloudService {
redirectUrl: _redirectUrl,
callbackUrl: _callbackUrl,
clientId: _clientId,
- clientSecret: _clientSecret,
);
}
@@ -47,10 +41,7 @@ class HuaweiCloudService extends CloudService {
Future authenticate() async {
bool isAuthorized = await huaweiCloud.isConnected();
if (!isAuthorized) {
- isAuthorized = await huaweiCloud.connect(
- context,
- windowName: S.current.cloudTypeHuaweiCloudAuthenticateWindowName,
- );
+ isAuthorized = await huaweiCloud.connect();
}
if (isAuthorized) {
HuaweiCloudUserInfo? info = await fetchInfo();
diff --git a/lib/TokenUtils/Cloud/onedrive_cloud_service.dart b/lib/TokenUtils/Cloud/onedrive_cloud_service.dart
index eec25731..bd68b459 100644
--- a/lib/TokenUtils/Cloud/onedrive_cloud_service.dart
+++ b/lib/TokenUtils/Cloud/onedrive_cloud_service.dart
@@ -1,13 +1,11 @@
import 'dart:typed_data';
-import 'package:flutter/cupertino.dart';
import 'package:flutter_cloud/onedrive.dart';
import 'package:flutter_cloud/onedrive_response.dart';
import 'package:path/path.dart';
import '../../Models/cloud_service_config.dart';
import '../../Utils/hive_util.dart';
-import '../../generated/l10n.dart';
import '../export_token_util.dart';
import 'cloud_service.dart';
@@ -21,11 +19,9 @@ class OneDriveCloudService extends CloudService {
static const String _onedrivePath = '/CloudOTP';
final CloudServiceConfig _config;
late OneDrive onedrive;
- late BuildContext context;
Function(CloudServiceConfig)? onConfigChanged;
OneDriveCloudService(
- this.context,
this._config, {
this.onConfigChanged,
}) {
@@ -45,10 +41,7 @@ class OneDriveCloudService extends CloudService {
Future authenticate() async {
bool isAuthorized = await onedrive.isConnected();
if (!isAuthorized) {
- isAuthorized = await onedrive.connect(
- context,
- windowName: S.current.cloudTypeOneDriveAuthenticateWindowName,
- );
+ isAuthorized = await onedrive.connect();
}
if (isAuthorized) {
await fetchInfo();
diff --git a/lib/Utils/constant.dart b/lib/Utils/constant.dart
index 1fefea09..c644de12 100644
--- a/lib/Utils/constant.dart
+++ b/lib/Utils/constant.dart
@@ -34,6 +34,9 @@ const String repoUrl = "https://github.com/Robert-Stackflow/CloudOTP";
const String releaseUrl =
"https://github.com/Robert-Stackflow/CloudOTP/releases";
const String issueUrl = "https://github.com/Robert-Stackflow/CloudOTP/issues";
+const String privacyPolicyUrl =
+ "https://apps.cloudchewie.com/cloudotp/privacy/";
+const String serviceTermUrl = "https://apps.cloudchewie.com/cloudotp/service/";
AndroidAuthMessages androidAuthMessages = AndroidAuthMessages(
cancelButton: S.current.biometricCancelButton,
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index fd27abc7..062aca62 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -243,6 +243,8 @@
"webDavPushBackup": "Push backups",
"webDavBackupFiles": "Backup list (total {count} backups)",
"webDavLogout": "Logout",
+ "webDavLoggingOut": "Logging out...",
+ "webDavLogoutSuccess": "Logout successful",
"webDavSignin": "Sign in",
"s3Endpoint": "Endpoint",
"s3EndpointTip": "S3 cloud service endpoint",
@@ -371,6 +373,8 @@
"changeLog": "Change log",
"bugReport": "Report BUG",
"githubRepo": "GitHub repository",
+ "privacyPolicy": "Privacy policy",
+ "serviceTerm": "Terms of service",
"rate": "Rate it",
"rateTitle": "Rate CloudOTP",
"pleaseRate": "Please rate",
diff --git a/lib/l10n/intl_zh_CN.arb b/lib/l10n/intl_zh_CN.arb
index a6d7dcf0..88d6a62e 100644
--- a/lib/l10n/intl_zh_CN.arb
+++ b/lib/l10n/intl_zh_CN.arb
@@ -242,6 +242,8 @@
"webDavPullFailed": "拉取失败",
"webDavPushBackup": "备份到云端",
"webDavLogout": "退出帐户",
+ "webDavLoggingOut": "退出中...",
+ "webDavLogoutSuccess": "退出成功",
"webDavSignin": "登录",
"s3Endpoint": "端点",
"s3EndpointTip": "S3云服务端点",
@@ -370,6 +372,8 @@
"changeLog": "更新日志",
"bugReport": "报告BUG",
"githubRepo": "GitHub仓库",
+ "privacyPolicy": "隐私政策",
+ "serviceTerm": "服务条款",
"rate": "评个分吧",
"rateTitle": "为CloudOTP评个分吧",
"pleaseRate": "请评分",
diff --git a/pubspec.yaml b/pubspec.yaml
index 1043e62a..76cced69 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,5 @@
name: cloudotp
-version: 2.2.0
-description: An awesome two-factor authenticator which supports cloud storage and multiple platforms.
+dddddescription: An awesome two-factor authenticator which supports cloud storage and multiple platforms.
publish_to: none
environment:
diff --git a/third-party/flutter_cloud/lib/dropbox.dart b/third-party/flutter_cloud/lib/dropbox.dart
index 64de3246..82a0a475 100644
--- a/third-party/flutter_cloud/lib/dropbox.dart
+++ b/third-party/flutter_cloud/lib/dropbox.dart
@@ -6,26 +6,27 @@ import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_cloud/status.dart';
import 'package:flutter_cloud/token_manager.dart';
-import 'package:hashlib/hashlib.dart';
import 'package:http/http.dart' as http;
import 'dropbox_response.dart';
import 'oauth2_helper.dart';
class Dropbox with ChangeNotifier {
- static const String authHost = "www.dropbox.com";
- static const String authEndpoint = "/oauth2/authorize";
- static const String tokenEndpoint = "https://$authHost/oauth2/token";
+ static const String authEndpoint = "https://www.dropbox.com/oauth2/authorize";
+ static const String tokenEndpoint = "https://www.dropbox.com/oauth2/token";
+ static const String revokeEndpoint =
+ "https://api.dropboxapi.com/2/auth/token/revoke";
static const String apiContentEndpoint = "https://content.dropboxapi.com/2";
static const String apiEndpoint = "https://api.dropboxapi.com/2";
- static const String errCANCELED = "CANCELED";
- static const permissionFilesReadWriteAll =
+ static const permission =
"account_info.read files.metadata.write files.metadata.read files.content.write files.content.read file_requests.write file_requests.read";
static const String expireInKey = "__dropbox_tokenExpire";
static const String accessTokenKey = "__dropbox_accessToken";
static const String refreshTokenKey = "__dropbox_refreshToken";
+ static const String idTokenKey = "__dropbox_idToken";
late final ITokenManager _tokenManager;
late final String redirectUrl;
@@ -38,7 +39,7 @@ class Dropbox with ChangeNotifier {
required this.clientId,
required this.callbackUrl,
required this.redirectUrl,
- this.scopes = permissionFilesReadWriteAll,
+ this.scopes = permission,
ITokenManager? tokenManager,
}) {
state = OAuth2Helper.generateStateParameter();
@@ -47,10 +48,12 @@ class Dropbox with ChangeNotifier {
tokenEndpoint: tokenEndpoint,
clientId: clientId,
redirectUrl: redirectUrl,
+ revokeUrl: revokeEndpoint,
scope: scopes,
- expireInKey: expireInKey,
+ expireAtKey: expireInKey,
accessTokenKey: accessTokenKey,
refreshTokenKey: refreshTokenKey,
+ idTokenKey: idTokenKey,
);
}
@@ -64,27 +67,14 @@ class Dropbox with ChangeNotifier {
return (accessToken?.isNotEmpty) ?? false;
}
- String generateCodeVerifier() {
- return myBase64Encode(randomBytes(32));
- }
-
- String myBase64Encode(List input) {
- return base64Encode(input)
- .replaceAll("+", '-')
- .replaceAll("/", '_')
- .replaceAll("=", '');
- }
-
- Future connect(
- BuildContext context, {
- String? windowName,
- }) async {
+ Future connect() async {
try {
- String codeVerifier = generateCodeVerifier();
+ String codeVerifier = OAuth2Helper.generateCodeVerifier();
- String codeChanllenge = myBase64Encode(sha256.string(codeVerifier).bytes);
+ String codeChanllenge = OAuth2Helper.generateCodeChanllenge(codeVerifier);
- final authUri = Uri.https(authHost, authEndpoint, {
+ Uri uri = Uri.parse(authEndpoint);
+ final authUri = Uri.https(uri.authority, uri.path, {
'code_challenge': codeChanllenge,
"code_challenge_method": "S256",
'client_id': clientId,
@@ -106,7 +96,6 @@ class Dropbox with ChangeNotifier {
}
http.Response? result = await OAuth2Helper.browserAuthWithVerifier(
- context: context,
authEndpoint: authUri,
tokenEndpoint: Uri.parse(tokenEndpoint),
callbackUrl: callbackUrl,
@@ -115,7 +104,6 @@ class Dropbox with ChangeNotifier {
redirectUrl: redirectUrl,
codeVerifier: codeVerifier,
scopes: scopes,
- windowName: windowName,
state: state,
);
@@ -127,10 +115,7 @@ class Dropbox with ChangeNotifier {
} else {
return false;
}
- } on PlatformException catch (err, trace) {
- if (err.code != errCANCELED) {
- debugPrint("# Dropbox -> connect: $err\n$trace");
- }
+ } on PlatformException {
return false;
} catch (err, trace) {
debugPrint("# Dropbox -> connect: $err\n$trace");
@@ -139,85 +124,121 @@ class Dropbox with ChangeNotifier {
}
Future disconnect() async {
- await _tokenManager.clearStoredToken();
- notifyListeners();
+ try {
+ final accessToken = await checkToken();
+ Uri uri = Uri.parse(revokeEndpoint);
+ final resp = await http.post(
+ uri,
+ headers: {"Authorization": "Bearer $accessToken"},
+ );
+ debugPrint(
+ "# Dropbox -> disconnect: revoke access token: ${resp.statusCode}");
+ } catch (err, trace) {
+ debugPrint("# Dropbox -> disconnect: $err\n$trace");
+ } finally {
+ await _tokenManager.clearStoredToken();
+ notifyListeners();
+ }
}
- Future getInfo() async {
+ Future checkToken() async {
final accessToken = await _tokenManager.getAccessToken();
if (accessToken == null) {
- return DropboxResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
+ throw NullAccessTokenException();
+ } else {
+ return accessToken;
+ }
+ }
+
+ http.Response processResponse(http.Response response) {
+ if (response.statusCode == 401) {
+ disconnect();
}
- final url = Uri.parse("$apiEndpoint/users/get_current_account");
- final storageUrl = Uri.parse("$apiEndpoint/users/get_space_usage");
+ return response;
+ }
+
+ Future post(
+ Uri url, {
+ dynamic headers,
+ dynamic body,
+ }) async {
+ final accessToken = await checkToken();
+ var tmpHeaders = {"Authorization": "Bearer $accessToken"};
+ if (headers != null) {
+ tmpHeaders.addAll(headers);
+ }
+ return processResponse(await http.post(
+ url,
+ headers: tmpHeaders,
+ body: body,
+ ));
+ }
+ Future get(
+ Uri url, {
+ dynamic headers,
+ }) async {
+ final accessToken = await checkToken();
+ var tmpHeaders = {"Authorization": "Bearer $accessToken"};
+ if (headers != null) {
+ tmpHeaders.addAll(headers);
+ }
+ return processResponse(await http.get(
+ url,
+ headers: tmpHeaders,
+ ));
+ }
+
+ Future getInfo() async {
try {
- final resp = await http.post(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final url = Uri.parse("$apiEndpoint/users/get_current_account");
+ final storageUrl = Uri.parse("$apiEndpoint/users/get_space_usage");
- debugPrint(
- "# Dropbox -> getInfo: ${resp.statusCode}\n# Body: ${resp.body}");
+ final resp = await post(url);
if (resp.statusCode == 200 || resp.statusCode == 201) {
- final usageResp = await http.post(
- storageUrl,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ debugPrint("# Dropbox -> getInfo success: ${jsonDecode(resp.body)}");
- debugPrint(
- "# Dropbox -> getStorageInfo: ${usageResp.statusCode}\n# Body: ${usageResp.body}");
+ final usageResp = await post(storageUrl);
if (usageResp.statusCode == 200 || usageResp.statusCode == 201) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- userInfo: DropboxUserInfo.fromJson(
- jsonDecode(resp.body), jsonDecode(usageResp.body)),
- message: "Get Info successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
+ debugPrint(
+ "# Dropbox -> getStorageInfo success: ${jsonDecode(usageResp.body)}");
+
+ return DropboxResponse.fromResponse(
+ response: usageResp,
+ userInfo: DropboxUserInfo.fromJson(
+ jsonDecode(resp.body), jsonDecode(usageResp.body)),
+ message: "Get Info successfully.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while get storage info.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# Dropbox -> getStorageInfo failed: ${usageResp.statusCode} # Body: ${usageResp.body}");
+ return DropboxResponse.fromResponse(
+ response: usageResp,
+ message: "Error while get storage info.",
+ );
}
- } else if (resp.statusCode == 404) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "${url.toString()} not found.",
- bodyBytes: Uint8List(0));
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while get info.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# Dropbox -> getInfo failed: ${resp.statusCode} # Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Error while get info.",
+ );
}
} catch (err) {
- debugPrint("# Dropbox -> getInfo: $err");
+ debugPrint("# Dropbox -> getInfo error: $err");
return DropboxResponse(message: "Unexpected exception: $err");
}
}
Future list(String remotePath) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return DropboxResponse(message: "Null access token");
- }
-
- final url = Uri.parse("$apiEndpoint/files/list_folder");
-
try {
- final resp = await http.post(
+ final url = Uri.parse("$apiEndpoint//files/list_folder");
+ final resp = await post(
url,
headers: {
- "Authorization": "Bearer $accessToken",
"Content-Type": "application/json",
},
body: jsonEncode({
@@ -231,8 +252,6 @@ class Dropbox with ChangeNotifier {
}),
);
- debugPrint("# Dropbox -> list: ${resp.statusCode}\n# Body: ${resp.body}");
-
if (resp.statusCode == 200 || resp.statusCode == 201) {
Map body = jsonDecode(resp.body);
List files = [];
@@ -240,26 +259,22 @@ class Dropbox with ChangeNotifier {
if (item['.tag'] == "folder") continue;
files.add(DropboxFileInfo.fromJson(item));
}
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- files: files,
- message: "List files successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Url not found.");
+ debugPrint("# Dropbox -> list successfully");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ files: files,
+ message: "List files successfully.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while listing files.");
+ debugPrint(
+ "# Dropbox -> list failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Error while listing files.",
+ );
}
} catch (err) {
- debugPrint("# Dropbox -> list: $err");
+ debugPrint("# Dropbox -> list error: $err");
return DropboxResponse(message: "Unexpected exception: $err");
}
}
@@ -267,46 +282,31 @@ class Dropbox with ChangeNotifier {
Future pull(
String path,
) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return DropboxResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- final url = Uri.parse("$apiContentEndpoint/files/download");
-
try {
- final resp = await http.get(
+ final url = Uri.parse("$apiContentEndpoint/files/download");
+
+ final resp = await get(
url,
headers: {
- "Authorization": "Bearer $accessToken",
"Dropbox-API-Arg": jsonEncode({
"path": path,
}),
},
);
- debugPrint("# Dropbox -> pull: ${resp.statusCode}\n# Body: ${resp.body}");
-
if (resp.statusCode == 200 || resp.statusCode == 201) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Download successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "File not found.",
- bodyBytes: Uint8List(0));
+ debugPrint("# Dropbox -> pull successfully");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Download successfully.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while downloading file.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# Dropbox -> pull failed : ${resp.statusCode}\n# Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Error while downloading file.",
+ );
}
} catch (err) {
debugPrint("# Dropbox -> pull: $err");
@@ -315,67 +315,44 @@ class Dropbox with ChangeNotifier {
}
Future delete(String path) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return DropboxResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- final url = Uri.parse("$apiEndpoint/files/delete_v2");
-
try {
- final resp = await http.post(
+ final url = Uri.parse("$apiEndpoint/files/delete_v2");
+
+ final resp = await post(
url,
headers: {
- "Authorization": "Bearer $accessToken",
"Content-Type": "application/json",
},
body: jsonEncode({"path": path}),
);
- debugPrint(
- "# Dropbox -> delete: ${resp.statusCode}\n# Body: ${resp.body}");
-
if (resp.statusCode == 200 || resp.statusCode == 201) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Delete successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "File not found.",
- bodyBytes: Uint8List(0));
+ debugPrint("# Dropbox -> delete successfully");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Delete successfully.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while deleting file.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# Dropbox -> delete failed ${resp.statusCode}\n# Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Error while deleting file.",
+ );
}
} catch (err) {
- debugPrint("# Dropbox -> delete: $err");
+ debugPrint("# Dropbox -> delete error: $err");
return DropboxResponse(message: "Unexpected exception: $err");
}
}
Future deleteBatch(List paths) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return DropboxResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- final url = Uri.parse("$apiEndpoint/files/delete_batch");
-
try {
- final resp = await http.post(
+ final url = Uri.parse("$apiEndpoint/files/delete_batch");
+
+ final resp = await post(
url,
headers: {
- "Authorization": "Bearer $accessToken",
"Content-Type": "application/json",
},
body: jsonEncode(
@@ -385,31 +362,22 @@ class Dropbox with ChangeNotifier {
),
);
- debugPrint(
- "# Dropbox -> deleteBatch: ${resp.statusCode}\n# Body: ${resp.body}");
-
if (resp.statusCode == 200 || resp.statusCode == 201) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Delete successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "File not found.",
- bodyBytes: Uint8List(0));
+ debugPrint("# Dropbox -> deleteBatch successfully");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Delete batch successfully.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while deleting file.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# Dropbox -> deleteBatch failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Error while deleting file.",
+ );
}
} catch (err) {
- debugPrint("# Dropbox -> deleteBatch: $err");
+ debugPrint("# Dropbox -> deleteBatch error: $err");
return DropboxResponse(message: "Unexpected exception: $err");
}
}
@@ -419,18 +387,12 @@ class Dropbox with ChangeNotifier {
String remotePath, {
Function(int p1, int p2)? onProgress,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return DropboxResponse(message: "Null access token.");
- }
-
try {
var url = Uri.parse("$apiContentEndpoint/files/upload");
- var resp = await http.post(
+ var resp = await post(
url,
headers: {
- "Authorization": "Bearer $accessToken",
"Dropbox-API-Arg": jsonEncode({
"autorename": false,
"mode": "add",
@@ -443,23 +405,23 @@ class Dropbox with ChangeNotifier {
body: bytes,
);
- debugPrint("# Upload response: ${resp.statusCode}\n# Body: ${resp.body}");
if (resp.statusCode == 200 || resp.statusCode == 201) {
onProgress?.call(1, 1);
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Upload finished.",
- isSuccess: true);
+ debugPrint("# Dropbox -> Upload successfully");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Upload finished.",
+ );
} else {
- return DropboxResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Upload failed.");
+ debugPrint("# Dropbox -> Upload failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return DropboxResponse.fromResponse(
+ response: resp,
+ message: "Upload failed.",
+ );
}
} catch (err) {
- debugPrint("# Upload error: $err");
+ debugPrint("# Dropbox -> Upload error: $err");
return DropboxResponse(message: "Unexpected exception: $err");
}
}
diff --git a/third-party/flutter_cloud/lib/dropbox_response.dart b/third-party/flutter_cloud/lib/dropbox_response.dart
index 6f3dc0b2..d362766c 100644
--- a/third-party/flutter_cloud/lib/dropbox_response.dart
+++ b/third-party/flutter_cloud/lib/dropbox_response.dart
@@ -1,24 +1,47 @@
import 'dart:typed_data';
+import 'package:flutter_cloud/status.dart';
+import 'package:http/http.dart' as http;
+
class DropboxResponse {
+ final ResponseStatus status;
final int? statusCode;
final String? body;
final String? message;
+ final String? accessToken;
final bool isSuccess;
final Uint8List? bodyBytes;
final DropboxUserInfo? userInfo;
final List files;
DropboxResponse({
+ this.status = ResponseStatus.success,
this.statusCode,
this.body,
- this.message,
this.bodyBytes,
+ this.accessToken,
this.userInfo,
+ this.message,
this.files = const [],
this.isSuccess = false,
});
+ DropboxResponse.fromResponse({
+ required http.Response response,
+ this.userInfo,
+ this.message,
+ this.files = const [],
+ }) : body = response.body,
+ accessToken = "",
+ statusCode = response.statusCode,
+ bodyBytes = response.bodyBytes,
+ isSuccess = response.statusCode == 200 ||
+ response.statusCode == 201 ||
+ response.statusCode == 204,
+ status = ResponseStatus.values.firstWhere(
+ (element) => element.code == response.statusCode,
+ orElse: () => ResponseStatus.success);
+
@override
String toString() {
return "DropboxResponse("
diff --git a/third-party/flutter_cloud/lib/googledrive.dart b/third-party/flutter_cloud/lib/googledrive.dart
index d4ced71b..fe70e370 100644
--- a/third-party/flutter_cloud/lib/googledrive.dart
+++ b/third-party/flutter_cloud/lib/googledrive.dart
@@ -7,8 +7,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cloud/token_manager.dart';
+// import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/drive/v3.dart' as drive;
-import 'package:hashlib/hashlib.dart';
import 'package:http/http.dart' as http;
import 'googledrive_response.dart';
@@ -28,32 +28,32 @@ class GoogleAuthClient extends http.BaseClient {
}
class GoogleDrive with ChangeNotifier {
- static const String authHost = "accounts.google.com";
- static const String authEndpoint = "/o/oauth2/v2/auth";
+ static const String authEndpoint =
+ "https://accounts.google.com/o/oauth2/v2/auth";
static const String tokenEndpoint =
"https://www.googleapis.com/oauth2/v4/token";
+ static const String revokeEndpoint =
+ "https://www.googleapis.com/oauth2/v4/revoke";
static const String apiEndpoint = "https://content.googleapis.com/drive/v3";
- static const String apiUpload =
+ static const String apiUploadEndpoint =
"https://www.googleapis.com/upload/drive/v3/files";
- static const String errCANCELED = "CANCELED";
static const permission = "https://www.googleapis.com/auth/drive.file";
static const String expireInKey = "__googledrive_tokenExpire";
static const String accessTokenKey = "__googledrive_accessToken";
static const String refreshTokenKey = "__googledrive_refreshToken";
+ static const String idTokenKey = "__googledrive_idToken";
late final ITokenManager _tokenManager;
late final String redirectUrl;
late final String callbackUrl;
final String scopes;
final String clientId;
- final String clientSecret;
late final String state;
GoogleDrive({
required this.clientId,
- required this.clientSecret,
required this.callbackUrl,
required this.redirectUrl,
this.scopes = permission,
@@ -65,10 +65,12 @@ class GoogleDrive with ChangeNotifier {
tokenEndpoint: tokenEndpoint,
clientId: clientId,
redirectUrl: redirectUrl,
+ revokeUrl: revokeEndpoint,
scope: scopes,
- expireInKey: expireInKey,
+ expireAtKey: expireInKey,
accessTokenKey: accessTokenKey,
refreshTokenKey: refreshTokenKey,
+ idTokenKey: idTokenKey,
);
}
@@ -92,32 +94,38 @@ class GoogleDrive with ChangeNotifier {
return (accessToken?.isNotEmpty) ?? false;
}
- String generateCodeVerifier() {
- return myBase64Encode(randomBytes(32));
- }
-
- String myBase64Encode(List input) {
- return base64Encode(input)
- .replaceAll("+", '-')
- .replaceAll("/", '_')
- .replaceAll("=", '');
+ Future connect() async {
+ // GoogleSignIn googleSignIn = GoogleSignIn(
+ // clientId: clientId,
+ // scopes: [permission],
+ // forceCodeForRefreshToken: true,
+ // signInOption: SignInOption.standard,
+ // );
+ // try {
+ // GoogleSignInAccount? currentUser = await googleSignIn.signIn();
+ // GoogleSignInAuthentication? authentication =
+ // await currentUser?.authentication;
+ // print(authentication?.accessToken);
+ // return true;
+ // } catch (e, t) {
+ // print("$e\n$t");
+ return false;
+ // }
}
- Future connect(
- BuildContext context, {
- String? windowName,
- }) async {
+ Future connects() async {
try {
- String codeVerifier = generateCodeVerifier();
+ String codeVerifier = OAuth2Helper.generateCodeVerifier();
- String codeChanllenge = myBase64Encode(sha256.string(codeVerifier).bytes);
+ String codeChanllenge = OAuth2Helper.generateCodeChanllenge(codeVerifier);
- final authUri = Uri.https(authHost, authEndpoint, {
+ Uri uri = Uri.parse(authEndpoint);
+ final authUri = Uri.https(uri.authority, uri.path, {
'code_challenge': codeChanllenge,
"code_challenge_method": "S256",
- 'response_type': 'code',
'client_id': clientId,
'redirect_uri': redirectUrl,
+ 'response_type': 'code',
"access_type": "offline",
'scope': scopes,
'state': state,
@@ -134,31 +142,25 @@ class GoogleDrive with ChangeNotifier {
}
http.Response? result = await OAuth2Helper.browserAuthWithVerifier(
- context: context,
authEndpoint: authUri,
tokenEndpoint: Uri.parse(tokenEndpoint),
callbackUrl: callbackUrl,
callbackUrlScheme: callbackUrlScheme,
state: state,
clientId: clientId,
- clientSecret: clientSecret,
codeVerifier: codeVerifier,
redirectUrl: redirectUrl,
scopes: scopes,
- windowName: windowName,
);
if (result != null) {
notifyListeners();
- bool res = (await _tokenManager.saveTokenResp(result)) as bool;
+ bool res = (await _tokenManager.saveTokenResp(result));
return res;
} else {
return false;
}
- } on PlatformException catch (err) {
- if (err.code != errCANCELED) {
- debugPrint("# GoogleDrive -> connect: $err");
- }
+ } on PlatformException {
return false;
} catch (err) {
debugPrint("# GoogleDrive -> connect: $err");
diff --git a/third-party/flutter_cloud/lib/huaweicloud.dart b/third-party/flutter_cloud/lib/huaweicloud.dart
index 396e0ab7..f55812a8 100644
--- a/third-party/flutter_cloud/lib/huaweicloud.dart
+++ b/third-party/flutter_cloud/lib/huaweicloud.dart
@@ -28,19 +28,18 @@ class HuaweiCloud with ChangeNotifier {
static const String expireInKey = "__huaweicloud_tokenExpire";
static const String accessTokenKey = "__huaweicloud_accessToken";
static const String refreshTokenKey = "__huaweicloud_refreshToken";
+ static const String idTokenKey = "__huaweicloud_idToken";
late final ITokenManager _tokenManager;
late final String redirectUrl;
late final String callbackUrl;
final String scopes;
final String clientId;
- final String clientSecret;
late final String state;
HuaweiCloud({
required this.clientId,
- required this.clientSecret,
required this.redirectUrl,
required this.callbackUrl,
this.scopes = permission,
@@ -49,14 +48,15 @@ class HuaweiCloud with ChangeNotifier {
state = OAuth2Helper.generateStateParameter();
_tokenManager = tokenManager ??
DefaultTokenManager(
- tokenEndpoint: tokenEndpoint,
clientId: clientId,
- clientSecret: clientSecret,
redirectUrl: redirectUrl,
+ revokeUrl: revokeEndpoint,
+ tokenEndpoint: tokenEndpoint,
scope: scopes,
- expireInKey: expireInKey,
+ expireAtKey: expireInKey,
accessTokenKey: accessTokenKey,
refreshTokenKey: refreshTokenKey,
+ idTokenKey: idTokenKey,
);
}
@@ -70,18 +70,21 @@ class HuaweiCloud with ChangeNotifier {
return (accessToken?.isNotEmpty) ?? false;
}
- Future connect(
- BuildContext context, {
- String? windowName,
- }) async {
+ Future connect() async {
try {
+ String codeVerifier = OAuth2Helper.generateCodeVerifier();
+
+ String codeChanllenge = OAuth2Helper.generateCodeChanllenge(codeVerifier);
+
final authUri = Uri.https(authHost, authEndpoint, {
- 'response_type': 'code',
+ 'code_challenge': codeChanllenge,
+ "code_challenge_method": "S256",
'client_id': clientId,
'redirect_uri': redirectUrl,
+ 'response_type': 'code',
+ "access_type": "offline",
'scope': scopes,
'state': state,
- "access_type": "offline",
});
String callbackUrlScheme = "";
@@ -94,18 +97,16 @@ class HuaweiCloud with ChangeNotifier {
callbackUrlScheme = callbackUri.toString();
}
- http.Response? result = await OAuth2Helper.browserAuth(
- context: context,
+ http.Response? result = await OAuth2Helper.browserAuthWithVerifier(
authEndpoint: authUri,
tokenEndpoint: Uri.parse(tokenEndpoint),
- callbackUrl: callbackUrl,
- callbackUrlScheme: callbackUrlScheme,
state: state,
clientId: clientId,
- clientSecret: clientSecret,
- redirectUrl: redirectUrl,
scopes: scopes,
- windowName: windowName,
+ redirectUrl: redirectUrl,
+ callbackUrl: callbackUrl,
+ callbackUrlScheme: callbackUrlScheme,
+ codeVerifier: codeVerifier,
);
if (result != null) {
await _tokenManager.saveTokenResp(result);
diff --git a/third-party/flutter_cloud/lib/oauth2_helper.dart b/third-party/flutter_cloud/lib/oauth2_helper.dart
index 48232ed3..7f91cd34 100644
--- a/third-party/flutter_cloud/lib/oauth2_helper.dart
+++ b/third-party/flutter_cloud/lib/oauth2_helper.dart
@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
+import 'package:hashlib/hashlib.dart';
import 'package:http/http.dart' as http;
class OAuth2Helper {
@@ -13,8 +14,22 @@ class OAuth2Helper {
return base64Url.encode(values);
}
+ static String generateCodeVerifier() {
+ return myBase64Encode(randomBytes(32));
+ }
+
+ static String myBase64Encode(List input) {
+ return base64Encode(input)
+ .replaceAll("+", '-')
+ .replaceAll("/", '_')
+ .replaceAll("=", '');
+ }
+
+ static String generateCodeChanllenge(String codeVerifier){
+ return myBase64Encode(sha256.string(codeVerifier).bytes);
+ }
+
static Future browserAuth({
- required BuildContext? context,
required Uri authEndpoint,
required Uri tokenEndpoint,
required String callbackUrl,
@@ -22,8 +37,6 @@ class OAuth2Helper {
required String clientId,
required String redirectUrl,
required String state,
- String? clientSecret,
- String? windowName,
String? scopes,
}) async {
try {
@@ -31,9 +44,8 @@ class OAuth2Helper {
url: authEndpoint.toString(),
callbackUrl: callbackUrl,
callbackUrlScheme: callbackUrlScheme,
- options: FlutterWebAuth2Options(
+ options: const FlutterWebAuth2Options(
timeout: 60,
- windowName: windowName,
useWebview: false,
),
);
@@ -49,9 +61,6 @@ class OAuth2Helper {
'grant_type': 'authorization_code',
'code': code,
};
- if (clientSecret != null && clientSecret.isNotEmpty) {
- body['client_secret'] = clientSecret;
- }
http.Response resp = await http.post(tokenEndpoint, body: body);
return resp;
} catch (e, t) {
@@ -61,7 +70,6 @@ class OAuth2Helper {
}
static Future browserAuthWithVerifier({
- required BuildContext? context,
required Uri authEndpoint,
required Uri tokenEndpoint,
required String callbackUrl,
@@ -70,8 +78,6 @@ class OAuth2Helper {
required String redirectUrl,
required String codeVerifier,
required String state,
- String? clientSecret,
- String? windowName,
String? scopes,
}) async {
try {
@@ -79,9 +85,8 @@ class OAuth2Helper {
url: authEndpoint.toString(),
callbackUrl: callbackUrl,
callbackUrlScheme: callbackUrlScheme,
- options: FlutterWebAuth2Options(
+ options: const FlutterWebAuth2Options(
timeout: 60,
- windowName: windowName,
useWebview: false,
),
);
@@ -98,9 +103,6 @@ class OAuth2Helper {
'grant_type': 'authorization_code',
'code': code,
};
- if (clientSecret != null && clientSecret.isNotEmpty) {
- body['client_secret'] = clientSecret;
- }
http.Response resp = await http.post(tokenEndpoint, body: body);
return resp;
} catch (e, t) {
diff --git a/third-party/flutter_cloud/lib/onedrive.dart b/third-party/flutter_cloud/lib/onedrive.dart
index 28605af7..9c3ee585 100644
--- a/third-party/flutter_cloud/lib/onedrive.dart
+++ b/third-party/flutter_cloud/lib/onedrive.dart
@@ -7,6 +7,7 @@ import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_cloud/status.dart';
import 'package:flutter_cloud/token_manager.dart';
import 'package:http/http.dart' as http;
@@ -14,20 +15,21 @@ import 'oauth2_helper.dart';
import 'onedrive_response.dart';
class OneDrive with ChangeNotifier {
- static const String authHost = "login.microsoftonline.com";
- static const String authEndpoint = "/consumers/oauth2/v2.0/authorize";
+ static const String authEndpoint =
+ "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
static const String tokenEndpoint =
- "https://$authHost/consumers/oauth2/v2.0/token";
- static const String apiEndpoint = "https://graph.microsoft.com/v1.0/";
- static const String errCANCELED = "CANCELED";
+ "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
+ static const String revokeEndpoint =
+ "https://login.microsoftonline.com/consumers/oauth2/v2.0/revoke";
+ static const String apiEndpoint = "https://graph.microsoft.com/v1.0";
static const _appRootFolder = "special/approot";
static const _defaultRootFolder = "root";
- static const permissionFilesReadWriteAll = "Files.ReadWrite.All";
- static const permissionOfflineAccess = "offline_access";
+ static const permission = "Files.ReadWrite.All offline_access";
static const String expireInKey = "__onedrive_tokenExpire";
static const String accessTokenKey = "__onedrive_accessToken";
static const String refreshTokenKey = "__onedrive_refreshToken";
+ static const String idTokenKey = "__onedrive_idToken";
late final ITokenManager _tokenManager;
late final String redirectUrl;
@@ -41,7 +43,7 @@ class OneDrive with ChangeNotifier {
required this.clientId,
required this.callbackUrl,
required this.redirectUrl,
- this.scopes = "$permissionFilesReadWriteAll $permissionOfflineAccess",
+ this.scopes = permission,
ITokenManager? tokenManager,
}) {
state = OAuth2Helper.generateStateParameter();
@@ -50,10 +52,12 @@ class OneDrive with ChangeNotifier {
tokenEndpoint: tokenEndpoint,
clientId: clientId,
redirectUrl: redirectUrl,
+ revokeUrl: revokeEndpoint,
scope: scopes,
- expireInKey: expireInKey,
+ expireAtKey: expireInKey,
accessTokenKey: accessTokenKey,
refreshTokenKey: refreshTokenKey,
+ idTokenKey: idTokenKey,
);
}
@@ -67,12 +71,16 @@ class OneDrive with ChangeNotifier {
return (accessToken?.isNotEmpty) ?? false;
}
- Future connect(
- BuildContext context, {
- String? windowName,
- }) async {
+ Future connect() async {
try {
- final authUri = Uri.https(authHost, authEndpoint, {
+ String codeVerifier = OAuth2Helper.generateCodeVerifier();
+
+ String codeChanllenge = OAuth2Helper.generateCodeChanllenge(codeVerifier);
+
+ Uri uri = Uri.parse(authEndpoint);
+ final authUri = Uri.https(uri.authority, uri.path, {
+ 'code_challenge': codeChanllenge,
+ "code_challenge_method": "S256",
'response_type': 'code',
'client_id': clientId,
'redirect_uri': redirectUrl,
@@ -90,8 +98,7 @@ class OneDrive with ChangeNotifier {
callbackUrlScheme = callbackUri.toString();
}
- http.Response? result = await OAuth2Helper.browserAuth(
- context: context,
+ http.Response? result = await OAuth2Helper.browserAuthWithVerifier(
authEndpoint: authUri,
tokenEndpoint: Uri.parse(tokenEndpoint),
callbackUrl: callbackUrl,
@@ -100,7 +107,7 @@ class OneDrive with ChangeNotifier {
redirectUrl: redirectUrl,
state: state,
scopes: scopes,
- windowName: windowName,
+ codeVerifier: codeVerifier,
);
if (result != null) {
await _tokenManager.saveTokenResp(result);
@@ -109,13 +116,10 @@ class OneDrive with ChangeNotifier {
} else {
return false;
}
- } on PlatformException catch (err) {
- if (err.code != errCANCELED) {
- debugPrint("# OneDrive -> connect: $err");
- }
+ } on PlatformException {
return false;
} catch (err) {
- debugPrint("# OneDrive -> connect: $err");
+ debugPrint("# OneDrive -> connect error: $err");
return false;
}
}
@@ -125,47 +129,94 @@ class OneDrive with ChangeNotifier {
notifyListeners();
}
- Future getInfo() async {
+ Future checkToken() async {
final accessToken = await _tokenManager.getAccessToken();
if (accessToken == null) {
- debugPrint("# OneDrive -> getInfo: Null access token");
- return OneDriveResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
+ throw NullAccessTokenException();
+ } else {
+ return accessToken;
+ }
+ }
+
+ http.Response processResponse(http.Response response) {
+ if (response.statusCode == 401) {
+ disconnect();
+ }
+ return response;
+ }
+
+ Future post(
+ Uri url, {
+ dynamic headers,
+ dynamic body,
+ }) async {
+ final accessToken = await checkToken();
+ var tmpHeaders = {"Authorization": "Bearer $accessToken"};
+ if (headers != null) {
+ tmpHeaders.addAll(headers);
+ }
+ return processResponse(await http.post(
+ url,
+ headers: tmpHeaders,
+ body: body,
+ ));
+ }
+
+ Future get(
+ Uri url, {
+ dynamic headers,
+ }) async {
+ final accessToken = await checkToken();
+ var tmpHeaders = {"Authorization": "Bearer $accessToken"};
+ if (headers != null) {
+ tmpHeaders.addAll(headers);
}
- final url = Uri.parse("$apiEndpoint/drive?select=owner,quota");
+ return processResponse(await http.get(
+ url,
+ headers: tmpHeaders,
+ ));
+ }
+ Future delete(
+ Uri url, {
+ dynamic headers,
+ }) async {
+ final accessToken = await checkToken();
+ var tmpHeaders = {"Authorization": "Bearer $accessToken"};
+ if (headers != null) {
+ tmpHeaders.addAll(headers);
+ }
+ return processResponse(await http.delete(
+ url,
+ headers: tmpHeaders,
+ ));
+ }
+
+ Future getInfo() async {
try {
- final resp = await http.get(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final url = Uri.parse("$apiEndpoint/drive?select=owner,quota");
- debugPrint(
- "# OneDrive -> getInfo: ${resp.statusCode}\n# Body: ${resp.body}");
+ final resp = await get(url);
if (resp.statusCode == 200 || resp.statusCode == 201) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- userInfo: OneDriveUserInfo.fromJson(jsonDecode(resp.body)),
- message: "Get Info successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "${url.toString()} not found.",
- bodyBytes: Uint8List(0));
+ OneDriveUserInfo userInfo =
+ OneDriveUserInfo.fromJson(jsonDecode(resp.body));
+ debugPrint("# OneDrive -> get info successfully: $userInfo");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ userInfo: userInfo,
+ message: "Get Info successfully.",
+ );
} else {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while get info.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# OneDrive -> get info failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Error while get info.",
+ );
}
} catch (err) {
- debugPrint("# OneDrive -> getInfo: $err");
+ debugPrint("# OneDrive -> getInfo error: $err");
return OneDriveResponse(message: "Unexpected exception: $err");
}
}
@@ -174,24 +225,15 @@ class OneDrive with ChangeNotifier {
String remotePath, {
bool isAppFolder = false,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return OneDriveResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- if (isAppFolder) {
- await getMetadata(remotePath, isAppFolder: isAppFolder);
- }
+ try {
+ if (isAppFolder) {
+ await getMetadata(remotePath, isAppFolder: isAppFolder);
+ }
- final url = Uri.parse(
- "${apiEndpoint}me/drive/${_getRootFolder(isAppFolder)}:$remotePath:/children?select=id,name,size,createdDateTime,lastModifiedDateTime,file,description");
+ final url = Uri.parse(
+ "$apiEndpoint/me/drive/${_getRootFolder(isAppFolder)}:$remotePath:/children?select=id,name,size,createdDateTime,lastModifiedDateTime,file,description");
- try {
- final resp = await http.get(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final resp = await get(url);
if (resp.statusCode == 200 || resp.statusCode == 201) {
Map body = jsonDecode(resp.body);
@@ -199,25 +241,19 @@ class OneDrive with ChangeNotifier {
for (var item in body['value']) {
files.add(OneDriveFileInfo.fromJson(item));
}
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- files: files,
- message: "List files successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Url not found.",
- bodyBytes: Uint8List(0));
+ debugPrint("# OneDrive -> list successfully");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ files: files,
+ message: "List files successfully.",
+ );
} else {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while listing files.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# OneDrive -> list failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Error while listing files.",
+ );
}
} catch (err) {
debugPrint("# OneDrive -> list: $err");
@@ -229,42 +265,24 @@ class OneDrive with ChangeNotifier {
String id, {
bool isAppFolder = false,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return OneDriveResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- final url = Uri.parse("${apiEndpoint}me/drive/items/$id/content");
-
try {
- final resp = await http.get(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final url = Uri.parse("$apiEndpoint/me/drive/items/$id/content");
- debugPrint(
- "# OneDrive -> pull: ${resp.statusCode}\n# Body: ${resp.body}");
+ final resp = await get(url);
if (resp.statusCode == 200 || resp.statusCode == 201) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Download successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "File not found.",
- bodyBytes: Uint8List(0));
+ debugPrint("# OneDrive -> pull successfully");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Download successfully.",
+ );
} else {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while downloading file.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# OneDrive -> pull failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Error while downloading file.",
+ );
}
} catch (err) {
debugPrint("# OneDrive -> pull: $err");
@@ -276,42 +294,26 @@ class OneDrive with ChangeNotifier {
String id, {
bool isAppFolder = false,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return OneDriveResponse(
- message: "Null access token", bodyBytes: Uint8List(0));
- }
-
- final url = Uri.parse("${apiEndpoint}me/drive/items/$id");
-
try {
- final resp = await http.delete(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final url = Uri.parse("$apiEndpoint/me/drive/items/$id");
- debugPrint(
- "# OneDrive -> delete: ${resp.statusCode}\n# Body: ${resp.body}");
+ final resp = await delete(url);
- if (resp.statusCode == 200 || resp.statusCode == 201) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Delete successfully.",
- bodyBytes: resp.bodyBytes,
- isSuccess: true);
- } else if (resp.statusCode == 404) {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "File not found.",
- bodyBytes: Uint8List(0));
+ if (resp.statusCode == 200 ||
+ resp.statusCode == 201 ||
+ resp.statusCode == 204) {
+ debugPrint("# OneDrive -> delete successfully");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Delete successfully.",
+ );
} else {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Error while deleting file.",
- bodyBytes: Uint8List(0));
+ debugPrint(
+ "# OneDrive -> delete failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Error while deleting file.",
+ );
}
} catch (err) {
debugPrint("# OneDrive -> delete: $err");
@@ -325,39 +327,28 @@ class OneDrive with ChangeNotifier {
bool isAppFolder = false,
Function(int p1, int p2)? onProgress,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return OneDriveResponse(message: "Null access token.");
- }
-
try {
if (isAppFolder) {
await getMetadata(remotePath, isAppFolder: isAppFolder);
}
- const int pageSize = 1024 * 1024; // page size
- final int maxPage =
- (bytes.length / pageSize.toDouble()).ceil(); // total pages
+ const int pageSize = 1024 * 1024;
+ final int maxPage = (bytes.length / pageSize.toDouble()).ceil();
var now = DateTime.now();
var url = Uri.parse(
"$apiEndpoint/me/drive/${_getRootFolder(isAppFolder)}:$remotePath:/createUploadSession");
- var resp = await http.post(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ var resp = await post(url);
+
debugPrint(
- "# Create Session: ${DateTime.now().difference(now).inMilliseconds} ms");
+ "# OneDrive -> Upload Create Session: ${DateTime.now().difference(now).inMilliseconds} ms");
if (resp.statusCode == 200) {
final Map respJson = jsonDecode(resp.body);
final String uploadUrl = respJson["uploadUrl"];
url = Uri.parse(uploadUrl);
- debugPrint(
- "# Upload to: $url\n# Total pages: $maxPage\n# Page size: $pageSize");
-
for (var pageIndex = 0; pageIndex < maxPage; pageIndex++) {
now = DateTime.now();
final int start = pageIndex * pageSize;
@@ -369,42 +360,40 @@ class OneDrive with ChangeNotifier {
final pageData = bytes.getRange(start, end).toList();
final contentLength = pageData.length.toString();
- final headers = {
- "Content-Length": contentLength,
- "Content-Range": range,
- };
-
resp = await http.put(
url,
- headers: headers,
+ headers: {
+ "Content-Length": contentLength,
+ "Content-Range": range,
+ },
body: pageData,
);
debugPrint(
- "# Upload [${pageIndex + 1}/$maxPage]: ${DateTime.now().difference(now).inMilliseconds} ms, start: $start, end: $end, contentLength: $contentLength, range: $range");
+ "# OneDrive -> Upload [${pageIndex + 1}/$maxPage]: ${DateTime.now().difference(now).inMilliseconds} ms, start: $start, end: $end, contentLength: $contentLength, range: $range");
if (resp.statusCode == 202) {
onProgress?.call(pageIndex + 1, maxPage);
continue;
} else if (resp.statusCode == 200 || resp.statusCode == 201) {
onProgress?.call(pageIndex + 1, maxPage);
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Upload finished.",
- isSuccess: true);
+ debugPrint("# OneDrive -> Upload finished");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Upload finished.",
+ );
} else {
- return OneDriveResponse(
- statusCode: resp.statusCode,
- body: resp.body,
- message: "Upload failed.");
+ debugPrint(
+ "# OneDrive -> Upload failed: ${resp.statusCode}\n# Body: ${resp.body}");
+ return OneDriveResponse.fromResponse(
+ response: resp,
+ message: "Upload failed.",
+ );
}
}
}
-
- debugPrint("# Upload response: ${resp.statusCode}\n# Body: ${resp.body}");
} catch (err) {
- debugPrint("# Upload error: $err");
+ debugPrint("# OneDrive -> Upload error: $err");
return OneDriveResponse(message: "Unexpected exception: $err");
}
@@ -419,28 +408,21 @@ class OneDrive with ChangeNotifier {
String remotePath, {
bool isAppFolder = false,
}) async {
- final accessToken = await _tokenManager.getAccessToken();
- if (accessToken == null) {
- return Uint8List(0);
- }
-
- final url =
- Uri.parse("${apiEndpoint}me/drive/${_getRootFolder(isAppFolder)}");
-
try {
- final resp = await http.get(
- url,
- headers: {"Authorization": "Bearer $accessToken"},
- );
+ final url =
+ Uri.parse("$apiEndpoint/me/drive/${_getRootFolder(isAppFolder)}");
+
+ final resp = await get(url);
if (resp.statusCode == 200 || resp.statusCode == 201) {
+ debugPrint("# OneDrive -> metadata success: ${resp.body}");
return resp.bodyBytes;
} else if (resp.statusCode == 404) {
return Uint8List(0);
+ } else {
+ debugPrint(
+ "# OneDrive -> metadata failed: ${resp.statusCode}\n# Body: ${resp.body}");
}
-
- debugPrint(
- "# OneDrive -> metadata: ${resp.statusCode}\n# Body: ${resp.body}");
} catch (err) {
debugPrint("# OneDrive -> metadata: $err");
}
diff --git a/third-party/flutter_cloud/lib/onedrive_response.dart b/third-party/flutter_cloud/lib/onedrive_response.dart
index 4dde134e..ae26220f 100644
--- a/third-party/flutter_cloud/lib/onedrive_response.dart
+++ b/third-party/flutter_cloud/lib/onedrive_response.dart
@@ -1,24 +1,46 @@
import 'dart:typed_data';
+import 'package:flutter_cloud/status.dart';
+import 'package:http/http.dart' as http;
class OneDriveResponse {
+ final ResponseStatus status;
final int? statusCode;
final String? body;
final String? message;
final bool isSuccess;
final Uint8List? bodyBytes;
+ final String? accessToken;
final OneDriveUserInfo? userInfo;
final List files;
OneDriveResponse({
+ this.status = ResponseStatus.success,
this.statusCode,
this.body,
this.message,
+ this.accessToken,
this.bodyBytes,
this.userInfo,
this.files = const [],
this.isSuccess = false,
});
+ OneDriveResponse.fromResponse({
+ required http.Response response,
+ this.userInfo,
+ this.message,
+ this.files = const [],
+ }) : body = response.body,
+ accessToken = "",
+ statusCode = response.statusCode,
+ bodyBytes = response.bodyBytes,
+ isSuccess = response.statusCode == 200 ||
+ response.statusCode == 201 ||
+ response.statusCode == 204,
+ status = ResponseStatus.values.firstWhere(
+ (element) => element.code == response.statusCode,
+ orElse: () => ResponseStatus.success);
+
@override
String toString() {
return "OneDriveResponse("
diff --git a/third-party/flutter_cloud/lib/status.dart b/third-party/flutter_cloud/lib/status.dart
new file mode 100644
index 00000000..eed5e09a
--- /dev/null
+++ b/third-party/flutter_cloud/lib/status.dart
@@ -0,0 +1,16 @@
+enum ResponseStatus {
+ success(200, 'Success'),
+ connectionError(500, 'Connection Error'),
+ unauthorized(401, 'Unauthorized'),
+ nullAccessToken(9001, 'Null Access Token'),
+ notFound(404, 'Not Found');
+
+ final int code;
+ final String message;
+
+ const ResponseStatus(this.code, this.message);
+}
+
+class NullAccessTokenException implements Exception {
+ NullAccessTokenException();
+}
diff --git a/third-party/flutter_cloud/lib/token_manager.dart b/third-party/flutter_cloud/lib/token_manager.dart
index 2eaa50d5..9db96576 100644
--- a/third-party/flutter_cloud/lib/token_manager.dart
+++ b/third-party/flutter_cloud/lib/token_manager.dart
@@ -23,35 +23,38 @@ class DefaultTokenManager extends ITokenManager {
final String scope;
final String tokenEndpoint;
final String clientId;
- final String? clientSecret;
final String redirectUrl;
+ final String revokeUrl;
- final String expireInKey;
+ final String expireAtKey;
final String accessTokenKey;
final String refreshTokenKey;
+ final String idTokenKey;
DefaultTokenManager({
required this.tokenEndpoint,
required this.clientId,
- this.clientSecret,
required this.redirectUrl,
+ required this.revokeUrl,
required this.scope,
- required this.expireInKey,
+ required this.expireAtKey,
required this.accessTokenKey,
required this.refreshTokenKey,
+ required this.idTokenKey,
});
@override
Future saveTokenResp(http.Response resp) async {
Map body = jsonDecode(resp.body);
try {
- String expireIn =
+ String expireAt =
DateTime.now().add(Duration(seconds: body['expires_in'])).toString();
- await secureStorage.write(key: expireInKey, value: expireIn);
+ await secureStorage.write(key: expireAtKey, value: expireAt);
await secureStorage.write(
key: accessTokenKey, value: body['access_token']);
await secureStorage.write(
key: refreshTokenKey, value: body['refresh_token']);
+ await secureStorage.write(key: idTokenKey, value: body['id_token'] ?? "");
return true;
} catch (err) {
debugPrint("# DefaultTokenManager -> _saveTokenMap: $err");
@@ -63,7 +66,7 @@ class DefaultTokenManager extends ITokenManager {
Future isAuthorized() async {
try {
final accessToken = await secureStorage.read(key: accessTokenKey);
- final accessTokenExpiresAt = await secureStorage.read(key: expireInKey);
+ final accessTokenExpiresAt = await secureStorage.read(key: expireAtKey);
if (((accessToken?.isEmpty) ?? true) &&
((accessTokenExpiresAt?.isEmpty) ?? true)) {
return false;
@@ -79,7 +82,7 @@ class DefaultTokenManager extends ITokenManager {
Future clearStoredToken() async {
try {
await Future.wait([
- secureStorage.delete(key: expireInKey),
+ secureStorage.delete(key: expireAtKey),
secureStorage.delete(key: accessTokenKey),
secureStorage.delete(key: refreshTokenKey),
]);
@@ -96,22 +99,16 @@ class DefaultTokenManager extends ITokenManager {
return null;
}
- final accessTokenExpiresAt = await secureStorage.read(key: expireInKey);
- if ((accessTokenExpiresAt?.isEmpty) ?? true) {
+ final expiresAt = await secureStorage.read(key: expireAtKey);
+ if ((expiresAt?.isEmpty) ?? true) {
return null;
}
- final expAt = DateTime.parse(accessTokenExpiresAt!)
- .add(const Duration(minutes: -2));
+ final expAt = DateTime.parse(expiresAt!).add(const Duration(minutes: -2));
if (DateTime.now().toUtc().isAfter(expAt)) {
- // expired, refresh
final tokenMap = await _refreshToken();
- if (tokenMap == null) {
- // refresh failed
- return null;
- }
- // refresh success
+ if (tokenMap == null) return null;
return tokenMap['access_token'];
}
@@ -137,13 +134,8 @@ class DefaultTokenManager extends ITokenManager {
'redirect_uri': redirectUrl,
};
- if (clientSecret != null && clientSecret!.isNotEmpty) {
- body['client_secret'] = clientSecret;
- }
-
final resp = await http.post(Uri.parse(tokenEndpoint), body: body);
if (resp.statusCode != 200) {
- // refresh failed
debugPrint(
"# DefaultTokenManager -> _refreshToken: ${resp.statusCode}\n# Body: ${resp.body}");
@@ -153,8 +145,7 @@ class DefaultTokenManager extends ITokenManager {
debugPrint("# Refresh token: Success");
final Map tokenMap = jsonDecode(resp.body);
- await _saveTokenMap(tokenMap);
-
+ await saveTokenResp(resp);
return tokenMap;
} catch (err) {
debugPrint("# DefaultTokenManager -> _refreshToken: $err");
@@ -165,19 +156,4 @@ class DefaultTokenManager extends ITokenManager {
return null;
}
-
- Future _saveTokenMap(Map tokenObj) async {
- try {
- final expAt =
- DateTime.now().toUtc().add(Duration(seconds: tokenObj['expires_in']));
- debugPrint("# Expres at: $expAt");
-
- secureStorage.write(key: expireInKey, value: expAt.toString());
- secureStorage.write(key: accessTokenKey, value: tokenObj['access_token']);
- secureStorage.write(
- key: refreshTokenKey, value: tokenObj['refresh_token']);
- } catch (err) {
- debugPrint("# DefaultTokenManager -> _saveTokenMap: $err");
- }
- }
}
diff --git a/third-party/flutter_cloud/pubspec.lock b/third-party/flutter_cloud/pubspec.lock
index 232c6bea..aeb452bb 100644
--- a/third-party/flutter_cloud/pubspec.lock
+++ b/third-party/flutter_cloud/pubspec.lock
@@ -49,14 +49,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.18.0"
- desktop_webview_window:
- dependency: transitive
- description:
- name: desktop_webview_window
- sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0"
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "0.2.3"
fake_async:
dependency: transitive
description:
@@ -158,6 +150,54 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ google_identity_services_web:
+ dependency: transitive
+ description:
+ name: google_identity_services_web
+ sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.3.1+4"
+ google_sign_in:
+ dependency: "direct main"
+ description:
+ name: google_sign_in
+ sha256: "0b8787cb9c1a68ad398e8010e8c8766bfa33556d2ab97c439fb4137756d7308f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.2.1"
+ google_sign_in_android:
+ dependency: transitive
+ description:
+ name: google_sign_in_android
+ sha256: "5a47ebec9af97daf0822e800e4f101c3340b5ebc3f6898cf860c1a71b53cf077"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.1.28"
+ google_sign_in_ios:
+ dependency: transitive
+ description:
+ name: google_sign_in_ios
+ sha256: a058c9880be456f21e2e8571c1126eaacd570bdc5b6c6d9d15aea4bdf22ca9fe
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.7.6"
+ google_sign_in_platform_interface:
+ dependency: transitive
+ description:
+ name: google_sign_in_platform_interface
+ sha256: "1f6e5787d7a120cc0359ddf315c92309069171306242e181c09472d1b00a2971"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.5"
+ google_sign_in_web:
+ dependency: transitive
+ description:
+ name: google_sign_in_web
+ sha256: "042805a21127a85b0dc46bba98a37926f17d2439720e8a459d27045d8ef68055"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.12.4+2"
googleapis:
dependency: "direct main"
description:
@@ -334,6 +374,54 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.8"
+ protocol_handler:
+ dependency: transitive
+ description:
+ name: protocol_handler
+ sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
+ protocol_handler_android:
+ dependency: transitive
+ description:
+ name: protocol_handler_android
+ sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
+ protocol_handler_ios:
+ dependency: transitive
+ description:
+ name: protocol_handler_ios
+ sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
+ protocol_handler_macos:
+ dependency: transitive
+ description:
+ name: protocol_handler_macos
+ sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
+ protocol_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: protocol_handler_platform_interface
+ sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
+ protocol_handler_windows:
+ dependency: transitive
+ description:
+ name: protocol_handler_windows
+ sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.0"
sky_engine:
dependency: transitive
description: flutter
@@ -483,6 +571,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.5.4"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.4"
window_to_front:
dependency: transitive
description:
diff --git a/third-party/flutter_cloud/pubspec.yaml b/third-party/flutter_cloud/pubspec.yaml
index 9e8565d4..d0677097 100644
--- a/third-party/flutter_cloud/pubspec.yaml
+++ b/third-party/flutter_cloud/pubspec.yaml
@@ -17,6 +17,7 @@ dependencies:
path: ../flutter_web_auth_2
googleapis: ^13.2.0
hashlib: ^1.19.2
+# google_sign_in: ^6.2.1
dev_dependencies:
flutter_test:
diff --git a/tools/CloudOTP.iss b/tools/CloudOTP.iss
index 20794ac5..6700df70 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.2.0"
+#define MyAppVersion "2.3.0"
#define MyAppPublisher "Cloudchewie"
#define MyAppURL "https://apps.cloudchewie.com/cloudotp"
#define MyAppExeName "CloudOTP.exe"
diff --git a/art/addtoken.jpg b/tools/art/addtoken.jpg
similarity index 100%
rename from art/addtoken.jpg
rename to tools/art/addtoken.jpg
diff --git a/art/darkmode.jpg b/tools/art/darkmode.jpg
similarity index 100%
rename from art/darkmode.jpg
rename to tools/art/darkmode.jpg
diff --git a/art/dropbox.jpg b/tools/art/dropbox.jpg
similarity index 100%
rename from art/dropbox.jpg
rename to tools/art/dropbox.jpg
diff --git a/art/export_import.jpg b/tools/art/export_import.jpg
similarity index 100%
rename from art/export_import.jpg
rename to tools/art/export_import.jpg
diff --git a/art/lightmode.jpg b/tools/art/lightmode.jpg
similarity index 100%
rename from art/lightmode.jpg
rename to tools/art/lightmode.jpg
diff --git a/art/lock.jpg b/tools/art/lock.jpg
similarity index 100%
rename from art/lock.jpg
rename to tools/art/lock.jpg
diff --git a/art/setting.jpg b/tools/art/setting.jpg
similarity index 100%
rename from art/setting.jpg
rename to tools/art/setting.jpg
diff --git a/art/theme.jpg b/tools/art/theme.jpg
similarity index 100%
rename from art/theme.jpg
rename to tools/art/theme.jpg
diff --git a/dll/msvcp140.dll b/tools/dll/msvcp140.dll
similarity index 100%
rename from dll/msvcp140.dll
rename to tools/dll/msvcp140.dll
diff --git a/dll/sqlcipher.dll b/tools/dll/sqlcipher.dll
similarity index 100%
rename from dll/sqlcipher.dll
rename to tools/dll/sqlcipher.dll
diff --git a/dll/sqlite3-unencrypt.dll b/tools/dll/sqlite3-unencrypt.dll
similarity index 100%
rename from dll/sqlite3-unencrypt.dll
rename to tools/dll/sqlite3-unencrypt.dll
diff --git a/dll/sqlite3.dll b/tools/dll/sqlite3.dll
similarity index 100%
rename from dll/sqlite3.dll
rename to tools/dll/sqlite3.dll
diff --git a/dll/vcruntime140.dll b/tools/dll/vcruntime140.dll
similarity index 100%
rename from dll/vcruntime140.dll
rename to tools/dll/vcruntime140.dll
diff --git a/dll/vcruntime140_1.dll b/tools/dll/vcruntime140_1.dll
similarity index 100%
rename from dll/vcruntime140_1.dll
rename to tools/dll/vcruntime140_1.dll
diff --git a/tools/generate.py b/tools/generate.py
index e45b4e0d..d3af06ea 100644
--- a/tools/generate.py
+++ b/tools/generate.py
@@ -9,7 +9,7 @@
"D:\\Repositories\\CloudOTP\\build\\windows\\x64\\runner\\Release"
)
downloads_path = "D:\\Ruida\\Downloads"
-dll_path = "D:\\Repositories\\CloudOTP\\dll\\sqlite3.dll"
+dll_path = "D:\\Repositories\\CloudOTP\\tools\\dll\\sqlite3.dll"
iss_path = "D:\\Repositories\\CloudOTP\\tools\\CloudOTP.iss"
iscc_path = "D:\\Program Files\\Inno Setup 6\\ISCC.exe"
@@ -64,7 +64,6 @@ def zip_windows(version):
# generate the installer
def generate_installer(version):
print("start generate installer...")
- # 打开iss文件,修改版本号,即替换#define MyAppVersion "2.1.0"中的2.1.0为指定的版本号
with open(iss_path, "r") as f:
lines = f.readlines()
with open(iss_path, "w") as f:
@@ -98,11 +97,6 @@ def release_windows(version):
print("release windows runner done.")
-# 使用argparse处理命令行参数,
-# -v或--version参数指定版本号,
-# -a或--android参数指定是否生成apk,
-# -w或--windows参数指定是否生成windows,
-# -s或--split参数指定是否生成abi分包apk
if __name__ == "__main__":
import argparse