mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 13:33:28 +01:00 
			
		
		
		
	Merge pull request #177 from ImranR98/dev
Added very basic categorization support
This commit is contained in:
		| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "No APK found", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "No version detection", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", |         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", | ||||||
|         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" |         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" | ||||||
|   | |||||||
| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "No APK found", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "No version detection", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Too many requests (rate limited) - try again in {} minute", |         "one": "Too many requests (rate limited) - try again in {} minute", | ||||||
|         "other": "Too many requests (rate limited) - try again in {} minutes" |         "other": "Too many requests (rate limited) - try again in {} minutes" | ||||||
|   | |||||||
| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "No APK found", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "No version detection", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", |         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", | ||||||
|         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" |         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" | ||||||
|   | |||||||
| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "No APK found", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "No version detection", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", |         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", | ||||||
|         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" |         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" | ||||||
|   | |||||||
| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "アプリのダウンロード状況を通知する", |     "downloadNotifDescription": "アプリのダウンロード状況を通知する", | ||||||
|     "noAPKFound": "APKが見つかりません", |     "noAPKFound": "APKが見つかりません", | ||||||
|     "noVersionDetection": "バージョン検出を行わない", |     "noVersionDetection": "バージョン検出を行わない", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", |         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", | ||||||
|         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" |         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" | ||||||
|   | |||||||
| @@ -199,6 +199,14 @@ | |||||||
|     "downloadNotifDescription": "通知用户下载进度", |     "downloadNotifDescription": "通知用户下载进度", | ||||||
|     "noAPKFound": "未找到安装包", |     "noAPKFound": "未找到安装包", | ||||||
|     "noVersionDetection": "无版本检测", |     "noVersionDetection": "无版本检测", | ||||||
|  |     "categorize": "Categorize", | ||||||
|  |     "categories": "Categories", | ||||||
|  |     "category": "Category", | ||||||
|  |     "noCategory": "No Category", | ||||||
|  |     "deleteCategoryQuestion": "Delete Category?", | ||||||
|  |     "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", | ||||||
|  |     "addCategory": "Add Category", | ||||||
|  |     "label": "Label", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", |         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", | ||||||
|         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" |         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; | |||||||
| // ignore: implementation_imports | // ignore: implementation_imports | ||||||
| import 'package:easy_localization/src/localization.dart'; | import 'package:easy_localization/src/localization.dart'; | ||||||
|  |  | ||||||
| const String currentVersion = '0.8.23'; | const String currentVersion = '0.9.0'; | ||||||
| const String currentReleaseTag = | const String currentReleaseTag = | ||||||
|     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES |     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:obtainium/components/generated_form.dart'; | ||||||
| import 'package:obtainium/components/generated_form_modal.dart'; | import 'package:obtainium/components/generated_form_modal.dart'; | ||||||
| import 'package:obtainium/custom_errors.dart'; | import 'package:obtainium/custom_errors.dart'; | ||||||
| import 'package:obtainium/main.dart'; | import 'package:obtainium/main.dart'; | ||||||
| @@ -33,6 +34,7 @@ class _AppPageState extends State<AppPage> { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     var categories = settingsProvider.categories; | ||||||
|     var sourceProvider = SourceProvider(); |     var sourceProvider = SourceProvider(); | ||||||
|     AppInMemory? app = appsProvider.apps[widget.appId]; |     AppInMemory? app = appsProvider.apps[widget.appId]; | ||||||
|     var source = app != null ? sourceProvider.getSource(app.app.url) : null; |     var source = app != null ? sourceProvider.getSource(app.app.url) : null; | ||||||
| @@ -148,7 +150,51 @@ class _AppPageState extends State<AppPage> { | |||||||
|                           textAlign: TextAlign.center, |                           textAlign: TextAlign.center, | ||||||
|                           style: const TextStyle( |                           style: const TextStyle( | ||||||
|                               fontStyle: FontStyle.italic, fontSize: 12), |                               fontStyle: FontStyle.italic, fontSize: 12), | ||||||
|  |                         ), | ||||||
|  |                         const SizedBox( | ||||||
|  |                           height: 32, | ||||||
|  |                         ), | ||||||
|  |                         app?.app.category != null | ||||||
|  |                             ? Chip( | ||||||
|  |                                 label: Text(app!.app.category!), | ||||||
|  |                                 backgroundColor: | ||||||
|  |                                     Color(categories[app.app.category!] ?? 0x0), | ||||||
|  |                                 onDeleted: () { | ||||||
|  |                                   app.app.category = null; | ||||||
|  |                                   appsProvider.saveApps([app.app]); | ||||||
|  |                                 }, | ||||||
|  |                                 visualDensity: VisualDensity.compact, | ||||||
|                               ) |                               ) | ||||||
|  |                             : Row( | ||||||
|  |                                 mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                                 children: [ | ||||||
|  |                                     TextButton( | ||||||
|  |                                         onPressed: () { | ||||||
|  |                                           showDialog<Map<String, String>?>( | ||||||
|  |                                               context: context, | ||||||
|  |                                               builder: (BuildContext ctx) { | ||||||
|  |                                                 return GeneratedFormModal( | ||||||
|  |                                                     title: 'Pick a Category', | ||||||
|  |                                                     items: [ | ||||||
|  |                                                       [ | ||||||
|  |                                                         settingsProvider | ||||||
|  |                                                             .getCategoryFormItem() | ||||||
|  |                                                       ] | ||||||
|  |                                                     ]); | ||||||
|  |                                               }).then((value) { | ||||||
|  |                                             if (value != null && app != null) { | ||||||
|  |                                               String? cat = (value['category'] | ||||||
|  |                                                           ?.isNotEmpty ?? | ||||||
|  |                                                       false) | ||||||
|  |                                                   ? value['category'] | ||||||
|  |                                                   : null; | ||||||
|  |                                               app.app.category = cat; | ||||||
|  |                                               appsProvider.saveApps([app.app]); | ||||||
|  |                                             } | ||||||
|  |                                           }); | ||||||
|  |                                         }, | ||||||
|  |                                         child: Text(tr('categorize'))) | ||||||
|  |                                   ]) | ||||||
|                       ], |                       ], | ||||||
|                     )), |                     )), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -80,9 +80,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|             !(filter!.includeNonInstalled)) { |             !(filter!.includeNonInstalled)) { | ||||||
|           return false; |           return false; | ||||||
|         } |         } | ||||||
|         if (filter!.nameFilter.isEmpty && filter!.authorFilter.isEmpty) { |         if (filter!.nameFilter.isNotEmpty || filter!.authorFilter.isNotEmpty) { | ||||||
|           return true; |  | ||||||
|         } |  | ||||||
|           List<String> nameTokens = filter!.nameFilter |           List<String> nameTokens = filter!.nameFilter | ||||||
|               .split(' ') |               .split(' ') | ||||||
|               .where((element) => element.trim().isNotEmpty) |               .where((element) => element.trim().isNotEmpty) | ||||||
| @@ -103,6 +101,11 @@ class AppsPageState extends State<AppsPage> { | |||||||
|               return false; |               return false; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |         } | ||||||
|  |         if (filter!.categoryFilter.isNotEmpty && | ||||||
|  |             filter!.categoryFilter != app.app.category) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|         return true; |         return true; | ||||||
|       }).toList(); |       }).toList(); | ||||||
|     } |     } | ||||||
| @@ -225,7 +228,15 @@ class AppsPageState extends State<AppsPage> { | |||||||
|               String? changesUrl = SourceProvider() |               String? changesUrl = SourceProvider() | ||||||
|                   .getSource(sortedApps[index].app.url) |                   .getSource(sortedApps[index].app.url) | ||||||
|                   .changeLogPageFromStandardUrl(sortedApps[index].app.url); |                   .changeLogPageFromStandardUrl(sortedApps[index].app.url); | ||||||
|               return ListTile( |               return Container( | ||||||
|  |                   decoration: BoxDecoration( | ||||||
|  |                       border: Border.symmetric( | ||||||
|  |                           vertical: BorderSide( | ||||||
|  |                               width: 3, | ||||||
|  |                               color: Color(settingsProvider.categories[ | ||||||
|  |                                       sortedApps[index].app.category] ?? | ||||||
|  |                                   const Color.fromARGB(0, 0, 0, 0).value)))), | ||||||
|  |                   child: ListTile( | ||||||
|                     tileColor: sortedApps[index].app.pinned |                     tileColor: sortedApps[index].app.pinned | ||||||
|                         ? Colors.grey.withOpacity(0.1) |                         ? Colors.grey.withOpacity(0.1) | ||||||
|                         : Colors.transparent, |                         : Colors.transparent, | ||||||
| @@ -249,9 +260,11 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                       style: TextStyle( |                       style: TextStyle( | ||||||
|                         fontWeight: sortedApps[index].app.pinned |                         fontWeight: sortedApps[index].app.pinned | ||||||
|                             ? FontWeight.bold |                             ? FontWeight.bold | ||||||
|                           : FontWeight.normal), |                             : FontWeight.normal, | ||||||
|                       ), |                       ), | ||||||
|                 subtitle: Text(tr('byX', args: [sortedApps[index].app.author]), |                     ), | ||||||
|  |                     subtitle: Text( | ||||||
|  |                         tr('byX', args: [sortedApps[index].app.author]), | ||||||
|                         style: TextStyle( |                         style: TextStyle( | ||||||
|                             fontWeight: sortedApps[index].app.pinned |                             fontWeight: sortedApps[index].app.pinned | ||||||
|                                 ? FontWeight.bold |                                 ? FontWeight.bold | ||||||
| @@ -277,9 +290,14 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                         overflow: TextOverflow.fade, |                                         overflow: TextOverflow.fade, | ||||||
|                                         textAlign: TextAlign.end, |                                         textAlign: TextAlign.end, | ||||||
|                                       )), |                                       )), | ||||||
|                               sortedApps[index].app.installedVersion != null && |  | ||||||
|                                   sortedApps[index].app.installedVersion != |                                   sortedApps[index].app.installedVersion != | ||||||
|                                           sortedApps[index].app.latestVersion |                                               null && | ||||||
|  |                                           sortedApps[index] | ||||||
|  |                                                   .app | ||||||
|  |                                                   .installedVersion != | ||||||
|  |                                               sortedApps[index] | ||||||
|  |                                                   .app | ||||||
|  |                                                   .latestVersion | ||||||
|                                       ? GestureDetector( |                                       ? GestureDetector( | ||||||
|                                           onTap: changesUrl == null |                                           onTap: changesUrl == null | ||||||
|                                               ? null |                                               ? null | ||||||
| @@ -288,13 +306,16 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                                       mode: LaunchMode |                                                       mode: LaunchMode | ||||||
|                                                           .externalApplication); |                                                           .externalApplication); | ||||||
|                                                 }, |                                                 }, | ||||||
|                                       child: appsProvider.areDownloadsRunning() |                                           child: appsProvider | ||||||
|  |                                                   .areDownloadsRunning() | ||||||
|                                               ? Text(tr('pleaseWait')) |                                               ? Text(tr('pleaseWait')) | ||||||
|                                               : Text( |                                               : Text( | ||||||
|                                                   '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', |                                                   '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||||
|                                                   style: TextStyle( |                                                   style: TextStyle( | ||||||
|                                                   fontStyle: FontStyle.italic, |                                                       fontStyle: | ||||||
|                                                   decoration: changesUrl == null |                                                           FontStyle.italic, | ||||||
|  |                                                       decoration: changesUrl == | ||||||
|  |                                                               null | ||||||
|                                                           ? TextDecoration.none |                                                           ? TextDecoration.none | ||||||
|                                                           : TextDecoration |                                                           : TextDecoration | ||||||
|                                                               .underline), |                                                               .underline), | ||||||
| @@ -314,7 +335,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                         ); |                         ); | ||||||
|                       } |                       } | ||||||
|                     }, |                     }, | ||||||
|               ); |                   )); | ||||||
|             }, childCount: sortedApps.length)) |             }, childCount: sortedApps.length)) | ||||||
|           ])), |           ])), | ||||||
|       persistentFooterButtons: [ |       persistentFooterButtons: [ | ||||||
| @@ -708,6 +729,10 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                         label: tr('nonInstalledApps'), |                                         label: tr('nonInstalledApps'), | ||||||
|                                         type: FormItemType.bool, |                                         type: FormItemType.bool, | ||||||
|                                         defaultValue: vals['nonInstalledApps']) |                                         defaultValue: vals['nonInstalledApps']) | ||||||
|  |                                   ], | ||||||
|  |                                   [ | ||||||
|  |                                     settingsProvider.getCategoryFormItem( | ||||||
|  |                                         initCategory: vals['category'] ?? '') | ||||||
|                                   ] |                                   ] | ||||||
|                                 ]); |                                 ]); | ||||||
|                           }).then((values) { |                           }).then((values) { | ||||||
| @@ -734,19 +759,22 @@ class AppsFilter { | |||||||
|   late String authorFilter; |   late String authorFilter; | ||||||
|   late bool includeUptodate; |   late bool includeUptodate; | ||||||
|   late bool includeNonInstalled; |   late bool includeNonInstalled; | ||||||
|  |   late String categoryFilter; | ||||||
|  |  | ||||||
|   AppsFilter( |   AppsFilter( | ||||||
|       {this.nameFilter = '', |       {this.nameFilter = '', | ||||||
|       this.authorFilter = '', |       this.authorFilter = '', | ||||||
|       this.includeUptodate = true, |       this.includeUptodate = true, | ||||||
|       this.includeNonInstalled = true}); |       this.includeNonInstalled = true, | ||||||
|  |       this.categoryFilter = ''}); | ||||||
|  |  | ||||||
|   Map<String, String> toValuesMap() { |   Map<String, String> toValuesMap() { | ||||||
|     return { |     return { | ||||||
|       'appName': nameFilter, |       'appName': nameFilter, | ||||||
|       'author': authorFilter, |       'author': authorFilter, | ||||||
|       'upToDateApps': includeUptodate ? 'true' : '', |       'upToDateApps': includeUptodate ? 'true' : '', | ||||||
|       'nonInstalledApps': includeNonInstalled ? 'true' : '' |       'nonInstalledApps': includeNonInstalled ? 'true' : '', | ||||||
|  |       'category': categoryFilter | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -755,11 +783,13 @@ class AppsFilter { | |||||||
|     authorFilter = values['author']!; |     authorFilter = values['author']!; | ||||||
|     includeUptodate = values['upToDateApps'] == 'true'; |     includeUptodate = values['upToDateApps'] == 'true'; | ||||||
|     includeNonInstalled = values['nonInstalledApps'] == 'true'; |     includeNonInstalled = values['nonInstalledApps'] == 'true'; | ||||||
|  |     categoryFilter = values['category']!; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool isIdenticalTo(AppsFilter other) => |   bool isIdenticalTo(AppsFilter other) => | ||||||
|       authorFilter.trim() == other.authorFilter.trim() && |       authorFilter.trim() == other.authorFilter.trim() && | ||||||
|       nameFilter.trim() == other.nameFilter.trim() && |       nameFilter.trim() == other.nameFilter.trim() && | ||||||
|       includeUptodate == other.includeUptodate && |       includeUptodate == other.includeUptodate && | ||||||
|       includeNonInstalled == other.includeNonInstalled; |       includeNonInstalled == other.includeNonInstalled && | ||||||
|  |       categoryFilter.trim() == other.categoryFilter.trim(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,12 @@ | |||||||
|  | import 'dart:math'; | ||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:obtainium/components/custom_app_bar.dart'; | import 'package:obtainium/components/custom_app_bar.dart'; | ||||||
| import 'package:obtainium/components/generated_form.dart'; | import 'package:obtainium/components/generated_form.dart'; | ||||||
|  | import 'package:obtainium/components/generated_form_modal.dart'; | ||||||
| import 'package:obtainium/custom_errors.dart'; | import 'package:obtainium/custom_errors.dart'; | ||||||
|  | import 'package:obtainium/providers/apps_provider.dart'; | ||||||
| import 'package:obtainium/providers/logs_provider.dart'; | import 'package:obtainium/providers/logs_provider.dart'; | ||||||
| import 'package:obtainium/providers/settings_provider.dart'; | import 'package:obtainium/providers/settings_provider.dart'; | ||||||
| import 'package:obtainium/providers/source_provider.dart'; | import 'package:obtainium/providers/source_provider.dart'; | ||||||
| @@ -17,11 +21,27 @@ class SettingsPage extends StatefulWidget { | |||||||
|   State<SettingsPage> createState() => _SettingsPageState(); |   State<SettingsPage> createState() => _SettingsPageState(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Generates a random light color | ||||||
|  | // Courtesy of ChatGPT 😭 (with a bugfix 🥳) | ||||||
|  | Color generateRandomLightColor() { | ||||||
|  |   // Create a random number generator | ||||||
|  |   final Random random = Random(); | ||||||
|  |  | ||||||
|  |   // Generate random hue, saturation, and value values | ||||||
|  |   final double hue = random.nextDouble() * 360; | ||||||
|  |   final double saturation = 0.5 + random.nextDouble() * 0.5; | ||||||
|  |   final double value = 0.9 + random.nextDouble() * 0.1; | ||||||
|  |  | ||||||
|  |   // Create a HSV color with the random values | ||||||
|  |   return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor(); | ||||||
|  | } | ||||||
|  |  | ||||||
| class _SettingsPageState extends State<SettingsPage> { | class _SettingsPageState extends State<SettingsPage> { | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     SettingsProvider settingsProvider = context.watch<SettingsProvider>(); |     SettingsProvider settingsProvider = context.watch<SettingsProvider>(); | ||||||
|     SourceProvider sourceProvider = SourceProvider(); |     SourceProvider sourceProvider = SourceProvider(); | ||||||
|  |     AppsProvider appsProvider = context.read<AppsProvider>(); | ||||||
|     if (settingsProvider.prefs == null) { |     if (settingsProvider.prefs == null) { | ||||||
|       settingsProvider.initializeSettings(); |       settingsProvider.initializeSettings(); | ||||||
|     } |     } | ||||||
| @@ -157,6 +177,8 @@ class _SettingsPageState extends State<SettingsPage> { | |||||||
|       height: 16, |       height: 16, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     var categories = settingsProvider.categories; | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|         backgroundColor: Theme.of(context).colorScheme.surface, |         backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|         body: CustomScrollView(slivers: <Widget>[ |         body: CustomScrollView(slivers: <Widget>[ | ||||||
| @@ -232,6 +254,94 @@ class _SettingsPageState extends State<SettingsPage> { | |||||||
|                                   color: Theme.of(context).colorScheme.primary), |                                   color: Theme.of(context).colorScheme.primary), | ||||||
|                             ), |                             ), | ||||||
|                             ...sourceSpecificFields, |                             ...sourceSpecificFields, | ||||||
|  |                             intervalDropdown, | ||||||
|  |                             const Divider( | ||||||
|  |                               height: 48, | ||||||
|  |                             ), | ||||||
|  |                             Text( | ||||||
|  |                               tr('categories'), | ||||||
|  |                               style: TextStyle( | ||||||
|  |                                   color: Theme.of(context).colorScheme.primary), | ||||||
|  |                             ), | ||||||
|  |                             height16, | ||||||
|  |                             Wrap( | ||||||
|  |                               children: [ | ||||||
|  |                                 ...categories.entries.toList().map((e) { | ||||||
|  |                                   return Padding( | ||||||
|  |                                       padding: const EdgeInsets.symmetric( | ||||||
|  |                                           horizontal: 4), | ||||||
|  |                                       child: Chip( | ||||||
|  |                                         label: Text(e.key), | ||||||
|  |                                         backgroundColor: Color(e.value), | ||||||
|  |                                         visualDensity: VisualDensity.compact, | ||||||
|  |                                         onDeleted: () { | ||||||
|  |                                           showDialog<Map<String, String>?>( | ||||||
|  |                                               context: context, | ||||||
|  |                                               builder: (BuildContext ctx) { | ||||||
|  |                                                 return GeneratedFormModal( | ||||||
|  |                                                     title: tr( | ||||||
|  |                                                         'deleteCategoryQuestion'), | ||||||
|  |                                                     message: tr( | ||||||
|  |                                                         'categoryDeleteWarning', | ||||||
|  |                                                         args: [e.key]), | ||||||
|  |                                                     items: []); | ||||||
|  |                                               }).then((value) { | ||||||
|  |                                             if (value != null) { | ||||||
|  |                                               setState(() { | ||||||
|  |                                                 categories.remove(e.key); | ||||||
|  |                                                 settingsProvider.categories = | ||||||
|  |                                                     categories; | ||||||
|  |                                               }); | ||||||
|  |                                               appsProvider.saveApps(appsProvider | ||||||
|  |                                                   .apps.values | ||||||
|  |                                                   .where((element) => | ||||||
|  |                                                       element.app.category == | ||||||
|  |                                                       e.key) | ||||||
|  |                                                   .map((e) { | ||||||
|  |                                                 var a = e.app; | ||||||
|  |                                                 a.category = null; | ||||||
|  |                                                 return a; | ||||||
|  |                                               }).toList()); | ||||||
|  |                                             } | ||||||
|  |                                           }); | ||||||
|  |                                         }, | ||||||
|  |                                       )); | ||||||
|  |                                 }), | ||||||
|  |                                 Padding( | ||||||
|  |                                     padding: const EdgeInsets.symmetric( | ||||||
|  |                                         horizontal: 4), | ||||||
|  |                                     child: IconButton( | ||||||
|  |                                       onPressed: () { | ||||||
|  |                                         showDialog<Map<String, String>?>( | ||||||
|  |                                             context: context, | ||||||
|  |                                             builder: (BuildContext ctx) { | ||||||
|  |                                               return GeneratedFormModal( | ||||||
|  |                                                   title: tr('addCategory'), | ||||||
|  |                                                   items: [ | ||||||
|  |                                                     [ | ||||||
|  |                                                       GeneratedFormItem('label', | ||||||
|  |                                                           label: tr('label')) | ||||||
|  |                                                     ] | ||||||
|  |                                                   ]); | ||||||
|  |                                             }).then((value) { | ||||||
|  |                                           String? label = value?['label']; | ||||||
|  |                                           if (label != null) { | ||||||
|  |                                             setState(() { | ||||||
|  |                                               categories[label] = | ||||||
|  |                                                   generateRandomLightColor() | ||||||
|  |                                                       .value; | ||||||
|  |                                               settingsProvider.categories = | ||||||
|  |                                                   categories; | ||||||
|  |                                             }); | ||||||
|  |                                           } | ||||||
|  |                                         }); | ||||||
|  |                                       }, | ||||||
|  |                                       icon: const Icon(Icons.add), | ||||||
|  |                                       visualDensity: VisualDensity.compact, | ||||||
|  |                                       tooltip: tr('add'), | ||||||
|  |                                     )) | ||||||
|  |                               ], | ||||||
|  |                             ) | ||||||
|                           ], |                           ], | ||||||
|                         ))), |                         ))), | ||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| // Exposes functions used to save/load app settings | // Exposes functions used to save/load app settings | ||||||
|  |  | ||||||
|  | import 'dart:convert'; | ||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:fluttertoast/fluttertoast.dart'; | import 'package:fluttertoast/fluttertoast.dart'; | ||||||
| import 'package:obtainium/app_sources/github.dart'; | import 'package:obtainium/app_sources/github.dart'; | ||||||
|  | import 'package:obtainium/components/generated_form.dart'; | ||||||
| import 'package:permission_handler/permission_handler.dart'; | import 'package:permission_handler/permission_handler.dart'; | ||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
|  |  | ||||||
| @@ -144,4 +147,20 @@ class SettingsProvider with ChangeNotifier { | |||||||
|     prefs?.setString(settingId, value); |     prefs?.setString(settingId, value); | ||||||
|     notifyListeners(); |     notifyListeners(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Map<String, int> get categories => | ||||||
|  |       Map<String, int>.from(jsonDecode(prefs?.getString('categories') ?? '{}')); | ||||||
|  |  | ||||||
|  |   set categories(Map<String, int> cats) { | ||||||
|  |     prefs?.setString('categories', jsonEncode(cats)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getCategoryFormItem({String initCategory = ''}) => | ||||||
|  |       GeneratedFormItem('category', | ||||||
|  |           label: tr('category'), | ||||||
|  |           opts: [ | ||||||
|  |             MapEntry('', tr('noCategory')), | ||||||
|  |             ...categories.entries.map((e) => MapEntry(e.key, e.key)).toList() | ||||||
|  |           ], | ||||||
|  |           defaultValue: initCategory); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import 'package:obtainium/app_sources/steammobile.dart'; | |||||||
| import 'package:obtainium/components/generated_form.dart'; | import 'package:obtainium/components/generated_form.dart'; | ||||||
| import 'package:obtainium/custom_errors.dart'; | import 'package:obtainium/custom_errors.dart'; | ||||||
| import 'package:obtainium/mass_app_sources/githubstars.dart'; | import 'package:obtainium/mass_app_sources/githubstars.dart'; | ||||||
|  | import 'package:obtainium/providers/settings_provider.dart'; | ||||||
|  |  | ||||||
| class AppNames { | class AppNames { | ||||||
|   late String author; |   late String author; | ||||||
| @@ -47,6 +48,7 @@ class App { | |||||||
|   late Map<String, String> additionalSettings; |   late Map<String, String> additionalSettings; | ||||||
|   late DateTime? lastUpdateCheck; |   late DateTime? lastUpdateCheck; | ||||||
|   bool pinned = false; |   bool pinned = false; | ||||||
|  |   String? category; | ||||||
|   App( |   App( | ||||||
|       this.id, |       this.id, | ||||||
|       this.url, |       this.url, | ||||||
| @@ -58,7 +60,8 @@ class App { | |||||||
|       this.preferredApkIndex, |       this.preferredApkIndex, | ||||||
|       this.additionalSettings, |       this.additionalSettings, | ||||||
|       this.lastUpdateCheck, |       this.lastUpdateCheck, | ||||||
|       this.pinned); |       this.pinned, | ||||||
|  |       {this.category}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
| @@ -107,7 +110,8 @@ class App { | |||||||
|         json['lastUpdateCheck'] == null |         json['lastUpdateCheck'] == null | ||||||
|             ? null |             ? null | ||||||
|             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), |             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), | ||||||
|         json['pinned'] ?? false); |         json['pinned'] ?? false, | ||||||
|  |         category: json['category']); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() => { |   Map<String, dynamic> toJson() => { | ||||||
| @@ -121,7 +125,8 @@ class App { | |||||||
|         'preferredApkIndex': preferredApkIndex, |         'preferredApkIndex': preferredApkIndex, | ||||||
|         'additionalSettings': jsonEncode(additionalSettings), |         'additionalSettings': jsonEncode(additionalSettings), | ||||||
|         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, |         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, | ||||||
|         'pinned': pinned |         'pinned': pinned, | ||||||
|  |         'category': category | ||||||
|       }; |       }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -350,7 +355,8 @@ class SourceProvider { | |||||||
|         apk.apkUrls.length - 1, |         apk.apkUrls.length - 1, | ||||||
|         additionalSettings, |         additionalSettings, | ||||||
|         DateTime.now(), |         DateTime.now(), | ||||||
|         currentApp?.pinned ?? false); |         currentApp?.pinned ?? false, | ||||||
|  |         category: currentApp?.category); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Returns errors in [results, errors] instead of throwing them |   // Returns errors in [results, errors] instead of throwing them | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 0.8.23+87 # When changing this, update the tag in main() accordingly | version: 0.9.0+88 # When changing this, update the tag in main() accordingly | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: '>=2.18.2 <3.0.0' |   sdk: '>=2.18.2 <3.0.0' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user