Skip to content

Commit

Permalink
Release 2.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert-Stackflow committed Aug 19, 2024
1 parent fdb644d commit cf7a826
Show file tree
Hide file tree
Showing 59 changed files with 1,375 additions and 733 deletions.
35 changes: 10 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
## Introduction

This is a awesome two-factor authenticator for Android which supports dropbox.

The algorithm part comes from https://github.com/freeotp/freeotp-android.
This is an awesome two-factor authenticator based on Flutter for Android and Windows which supports cloud backup.

## Highlights

- Support TOTP and HOTP
- Support manual filling and QR code scanning to add tokens
- Support import/export of JSON/URI file
- Support import/export of encrypted files (using standard AES-256 algorithm)
- Support backing up encrypted files to Dropbox
- Support password lock and biometric identification
- Support dark mode and switching theme colors
- Support sort;batch export;batch delete
- Support multiple languages: English, Simplified Chinese, Traditional Chinese, Japanese
- Reconstructed based on Flutter architecture, supports Android and Windows
- Support TOTP, HOTP, MOTP, Steam, Yandex
- Supports scanning QR code to add, identify pictures, and manually enter keys
- Supports custom icons and categories, supports sorting and multiple token layouts
- Supports dark mode, multiple languages, and multiple themes
- Supports local backup and automatic backup, supports WebDav, Onedrive, GoogleDrive, Dropbox, S3 storage and other cloud backup methods
- Supports import/export of encrypted files and URI lists
- Supports database encryption and gesture password

## Screenshots

<img src="art/lightmode.jpg" alt="Light Mode" style="zoom: 25%;" /><img src="art/darkmode.jpg" alt="Dark Mode" style="zoom: 25%;" /><img src="art/addtoken.jpg" alt="Add Token" style="zoom: 25%;" />

<img src="art/setting.jpg" alt="Setting" style="zoom: 25%;" /><img src="art/theme.jpg" alt="Theme" style="zoom: 25%;" /><img src="art/lock.jpg" alt="Lock" style="zoom: 25%;" />

<img src="art/export_import.jpg" alt="Export and Import" style="zoom: 25%;" /><img src="art/dropbox.jpg" alt="Dropbox" style="zoom: 25%;" />
## TODO

- [ ] Support Google Drive
- [ ] Support WebDAV services such as Box
- [ ] Support more encryption algorithms
- [ ] Support encrypting local SQLite database
- [ ] ~~Support desktop widgets(No longer implemented due to security considerations)~~

### Known Bugs

- [ ] When exporting a file, if you overwrite an existing file, the original article content cannot be cleared.
- [ ] When importing an encrypted file, if the file name is illegal (such as containing spaces), the import will fail.
<img src="art/export_import.jpg" alt="Export and Import" style="zoom: 25%;" /><img src="art/dropbox.jpg" alt="Dropbox" style="zoom: 25%;" />
22 changes: 22 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## 介绍

基于 Flutter 的双因素验证器,支持Android和Windows平台,支持云备份。

## Highlights

- 基于Flutter架构重构,支持Android和Windows
- 支持TOTP、HOTP、MOTP、Steam、Yandex
- 支持扫码添加、识别图片、手动输入密钥
- 支持自定义图标和分类、支持排序和多种令牌布局
- 支持深色模式、多种语言、多种主题
- 支持本地备份和自动备份、支持WebDav、Onedrive、GoogleDrive、Dropbox、S3存储等多种云备份方式
- 支持导入/导出加密文件、URI列表
- 支持数据库加密、手势密码

## Screenshots

<img src="art/lightmode.jpg" alt="Light Mode" style="zoom: 25%;" /><img src="art/darkmode.jpg" alt="Dark Mode" style="zoom: 25%;" /><img src="art/addtoken.jpg" alt="Add Token" style="zoom: 25%;" />

<img src="art/setting.jpg" alt="Setting" style="zoom: 25%;" /><img src="art/theme.jpg" alt="Theme" style="zoom: 25%;" /><img src="art/lock.jpg" alt="Lock" style="zoom: 25%;" />

<img src="art/export_import.jpg" alt="Export and Import" style="zoom: 25%;" /><img src="art/dropbox.jpg" alt="Dropbox" style="zoom: 25%;" />
4 changes: 4 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
buildscript {
ext.kotlin_version = '1.8.0'
}

plugins {
id "com.android.application"
id "kotlin-android"
Expand Down
7 changes: 6 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ allprojects {

rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
afterEvaluate {
android {
compileSdkVersion 34
}
}
}
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}

Expand Down
4 changes: 2 additions & 2 deletions certificate/CloudOTP.iss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "CloudOTP"
#define MyAppVersion "2.0.0"
#define MyAppVersion "2.1.0"
#define MyAppPublisher "Cloudchewie"
#define MyAppURL "https://apps.cloudchewie.com/cloudotp"
#define MyAppExeName "CloudOTP.exe"
Expand Down Expand Up @@ -33,7 +33,7 @@ DisableProgramGroupPage=yes
LicenseFile=D:\Repositories\CloudOTP\LICENSE
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequiredOverridesAllowed=commandline
OutputDir=C:\Users\dell\Downloads
OutputDir=D:\Ruida\Downloads
OutputBaseFilename={#MyAppName}-{#MyAppVersion}
SetupIconFile=D:\Repositories\CloudOTP\assets\logo-transparent.ico
Compression=lzma
Expand Down
Binary file removed encrypted.db
Binary file not shown.
107 changes: 44 additions & 63 deletions lib/Screens/Backup/cloud_service_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,76 +71,57 @@ class _CloudServiceScreenState extends State<CloudServiceScreen>
}

_buildBody() {
return ListView(
shrinkWrap: true,
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
children: [
Column(
children: [
// Container(
// constraints: const BoxConstraints(maxWidth: 82),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(16),
// border:
// Border.all(color: Colors.grey.withOpacity(0.1), width: 0.5),
// ),
// child: ClipRRect(
// borderRadius: BorderRadius.circular(16),
// child: Image.asset(
// 'assets/logo.png',
// height: 80,
// width: 80,
// fit: BoxFit.contain,
// ),
// ),
// ),
// const SizedBox(height: 20),
_typeInfo(),
const SizedBox(height: 10),
SizedBox(
height: MediaQuery.sizeOf(context).height - 150,
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
children: const [
WebDavServiceScreen(),
OneDriveServiceScreen(),
GoogleDriveServiceScreen(),
DropboxServiceScreen(),
S3CloudServiceScreen(),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_typeInfo(),
SizedBox(
height: MediaQuery.of(context).size.height,
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
children: const [
WebDavServiceScreen(),
OneDriveServiceScreen(),
GoogleDriveServiceScreen(),
DropboxServiceScreen(),
S3CloudServiceScreen(),
],
),
],
),
],
),
],
),
);
}

_typeInfo() {
return ItemBuilder.buildContainerItem(
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(),
onSelected: (value, index, isSelected) {
setState(() {
_currentType = index.toCloudServiceType;
});
pageController.jumpToPage(index);
},
title: '',
),
],
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: ItemBuilder.buildContainerItem(
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(),
onSelected: (value, index, isSelected) {
setState(() {
_currentType = index.toCloudServiceType;
});
pageController.jumpToPage(index);
},
title: '',
),
],
),
),
);
}
Expand Down
88 changes: 18 additions & 70 deletions lib/Screens/Backup/dropbox_service_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import '../../TokenUtils/export_token_util.dart';
import '../../TokenUtils/import_token_util.dart';
import '../../Widgets/BottomSheet/bottom_sheet_builder.dart';
import '../../Widgets/BottomSheet/dropbox_backups_bottom_sheet.dart';
import '../../Widgets/BottomSheet/input_bottom_sheet.dart';
import '../../Widgets/Dialog/custom_dialog.dart';
import '../../Widgets/Dialog/progress_dialog.dart';
import '../../Widgets/Item/input_item.dart';
Expand Down Expand Up @@ -53,7 +52,7 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
@override
void initState() {
super.initState();
loadConfig();
if (!ResponsiveUtil.isDesktop()) loadConfig();
}

loadConfig() async {
Expand All @@ -80,6 +79,9 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
if (_dropboxCloudService != null) {
_dropboxCloudServiceConfig!.configured = _dropboxCloudServiceConfig!
.connected = await _dropboxCloudService!.isConnected();
if (!_dropboxCloudServiceConfig!.connected) {
IToast.showTop(S.current.cloudConnectionError);
}
updateConfig(_dropboxCloudServiceConfig!);
}
inited = true;
Expand Down Expand Up @@ -107,6 +109,8 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
context,
background: Colors.transparent,
text: S.current.cloudConnecting,
mainAxisAlignment: MainAxisAlignment.start,
topPadding: 100,
);
}

Expand All @@ -115,7 +119,7 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(height: 50),
const SizedBox(height: 100),
Text(S.current.cloudTypeNotSupport(S.current.cloudTypeDropbox)),
const SizedBox(height: 10),
],
Expand All @@ -132,7 +136,7 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
}
await currentService.authenticate().then((value) async {
setState(() {
currentConfig.connected = value == CloudServiceStatus.success;
currentConfig.connected = (value == CloudServiceStatus.success);
});
if (!currentConfig.connected) {
switch (value) {
Expand All @@ -147,8 +151,7 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
break;
}
} else {
_dropboxCloudServiceConfig!.configured =
await _dropboxCloudService!.isConnected();
_dropboxCloudServiceConfig!.configured = true;
updateConfig(_dropboxCloudServiceConfig!);
if (showSuccessToast) IToast.show(S.current.cloudAuthSuccess);
}
Expand Down Expand Up @@ -258,13 +261,15 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
color: Theme.of(context).primaryColor,
fontSizeDelta: 2,
onTap: () async {
CustomLoadingDialog.showLoading(
title: S.current.webDavPulling,
dismissible: true,
);
CustomLoadingDialog.showLoading(title: S.current.webDavPulling);
try {
List<DropboxFileInfo> files =
List<DropboxFileInfo>? files =
await _dropboxCloudService!.listBackups();
if (files == null) {
CustomLoadingDialog.dismissLoading();
IToast.show(S.current.webDavPullFailed);
return;
}
CloudServiceConfigDao.updateLastPullTime(
_dropboxCloudServiceConfig!);
CustomLoadingDialog.dismissLoading();
Expand All @@ -282,71 +287,14 @@ class _DropboxServiceScreenState extends State<DropboxServiceScreen>
msg: S.current.webDavPulling,
showProgress: true,
);
Uint8List res =
Uint8List? res =
await _dropboxCloudService!.downloadFile(
selectedFile.id,
onProgress: (c, t) {
dialog.updateProgress(progress: c / t);
},
);

dialog.updateMessage(
msg: S.current.importing,
showProgress: false,
);

bool success = await ImportTokenUtil.importBackupFile(
res,
showLoading: false,
);
dialog.dismiss();
if (!success) {
BottomSheetBuilder.showBottomSheet(
context,
responsive: true,
(context) => InputBottomSheet(
validator: (value) {
if (value.isEmpty) {
return S
.current.autoBackupPasswordCannotBeEmpty;
}
return null;
},
validateAsyncController:
InputValidateAsyncController(
listen: false,
validator: (text) async {
dialog.show(
msg: S.current.importing,
showProgress: false,
);
bool success =
await ImportTokenUtil.importBackupFile(
password: text,
res,
showLoading: false,
);
dialog.dismiss();
if (success) {
return null;
} else {
return S
.current.invalidPasswordOrDataCorrupted;
}
},
controller: TextEditingController(),
),
title: S.current.inputImportPasswordTitle,
message: S.current.inputImportPasswordTip,
hint: S.current.inputImportPasswordHint,
inputFormatters: [
RegexInputFormatter.onlyNumberAndLetter,
],
tailingType: InputItemTailingType.password,
onValidConfirm: (password) async {},
),
);
}
ImportTokenUtil.importFromCloud(context, res, dialog);
},
),
);
Expand Down
Loading

0 comments on commit cf7a826

Please sign in to comment.