Started work on new unified category selector/editor

This commit is contained in:
Imran Remtulla
2022-12-21 23:54:36 -05:00
parent 2ff6acb701
commit 6977858b99
2 changed files with 177 additions and 81 deletions

View File

@@ -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<String, String>? deleteConfirmationMessage;
GeneratedFormTagInput(String key,
{String label = 'Input',
List<Widget> belowWidgets = const [],
Map<String, MapEntry<int, bool>> defaultValue = const {},
List<String? Function(Map<String, MapEntry<int, bool>> value)>
additionalValidators = const [],
this.deleteConfirmationMessage})
: super(key,
label: label,
belowWidgets: belowWidgets,
defaultValue: defaultValue,
additionalValidators: additionalValidators);
@override
Map<String, MapEntry<int, bool>> ensureType(val) {
return val is Map<String, MapEntry<int, bool>> ? val : {};
}
}
typedef OnValueChanges = void Function(
Map<String, dynamic> values, bool valid, bool isBuilding);
@@ -120,6 +145,21 @@ class _GeneratedFormState extends State<GeneratedForm> {
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<GeneratedForm> {
})
],
);
} else if (widget.items[r][e] is GeneratedFormTagInput) {
formInputs[r][e] = Wrap(
children: [
...(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
?.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<String,
MapEntry<int, bool>>)[e2.key] =
MapEntry(
(values[widget.items[r][e].key] as Map<
String,
MapEntry<int, bool>>)[e2.key]!
.key,
value);
someValueChanged();
});
},
));
}) ??
[const SizedBox.shrink()],
(values[widget.items[r][e].key]
as Map<String, MapEntry<int, bool>>?)
?.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<String, MapEntry<int, bool>>;
temp.removeWhere((key, value) => value.value);
values[widget.items[r][e].key] = temp;
someValueChanged();
});
}
if ((widget.items[r][e] as GeneratedFormTagInput)
.deleteConfirmationMessage !=
null) {
showDialog<Map<String, dynamic>?>(
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<Map<String, dynamic>?>(
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<String, MapEntry<int, bool>>?;
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'),
)),
],
);
}
}
}

View File

@@ -174,12 +174,21 @@ class _SettingsPageState extends State<SettingsPage> {
}
});
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: <Widget>[
@@ -264,85 +273,7 @@ class _SettingsPageState extends State<SettingsPage> {
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, dynamic>?>(
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, dynamic>?>(
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<LogsDialog> {
);
}
}
class CategoryEditorSelector extends StatefulWidget {
const CategoryEditorSelector({super.key});
@override
State<CategoryEditorSelector> createState() => _CategoryEditorSelectorState();
}
class _CategoryEditorSelectorState extends State<CategoryEditorSelector> {
@override
Widget build(BuildContext context) {
return Container();
}
}