Skip to content

Commit

Permalink
add empty card and function improve (#10)
Browse files Browse the repository at this point in the history
* add empty card and function improve

* add notes
  • Loading branch information
Wangggym authored Nov 5, 2024
1 parent 6e4a220 commit a5d78e5
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 29 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Flutter OTP Manager

A secure and user-friendly OTP (One-Time Password) manager application built with Flutter.
A secure and user-friendly OTP (One-Time Password) manager application built with Flutter, supporting multiple platforms and languages.

[@Latest Release](https://github.com/Wangggym/two_factor_authentication/releases)

## Features

- Generate TOTP (Time-based One-Time Password) codes
- Add and manage multiple accounts
- Generate TOTP (Time-based One-Time Password) codes with automatic refresh
- Scan QR codes to add accounts (camera on mobile, screen capture on desktop)
- Secure storage of account secrets
- Edit existing accounts
- Clean and intuitive user interface
- Automatic code refresh
- Copy OTP codes with one click
- Visual countdown timer with color indicators
- Multi-language support (English, 简体中文, 日本語)
- Cross-platform compatibility (Mobile & Desktop)

## Getting Started

Expand All @@ -21,4 +24,4 @@ A secure and user-friendly OTP (One-Time Password) manager application built wit

### Installation

1. Clone the repository
1. Clone the repository
7 changes: 6 additions & 1 deletion assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@
"capture_qr_code": "Capture QR Code",
"scan_qr_code_desktop": "Capture Screen QR Code",
"scan_qr_position_hint": "Position the QR code on your screen and click the button above to scan it.",
"failed_capture_qr": "Failed to capture or decode QR code: {error}"
"failed_capture_qr": "Failed to capture or decode QR code: {error}",
"no_accounts": "Welcome! Click the button below to add your first authenticator account ✨",
"version": "Version {version}",
"created_by": "Created by {author}",
"disclaimer": "Thank you for using Auth2! For your account security, please keep your keys safe.",
"close": "Close"
}
7 changes: 6 additions & 1 deletion assets/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@
"capture_qr_code": "QRコードをキャプチャ",
"scan_qr_code_desktop": "画面のQRコードをキャプチャ",
"scan_qr_position_hint": "QRコードを画面上に配置し、上のボタンをクリックしてスキャンしてください。",
"failed_capture_qr": "QRコードのキャプチャまたはデコードに失敗しました:{error}"
"failed_capture_qr": "QRコードのキャプチャまたはデコードに失敗しました:{error}",
"no_accounts": "ようこそ!下のボタンをクリックして最初の認証アカウントを追加しましょう ✨",
"version": "バージョン {version}",
"created_by": "開発者:{author}",
"disclaimer": "Auth2をご利用いただき、ありがとうございます!アカウントのセキュリティのため、キー情報を安全に保管してください。",
"close": "閉じる"
}
9 changes: 7 additions & 2 deletions assets/translations/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"has_been_pinned": "{name} 已置顶",
"has_been_unpinned": "{name} 已取消置顶",
"has_been_deleted": "{name} 已删除",
"edit_account": "编辑账户",
"edit_account": "编辑账",
"save": "保存",
"key_uri": "密钥URI",
"note_changes": "注意:更改不会应用到密钥URI",
Expand All @@ -37,5 +37,10 @@
"capture_qr_code": "捕获二维码",
"scan_qr_code_desktop": "捕获屏幕二维码",
"scan_qr_position_hint": "将二维码放置在屏幕上,然后点击上方按钮进行扫描。",
"failed_capture_qr": "捕获或解码二维码失败:{error}"
"failed_capture_qr": "捕获或解码二维码失败:{error}",
"no_accounts": "欢迎使用!点击下方按钮添加您的第一个验证器账户吧 ✨",
"version": "版本 {version}",
"created_by": "开发者:{author}",
"disclaimer": "感谢使用Auth2!为了您的账户安全,请妥善保管密钥信息。",
"close": "关闭"
}
43 changes: 31 additions & 12 deletions lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../widgets/otp_card.dart';
import 'add_account_screen.dart';
import 'edit_account_screen.dart';
import 'more_options_screen.dart';
import '../widgets/empty_state_widget.dart';

class HomeScreen extends StatefulWidget {
final Function(Locale) onLocaleChanged;
Expand Down Expand Up @@ -208,18 +209,36 @@ class _HomeScreenState extends State<HomeScreen> {
),
],
),
body: ListView.builder(
itemCount: _accounts.length,
itemBuilder: (context, index) {
return OTPCard(
account: _accounts[index],
onDelete: _deleteAccount,
onEdit: _editAccount,
onPin: _pinAccount,
isPinned: _pinnedAccountNames.contains(_accounts[index].name),
);
},
),
body: _accounts.isEmpty
? EmptyStateWidget(
onAddPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddAccountScreen(),
),
);
if (result != null && result is OTPAccount) {
setState(() {
_accounts.add(result);
_sortAccounts();
});
_saveAccounts();
}
},
)
: ListView.builder(
itemCount: _accounts.length,
itemBuilder: (context, index) {
return OTPCard(
account: _accounts[index],
onDelete: _deleteAccount,
onEdit: _editAccount,
onPin: _pinAccount,
isPinned: _pinnedAccountNames.contains(_accounts[index].name),
);
},
),
);
}
}
7 changes: 3 additions & 4 deletions lib/screens/more_options_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'language_screen.dart';
import '../services/localization_service.dart';
import '../widgets/custom_about_dialog.dart';

class MoreOptionsScreen extends StatelessWidget {
final Function() onAddRandomAccount;
Expand Down Expand Up @@ -53,11 +54,9 @@ class MoreOptionsScreen extends StatelessWidget {
leading: const Icon(Icons.info_outline),
title: Text(l10n.translate('about')),
onTap: () {
showAboutDialog(
showDialog(
context: context,
applicationName: l10n.translate('app_name'),
applicationVersion: '1.0.0',
applicationLegalese: '© 2024 Auth2',
builder: (context) => const CustomAboutDialog(),
);
},
),
Expand Down
4 changes: 2 additions & 2 deletions lib/services/language_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ class LanguageService {
static const String _languageKey = 'selected_language';

static final Map<String, Locale> supportedLocales = {
'English': const Locale('en'),
'简体中文': const Locale('zh'),
'English': const Locale('en'),
'日本語': const Locale('ja'),
// 'Español': const Locale('es'),
// 'Français': const Locale('fr'),
};

static Future<Locale> getSelectedLocale() async {
final prefs = await SharedPreferences.getInstance();
final languageCode = prefs.getString(_languageKey) ?? 'en';
final languageCode = prefs.getString(_languageKey) ?? 'zh';
return Locale(languageCode);
}

Expand Down
71 changes: 71 additions & 0 deletions lib/widgets/custom_about_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import '../services/localization_service.dart';

class CustomAboutDialog extends StatelessWidget {
const CustomAboutDialog({super.key});

@override
Widget build(BuildContext context) {
final l10n = LocalizationService.of(context);
final textTheme = Theme.of(context).textTheme;

return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.translate('app_name'),
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Text(
l10n.translate('version', args: {'version': '1.0.0'}),
style: textTheme.bodyLarge,
),
const SizedBox(height: 8),
Text(
'© 2024 Auth2',
style: textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
),
// const SizedBox(height: 16),
// Text(
// l10n.translate('created_by', args: {'author': 'Wang Yimin'}),
// style: textTheme.bodyMedium,
// ),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(
l10n.translate('disclaimer'),
style: textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 24),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.translate('close')),
),
],
),
),
),
);
}
}
41 changes: 41 additions & 0 deletions lib/widgets/empty_state_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../services/localization_service.dart';

class EmptyStateWidget extends StatelessWidget {
final VoidCallback onAddPressed;

const EmptyStateWidget({
super.key,
required this.onAddPressed,
});

@override
Widget build(BuildContext context) {
final l10n = LocalizationService.of(context);

return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.translate('no_accounts'),
style: const TextStyle(
fontSize: 18,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: onAddPressed,
icon: const Icon(Icons.add),
label: Text(l10n.translate('add_account')),
),
],
),
),
);
}
}

0 comments on commit a5d78e5

Please sign in to comment.