From 6977858b991ca56a0814f7813bb5ce0d6efe27db Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Wed, 21 Dec 2022 23:54:36 -0500 Subject: [PATCH 01/10] Started work on new unified category selector/editor --- lib/components/generated_form.dart | 151 +++++++++++++++++++++++++++++ lib/pages/settings.dart | 107 +++++--------------- 2 files changed, 177 insertions(+), 81 deletions(-) diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index f1a845a..14d6607 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -1,5 +1,9 @@ +import 'dart:math'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:obtainium/components/generated_form_modal.dart'; +import 'package:obtainium/providers/settings_provider.dart'; abstract class GeneratedFormItem { late String key; @@ -82,6 +86,27 @@ class GeneratedFormSwitch extends GeneratedFormItem { } } +class GeneratedFormTagInput extends GeneratedFormItem { + late MapEntry? deleteConfirmationMessage; + GeneratedFormTagInput(String key, + {String label = 'Input', + List belowWidgets = const [], + Map> defaultValue = const {}, + List> value)> + additionalValidators = const [], + this.deleteConfirmationMessage}) + : super(key, + label: label, + belowWidgets: belowWidgets, + defaultValue: defaultValue, + additionalValidators: additionalValidators); + + @override + Map> ensureType(val) { + return val is Map> ? val : {}; + } +} + typedef OnValueChanges = void Function( Map values, bool valid, bool isBuilding); @@ -120,6 +145,21 @@ class _GeneratedFormState extends State { widget.onValueChanges(returnValues, valid, isBuilding); } + // 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(); + } + @override void initState() { super.initState(); @@ -212,6 +252,117 @@ class _GeneratedFormState extends State { }) ], ); + } else if (widget.items[r][e] is GeneratedFormTagInput) { + formInputs[r][e] = Wrap( + children: [ + ...(values[widget.items[r][e].key] + as Map>?) + ?.entries + .map((e2) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ChoiceChip( + label: Text(e2.key), + backgroundColor: Color(e2.value.key).withAlpha(200), + selectedColor: Color(e2.value.key), + visualDensity: VisualDensity.compact, + selected: e2.value.value, + onSelected: (value) { + setState(() { + (values[widget.items[r][e].key] as Map>)[e2.key] = + MapEntry( + (values[widget.items[r][e].key] as Map< + String, + MapEntry>)[e2.key]! + .key, + value); + someValueChanged(); + }); + }, + )); + }) ?? + [const SizedBox.shrink()], + (values[widget.items[r][e].key] + as Map>?) + ?.values + .where((e) => e.value) + .isNotEmpty == + true + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: IconButton( + onPressed: () { + fn() { + setState(() { + var temp = values[widget.items[r][e].key] + as Map>; + temp.removeWhere((key, value) => value.value); + values[widget.items[r][e].key] = temp; + someValueChanged(); + }); + } + + if ((widget.items[r][e] as GeneratedFormTagInput) + .deleteConfirmationMessage != + null) { + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: tr('deleteCategoryQuestion'), + message: tr('categoryDeleteWarning'), + items: const []); + }).then((value) { + if (value != null) { + fn(); + } + }); + } else { + fn(); + } + }, + icon: const Icon(Icons.remove), + visualDensity: VisualDensity.compact, + tooltip: tr('remove'), + )) + : const SizedBox.shrink(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: IconButton( + onPressed: () { + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: widget.items[r][e].label, + items: [ + [ + GeneratedFormTextField('label', + label: tr('label')) + ] + ]); + }).then((value) { + String? label = value?['label']; + if (label != null) { + setState(() { + var temp = values[widget.items[r][e].key] + as Map>?; + temp ??= {}; + temp[label] = MapEntry( + generateRandomLightColor().value, false); + values[widget.items[r][e].key] = temp; + someValueChanged(); + }); + } + }); + }, + icon: const Icon(Icons.add), + visualDensity: VisualDensity.compact, + tooltip: tr('add'), + )), + ], + ); } } } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index bc0e70e..49009f3 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -174,12 +174,21 @@ class _SettingsPageState extends State { } }); + var categories = settingsProvider.categories; + var categoryTagInput = GeneratedForm(items: [ + [ + GeneratedFormTagInput('categories', + defaultValue: categories + .map((key, value) => MapEntry(key, MapEntry(value, false))), + deleteConfirmationMessage: MapEntry( + tr('deleteCategoryQuestion'), tr('categoryDeleteWarning'))) + ] + ], onValueChanges: ((values, valid, isBuilding) {})); + const height16 = SizedBox( height: 16, ); - var categories = settingsProvider.categories; - return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: CustomScrollView(slivers: [ @@ -264,85 +273,7 @@ class _SettingsPageState extends State { 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?>( - 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?>( - context: context, - builder: (BuildContext ctx) { - return GeneratedFormModal( - title: tr('addCategory'), - items: [ - [ - GeneratedFormTextField( - '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'), - )) - ], - ) + categoryTagInput ], ))), SliverToBoxAdapter( @@ -457,3 +388,17 @@ class _LogsDialogState extends State { ); } } + +class CategoryEditorSelector extends StatefulWidget { + const CategoryEditorSelector({super.key}); + + @override + State createState() => _CategoryEditorSelectorState(); +} + +class _CategoryEditorSelectorState extends State { + @override + Widget build(BuildContext context) { + return Container(); + } +} From a3f9947f28fd128c3bbcabb3d5ea8f036cc2fbe0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 01:24:35 -0500 Subject: [PATCH 02/10] Finished new category editor (needs to be used) --- lib/components/generated_form.dart | 23 ++++++++++++- lib/pages/settings.dart | 48 +++++++++++++++++++--------- lib/providers/settings_provider.dart | 1 + 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 14d6607..1411217 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -88,13 +88,15 @@ class GeneratedFormSwitch extends GeneratedFormItem { class GeneratedFormTagInput extends GeneratedFormItem { late MapEntry? deleteConfirmationMessage; + late bool singleSelect; GeneratedFormTagInput(String key, {String label = 'Input', List belowWidgets = const [], Map> defaultValue = const {}, List> value)> additionalValidators = const [], - this.deleteConfirmationMessage}) + this.deleteConfirmationMessage, + this.singleSelect = false}) : super(key, label: label, belowWidgets: belowWidgets, @@ -277,6 +279,25 @@ class _GeneratedFormState extends State { MapEntry>)[e2.key]! .key, value); + if ((widget.items[r][e] as GeneratedFormTagInput) + .singleSelect && + value == true) { + for (var key in (values[widget.items[r][e].key] + as Map>) + .keys) { + if (key != e2.key) { + (values[widget.items[r][e].key] as Map< + String, + MapEntry>)[key] = MapEntry( + (values[widget.items[r][e].key] as Map< + String, + MapEntry>)[key]! + .key, + false); + } + } + } someValueChanged(); }); }, diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 49009f3..5e86d0d 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -41,7 +41,6 @@ class _SettingsPageState extends State { Widget build(BuildContext context) { SettingsProvider settingsProvider = context.watch(); SourceProvider sourceProvider = SourceProvider(); - AppsProvider appsProvider = context.read(); if (settingsProvider.prefs == null) { settingsProvider.initializeSettings(); } @@ -174,17 +173,6 @@ class _SettingsPageState extends State { } }); - var categories = settingsProvider.categories; - var categoryTagInput = GeneratedForm(items: [ - [ - GeneratedFormTagInput('categories', - defaultValue: categories - .map((key, value) => MapEntry(key, MapEntry(value, false))), - deleteConfirmationMessage: MapEntry( - tr('deleteCategoryQuestion'), tr('categoryDeleteWarning'))) - ] - ], onValueChanges: ((values, valid, isBuilding) {})); - const height16 = SizedBox( height: 16, ); @@ -273,7 +261,7 @@ class _SettingsPageState extends State { color: Theme.of(context).colorScheme.primary), ), height16, - categoryTagInput + const CategoryEditorSelector() ], ))), SliverToBoxAdapter( @@ -390,15 +378,45 @@ class _LogsDialogState extends State { } class CategoryEditorSelector extends StatefulWidget { - const CategoryEditorSelector({super.key}); + final void Function(List categories)? onSelected; + final bool singleSelect; + const CategoryEditorSelector( + {super.key, this.onSelected, this.singleSelect = false}); @override State createState() => _CategoryEditorSelectorState(); } class _CategoryEditorSelectorState extends State { + Map> storedValues = {}; + @override Widget build(BuildContext context) { - return Container(); + var settingsProvider = context.watch(); + storedValues = settingsProvider.categories.map((key, value) => + MapEntry(key, MapEntry(value, storedValues[key]?.value ?? false))); + return GeneratedForm( + items: [ + [ + GeneratedFormTagInput('categories', + defaultValue: storedValues, + deleteConfirmationMessage: MapEntry( + tr('deleteCategoryQuestion'), tr('categoryDeleteWarning')), + singleSelect: widget.singleSelect) + ] + ], + onValueChanges: ((values, valid, isBuilding) { + if (!isBuilding) { + storedValues = + values['categories'] as Map>; + settingsProvider.categories = + storedValues.map((key, value) => MapEntry(key, value.key)); + if (widget.onSelected != null) { + widget.onSelected!(storedValues.keys + .where((k) => storedValues[k]!.value) + .toList()); + } + } + })); } } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 9e887b0..ad73ad3 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -153,6 +153,7 @@ class SettingsProvider with ChangeNotifier { set categories(Map cats) { prefs?.setString('categories', jsonEncode(cats)); + notifyListeners(); } getCategoryFormItem({String initCategory = ''}) => GeneratedFormDropdown( From fa4d46b622c44067d5b74e6906f9c35cce99a10d Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 02:13:21 -0500 Subject: [PATCH 03/10] Bugfix es+ new category picker on App page --- assets/translations/de.json | 5 +-- assets/translations/en.json | 5 +-- assets/translations/hu.json | 5 +-- assets/translations/it.json | 5 +-- assets/translations/ja.json | 5 +-- assets/translations/zh.json | 5 +-- lib/components/generated_form.dart | 36 ++++++++++++++++--- lib/pages/app.dart | 58 +++++++++--------------------- lib/pages/settings.dart | 20 ++++++++--- 9 files changed, 81 insertions(+), 63 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index aea452e..01756a5 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -203,8 +203,9 @@ "categories": "Categories", "category": "Category", "noCategory": "No Category", - "deleteCategoryQuestion": "Delete Category?", - "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", "tooManyRequestsTryAgainInMinutes": { diff --git a/assets/translations/en.json b/assets/translations/en.json index 99b57e7..49914df 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -203,8 +203,9 @@ "categories": "Categories", "category": "Category", "noCategory": "No Category", - "deleteCategoryQuestion": "Delete Category?", - "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", "tooManyRequestsTryAgainInMinutes": { diff --git a/assets/translations/hu.json b/assets/translations/hu.json index fec9af3..84ff5e9 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -203,8 +203,9 @@ "categories": "Categories", "category": "Category", "noCategory": "No Category", - "deleteCategoryQuestion": "Delete Category?", - "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", "tooManyRequestsTryAgainInMinutes": { diff --git a/assets/translations/it.json b/assets/translations/it.json index d4a660b..6c897a2 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -203,8 +203,9 @@ "categories": "Categories", "category": "Category", "noCategory": "No Category", - "deleteCategoryQuestion": "Delete Category?", - "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", "tooManyRequestsTryAgainInMinutes": { diff --git a/assets/translations/ja.json b/assets/translations/ja.json index b500a4e..5cebd61 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -203,8 +203,9 @@ "categories": "カテゴリ", "category": "カテゴリ", "noCategory": "カテゴリなし", - "deleteCategoryQuestion": "カテゴリを削除しますか?", - "categoryDeleteWarning": "「{}」内のすべてのアプリは未分類に設定されます。", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "カテゴリを追加", "label": "ラベル", "tooManyRequestsTryAgainInMinutes": { diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 2d968ca..0f1286f 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -203,8 +203,9 @@ "categories": "Categories", "category": "Category", "noCategory": "No Category", - "deleteCategoryQuestion": "Delete Category?", - "categoryDeleteWarning": "All Apps in {} will be set to uncategorized.", + "noCategories": "No Categories", + "deleteCategoriesQuestion": "Delete Categories?", + "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", "tooManyRequestsTryAgainInMinutes": { diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 1411217..bc0e237 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -89,6 +89,8 @@ class GeneratedFormSwitch extends GeneratedFormItem { class GeneratedFormTagInput extends GeneratedFormItem { late MapEntry? deleteConfirmationMessage; late bool singleSelect; + late WrapAlignment alignment; + late String emptyMessage; GeneratedFormTagInput(String key, {String label = 'Input', List belowWidgets = const [], @@ -96,7 +98,9 @@ class GeneratedFormTagInput extends GeneratedFormItem { List> value)> additionalValidators = const [], this.deleteConfirmationMessage, - this.singleSelect = false}) + this.singleSelect = false, + this.alignment = WrapAlignment.start, + this.emptyMessage = 'Input'}) : super(key, label: label, belowWidgets: belowWidgets, @@ -256,7 +260,19 @@ class _GeneratedFormState extends State { ); } else if (widget.items[r][e] is GeneratedFormTagInput) { formInputs[r][e] = Wrap( + alignment: (widget.items[r][e] as GeneratedFormTagInput).alignment, + crossAxisAlignment: WrapCrossAlignment.center, children: [ + (values[widget.items[r][e].key] + as Map>?) + ?.isEmpty == + true + ? Text( + (widget.items[r][e] as GeneratedFormTagInput) + .emptyMessage, + style: const TextStyle(fontWeight: FontWeight.bold), + ) + : const SizedBox.shrink(), ...(values[widget.items[r][e].key] as Map>?) ?.entries @@ -265,7 +281,7 @@ class _GeneratedFormState extends State { padding: const EdgeInsets.symmetric(horizontal: 4), child: ChoiceChip( label: Text(e2.key), - backgroundColor: Color(e2.value.key).withAlpha(200), + backgroundColor: Color(e2.value.key).withAlpha(50), selectedColor: Color(e2.value.key), visualDensity: VisualDensity.compact, selected: e2.value.value, @@ -327,12 +343,15 @@ class _GeneratedFormState extends State { if ((widget.items[r][e] as GeneratedFormTagInput) .deleteConfirmationMessage != null) { + var message = + (widget.items[r][e] as GeneratedFormTagInput) + .deleteConfirmationMessage!; showDialog?>( context: context, builder: (BuildContext ctx) { return GeneratedFormModal( - title: tr('deleteCategoryQuestion'), - message: tr('categoryDeleteWarning'), + title: message.key, + message: message.value, items: const []); }).then((value) { if (value != null) { @@ -370,8 +389,15 @@ class _GeneratedFormState extends State { var temp = values[widget.items[r][e].key] as Map>?; temp ??= {}; + var singleSelect = + (widget.items[r][e] as GeneratedFormTagInput) + .singleSelect; + var someSelected = temp.entries + .where((element) => element.value.value) + .isNotEmpty; temp[label] = MapEntry( - generateRandomLightColor().value, false); + generateRandomLightColor().value, + !(someSelected && singleSelect)); values[widget.items[r][e].key] = temp; someValueChanged(); }); diff --git a/lib/pages/app.dart b/lib/pages/app.dart index ae6c070..3315569 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -5,6 +5,7 @@ import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/main.dart'; +import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -152,49 +153,22 @@ class _AppPageState extends State { fontStyle: FontStyle.italic, fontSize: 12), ), const SizedBox( - height: 32, + height: 48, ), - 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?>( - 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'))) - ]) + CategoryEditorSelector( + alignment: WrapAlignment.center, + singleSelect: true, + preselected: app?.app.category != null + ? {app!.app.category!} + : {}, + onSelected: (categories) { + if (app != null) { + app.app.category = categories.isNotEmpty + ? categories[0] + : null; + appsProvider.saveApps([app.app]); + } + }) ], )), ], diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 5e86d0d..d4a4a3c 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -380,8 +380,14 @@ class _LogsDialogState extends State { class CategoryEditorSelector extends StatefulWidget { final void Function(List categories)? onSelected; final bool singleSelect; + final Set preselected; + final WrapAlignment alignment; const CategoryEditorSelector( - {super.key, this.onSelected, this.singleSelect = false}); + {super.key, + this.onSelected, + this.singleSelect = false, + this.preselected = const {}, + this.alignment = WrapAlignment.start}); @override State createState() => _CategoryEditorSelectorState(); @@ -393,15 +399,21 @@ class _CategoryEditorSelectorState extends State { @override Widget build(BuildContext context) { var settingsProvider = context.watch(); - storedValues = settingsProvider.categories.map((key, value) => - MapEntry(key, MapEntry(value, storedValues[key]?.value ?? false))); + storedValues = settingsProvider.categories.map((key, value) => MapEntry( + key, + MapEntry(value, + storedValues[key]?.value ?? widget.preselected.contains(key)))); return GeneratedForm( items: [ [ GeneratedFormTagInput('categories', + label: tr('category'), + emptyMessage: tr('noCategories'), defaultValue: storedValues, + alignment: widget.alignment, deleteConfirmationMessage: MapEntry( - tr('deleteCategoryQuestion'), tr('categoryDeleteWarning')), + tr('deleteCategoriesQuestion'), + tr('categoryDeleteWarning')), singleSelect: widget.singleSelect) ] ], From 0f6a683faaf5cd857b4635f47528648e7b5d37ea Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 02:26:13 -0500 Subject: [PATCH 04/10] Increment version --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6614be9..8c4cd51 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.9.2'; +const String currentVersion = '0.9.3'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.yaml b/pubspec.yaml index 4e85d02..6bd4ff3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 0.9.2+90 # When changing this, update the tag in main() accordingly +version: 0.9.3+91 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0' From ed2a4e674fb7e983e8cb783cc36ce06213f6b8ef Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 03:13:55 -0500 Subject: [PATCH 05/10] Added language setting (mostly working) - #165 --- assets/translations/de.json | 1 + assets/translations/en.json | 1 + assets/translations/hu.json | 1 + assets/translations/it.json | 1 + assets/translations/ja.json | 1 + assets/translations/zh.json | 1 + lib/pages/settings.dart | 22 ++++++++++++++++++++++ lib/providers/settings_provider.dart | 21 +++++++++++++++++++++ 8 files changed, 49 insertions(+) diff --git a/assets/translations/de.json b/assets/translations/de.json index 01756a5..97576c4 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" diff --git a/assets/translations/en.json b/assets/translations/en.json index 49914df..8231108 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "one": "Too many requests (rate limited) - try again in {} minute", "other": "Too many requests (rate limited) - try again in {} minutes" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 84ff5e9..19fcf8c 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "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" diff --git a/assets/translations/it.json b/assets/translations/it.json index 6c897a2..2499dfb 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 5cebd61..f84efab 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "カテゴリを追加", "label": "ラベル", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 0f1286f..36cf8c2 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -208,6 +208,7 @@ "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", "addCategory": "Add Category", "label": "Label", + "language": "Language", "tooManyRequestsTryAgainInMinutes": { "one": "请求过多 (API 限制) - 在 {} 分钟后重试", "other": "请求过多 (API 限制) - 在 {} 分钟后重试" diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index d4a4a3c..2e107ba 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -6,6 +6,7 @@ import 'package:obtainium/components/custom_app_bar.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/main.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; @@ -129,6 +130,25 @@ class _SettingsPageState extends State { } }); + var localeDropdown = DropdownButtonFormField( + decoration: InputDecoration(labelText: tr('language')), + value: settingsProvider.forcedLocale, + items: [ + DropdownMenuItem( + value: null, + child: Text(tr('followSystem')), + ), + ...supportedLocales.map((e) => DropdownMenuItem( + value: e.toLanguageTag(), + child: Text(e.toLanguageTag().toUpperCase()), + )) + ], + onChanged: (value) { + settingsProvider.forcedLocale = value; + context.setLocale(Locale(settingsProvider.forcedLocale ?? + context.fallbackLocale!.languageCode)); + }); + var intervalDropdown = DropdownButtonFormField( decoration: InputDecoration(labelText: tr('bgUpdateCheckInterval')), value: settingsProvider.updateInterval, @@ -210,6 +230,8 @@ class _SettingsPageState extends State { ], ), height16, + localeDropdown, + height16, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index ad73ad3..07fcbee 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/components/generated_form.dart'; +import 'package:obtainium/main.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -164,4 +165,24 @@ class SettingsProvider with ChangeNotifier { ...categories.entries.map((e) => MapEntry(e.key, e.key)).toList() ], defaultValue: initCategory); + + String? get forcedLocale { + var fl = prefs?.getString('forcedLocale'); + return supportedLocales + .where((element) => element.toLanguageTag() == fl) + .isNotEmpty + ? fl + : null; + } + + set forcedLocale(String? fl) { + if (fl == null) { + prefs?.remove('forcedLocale'); + } else if (supportedLocales + .where((element) => element.toLanguageTag() == fl) + .isNotEmpty) { + prefs?.setString('forcedLocale', fl); + } + notifyListeners(); + } } From beeec356e5111c76a9d52d92016334f788110608 Mon Sep 17 00:00:00 2001 From: bluefly000 <101441707+bluefly000@users.noreply.github.com> Date: Thu, 22 Dec 2022 18:03:20 +0900 Subject: [PATCH 06/10] Update Japanese translation --- assets/translations/ja.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/translations/ja.json b/assets/translations/ja.json index f84efab..86c9b3f 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -27,7 +27,7 @@ "invalidRegEx": "無効な正規表現", "noDescription": "説明はありません", "cancel": "キャンセル", - "continue": "続ける", + "continue": "続行", "requiredInBrackets": "(必須)", "dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのオプションが必要です", "colour": "カラー", @@ -64,7 +64,7 @@ "notInstalled": "未インストール", "estimateInBrackets": "(推定)", "selectAll": "すべて選択", - "deselectN": "{}件を選択解除", + "deselectN": "{}件の選択を解除", "xWillBeRemovedButRemainInstalled": "{}はObtainiumから削除されますが、デバイスにはインストールされたままです。", "removeSelectedAppsQuestion": "選択したアプリを削除しますか?", "removeSelectedApps": "選択したアプリを削除する", @@ -135,7 +135,7 @@ "appearance": "外観", "showWebInAppView": "アプリビューにソースウェブページを表示する", "pinUpdates": "アップデートがあるアプリをトップに固定する", - "updates": "更新", + "updates": "アップデート", "sourceSpecific": "Github アクセストークン", "appSource": "アプリのソース", "noLogs": "ログはありません", @@ -144,7 +144,7 @@ "share": "共有", "appNotFound": "アプリが見つかりません", "obtainiumExportHyphenatedLowercase": "obtainium-エクスポート", - "pickAnAPK": "APKを選ぶ", + "pickAnAPK": "APKを選択", "appHasMoreThanOnePackage": "{}は複数のパッケージが存在します: ", "deviceSupportsXArch": "お使いのデバイスは{} CPUアーキテクチャに対応しています。", "deviceSupportsFollowingArchs": "お使いのデバイスは、以下のCPUアーキテクチャをサポートしています:", @@ -203,12 +203,12 @@ "categories": "カテゴリ", "category": "カテゴリ", "noCategory": "カテゴリなし", - "noCategories": "No Categories", - "deleteCategoriesQuestion": "Delete Categories?", - "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", + "noCategories": "カテゴリなし", + "deleteCategoriesQuestion": "カテゴリを削除しますか?", + "categoryDeleteWarning": "削除されたカテゴリ内のアプリは未分類に設定されます。", "addCategory": "カテゴリを追加", "label": "ラベル", - "language": "Language", + "language": "言語", "tooManyRequestsTryAgainInMinutes": { "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" From 1e5aa0999ac1db2f2937b931cb5fe1f311f386fb Mon Sep 17 00:00:00 2001 From: Markus <82206269+markus-gitdev@users.noreply.github.com> Date: Thu, 22 Dec 2022 10:21:04 +0100 Subject: [PATCH 07/10] Update DE translation Update german translation to match newly added localized strings --- assets/translations/de.json | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index 97576c4..441421c 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -188,27 +188,27 @@ "steam": "Steam", "steamMobile": "Steam Mobile", "steamChat": "Steam Chat", - "install": "Install", - "markInstalled": "Mark Installed", - "update": "Update", - "markUpdated": "Mark Updated", - "additionalOptions": "Additional Options", - "disableVersionDetection": "Disable Version Detection", - "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", - "downloadingX": "Downloading {}", - "downloadNotifDescription": "Notifies the user of the progress in downloading an App", - "noAPKFound": "No APK found", - "noVersionDetection": "No version detection", - "categorize": "Categorize", - "categories": "Categories", - "category": "Category", - "noCategory": "No Category", - "noCategories": "No Categories", - "deleteCategoriesQuestion": "Delete Categories?", - "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", - "addCategory": "Add Category", - "label": "Label", - "language": "Language", + "install": "Installieren", + "markInstalled": "Als Installiert markieren", + "update": "Aktualisieren", + "markUpdated": "Als Aktuell markieren", + "additionalOptions": "Zusätzliche Optionen", + "disableVersionDetection": "Versionsermittlung deaktivieren", + "noVersionDetectionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert.", + "downloadingX": "Lade {} herunter", + "downloadNotifDescription": "Benachrichtigt den Nutzer über den Fortschritt beim Herunterladen einer App", + "noAPKFound": "Keine APK gefunden", + "noVersionDetection": "Keine Versionserkennung", + "categorize": "Kategorisieren", + "categories": "Kategorien", + "category": "Kategorie", + "noCategory": "Keine Kategorie", + "noCategories": "Keine Kategorien", + "deleteCategoriesQuestion": "Kategorien löschen?", + "categoryDeleteWarning": "Alle Apps in gelöschten Kategorien werden auf nicht kategorisiert gesetzt.", + "addCategory": "Kategorie hinzufügen", + "label": "Bezeichnung", + "language": "Sprache", "tooManyRequestsTryAgainInMinutes": { "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" From 45a23e9025a22dfa631515ef830e61e62ce88031 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 07:57:21 -0500 Subject: [PATCH 08/10] Language fix for #185 --- lib/main.dart | 7 ++++++- lib/pages/settings.dart | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8c4cd51..c66a53b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,12 +43,16 @@ final globalNavigatorKey = GlobalKey(); Future loadTranslations() async { // See easy_localization/issues/210 await EasyLocalizationController.initEasyLocation(); + var s = SettingsProvider(); + await s.initializeSettings(); + var forceLocale = s.forcedLocale; final controller = EasyLocalizationController( saveLocale: true, + forceLocale: forceLocale != null ? Locale(forceLocale) : null, fallbackLocale: fallbackLocale, supportedLocales: supportedLocales, assetLoader: const RootBundleAssetLoader(), - useOnlyLangCode: false, + useOnlyLangCode: true, useFallbackTranslations: true, path: localeDir, onLoadError: (FlutterError e) { @@ -160,6 +164,7 @@ void main() async { supportedLocales: supportedLocales, path: localeDir, fallbackLocale: fallbackLocale, + useOnlyLangCode: true, child: const Obtainium()), )); } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 2e107ba..ecf1d5b 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -145,8 +145,11 @@ class _SettingsPageState extends State { ], onChanged: (value) { settingsProvider.forcedLocale = value; - context.setLocale(Locale(settingsProvider.forcedLocale ?? - context.fallbackLocale!.languageCode)); + if (value != null) { + context.setLocale(Locale(value)); + } else { + context.resetLocale(); + } }); var intervalDropdown = DropdownButtonFormField( From 4e0c655538fd32af0ec44887c8beddf9429e328e Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 08:01:26 -0500 Subject: [PATCH 09/10] F-Droid repo URL matching made more general (#188) --- README.md | 3 ++- lib/app_sources/fdroidrepo.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3d21466..e218721 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ Currently supported App sources: - [Signal](https://signal.org/) - [SourceForge](https://sourceforge.net/) - [APKMirror](https://apkmirror.com/) (Track-Only) -- Third Party F-Droid Repos (URLs ending with `/fdroid/repo`) +- Third Party F-Droid Repos + - Any URLs ending with `/fdroid/`, where `` can be anything - most often `repo` - [Steam](https://store.steampowered.com/mobile) ## Limitations diff --git a/lib/app_sources/fdroidrepo.dart b/lib/app_sources/fdroidrepo.dart index 0f63de5..0e9a7f7 100644 --- a/lib/app_sources/fdroidrepo.dart +++ b/lib/app_sources/fdroidrepo.dart @@ -22,7 +22,7 @@ class FDroidRepo extends AppSource { @override String standardizeURL(String url) { RegExp standardUrlRegExp = - RegExp('^https?://.+/fdroid/(repo(/|\\?)|repo\$)'); + RegExp('^https?://.+/fdroid/([^/]+(/|\\?)|[^/]+\$)'); RegExpMatch? match = standardUrlRegExp.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); From 226cfa25e05c422a625cc1a7bbe7bf762516e526 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 22 Dec 2022 08:01:52 -0500 Subject: [PATCH 10/10] Increment version --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c66a53b..6f90007 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.9.3'; +const String currentVersion = '0.9.4'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.yaml b/pubspec.yaml index 6bd4ff3..043e466 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 0.9.3+91 # When changing this, update the tag in main() accordingly +version: 0.9.4+92 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'