فهرست منبع

Persian laguage and RTL Style added, Readme Updated

Sasan Salamzadeh 1 سال پیش
والد
کامیت
581057050d

+ 106 - 15
README.md

@@ -1,23 +1,114 @@
 # PB Authenticator
-A two-factor authentication app built with Flutter.
 
-Not ready for production.\
-There will likely be bugs and upgrade issues.
+A modern, open-source two-factor authentication (2FA) app built with Flutter. PB Authenticator supports TOTP, QR code scanning, secure local storage, and a beautiful, customizable UI. Now with full Persian (Farsi) language support, RTL layout, and the Vazirmatn font for a seamless experience for Persian users.
 
-## Development
-`flutter run`
+---
 
-## Features
-- Scan QR or manually input account details
-- Material Theme
+## 🚀 Features
 
-## Proposed
-- View secrets
-- Backup secrets to cloud storage
-- UI tests
+- **Time-based One-Time Passwords (TOTP)**: Securely generate 2FA codes for your accounts.
+- **QR Code Scanning**: Quickly add accounts by scanning QR codes.
+- **Manual Entry**: Add accounts manually if QR is not available.
+- **Material Design**: Clean, modern, and responsive UI.
+- **Icon Support**: Large collection of SVG icons for popular services.
+- **Account Management**: Edit, remove, and organize your 2FA accounts.
+- **Export/Import**: Transfer your accounts between devices securely.
+- **Security**: PIN code and biometric authentication support.
+- **Screen Capture Prevention**: Optional protection against screenshots.
+- **Theme Support**: Light, dark, and system themes.
+- **Localization**: 
+  - English and Persian (Farsi) language support
+  - Language switcher in settings
+  - RTL layout and Vazirmatn font for Persian
+- **Cross-Platform**: Runs on Android, iOS, and desktop (Flutter-supported platforms).
 
-Some outdated screenshots:
+---
 
-|Material|
+## 🌐 Localization & RTL
+
+- **Persian (Farsi) Support**: All UI elements are fully translated.
+- **RTL Layout**: The app automatically switches to right-to-left layout and uses the Vazirmatn font when Persian is selected.
+- **Language Switcher**: Instantly change the app language from the settings menu.
+
+---
+
+## 🖼️ Icon & Asset Support
+
+- Hundreds of SVG icons for popular services (see `graphics/aegis-icons-outline/SVG/`).
+- Custom app icon included.
+
+---
+
+## 🔒 Security
+
+- All data is stored **locally** on your device.
+- PIN code and biometric authentication options.
+- Optional prevention of screen capture for sensitive screens.
+
+---
+
+## 📦 Getting Started
+
+### Prerequisites
+- [Flutter SDK](https://flutter.dev/docs/get-started/install)
+- Android/iOS device or emulator, or desktop (for supported platforms)
+
+### Installation
+```sh
+flutter pub get
+flutter run
+```
+
+### Directory Structure
+- `lib/` — Main app code
+- `lib/pages/` — UI pages (settings, add, import/export, etc.)
+- `lib/l10n/` — Localization files (ARB, generated Dart)
+- `graphics/` — App icons and SVG icon set
+- `assets/fonts/` — Custom fonts (Vazirmatn)
+- `test/`, `state/test/`, `totp/test/` — Unit and widget tests
+
+---
+
+## 🧪 Testing
+Run all tests:
+```sh
+flutter test
+```
+
+---
+
+## 🤝 Contributing
+Contributions, issues, and feature requests are welcome!
+- Fork the repo
+- Create your feature branch (`git checkout -b feature/your-feature`)
+- Commit your changes (`git commit -am 'Add new feature'`)
+- Push to the branch (`git push origin feature/your-feature`)
+- Open a pull request
+
+---
+
+## 📄 License
+This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
+
+---
+
+## 🙏 Credits
+- [Vazirmatn Font](https://github.com/rastikerdar/vazirmatn) by [rastikerdar](https://github.com/rastikerdar)
+- [Aegis Simple Icons Outlined](https://github.com/michaelschattgen/aegis-simple-icons-outlined) by [michaelschattgen](https://github.com/michaelschattgen)
+- Flutter and the open-source community
+
+---
+
+## 📷 Screenshots
+
+| Material Theme |
 |----|
-|<img src="../readme-assets/material.png" alt="Material" height="450">|
+|<img src="./readme-assets/screenshot (1).jpg" alt="Import Accounts" height="450">|
+|<img src="./readme-assets/screenshot (2).jpg" alt="Add Account with Manual Input" height="450">|
+|<img src="./readme-assets/screenshot (3).jpg" alt="Main Page Code List" height="450">|
+|<img src="./readme-assets/screenshot (4).jpg" alt="PB Authenticator Settins" height="450">|
+|<img src="./readme-assets/screenshot (5).jpg" alt="How It Works" height="450">|
+
+---
+
+> **Disclaimer:** PB Authenticator is not ready for production. There may be bugs and upgrade issues. Use at your own risk.

BIN
assets/fonts/Vazirmatn.ttf


+ 63 - 0
lib/l10n/app_fa.arb

@@ -0,0 +1,63 @@
+{
+    "appName": "پی‌بی احرازگر",
+    "ok": "تأیید",
+    "yes": "بله",
+    "no": "خیر",
+    "error": "خطا",
+    "save": "ذخیره",
+    "edit": "ویرایش",
+    "add": "افزودن",
+    "addTitle": "افزودن حساب",
+    "addScanQR": "اسکن کد QR",
+    "addManualInput": "ورود دستی",
+    "addMethodPrompt": "لطفاً روش ورود را انتخاب کنید.",
+    "cancel": "انصراف",
+    "secret": "کلید مخفی",
+    "secretInvalidMessage": "کلید مخفی نامعتبر است",
+    "issuer": "ارائه‌دهنده/صادرکننده",
+    "accountName": "نام حساب",
+    "digits": "تعداد ارقام",
+    "period": "گام زمانی",
+    "settingsTitle": "تنظیمات",
+    "source": "کد منبع",
+    "clipboard": "در کلیپ‌بورد کپی شد",
+    "noAccounts": "شما هیچ حسابی ندارید،\n برای افزودن یک حساب دکمه + را فشار دهید.",
+    "loading": "در حال بارگذاری...",
+    "editTitle": "ویرایش حساب‌ها",
+    "removeAccounts": "حذف حساب‌ها",
+    "removeConfirmation": "لطفاً قبل از حذف، احراز هویت دو مرحله‌ای را غیرفعال کنید.\n\nآیا مطمئن هستید که می‌خواهید حساب(های) انتخاب شده را حذف کنید؟",
+    "exitEditTitle": "خروج",
+    "exitEditInfo": "آیا می‌خواهید بدون ذخیره از حالت ویرایش خارج شوید؟",
+    "errDuplicateAccount": "حساب تکراری",
+    "errIdCollision": "یک برخورد نادر شناسه رخ داده است. لطفاً دوباره تلاش کنید.",
+    "errNoCameraPermission": "دسترسی دوربین داده نشده است",
+    "errIncorrectFormat": "کد اسکن شده فرمت نادرست دارد",
+    "errUnknown": "خطای ناشناخته",
+    "howItWorksTitle": "نحوه کارکرد",
+    "howItWorksTitle1": "حساب‌های خود را ایمن کنید",
+    "howItWorksBody1": "حساب‌های خود را اضافه کنید تا رمزهای یکبار مصرف مبتنی بر زمان (TOTP) برای ورود امن تولید کنید.",
+    "howItWorksTitle2": "اسکن کدهای QR",
+    "howItWorksBody2": "به راحتی با اسکن کدهای QR ارائه شده توسط سرویس‌ها، حساب جدید اضافه کنید.",
+    "howItWorksTitle3": "پشتیبان‌گیری و بازیابی",
+    "howItWorksBody3": "حساب‌های خود را به صورت امن پشتیبان‌گیری و در صورت نیاز بازیابی کنید.",
+    "howItWorksTitle4": "آفلاین و خصوصی",
+    "howItWorksBody4": "تمام داده‌ها روی دستگاه شما باقی می‌ماند. برای تولید کدها نیازی به اینترنت نیست.",
+    "skip": "رد کردن",
+    "done": "انجام شد",
+    "transferCodesTitle": "انتقال کدها",
+    "transferCodesDescription": "حساب‌های خود را به دستگاه دیگری منتقل کنید.",
+    "exportAccounts": "خروجی گرفتن از حساب‌ها",
+    "importAccounts": "وارد کردن حساب‌ها",
+    "importSuccess": "حساب‌ها با موفقیت وارد شدند!",
+    "importDescription": "حساب‌های خود را از Google Authenticator یا PB Authenticator با اسکن کد QR وارد کنید.",
+    "exportDescription": "حساب‌هایی که می‌خواهید صادر کنید را انتخاب و یک کد QR برای انتقال به دستگاه دیگر تولید کنید.",
+    "generateQr": "تولید کد QR",
+    "noAccountsSelected": "هیچ حسابی انتخاب نشده است.",
+    "themeMode": "حالت نمایش",
+    "themeSystem": "سیستم",
+    "themeLight": "روشن",
+    "themeDark": "تاریک",
+    "preventScreenCapture": "جلوگیری از عکس‌برداری صفحه",
+    "helpTitle": "راهنما",
+    "helpBody": "نیاز به کمک دارید؟ اینجا می‌توانید پاسخ سوالات متداول و نکاتی برای استفاده از پی‌بی احرازگر را بیابید."
+} 

+ 8 - 2
lib/l10n/app_localizations.dart

@@ -6,6 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:intl/intl.dart' as intl;
 
 import 'app_localizations_en.dart';
+import 'app_localizations_fa.dart';
 
 // ignore_for_file: type=lint
 
@@ -92,7 +93,10 @@ abstract class AppLocalizations {
   ];
 
   /// A list of this localizations delegate's supported locales.
-  static const List<Locale> supportedLocales = <Locale>[Locale('en')];
+  static const List<Locale> supportedLocales = <Locale>[
+    Locale('en'),
+    Locale('fa')
+  ];
 
   /// No description provided for @appName.
   ///
@@ -472,7 +476,7 @@ class _AppLocalizationsDelegate
 
   @override
   bool isSupported(Locale locale) =>
-      <String>['en'].contains(locale.languageCode);
+      <String>['en', 'fa'].contains(locale.languageCode);
 
   @override
   bool shouldReload(_AppLocalizationsDelegate old) => false;
@@ -483,6 +487,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
   switch (locale.languageCode) {
     case 'en':
       return AppLocalizationsEn();
+    case 'fa':
+      return AppLocalizationsFa();
   }
 
   throw FlutterError(

+ 205 - 0
lib/l10n/app_localizations_fa.dart

@@ -0,0 +1,205 @@
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
+import 'app_localizations.dart';
+
+// ignore_for_file: type=lint
+
+/// The translations for Persian (`fa`).
+class AppLocalizationsFa extends AppLocalizations {
+  AppLocalizationsFa([String locale = 'fa']) : super(locale);
+
+  @override
+  String get appName => 'پی‌بی احرازگر';
+
+  @override
+  String get ok => 'تأیید';
+
+  @override
+  String get yes => 'بله';
+
+  @override
+  String get no => 'خیر';
+
+  @override
+  String get error => 'خطا';
+
+  @override
+  String get save => 'ذخیره';
+
+  @override
+  String get edit => 'ویرایش';
+
+  @override
+  String get add => 'افزودن';
+
+  @override
+  String get addTitle => 'افزودن حساب';
+
+  @override
+  String get addScanQR => 'اسکن کد QR';
+
+  @override
+  String get addManualInput => 'ورود دستی';
+
+  @override
+  String get addMethodPrompt => 'لطفاً روش ورود را انتخاب کنید.';
+
+  @override
+  String get cancel => 'انصراف';
+
+  @override
+  String get secret => 'کلید مخفی';
+
+  @override
+  String get secretInvalidMessage => 'کلید مخفی نامعتبر است';
+
+  @override
+  String get issuer => 'ارائه‌دهنده/صادرکننده';
+
+  @override
+  String get accountName => 'نام حساب';
+
+  @override
+  String get digits => 'تعداد ارقام';
+
+  @override
+  String get period => 'گام زمانی';
+
+  @override
+  String get settingsTitle => 'تنظیمات';
+
+  @override
+  String get source => 'کد منبع';
+
+  @override
+  String get clipboard => 'در کلیپ‌بورد کپی شد';
+
+  @override
+  String get noAccounts =>
+      'شما هیچ حسابی ندارید،\n برای افزودن یک حساب دکمه + را فشار دهید.';
+
+  @override
+  String get loading => 'در حال بارگذاری...';
+
+  @override
+  String get editTitle => 'ویرایش حساب‌ها';
+
+  @override
+  String get removeAccounts => 'حذف حساب‌ها';
+
+  @override
+  String get removeConfirmation =>
+      'لطفاً قبل از حذف، احراز هویت دو مرحله‌ای را غیرفعال کنید.\n\nآیا مطمئن هستید که می‌خواهید حساب(های) انتخاب شده را حذف کنید؟';
+
+  @override
+  String get exitEditTitle => 'خروج';
+
+  @override
+  String get exitEditInfo =>
+      'آیا می‌خواهید بدون ذخیره از حالت ویرایش خارج شوید؟';
+
+  @override
+  String get errDuplicateAccount => 'حساب تکراری';
+
+  @override
+  String get errIdCollision =>
+      'یک برخورد نادر شناسه رخ داده است. لطفاً دوباره تلاش کنید.';
+
+  @override
+  String get errNoCameraPermission => 'دسترسی دوربین داده نشده است';
+
+  @override
+  String get errIncorrectFormat => 'کد اسکن شده فرمت نادرست دارد';
+
+  @override
+  String get errUnknown => 'خطای ناشناخته';
+
+  @override
+  String get howItWorksTitle => 'نحوه کارکرد';
+
+  @override
+  String get howItWorksTitle1 => 'حساب‌های خود را ایمن کنید';
+
+  @override
+  String get howItWorksBody1 =>
+      'حساب‌های خود را اضافه کنید تا رمزهای یکبار مصرف مبتنی بر زمان (TOTP) برای ورود امن تولید کنید.';
+
+  @override
+  String get howItWorksTitle2 => 'اسکن کدهای QR';
+
+  @override
+  String get howItWorksBody2 =>
+      'به راحتی با اسکن کدهای QR ارائه شده توسط سرویس‌ها، حساب جدید اضافه کنید.';
+
+  @override
+  String get howItWorksTitle3 => 'پشتیبان‌گیری و بازیابی';
+
+  @override
+  String get howItWorksBody3 =>
+      'حساب‌های خود را به صورت امن پشتیبان‌گیری و در صورت نیاز بازیابی کنید.';
+
+  @override
+  String get howItWorksTitle4 => 'آفلاین و خصوصی';
+
+  @override
+  String get howItWorksBody4 =>
+      'تمام داده‌ها روی دستگاه شما باقی می‌ماند. برای تولید کدها نیازی به اینترنت نیست.';
+
+  @override
+  String get skip => 'رد کردن';
+
+  @override
+  String get done => 'انجام شد';
+
+  @override
+  String get transferCodesTitle => 'انتقال کدها';
+
+  @override
+  String get transferCodesDescription =>
+      'حساب‌های خود را به دستگاه دیگری منتقل کنید.';
+
+  @override
+  String get exportAccounts => 'خروجی گرفتن از حساب‌ها';
+
+  @override
+  String get importAccounts => 'وارد کردن حساب‌ها';
+
+  @override
+  String get importSuccess => 'حساب‌ها با موفقیت وارد شدند!';
+
+  @override
+  String get importDescription =>
+      'حساب‌های خود را از Google Authenticator یا PB Authenticator با اسکن کد QR وارد کنید.';
+
+  @override
+  String get exportDescription =>
+      'حساب‌هایی که می‌خواهید صادر کنید را انتخاب و یک کد QR برای انتقال به دستگاه دیگر تولید کنید.';
+
+  @override
+  String get generateQr => 'تولید کد QR';
+
+  @override
+  String get noAccountsSelected => 'هیچ حسابی انتخاب نشده است.';
+
+  @override
+  String get themeMode => 'حالت نمایش';
+
+  @override
+  String get themeSystem => 'سیستم';
+
+  @override
+  String get themeLight => 'روشن';
+
+  @override
+  String get themeDark => 'تاریک';
+
+  @override
+  String get preventScreenCapture => 'جلوگیری از عکس‌برداری صفحه';
+
+  @override
+  String get helpTitle => 'راهنما';
+
+  @override
+  String get helpBody =>
+      'نیاز به کمک دارید؟ اینجا می‌توانید پاسخ سوالات متداول و نکاتی برای استفاده از پی‌بی احرازگر را بیابید.';
+}

+ 16 - 5
lib/main.dart

@@ -81,10 +81,17 @@ class _MainAppState extends State<MainApp> {
     return Consumer<AppState>(
       builder: (context, appState, _) {
         widget._updateScreenCapture(appState.screenCapturePrevented);
+        final isPersian = appState.locale.languageCode == 'fa';
         return MaterialApp(
           onGenerateTitle: (context) => AppLocalizations.of(context)!.appName,
-          theme: ThemeData.light(),
-          darkTheme: ThemeData.dark(),
+          theme: ThemeData(
+            brightness: Brightness.light,
+            fontFamily: isPersian ? 'Vazirmatn' : null,
+          ),
+          darkTheme: ThemeData(
+            brightness: Brightness.dark,
+            fontFamily: isPersian ? 'Vazirmatn' : null,
+          ),
           themeMode: appState.themeMode,
           initialRoute: AppRoutes.home,
           routes: {
@@ -97,11 +104,15 @@ class _MainAppState extends State<MainApp> {
             AppRoutes.transferCodes: (context) => _AuthGate(child: const TransferCodesPage()),
             AppRoutes.help: (context) => _AuthGate(child: const HelpPage()),
           },
-          localizationsDelegates: widget.localizationsDelegates,
-          supportedLocales: widget.supportedLocales,
+          localizationsDelegates: AppLocalizations.localizationsDelegates,
+          supportedLocales: const [Locale('en'), Locale('fa')],
+          locale: appState.locale,
           builder: (context, child) {
             if (_locked) return const SizedBox.shrink();
-            return child!;
+            return Directionality(
+              textDirection: isPersian ? TextDirection.rtl : TextDirection.ltr,
+              child: child!,
+            );
           },
         );
       },

+ 19 - 17
lib/pages/android/home.dart

@@ -115,35 +115,37 @@ class AndroidHomePage extends StatelessWidget {
                 ],
               ),
             ),
-            // Settings
-            ListTile(
-                title: Text(AppLocalizations.of(context)!.settingsTitle),
-                leading: const Icon(Icons.settings),
-                dense: true,
-                onTap: () {
-                  Navigator.pop(context);
-                  Navigator.pushNamed(context, AppRoutes.settings);
-                }),
-            // How it works
+            // Transfer codes
             ListTile(
-              title: Text(AppLocalizations.of(context)!.howItWorksTitle ?? 'How it works'),
-              leading: const Icon(Icons.info_outline),
+              title: Text(AppLocalizations.of(context)!.transferCodesTitle ?? 'Transfer codes'),
+              leading: const Icon(Icons.sync_alt),
               dense: true,
               onTap: () {
                 Navigator.pop(context);
-                Navigator.pushNamed(context, AppRoutes.howItWorks);
+                Navigator.pushNamed(context, AppRoutes.transferCodes);
               },
             ),
-            // Transfer codes
+            // How it works
             ListTile(
-              title: Text(AppLocalizations.of(context)!.transferCodesTitle ?? 'Transfer codes'),
-              leading: const Icon(Icons.sync_alt),
+              title: Text(AppLocalizations.of(context)!.howItWorksTitle ?? 'How it works'),
+              leading: const Icon(Icons.info_outline),
               dense: true,
               onTap: () {
                 Navigator.pop(context);
-                Navigator.pushNamed(context, AppRoutes.transferCodes);
+                Navigator.pushNamed(context, AppRoutes.howItWorks);
               },
             ),
+            // Settings
+            ListTile(
+                title: Text(AppLocalizations.of(context)!.settingsTitle),
+                leading: const Icon(Icons.settings),
+                dense: true,
+                onTap: () {
+                  Navigator.pop(context);
+                  Navigator.pushNamed(context, AppRoutes.settings);
+                }
+              ),
+
             const Divider(height: 10),
             // Source code (in-app browser link)
             ListTile(

+ 35 - 0
lib/pages/settings.dart

@@ -78,6 +78,41 @@ class SettingsPage extends StatelessWidget {
               child: ListView(
                 physics: const NeverScrollableScrollPhysics(),
                 children: [
+                  // Language selection
+                  Consumer<AppState>(
+                    builder: (context, appState, _) {
+                      final currentLocale = appState.locale.languageCode;
+                      return ListTile(
+                        dense: true,
+                        leading: const Icon(Icons.language),
+                        title: Text('Language', style: const TextStyle(fontSize: 15)),
+                        subtitle: Text(currentLocale == 'fa' ? 'فارسی' : 'English'),
+                        onTap: () async {
+                          final selected = await showDialog<String>(
+                            context: context,
+                            builder: (context) {
+                              return SimpleDialog(
+                                title: const Text('Select Language'),
+                                children: [
+                                  SimpleDialogOption(
+                                    child: const Text('English'),
+                                    onPressed: () => Navigator.pop(context, 'en'),
+                                  ),
+                                  SimpleDialogOption(
+                                    child: const Text('فارسی'),
+                                    onPressed: () => Navigator.pop(context, 'fa'),
+                                  ),
+                                ],
+                              );
+                            },
+                          );
+                          if (selected != null && selected != currentLocale) {
+                            appState.setLocale(Locale(selected));
+                          }
+                        },
+                      );
+                    },
+                  ),
                   // Theme selection
                   Consumer<AppState>(
                     builder: (context, appState, _) {

+ 17 - 0
lib/state/app_state.dart

@@ -38,6 +38,11 @@ class AppState extends ChangeNotifier {
   static const _forceMonochromeIconsKey = 'forceMonochromeIcons';
   bool get forceMonochromeIcons => _forceMonochromeIcons;
 
+  // Locale setting
+  Locale _locale = const Locale('en');
+  static const _localeKey = 'locale';
+  Locale get locale => _locale;
+
   bool get pinEnabled => _pinEnabled;
   bool get biometricEnabled => _biometricEnabled;
   DateTime? get lastUnlockTime => _lastUnlockTime;
@@ -91,6 +96,11 @@ class AppState extends ChangeNotifier {
     _biometricEnabled = prefs.getBool(_biometricEnabledKey) ?? false;
     _showIcons = prefs.getBool(_showIconsKey) ?? true;
     _forceMonochromeIcons = prefs.getBool(_forceMonochromeIconsKey) ?? false;
+    // Load locale
+    final localeCode = prefs.getString(_localeKey);
+    if (localeCode != null) {
+      _locale = Locale(localeCode);
+    }
     notifyListeners();
   }
 
@@ -137,6 +147,13 @@ class AppState extends ChangeNotifier {
     notifyListeners();
   }
 
+  Future<void> setLocale(Locale locale) async {
+    _locale = locale;
+    final prefs = await SharedPreferences.getInstance();
+    await prefs.setString(_localeKey, locale.languageCode);
+    notifyListeners();
+  }
+
   Future<void> setPin(String pin) async {
     await _secureStorage.write(key: _pinKey, value: pin);
   }

+ 5 - 1
pubspec.yaml

@@ -1,7 +1,7 @@
 name: pb_authenticator
 description: "PB two-factor authentication app."
 publish_to: 'none'
-version: 1.0.0+1
+version: 1.2.0+1
 
 environment:
   sdk: ^3.5.3
@@ -52,3 +52,7 @@ flutter:
     - graphics/aegis-icons-outline/
     - graphics/aegis-icons-outline/SVG/
     - graphics/icon.png
+  fonts:
+    - family: Vazirmatn
+      fonts:
+        - asset: assets/fonts/Vazirmatn.ttf

BIN
readme-assets/screenshot (1).jpg


BIN
readme-assets/screenshot (2).jpg


BIN
readme-assets/screenshot (3).jpg


BIN
readme-assets/screenshot (4).jpg


BIN
readme-assets/screenshot (5).jpg


BIN
readme-assets/screenshot (6).jpg


+ 11 - 3
state/pubspec.lock

@@ -98,10 +98,10 @@ packages:
     dependency: transitive
     description:
       name: fixnum
-      sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+      sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.0"
+    version: "1.1.1"
   flutter:
     dependency: transitive
     description: flutter
@@ -241,7 +241,7 @@ packages:
       path: "../totp"
       relative: true
     source: path
-    version: "1.0.0"
+    version: "1.2.0"
   pool:
     dependency: transitive
     description:
@@ -250,6 +250,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.5.1"
+  protobuf:
+    dependency: transitive
+    description:
+      name: protobuf
+      sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
   pub_semver:
     dependency: transitive
     description:

+ 1 - 1
state/pubspec.yaml

@@ -1,6 +1,6 @@
 name: pb_authenticator_state
 description: State for PB Authenticator.
-version: 1.0.0
+version: 1.2.0
 publish_to: none
 
 environment:

+ 1 - 1
totp/pubspec.yaml

@@ -1,6 +1,6 @@
 name: pb_authenticator_totp
 description: TOTP library used by pb_authenticator.
-version: 1.0.0
+version: 1.2.0
 publish_to: none
 
 environment: