Kaynağa Gözat

Add Theme and preventScreenCapture

Sasan Salamzadeh 1 yıl önce
ebeveyn
işleme
1b2974606e

+ 28 - 1
android/app/src/main/kotlin/ir/pbdp/pb_authenticator/MainActivity.kt

@@ -1,5 +1,32 @@
 package ir.pbdp.pb_authenticator
 
 import io.flutter.embedding.android.FlutterActivity
+import android.os.Bundle
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
+import android.view.WindowManager
 
-class MainActivity: FlutterActivity()
+class MainActivity: FlutterActivity() {
+    private val CHANNEL = "screen_capture"
+
+    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+        super.configureFlutterEngine(flutterEngine)
+        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
+            when (call.method) {
+                "prevent" -> {
+                    runOnUiThread {
+                        window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+                    }
+                    result.success(null)
+                }
+                "allow" -> {
+                    runOnUiThread {
+                        window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+                    }
+                    result.success(null)
+                }
+                else -> result.notImplemented()
+            }
+        }
+    }
+}

+ 1 - 1
android/settings.gradle

@@ -18,7 +18,7 @@ pluginManagement {
 
 plugins {
     id "dev.flutter.flutter-plugin-loader" version "1.0.0"
-    id "com.android.application" version "8.1.0" apply false
+    id "com.android.application" version "8.2.1" apply false
     id "org.jetbrains.kotlin.android" version "1.9.23" apply false
 }
 

+ 6 - 1
lib/l10n/app_en.arb

@@ -53,5 +53,10 @@
     "importDescription": "Import your accounts from Google Authenticator or PB Authenticator by scanning a QR code.",
     "exportDescription": "Select which accounts to export and generate a QR code to transfer them to another device.",
     "generateQr": "Generate QR",
-    "noAccountsSelected": "No accounts selected."
+    "noAccountsSelected": "No accounts selected.",
+    "themeMode": "Theme",
+    "themeSystem": "System",
+    "themeLight": "Light",
+    "themeDark": "Dark",
+    "preventScreenCapture": "Prevent screen capture"
 }

+ 30 - 0
lib/l10n/app_localizations.dart

@@ -423,6 +423,36 @@ abstract class AppLocalizations {
   /// In en, this message translates to:
   /// **'No accounts selected.'**
   String get noAccountsSelected;
+
+  /// No description provided for @themeMode.
+  ///
+  /// In en, this message translates to:
+  /// **'Theme'**
+  String get themeMode;
+
+  /// No description provided for @themeSystem.
+  ///
+  /// In en, this message translates to:
+  /// **'System'**
+  String get themeSystem;
+
+  /// No description provided for @themeLight.
+  ///
+  /// In en, this message translates to:
+  /// **'Light'**
+  String get themeLight;
+
+  /// No description provided for @themeDark.
+  ///
+  /// In en, this message translates to:
+  /// **'Dark'**
+  String get themeDark;
+
+  /// No description provided for @preventScreenCapture.
+  ///
+  /// In en, this message translates to:
+  /// **'Prevent screen capture'**
+  String get preventScreenCapture;
 }
 
 class _AppLocalizationsDelegate

+ 15 - 0
lib/l10n/app_localizations_en.dart

@@ -182,4 +182,19 @@ class AppLocalizationsEn extends AppLocalizations {
 
   @override
   String get noAccountsSelected => 'No accounts selected.';
+
+  @override
+  String get themeMode => 'Theme';
+
+  @override
+  String get themeSystem => 'System';
+
+  @override
+  String get themeLight => 'Light';
+
+  @override
+  String get themeDark => 'Dark';
+
+  @override
+  String get preventScreenCapture => 'Prevent screen capture';
 }

+ 33 - 19
lib/main.dart

@@ -31,6 +31,16 @@ class MainApp extends StatelessWidget {
     GlobalWidgetsLocalizations.delegate,
   ];
 
+  static const MethodChannel _platform = MethodChannel('screen_capture');
+
+  void _updateScreenCapture(bool prevent) async {
+    try {
+      await _platform.invokeMethod(prevent ? 'prevent' : 'allow');
+    } catch (e) {
+      // ignore
+    }
+  }
+
   MainApp({super.key});
 
   @override
@@ -38,26 +48,30 @@ class MainApp extends StatelessWidget {
     SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
         overlays: [SystemUiOverlay.top]);
 
-    // Material/Cupertino app depending on platform
-    return MaterialApp(
-      onGenerateTitle: (context) => AppLocalizations.of(context)!.appName,
-      theme: ThemeData.light(),
-      darkTheme: ThemeData.dark(),
-      themeMode: ThemeMode.system,
-      initialRoute: AppRoutes.home,
-      routes: {
-        AppRoutes.home: (context) => const AndroidHomePage(),
-        AppRoutes.edit: (context) => const AndroidEditPage(),
-        AppRoutes.add: (context) => const AddPage(),
-        AppRoutes.addScan: (context) => const ScanQRPage(),
-        AppRoutes.settings: (context) => const SettingsPage(),
-        AppRoutes.settingAcknowledgements: (context) =>
-            const AcknowledgementsPage(),
-        AppRoutes.howItWorks: (context) => const HowItWorksPage(),
-        AppRoutes.transferCodes: (context) => const TransferCodesPage(),
+    return Consumer<AppState>(
+      builder: (context, appState, _) {
+        _updateScreenCapture(appState.screenCapturePrevented);
+        return MaterialApp(
+          onGenerateTitle: (context) => AppLocalizations.of(context)!.appName,
+          theme: ThemeData.light(),
+          darkTheme: ThemeData.dark(),
+          themeMode: appState.themeMode,
+          initialRoute: AppRoutes.home,
+          routes: {
+            AppRoutes.home: (context) => const AndroidHomePage(),
+            AppRoutes.edit: (context) => const AndroidEditPage(),
+            AppRoutes.add: (context) => const AddPage(),
+            AppRoutes.addScan: (context) => const ScanQRPage(),
+            AppRoutes.settings: (context) => const SettingsPage(),
+            AppRoutes.settingAcknowledgements: (context) =>
+                const AcknowledgementsPage(),
+            AppRoutes.howItWorks: (context) => const HowItWorksPage(),
+            AppRoutes.transferCodes: (context) => const TransferCodesPage(),
+          },
+          localizationsDelegates: localizationsDelegates,
+          supportedLocales: supportedLocales,
+        );
       },
-      localizationsDelegates: localizationsDelegates,
-      supportedLocales: supportedLocales,
     );
   }
 }

+ 59 - 1
lib/pages/settings.dart

@@ -1,12 +1,14 @@
 import 'package:flutter/widgets.dart';
-import 'package:flutter/material.dart' show Icons, ListTile, Material;
+import 'package:flutter/material.dart' show Icons, ListTile, Material, ThemeMode, SimpleDialogOption, SimpleDialog, showDialog, SwitchListTile;
 import 'package:flutter/cupertino.dart' show CupertinoIcons;
 import '../l10n/app_localizations.dart';
+import '../state/app_state.dart';
 import '../ui/adaptive.dart' show AppScaffold, isPlatformAndroid;
 import '../helper/url.dart' show launchURL;
 import 'package:package_info_plus/package_info_plus.dart' show PackageInfo;
 import '../config/routes.dart';
 import '../l10n/constants.dart';
+import 'package:provider/provider.dart';
 
 /// Settings page.
 class SettingsPage extends StatelessWidget {
@@ -74,6 +76,62 @@ class SettingsPage extends StatelessWidget {
               child: ListView(
                 physics: const NeverScrollableScrollPhysics(),
                 children: [
+                  // Theme selection
+                  Consumer<AppState>(
+                    builder: (context, appState, _) {
+                      return ListTile(
+                        dense: true,
+                        leading: const Icon(Icons.brightness_6),
+                        title: Text(AppLocalizations.of(context)!.themeMode ?? 'Theme', style: const TextStyle(fontSize: 15)),
+                        subtitle: Text(
+                          appState.themeMode == ThemeMode.system
+                              ? (AppLocalizations.of(context)!.themeSystem ?? 'System')
+                              : appState.themeMode == ThemeMode.light
+                                  ? (AppLocalizations.of(context)!.themeLight ?? 'Light')
+                                  : (AppLocalizations.of(context)!.themeDark ?? 'Dark'),
+                        ),
+                        onTap: () async {
+                          final selected = await showDialog<ThemeMode>(
+                            context: context,
+                            builder: (context) {
+                              return SimpleDialog(
+                                title: Text(AppLocalizations.of(context)!.themeMode ?? 'Theme'),
+                                children: [
+                                  SimpleDialogOption(
+                                    child: Text(AppLocalizations.of(context)!.themeSystem ?? 'System'),
+                                    onPressed: () => Navigator.pop(context, ThemeMode.system),
+                                  ),
+                                  SimpleDialogOption(
+                                    child: Text(AppLocalizations.of(context)!.themeLight ?? 'Light'),
+                                    onPressed: () => Navigator.pop(context, ThemeMode.light),
+                                  ),
+                                  SimpleDialogOption(
+                                    child: Text(AppLocalizations.of(context)!.themeDark ?? 'Dark'),
+                                    onPressed: () => Navigator.pop(context, ThemeMode.dark),
+                                  ),
+                                ],
+                              );
+                            },
+                          );
+                          if (selected != null) {
+                            appState.setThemeMode(selected);
+                          }
+                        },
+                      );
+                    },
+                  ),
+                  // Screen capture prevention
+                  Consumer<AppState>(
+                    builder: (context, appState, _) {
+                      return SwitchListTile(
+                        dense: true,
+                        secondary: const Icon(Icons.security),
+                        title: Text(AppLocalizations.of(context)!.preventScreenCapture ?? 'Prevent screen capture', style: const TextStyle(fontSize: 15)),
+                        value: appState.screenCapturePrevented,
+                        onChanged: (val) => appState.setScreenCapturePrevented(val),
+                      );
+                    },
+                  ),
                   // Source
                   ListTile(
                     dense: true,

+ 36 - 1
lib/state/app_state.dart

@@ -2,6 +2,8 @@ import 'package:pb_authenticator_state/state.dart';
 import 'package:pb_authenticator_totp/totp.dart';
 import 'package:flutter/widgets.dart';
 import 'package:collection/collection.dart' show ListEquality;
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:flutter/material.dart';
 
 // TODO: Transition to new type
 typedef BaseItemType = LegacyAuthenticatorItem;
@@ -10,7 +12,16 @@ typedef BaseItemType = LegacyAuthenticatorItem;
 class AppState extends ChangeNotifier {
   final RepositoryBase<BaseItemType> _repository;
 
-  AppState(this._repository);
+  // Theme and screen capture settings
+  ThemeMode _themeMode = ThemeMode.system;
+  bool _screenCapturePrevented = false;
+
+  ThemeMode get themeMode => _themeMode;
+  bool get screenCapturePrevented => _screenCapturePrevented;
+
+  AppState(this._repository) {
+    _loadSettings();
+  }
 
   /// List of TOTP items (internal implementation).
   List<BaseItemType>? _items;
@@ -43,4 +54,28 @@ class AppState extends ChangeNotifier {
   bool itemsChanged(List<BaseItemType>? newItems) {
     return !const ListEquality().equals(items, newItems);
   }
+
+  Future<void> _loadSettings() async {
+    final prefs = await SharedPreferences.getInstance();
+    final themeIndex = prefs.getInt('themeMode') ?? 0;
+    _themeMode = ThemeMode.values[themeIndex];
+    _screenCapturePrevented = prefs.getBool('screenCapturePrevented') ?? false;
+    // Platform channel will handle screen capture prevention
+    notifyListeners();
+  }
+
+  Future<void> setThemeMode(ThemeMode mode) async {
+    _themeMode = mode;
+    final prefs = await SharedPreferences.getInstance();
+    await prefs.setInt('themeMode', mode.index);
+    notifyListeners();
+  }
+
+  Future<void> setScreenCapturePrevented(bool value) async {
+    _screenCapturePrevented = value;
+    final prefs = await SharedPreferences.getInstance();
+    await prefs.setBool('screenCapturePrevented', value);
+    // Platform channel will handle screen capture prevention
+    notifyListeners();
+  }
 }

+ 65 - 1
pubspec.lock

@@ -89,6 +89,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.3"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.1"
   fixnum:
     dependency: transitive
     description:
@@ -403,6 +411,62 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.1.0"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.3"
+  shared_preferences_android:
+    dependency: transitive
+    description:
+      name: shared_preferences_android
+      sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.10"
+  shared_preferences_foundation:
+    dependency: transitive
+    description:
+      name: shared_preferences_foundation
+      sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.4"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.1"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.1"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.3"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.1"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -610,4 +674,4 @@ packages:
     version: "1.0.4"
 sdks:
   dart: ">=3.7.0-0 <4.0.0"
-  flutter: ">=3.24.0"
+  flutter: ">=3.27.0"

+ 1 - 0
pubspec.yaml

@@ -19,6 +19,7 @@ dependencies:
   path_provider: ^2.1.4
   url_launcher: ^6.3.0
   package_info_plus: ^8.0.2
+  shared_preferences: ^2.2.2
 
 
   # Internal