diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index bc0e237..7698dd0 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -91,6 +91,7 @@ class GeneratedFormTagInput extends GeneratedFormItem { late bool singleSelect; late WrapAlignment alignment; late String emptyMessage; + late bool showLabelWhenNotEmpty; GeneratedFormTagInput(String key, {String label = 'Input', List belowWidgets = const [], @@ -100,7 +101,8 @@ class GeneratedFormTagInput extends GeneratedFormItem { this.deleteConfirmationMessage, this.singleSelect = false, this.alignment = WrapAlignment.start, - this.emptyMessage = 'Input'}) + this.emptyMessage = 'Input', + this.showLabelWhenNotEmpty = true}) : super(key, label: label, belowWidgets: belowWidgets, @@ -140,11 +142,11 @@ class _GeneratedFormState extends State { for (int r = 0; r < widget.items.length; r++) { for (int i = 0; i < widget.items[r].length; i++) { if (formInputs[r][i] is TextFormField) { - valid = valid && - ((formInputs[r][i].key as GlobalKey) - .currentState - ?.isValid ?? - false); + var fieldState = + (formInputs[r][i].key as GlobalKey).currentState; + if (fieldState != null) { + valid = valid && fieldState.isValid; + } } } } @@ -152,7 +154,7 @@ class _GeneratedFormState extends State { } // Generates a random light color -// Courtesy of ChatGPT 😭 (with a bugfix 🥳) + // Courtesy of ChatGPT 😭 (with a bugfix 🥳) Color generateRandomLightColor() { // Create a random number generator final Random random = Random(); @@ -259,157 +261,185 @@ 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 - .map((e2) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: ChoiceChip( - label: Text(e2.key), - backgroundColor: Color(e2.value.key).withAlpha(50), - 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); - 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( + formInputs[r][e] = + Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + if ((values[widget.items[r][e].key] + as Map>?) + ?.isNotEmpty == + true && + (widget.items[r][e] as GeneratedFormTagInput) + .showLabelWhenNotEmpty) + Column( + crossAxisAlignment: + (widget.items[r][e] as GeneratedFormTagInput).alignment == + WrapAlignment.center + ? CrossAxisAlignment.center + : CrossAxisAlignment.stretch, + children: [ + Text(widget.items[r][e].label), + const SizedBox( + height: 8, + ), + ], + ), + 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, + ) + : const SizedBox.shrink(), + ...(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(50), + 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>)[key]! + MapEntry>)[e2.key]! .key, - false); + 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(); - }); + 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) { + var message = + (widget.items[r][e] as GeneratedFormTagInput) + .deleteConfirmationMessage!; + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: message.key, + message: message.value, + items: const []); + }).then((value) { + if (value != null) { + fn(); + } + }); + } else { + fn(); + } }, - )); - }) ?? - [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() { + 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.removeWhere((key, value) => value.value); - values[widget.items[r][e].key] = temp; - someValueChanged(); - }); - } - - 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: message.key, - message: message.value, - items: const []); - }).then((value) { - if (value != null) { - fn(); + as Map>?; + temp ??= {}; + if (temp[label] == null) { + var singleSelect = (widget.items[r][e] + as GeneratedFormTagInput) + .singleSelect; + var someSelected = temp.entries + .where((element) => element.value.value) + .isNotEmpty; + temp[label] = MapEntry( + generateRandomLightColor().value, + !(someSelected && singleSelect)); + values[widget.items[r][e].key] = temp; + someValueChanged(); } }); - } 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 ??= {}; - var singleSelect = - (widget.items[r][e] as GeneratedFormTagInput) - .singleSelect; - var someSelected = temp.entries - .where((element) => element.value.value) - .isNotEmpty; - temp[label] = MapEntry( - generateRandomLightColor().value, - !(someSelected && singleSelect)); - values[widget.items[r][e].key] = temp; - someValueChanged(); - }); - } - }); - }, - icon: const Icon(Icons.add), - visualDensity: VisualDensity.compact, - tooltip: tr('add'), - )), - ], - ); + }); + }, + icon: const Icon(Icons.add), + visualDensity: VisualDensity.compact, + tooltip: tr('add'), + )), + ], + ) + ]); } } } diff --git a/lib/components/generated_form_modal.dart b/lib/components/generated_form_modal.dart index 498b20c..b961a9c 100644 --- a/lib/components/generated_form_modal.dart +++ b/lib/components/generated_form_modal.dart @@ -9,12 +9,14 @@ class GeneratedFormModal extends StatefulWidget { required this.title, required this.items, this.initValid = false, - this.message = ''}); + this.message = '', + this.additionalWidgets = const []}); final String title; final String message; final List> items; final bool initValid; + final List additionalWidgets; @override State createState() => _GeneratedFormModalState(); @@ -54,7 +56,8 @@ class _GeneratedFormModalState extends State { this.valid = valid; }); } - }) + }), + if (widget.additionalWidgets.isNotEmpty) ...widget.additionalWidgets ]), actions: [ TextButton( diff --git a/lib/main.dart b/lib/main.dart index d39261f..a13d8f2 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.8'; +const String currentVersion = '0.9.9'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 687667b..d39f034 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -8,6 +8,7 @@ import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/main.dart'; import 'package:obtainium/pages/app.dart'; import 'package:obtainium/pages/import_export.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'; @@ -29,6 +30,7 @@ class _AddAppPageState extends State { AppSource? pickedSource; Map additionalSettings = {}; bool additionalSettingsValid = true; + String? category; @override Widget build(BuildContext context) { @@ -37,25 +39,19 @@ class _AddAppPageState extends State { changeUserInput(String input, bool valid, bool isBuilding) { userInput = input; - fn() { - var source = valid ? sourceProvider.getSource(userInput) : null; - if (pickedSource.runtimeType != source.runtimeType) { - pickedSource = source; - additionalSettings = source != null - ? getDefaultValuesFromFormItems( - source.combinedAppSpecificSettingFormItems) - : {}; - additionalSettingsValid = source != null - ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) - : true; - } - } - - if (isBuilding) { - fn(); - } else { + if (!isBuilding) { setState(() { - fn(); + var source = valid ? sourceProvider.getSource(userInput) : null; + if (pickedSource.runtimeType != source.runtimeType) { + pickedSource = source; + additionalSettings = source != null + ? getDefaultValuesFromFormItems( + source.combinedAppSpecificSettingFormItems) + : {}; + additionalSettingsValid = source != null + ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) + : true; + } }); } } @@ -131,6 +127,9 @@ class _AddAppPageState extends State { if (app.additionalSettings['trackOnly'] == true) { app.installedVersion = app.latestVersion; } + if (category != null) { + app.category = category; + } await appsProvider.saveApps([app]); return app; @@ -238,7 +237,9 @@ class _AddAppPageState extends State { ] ], onValueChanges: (values, valid, isBuilding) { - if (values.isNotEmpty && valid) { + if (values.isNotEmpty && + valid && + !isBuilding) { setState(() { searchQuery = values['searchSomeSources']!.trim(); @@ -299,9 +300,7 @@ class _AddAppPageState extends State { child: Text(tr('search'))) ], ), - if (pickedSource != null && - (pickedSource! - .combinedAppSpecificSettingFormItems.isNotEmpty)) + if (pickedSource != null) Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -328,6 +327,21 @@ class _AddAppPageState extends State { }); } }), + Column( + children: [ + const SizedBox( + height: 16, + ), + CategoryEditorSelector( + alignment: WrapAlignment.start, + singleSelect: true, + onSelected: (categories) { + category = categories.isEmpty + ? null + : categories.first; + }), + ], + ), ], ) else diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 3315569..12d654c 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -1,7 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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'; @@ -35,7 +34,6 @@ class _AppPageState extends State { }); } - var categories = settingsProvider.categories; var sourceProvider = SourceProvider(); AppInMemory? app = appsProvider.apps[widget.appId]; var source = app != null ? sourceProvider.getSource(app.app.url) : null; @@ -72,11 +70,12 @@ class _AppPageState extends State { : Container() : CustomScrollView( slivers: [ - SliverFillRemaining( + SliverToBoxAdapter( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + const SizedBox(height: 150), app?.installedInfo != null ? Row( mainAxisAlignment: MainAxisAlignment.center, @@ -168,7 +167,8 @@ class _AppPageState extends State { : null; appsProvider.saveApps([app.app]); } - }) + }), + const SizedBox(height: 150) ], )), ], diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3113265..223526d 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -7,6 +7,7 @@ import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/main.dart'; import 'package:obtainium/pages/app.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'; @@ -22,7 +23,8 @@ class AppsPage extends StatefulWidget { } class AppsPageState extends State { - AppsFilter? filter; + AppsFilter filter = AppsFilter(); + final AppsFilter neutralFilter = AppsFilter(); var updatesOnlyFilter = AppsFilter(includeUptodate: false, includeNonInstalled: false); Set selectedApps = {}; @@ -53,8 +55,7 @@ class AppsPageState extends State { var appsProvider = context.watch(); var settingsProvider = context.watch(); var sortedApps = appsProvider.apps.values.toList(); - var currentFilterIsUpdatesOnly = - filter?.isIdenticalTo(updatesOnlyFilter) ?? false; + var currentFilterIsUpdatesOnly = filter.isIdenticalTo(updatesOnlyFilter); selectedApps = selectedApps .where((element) => sortedApps.map((e) => e.app).contains(element)) @@ -70,45 +71,42 @@ class AppsPageState extends State { }); } - if (filter != null) { - sortedApps = sortedApps.where((app) { - if (app.app.installedVersion == app.app.latestVersion && - !(filter!.includeUptodate)) { - return false; - } - if (app.app.installedVersion == null && - !(filter!.includeNonInstalled)) { - return false; - } - if (filter!.nameFilter.isNotEmpty || filter!.authorFilter.isNotEmpty) { - List nameTokens = filter!.nameFilter - .split(' ') - .where((element) => element.trim().isNotEmpty) - .toList(); - List authorTokens = filter!.authorFilter - .split(' ') - .where((element) => element.trim().isNotEmpty) - .toList(); + sortedApps = sortedApps.where((app) { + if (app.app.installedVersion == app.app.latestVersion && + !(filter.includeUptodate)) { + return false; + } + if (app.app.installedVersion == null && !(filter.includeNonInstalled)) { + return false; + } + if (filter.nameFilter.isNotEmpty || filter.authorFilter.isNotEmpty) { + List nameTokens = filter.nameFilter + .split(' ') + .where((element) => element.trim().isNotEmpty) + .toList(); + List authorTokens = filter.authorFilter + .split(' ') + .where((element) => element.trim().isNotEmpty) + .toList(); - for (var t in nameTokens) { - var name = app.installedInfo?.name ?? app.app.name; - if (!name.toLowerCase().contains(t.toLowerCase())) { - return false; - } - } - for (var t in authorTokens) { - if (!app.app.author.toLowerCase().contains(t.toLowerCase())) { - return false; - } + for (var t in nameTokens) { + var name = app.installedInfo?.name ?? app.app.name; + if (!name.toLowerCase().contains(t.toLowerCase())) { + return false; } } - if (filter!.categoryFilter.isNotEmpty && - filter!.categoryFilter != app.app.category) { - return false; + for (var t in authorTokens) { + if (!app.app.author.toLowerCase().contains(t.toLowerCase())) { + return false; + } } - return true; - }).toList(); - } + } + if (filter.categoryFilter.isNotEmpty && + !filter.categoryFilter.contains(app.app.category)) { + return false; + } + return true; + }).toList(); sortedApps.sort((a, b) { var nameA = a.installedInfo?.name ?? a.app.name; @@ -230,7 +228,7 @@ class AppsPageState extends State { decoration: BoxDecoration( border: Border.symmetric( vertical: BorderSide( - width: 3, + width: 4, color: Color(settingsProvider.categories[ sortedApps[index].app.category] ?? const Color.fromARGB(0, 0, 0, 0).value)))), @@ -339,21 +337,29 @@ class AppsPageState extends State { persistentFooterButtons: [ Row( children: [ - IconButton( - onPressed: () { - selectedApps.isEmpty - ? selectThese(sortedApps.map((e) => e.app).toList()) - : clearSelected(); - }, - icon: Icon( - selectedApps.isEmpty - ? Icons.select_all_outlined - : Icons.deselect_outlined, - color: Theme.of(context).colorScheme.primary, - ), - tooltip: selectedApps.isEmpty - ? tr('selectAll') - : tr('deselectN', args: [selectedApps.length.toString()])), + selectedApps.isEmpty + ? IconButton( + onPressed: () { + selectThese(sortedApps.map((e) => e.app).toList()); + }, + icon: Icon( + Icons.select_all_outlined, + color: Theme.of(context).colorScheme.primary, + ), + tooltip: tr('selectAll')) + : TextButton.icon( + onPressed: () { + selectedApps.isEmpty + ? selectThese(sortedApps.map((e) => e.app).toList()) + : clearSelected(); + }, + icon: Icon( + selectedApps.isEmpty + ? Icons.select_all_outlined + : Icons.deselect_outlined, + color: Theme.of(context).colorScheme.primary, + ), + label: Text(selectedApps.length.toString())), const VerticalDivider(), Expanded( child: Row( @@ -663,7 +669,7 @@ class AppsPageState extends State { onPressed: () { setState(() { if (currentFilterIsUpdatesOnly) { - filter = null; + filter = AppsFilter(); } else { filter = updatesOnlyFilter; } @@ -683,9 +689,11 @@ class AppsPageState extends State { ? const SizedBox() : TextButton.icon( label: Text( - filter == null ? tr('filter') : tr('filterActive'), + filter.isIdenticalTo(neutralFilter) + ? tr('filter') + : tr('filterActive'), style: TextStyle( - fontWeight: filter == null + fontWeight: filter.isIdenticalTo(neutralFilter) ? FontWeight.normal : FontWeight.bold), ), @@ -693,44 +701,48 @@ class AppsPageState extends State { showDialog?>( context: context, builder: (BuildContext ctx) { - var vals = filter == null - ? AppsFilter().toValuesMap() - : filter!.toValuesMap(); + var vals = filter.toFormValuesMap(); return GeneratedFormModal( - title: tr('filterApps'), - items: [ - [ - GeneratedFormTextField('appName', - label: tr('appName'), - required: false, - defaultValue: vals['appName']), - GeneratedFormTextField('author', - label: tr('author'), - required: false, - defaultValue: vals['author']) - ], - [ - GeneratedFormSwitch('upToDateApps', - label: tr('upToDateApps'), - defaultValue: vals['upToDateApps']) - ], - [ - GeneratedFormSwitch('nonInstalledApps', - label: tr('nonInstalledApps'), - defaultValue: vals['nonInstalledApps']) - ], - [ - settingsProvider.getCategoryFormItem( - initCategory: vals['category'] ?? '') - ] - ]); + initValid: true, + title: tr('filterApps'), + items: [ + [ + GeneratedFormTextField('appName', + label: tr('appName'), + required: false, + defaultValue: vals['appName']), + GeneratedFormTextField('author', + label: tr('author'), + required: false, + defaultValue: vals['author']) + ], + [ + GeneratedFormSwitch('upToDateApps', + label: tr('upToDateApps'), + defaultValue: vals['upToDateApps']) + ], + [ + GeneratedFormSwitch('nonInstalledApps', + label: tr('nonInstalledApps'), + defaultValue: vals['nonInstalledApps']) + ] + ], + additionalWidgets: [ + const SizedBox( + height: 16, + ), + CategoryEditorSelector( + preselected: filter.categoryFilter, + onSelected: (categories) { + filter.categoryFilter = categories.toSet(); + }, + ) + ], + ); }).then((values) { if (values != null) { setState(() { - filter = AppsFilter.fromValuesMap(values); - if (AppsFilter().isIdenticalTo(filter!)) { - filter = null; - } + filter.setFormValuesFromMap(values); }); } }); @@ -748,31 +760,29 @@ class AppsFilter { late String authorFilter; late bool includeUptodate; late bool includeNonInstalled; - late String categoryFilter; + late Set categoryFilter; AppsFilter( {this.nameFilter = '', this.authorFilter = '', this.includeUptodate = true, this.includeNonInstalled = true, - this.categoryFilter = ''}); + this.categoryFilter = const {}}); - Map toValuesMap() { + Map toFormValuesMap() { return { 'appName': nameFilter, 'author': authorFilter, 'upToDateApps': includeUptodate, - 'nonInstalledApps': includeNonInstalled, - 'category': categoryFilter + 'nonInstalledApps': includeNonInstalled }; } - AppsFilter.fromValuesMap(Map values) { + setFormValuesFromMap(Map values) { nameFilter = values['appName']!; authorFilter = values['author']!; includeUptodate = values['upToDateApps']; includeNonInstalled = values['nonInstalledApps']; - categoryFilter = values['category']!; } bool isIdenticalTo(AppsFilter other) => @@ -780,5 +790,7 @@ class AppsFilter { nameFilter.trim() == other.nameFilter.trim() && includeUptodate == other.includeUptodate && includeNonInstalled == other.includeNonInstalled && - categoryFilter.trim() == other.categoryFilter.trim(); + categoryFilter.length == other.categoryFilter.length && + categoryFilter.union(other.categoryFilter).length == + categoryFilter.length; } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index ecf1d5b..a198016 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -185,7 +185,7 @@ class _SettingsPageState extends State { return [e]; }).toList(), onValueChanges: (values, valid, isBuilding) { - if (valid) { + if (valid && !isBuilding) { values.forEach((key, value) { settingsProvider.setSettingString(key, value); }); @@ -286,7 +286,9 @@ class _SettingsPageState extends State { color: Theme.of(context).colorScheme.primary), ), height16, - const CategoryEditorSelector() + const CategoryEditorSelector( + showLabelWhenNotEmpty: false, + ) ], ))), SliverToBoxAdapter( @@ -407,12 +409,14 @@ class CategoryEditorSelector extends StatefulWidget { final bool singleSelect; final Set preselected; final WrapAlignment alignment; + final bool showLabelWhenNotEmpty; const CategoryEditorSelector( {super.key, this.onSelected, this.singleSelect = false, this.preselected = const {}, - this.alignment = WrapAlignment.start}); + this.alignment = WrapAlignment.start, + this.showLabelWhenNotEmpty = true}); @override State createState() => _CategoryEditorSelectorState(); @@ -439,7 +443,8 @@ class _CategoryEditorSelectorState extends State { deleteConfirmationMessage: MapEntry( tr('deleteCategoriesQuestion'), tr('categoryDeleteWarning')), - singleSelect: widget.singleSelect) + singleSelect: widget.singleSelect, + showLabelWhenNotEmpty: widget.showLabelWhenNotEmpty) ] ], onValueChanges: ((values, valid, isBuilding) { diff --git a/pubspec.lock b/pubspec.lock index 58556d1..b1d938b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -739,14 +739,14 @@ packages: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" webview_flutter_android: dependency: transitive description: name: webview_flutter_android url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.1" webview_flutter_platform_interface: dependency: transitive description: @@ -760,7 +760,7 @@ packages: name: webview_flutter_wkwebview url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f04b647..280d5e5 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.8+96 # When changing this, update the tag in main() accordingly +version: 0.9.9+97 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'