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(); + } +}