Kaynağa Gözat

Search Section added

Sasan Salamzadeh 11 ay önce
ebeveyn
işleme
7034784b28

+ 1 - 1
android/gradle/wrapper/gradle-wrapper.properties

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

+ 3 - 3
android/settings.gradle

@@ -17,9 +17,9 @@ pluginManagement {
 }
 
 plugins {
-    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
-    id "com.android.application" version "8.2.1" apply false
-    id "org.jetbrains.kotlin.android" version "1.9.23" apply false
+    id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+    id("com.android.application") version "8.7.3" apply false
+    id("org.jetbrains.kotlin.android") version "2.1.0" apply false
 }
 
 include ":app"

+ 16 - 1
lib/l10n/app_en.arb

@@ -59,5 +59,20 @@
     "themeDark": "Dark",
     "preventScreenCapture": "Prevent screen capture",
     "helpTitle": "Help",
-    "helpBody": "Need assistance? Here you can find answers to common questions and tips for using PB Authenticator."
+    "helpBody": "Need assistance? Here you can find answers to common questions and tips for using PB Authenticator.",
+    "search": "Search",
+    "searchHint": "Search by name or issuer",
+    "sortBy": "Sort by",
+    "sortByName": "Name",
+    "sortAscending": "A-Z",
+    "sortDescending": "Z-A",
+    "clearSearch": "Clear search",
+    "noSearchResults": "No accounts found for \"{query}\"",
+    "@noSearchResults": {
+        "placeholders": {
+            "query": {
+                "type": "String"
+            }
+        }
+    }
 }

+ 16 - 1
lib/l10n/app_fa.arb

@@ -59,5 +59,20 @@
     "themeDark": "تاریک",
     "preventScreenCapture": "جلوگیری از عکس‌برداری صفحه",
     "helpTitle": "راهنما",
-    "helpBody": "نیاز به کمک دارید؟ اینجا می‌توانید پاسخ سوالات متداول و نکاتی برای استفاده از پی‌بی احرازگر را بیابید."
+    "helpBody": "نیاز به کمک دارید؟ اینجا می‌توانید پاسخ سوالات متداول و نکاتی برای استفاده از پی‌بی احرازگر را بیابید.",
+    "search": "جستجو",
+    "searchHint": "جستجو بر اساس نام یا صادرکننده",
+    "sortBy": "مرتب‌سازی بر اساس",
+    "sortByName": "نام",
+    "sortAscending": "الف-ی",
+    "sortDescending": "ی-الف",
+    "clearSearch": "پاک کردن جستجو",
+    "noSearchResults": "هیچ حسابی برای \"{query}\" یافت نشد",
+    "@noSearchResults": {
+        "placeholders": {
+            "query": {
+                "type": "String"
+            }
+        }
+    }
 } 

+ 48 - 0
lib/l10n/app_localizations.dart

@@ -463,6 +463,54 @@ abstract class AppLocalizations {
   /// 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;
+
+  /// No description provided for @search.
+  ///
+  /// In en, this message translates to:
+  /// **'Search'**
+  String get search;
+
+  /// No description provided for @searchHint.
+  ///
+  /// In en, this message translates to:
+  /// **'Search by name or issuer'**
+  String get searchHint;
+
+  /// No description provided for @sortBy.
+  ///
+  /// In en, this message translates to:
+  /// **'Sort by'**
+  String get sortBy;
+
+  /// No description provided for @sortByName.
+  ///
+  /// In en, this message translates to:
+  /// **'Name'**
+  String get sortByName;
+
+  /// No description provided for @sortAscending.
+  ///
+  /// In en, this message translates to:
+  /// **'A-Z'**
+  String get sortAscending;
+
+  /// No description provided for @sortDescending.
+  ///
+  /// In en, this message translates to:
+  /// **'Z-A'**
+  String get sortDescending;
+
+  /// No description provided for @clearSearch.
+  ///
+  /// In en, this message translates to:
+  /// **'Clear search'**
+  String get clearSearch;
+
+  /// No description provided for @noSearchResults.
+  ///
+  /// In en, this message translates to:
+  /// **'No accounts found for \"{query}\"'**
+  String noSearchResults(String query);
 }
 
 class _AppLocalizationsDelegate

+ 26 - 0
lib/l10n/app_localizations_en.dart

@@ -201,4 +201,30 @@ class AppLocalizationsEn extends AppLocalizations {
   @override
   String get helpBody =>
       'Need assistance? Here you can find answers to common questions and tips for using PB Authenticator.';
+
+  @override
+  String get search => 'Search';
+
+  @override
+  String get searchHint => 'Search by name or issuer';
+
+  @override
+  String get sortBy => 'Sort by';
+
+  @override
+  String get sortByName => 'Name';
+
+  @override
+  String get sortAscending => 'A-Z';
+
+  @override
+  String get sortDescending => 'Z-A';
+
+  @override
+  String get clearSearch => 'Clear search';
+
+  @override
+  String noSearchResults(String query) {
+    return 'No accounts found for \"$query\"';
+  }
 }

+ 26 - 0
lib/l10n/app_localizations_fa.dart

@@ -202,4 +202,30 @@ class AppLocalizationsFa extends AppLocalizations {
   @override
   String get helpBody =>
       'نیاز به کمک دارید؟ اینجا می‌توانید پاسخ سوالات متداول و نکاتی برای استفاده از پی‌بی احرازگر را بیابید.';
+
+  @override
+  String get search => 'جستجو';
+
+  @override
+  String get searchHint => 'جستجو بر اساس نام یا صادرکننده';
+
+  @override
+  String get sortBy => 'مرتب‌سازی بر اساس';
+
+  @override
+  String get sortByName => 'نام';
+
+  @override
+  String get sortAscending => 'الف-ی';
+
+  @override
+  String get sortDescending => 'ی-الف';
+
+  @override
+  String get clearSearch => 'پاک کردن جستجو';
+
+  @override
+  String noSearchResults(String query) {
+    return 'هیچ حسابی برای \"$query\" یافت نشد';
+  }
 }

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

@@ -8,10 +8,159 @@ import '../../config/routes.dart';
 import '../../l10n/constants.dart';
 import './list_item.dart' show HomeListItem;
 
+enum SortOption { nameAsc, nameDesc }
+
 /// Android version of home page.
-class AndroidHomePage extends StatelessWidget {
+class AndroidHomePage extends StatefulWidget {
   const AndroidHomePage({super.key});
 
+  @override
+  State<AndroidHomePage> createState() => _AndroidHomePageState();
+}
+
+class _AndroidHomePageState extends State<AndroidHomePage> {
+  String _searchQuery = '';
+  SortOption _currentSort = SortOption.nameAsc;
+  final TextEditingController _searchController = TextEditingController();
+
+  @override
+  void dispose() {
+    _searchController.dispose();
+    super.dispose();
+  }
+
+  List<BaseItemType> _filterAndSortItems(List<BaseItemType> items) {
+    // Filter by search query
+    List<BaseItemType> filteredItems = items;
+    if (_searchQuery.isNotEmpty) {
+      filteredItems = items.where((item) {
+        final nameMatch = item.totp.accountName.toLowerCase().contains(_searchQuery.toLowerCase());
+        final issuerMatch = item.totp.issuer.toLowerCase().contains(_searchQuery.toLowerCase());
+        return nameMatch || issuerMatch;
+      }).toList();
+    }
+
+    // Sort items
+    filteredItems.sort((a, b) {
+      String aName = a.totp.accountName.isNotEmpty 
+          ? a.totp.accountName 
+          : (a.totp.issuer.isNotEmpty ? a.totp.issuer : 'Unknown');
+      String bName = b.totp.accountName.isNotEmpty 
+          ? b.totp.accountName 
+          : (b.totp.issuer.isNotEmpty ? b.totp.issuer : 'Unknown');
+      
+      if (_currentSort == SortOption.nameAsc) {
+        return aName.toLowerCase().compareTo(bName.toLowerCase());
+      } else {
+        return bName.toLowerCase().compareTo(aName.toLowerCase());
+      }
+    });
+
+    return filteredItems;
+  }
+
+  /// Builds search and sort controls.
+  Widget _buildSearchAndSort() {
+    return Container(
+      padding: const EdgeInsets.all(16.0),
+      child: Column(
+        children: [
+          // Search field
+          TextField(
+            controller: _searchController,
+            decoration: InputDecoration(
+              hintText: AppLocalizations.of(context)!.searchHint,
+              prefixIcon: const Icon(Icons.search),
+              suffixIcon: _searchQuery.isNotEmpty
+                  ? IconButton(
+                      icon: const Icon(Icons.clear),
+                      onPressed: () {
+                        setState(() {
+                          _searchQuery = '';
+                          _searchController.clear();
+                        });
+                      },
+                    )
+                  : null,
+              border: OutlineInputBorder(
+                borderRadius: BorderRadius.circular(12),
+              ),
+              contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+            ),
+            onChanged: (value) {
+              setState(() {
+                _searchQuery = value;
+              });
+            },
+          ),
+          const SizedBox(height: 12),
+          // Sort options
+          Row(
+            children: [
+              Text(
+                AppLocalizations.of(context)!.sortBy,
+                style: Theme.of(context).textTheme.titleSmall,
+              ),
+              const SizedBox(width: 16),
+              Expanded(
+                child: Row(
+                  children: [
+                    Expanded(
+                      child: RadioListTile<SortOption>(
+                        title: Row(
+                          mainAxisSize: MainAxisSize.min,
+                          children: [
+                            const Icon(Icons.arrow_upward, size: 16),
+                            const SizedBox(width: 4),
+                            Text(AppLocalizations.of(context)!.sortAscending),
+                          ],
+                        ),
+                        value: SortOption.nameAsc,
+                        groupValue: _currentSort,
+                        onChanged: (SortOption? value) {
+                          if (value != null) {
+                            setState(() {
+                              _currentSort = value;
+                            });
+                          }
+                        },
+                        contentPadding: EdgeInsets.zero,
+                        dense: true,
+                      ),
+                    ),
+                    Expanded(
+                      child: RadioListTile<SortOption>(
+                        title: Row(
+                          mainAxisSize: MainAxisSize.min,
+                          children: [
+                            const Icon(Icons.arrow_downward, size: 16),
+                            const SizedBox(width: 4),
+                            Text(AppLocalizations.of(context)!.sortDescending),
+                          ],
+                        ),
+                        value: SortOption.nameDesc,
+                        groupValue: _currentSort,
+                        onChanged: (SortOption? value) {
+                          if (value != null) {
+                            setState(() {
+                              _currentSort = value;
+                            });
+                          }
+                        },
+                        contentPadding: EdgeInsets.zero,
+                        dense: true,
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+
   /// Builds list of items.
   Widget _buildList() {
     return Consumer<AppState>(
@@ -32,12 +181,24 @@ class AndroidHomePage extends StatelessWidget {
                       style: const TextStyle(height: 1.2))));
         }
 
+        final filteredAndSortedItems = _filterAndSortItems(state.items!);
+
+        if (filteredAndSortedItems.isEmpty && _searchQuery.isNotEmpty) {
+          // No search results
+          return Center(
+              child: Padding(
+                  padding: const EdgeInsets.all(10),
+                  child: Text(AppLocalizations.of(context)!.noSearchResults(_searchQuery),
+                      textAlign: TextAlign.center,
+                      style: const TextStyle(height: 1.2))));
+        }
+
         return Container(
           margin: const EdgeInsets.all(10),
           child: ListView.builder(
-            itemCount: state.items!.length,
+            itemCount: filteredAndSortedItems.length,
             itemBuilder: (BuildContext context, int index) {
-              var item = state.items![index];
+              var item = filteredAndSortedItems[index];
               return HomeListItem(item, key: Key(item.id));
             },
           ),
@@ -190,7 +351,12 @@ class AndroidHomePage extends StatelessWidget {
           )
         ],
       ),
-      body: _buildList(),
+      body: Column(
+        children: [
+          _buildSearchAndSort(),
+          Expanded(child: _buildList()),
+        ],
+      ),
     );
   }
 }

+ 2 - 2
pubspec.lock

@@ -483,14 +483,14 @@ packages:
       path: state
       relative: true
     source: path
-    version: "1.0.0"
+    version: "1.2.0"
   pb_authenticator_totp:
     dependency: "direct main"
     description:
       path: totp
       relative: true
     source: path
-    version: "1.0.0"
+    version: "1.2.0"
   petitparser:
     dependency: transitive
     description: