Browse Source

Add help section and FAQ

Sasan Salamzadeh 1 year ago
parent
commit
2251d54d59

+ 3 - 1
lib/config/routes.dart

@@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
 
 import '../pages/how_it_works.dart';
 import '../pages/transfer_codes.dart';
+import '../pages/help.dart';
 
 class AppRoutes {
   static const String home = '/';
@@ -9,12 +10,13 @@ class AppRoutes {
   static const String addScan = '/add/scan';
   static const String edit = '/edit';
   static const String settings = '/settings';
-  static const String settingAcknowledgements = '/settings/acknowledgements';
   static const String howItWorks = '/howItWorks';
   static const String transferCodes = '/transferCodes';
+  static const String help = '/help';
 }
 
 final Map<String, WidgetBuilder> appRoutes = {
   AppRoutes.howItWorks: (context) => const HowItWorksPage(),
   AppRoutes.transferCodes: (context) => const TransferCodesPage(),
+  AppRoutes.help: (context) => const HelpPage(),
 };

+ 3 - 2
lib/l10n/app_en.arb

@@ -20,7 +20,6 @@
     "period": "Time step",
     "settingsTitle": "Settings",
     "source": "Source code",
-    "licenses": "Acknowledgements",
     "clipboard": "Copied to clipboard",
     "noAccounts": "You have no accounts,\n press the + button to add one.",
     "loading": "Loading...",
@@ -58,5 +57,7 @@
     "themeSystem": "System",
     "themeLight": "Light",
     "themeDark": "Dark",
-    "preventScreenCapture": "Prevent screen capture"
+    "preventScreenCapture": "Prevent screen capture",
+    "helpTitle": "Help",
+    "helpBody": "Need assistance? Here you can find answers to common questions and tips for using PB Authenticator."
 }

+ 12 - 6
lib/l10n/app_localizations.dart

@@ -220,12 +220,6 @@ abstract class AppLocalizations {
   /// **'Source code'**
   String get source;
 
-  /// No description provided for @licenses.
-  ///
-  /// In en, this message translates to:
-  /// **'Acknowledgements'**
-  String get licenses;
-
   /// No description provided for @clipboard.
   ///
   /// In en, this message translates to:
@@ -453,6 +447,18 @@ abstract class AppLocalizations {
   /// In en, this message translates to:
   /// **'Prevent screen capture'**
   String get preventScreenCapture;
+
+  /// No description provided for @helpTitle.
+  ///
+  /// In en, this message translates to:
+  /// **'Help'**
+  String get helpTitle;
+
+  /// No description provided for @helpBody.
+  ///
+  /// In en, this message translates to:
+  /// **'Need assistance? Here you can find answers to common questions and tips for using PB Authenticator.'**
+  String get helpBody;
 }
 
 class _AppLocalizationsDelegate

+ 7 - 3
lib/l10n/app_localizations_en.dart

@@ -71,9 +71,6 @@ class AppLocalizationsEn extends AppLocalizations {
   @override
   String get source => 'Source code';
 
-  @override
-  String get licenses => 'Acknowledgements';
-
   @override
   String get clipboard => 'Copied to clipboard';
 
@@ -197,4 +194,11 @@ class AppLocalizationsEn extends AppLocalizations {
 
   @override
   String get preventScreenCapture => 'Prevent screen capture';
+
+  @override
+  String get helpTitle => 'Help';
+
+  @override
+  String get helpBody =>
+      'Need assistance? Here you can find answers to common questions and tips for using PB Authenticator.';
 }

+ 1 - 2
lib/main.dart

@@ -63,10 +63,9 @@ class MainApp extends StatelessWidget {
             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(),
+            AppRoutes.help: (context) => const HelpPage(),
           },
           localizationsDelegates: localizationsDelegates,
           supportedLocales: supportedLocales,

+ 0 - 220
lib/pages/acknowledgements.dart

@@ -1,220 +0,0 @@
-// Acknowledgements page
-//
-// Various code from:
-// https://github.com/flutter/flutter/blob/efb1346767e67d2577461baf671eb2d5d2c4bb90/packages/flutter/lib/src/material/about.dart
-
-import 'package:flutter/foundation.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter/material.dart'
-    show
-        CircularProgressIndicator,
-        Divider,
-        ListTile,
-        Material,
-        MaterialPageRoute,
-        Theme;
-
-import '../ui/adaptive.dart' show AppScaffold;
-import '../l10n/app_localizations.dart';
-
-class AcknowledgementsPage extends StatefulWidget {
-  const AcknowledgementsPage({
-    super.key,
-  });
-
-  @override
-  State<AcknowledgementsPage> createState() => _AcknowledgementsPageState();
-}
-
-/// Acknowledgements page for used libraries.
-class _AcknowledgementsPageState extends State<AcknowledgementsPage> {
-  // Adapted from flutter/flutter
-  final Future<_LicenseData> licenses = LicenseRegistry.licenses
-      .fold<_LicenseData>(
-        _LicenseData(),
-        (_LicenseData prev, LicenseEntry license) => prev..addLicense(license),
-      )
-      .then((_LicenseData licenseData) => licenseData..sortPackages());
-
-  @override
-  Widget build(BuildContext context) {
-    return AppScaffold(
-      title: Text(AppLocalizations.of(context)!.licenses),
-      body: FutureBuilder<_LicenseData>(
-        future: licenses,
-        builder: (BuildContext context, AsyncSnapshot<_LicenseData> snapshot) {
-          return LayoutBuilder(
-            key: ValueKey<ConnectionState>(snapshot.connectionState),
-            builder: (BuildContext context, BoxConstraints constraints) {
-              switch (snapshot.connectionState) {
-                case ConnectionState.done:
-                  if (snapshot.hasError) {
-                    return Center(child: Text(snapshot.error.toString()));
-                  }
-                  return Material(
-                    child: ListView.separated(
-                      itemCount: snapshot.data!.count(),
-                      separatorBuilder: (BuildContext context, int index) =>
-                          const SizedBox(height: 0, width: 0),
-                      itemBuilder: (BuildContext context, int index) {
-                        return ListTile(
-                          dense: true,
-                          title: Text(snapshot.data!.getTitle(index)),
-                          onTap: () {
-                            Navigator.of(context).push(
-                              MaterialPageRoute(
-                                builder: (context) => FullScreenLicense(
-                                  snapshot.data!.getTitle(index),
-                                  snapshot.data!.getLicenses(index),
-                                ),
-                              ),
-                            );
-                          },
-                        );
-                      },
-                    ),
-                  );
-                case ConnectionState.none:
-                case ConnectionState.active:
-                case ConnectionState.waiting:
-                  return Material(
-                    color: Theme.of(context).cardColor,
-                    child: const Center(child: CircularProgressIndicator()),
-                  );
-              }
-            },
-          );
-        },
-      ),
-    );
-  }
-}
-
-/// Full screen license widget
-class FullScreenLicense extends StatelessWidget {
-  /// The name
-  final String title;
-
-  /// License
-  final List<LicenseEntry> licenseEntries;
-
-  const FullScreenLicense(this.title, this.licenseEntries, {super.key});
-
-  @override
-  Widget build(BuildContext context) {
-    List<Widget> licenses = <Widget>[];
-    for (var (index, licenseEntry) in licenseEntries.indexed) {
-      if (index != 0) {
-        licenses.add(const Divider());
-      }
-      licenses.addAll(licenseEntry.paragraphs.map((p) {
-        // Adapted from flutter/flutter
-        if (p.indent == LicenseParagraph.centeredIndent) {
-          return Padding(
-            padding: const EdgeInsets.only(top: 16.0),
-            child: Text(
-              p.text,
-              style: const TextStyle(fontWeight: FontWeight.bold),
-              textAlign: TextAlign.center,
-            ),
-          );
-        } else {
-          return Padding(
-            padding:
-                EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * p.indent),
-            child: Text(p.text),
-          );
-        }
-      }));
-    }
-
-    return AppScaffold(
-      title: Text(title),
-      body: Material(
-        child: SingleChildScrollView(
-          child: Padding(
-            padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12),
-            child: Column(children: licenses),
-          ),
-        ),
-      ),
-    );
-  }
-}
-
-/// Adapted from flutter/flutter
-/// With renames and addition of helper methods.
-///
-/// This is a collection of licenses and the packages to which they apply.
-/// [_packageLicenseBindings] records the m+:n+ relationship between the license
-/// and packages as a map of package names to license indexes.
-class _LicenseData {
-  final List<LicenseEntry> _licenses = <LicenseEntry>[];
-  final Map<String, List<int>> _packageLicenseBindings = <String, List<int>>{};
-  final List<String> _packages = <String>[];
-
-  // Special treatment for the first package since it should be the package
-  // for delivered application.
-  String? firstPackage;
-
-  void addLicense(LicenseEntry entry) {
-    // Before the license can be added, we must first record the packages to
-    // which it belongs.
-    for (final String package in entry.packages) {
-      _addPackage(package);
-      // Bind this license to the package using the next index value. This
-      // creates a contract that this license must be inserted at this same
-      // index value.
-      _packageLicenseBindings[package]!.add(_licenses.length);
-    }
-    _licenses.add(entry); // Completion of the contract above.
-  }
-
-  /// Add a package and initialize package license binding. This is a no-op if
-  /// the package has been seen before.
-  void _addPackage(String package) {
-    if (!_packageLicenseBindings.containsKey(package)) {
-      _packageLicenseBindings[package] = <int>[];
-      firstPackage ??= package;
-      _packages.add(package);
-    }
-  }
-
-  /// Sort the packages using some comparison method, or by the default manner,
-  /// which is to put the application package first, followed by every other
-  /// package in case-insensitive alphabetical order.
-  void sortPackages([int Function(String a, String b)? compare]) {
-    _packages.sort(compare ??
-        (String a, String b) {
-          // Based on how LicenseRegistry currently behaves, the first package
-          // returned is the end user application license. This should be
-          // presented first in the list. So here we make sure that first package
-          // remains at the front regardless of alphabetical sorting.
-          if (a == firstPackage) {
-            return -1;
-          }
-          if (b == firstPackage) {
-            return 1;
-          }
-          return a.toLowerCase().compareTo(b.toLowerCase());
-        });
-  }
-
-  int count() {
-    return _packages.length;
-  }
-
-  String getTitle(int index) {
-    return _packages[index];
-  }
-
-  List<LicenseEntry> getLicenses(int index) {
-    var package = _packages[index];
-    return _getLicensesForPackage(package);
-  }
-
-  List<LicenseEntry> _getLicensesForPackage(String package) {
-    var bindings = _packageLicenseBindings[package]!;
-    return bindings.map((b) => _licenses[b]).toList();
-  }
-}

+ 4 - 4
lib/pages/android/home.dart

@@ -154,14 +154,14 @@ class AndroidHomePage extends StatelessWidget {
                   Navigator.pop(context);
                   launchURL(Constants.repoUrl);
                 }),
-            // License
+            // Help
             ListTile(
-              title: Text(AppLocalizations.of(context)!.licenses),
-              leading: const Icon(Icons.book),
+              title: Text(AppLocalizations.of(context)!.helpTitle ?? 'Help'),
+              leading: const Icon(Icons.help_outline),
               dense: true,
               onTap: () {
                 Navigator.pop(context);
-                Navigator.pushNamed(context, AppRoutes.settingAcknowledgements);
+                Navigator.pushNamed(context, AppRoutes.help);
               },
             ),
           ],

+ 69 - 0
lib/pages/help.dart

@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+import '../ui/adaptive.dart' show AppScaffold;
+import '../l10n/app_localizations.dart';
+
+class HelpPage extends StatelessWidget {
+  const HelpPage({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final faqs = [
+      {
+        'question': 'What is PB Authenticator?',
+        'answer': 'PB Authenticator is an app for generating time-based one-time passwords (TOTP) to secure your online accounts with two-factor authentication.'
+      },
+      {
+        'question': 'How do I add a new account?',
+        'answer': 'Tap the + button on the home screen and choose to scan a QR code or enter details manually.'
+      },
+      {
+        'question': 'How can I transfer my accounts to a new device?',
+        'answer': 'Use the "Transfer Codes" menu to export your accounts as QR codes and import them on your new device.'
+      },
+      {
+        'question': 'Are my accounts backed up?',
+        'answer': 'Accounts are stored locally on your device. Use the export feature to create a backup.'
+      },
+      {
+        'question': 'Why are my codes not working?',
+        'answer': 'Ensure your device time is correct. TOTP codes depend on accurate time settings.'
+      },
+      {
+        'question': 'How do I prevent screen capture?',
+        'answer': 'Go to Settings and enable the "Prevent screen capture" option to block screenshots and screen recording.'
+      },
+      {
+        'question': 'Is PB Authenticator open source?',
+        'answer': 'Yes! You can view the source code from the menu or on the project’s repository.'
+      },
+    ];
+
+    return AppScaffold(
+      title: Text(AppLocalizations.of(context)!.helpTitle),
+      body: ListView(
+        padding: const EdgeInsets.all(24.0),
+        children: [
+          Text(
+            AppLocalizations.of(context)!.helpBody,
+            style: Theme.of(context).textTheme.bodyLarge,
+          ),
+          const SizedBox(height: 32),
+          Text(
+            'Frequently Asked Questions',
+            style: Theme.of(context).textTheme.titleMedium,
+          ),
+          const SizedBox(height: 16),
+          ...faqs.map((faq) => ExpansionTile(
+                title: Text(faq['question']!),
+                children: [
+                  Padding(
+                    padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+                    child: Text(faq['answer']!),
+                  ),
+                ],
+              )),
+        ],
+      ),
+    );
+  }
+} 

+ 1 - 1
lib/pages/pages.dart

@@ -1,6 +1,6 @@
 export './add.dart';
 export './qr.dart';
-export './acknowledgements.dart';
 export './settings.dart';
 export './android/home.dart';
 export './android/edit.dart';
+export './help.dart';

+ 5 - 5
lib/pages/settings.dart

@@ -142,17 +142,17 @@ class SettingsPage extends StatelessWidget {
                       launchURL(Constants.repoUrl);
                     },
                   ),
-                  // Acknowledgements
+                  // Help
                   ListTile(
                     dense: true,
                     leading: isPlatformAndroid()
-                        ? const Icon(Icons.book)
-                        : const Icon(CupertinoIcons.book),
-                    title: Text(AppLocalizations.of(context)!.licenses,
+                        ? const Icon(Icons.help_outline)
+                        : const Icon(CupertinoIcons.question_circle),
+                    title: Text(AppLocalizations.of(context)!.helpTitle ?? 'Help',
                         style: const TextStyle(fontSize: 15)),
                     onTap: () {
                       Navigator.of(context)
-                          .pushNamed(AppRoutes.settingAcknowledgements);
+                          .pushNamed(AppRoutes.help);
                     },
                   ),
                 ],

+ 2 - 2
test/widget_test.dart

@@ -6,7 +6,7 @@
 // tree, read text, and verify that the values of widget properties are correct.
 
 import 'package:pb_authenticator/l10n/app_localizations.dart';
-import 'package:pb_authenticator/pages/acknowledgements.dart';
+import 'package:pb_authenticator/pages/help.dart';
 import 'package:pb_authenticator/state/app_state.dart';
 import 'package:pb_authenticator/state/file_storage.dart';
 import 'package:pb_authenticator/main.dart';
@@ -54,7 +54,7 @@ TestLicense $i
           GlobalWidgetsLocalizations.delegate,
         ],
         locale: const Locale('en'),
-        child: const AcknowledgementsPage(),
+        child: const HelpPage(),
       )),
     );