mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-18 20:49:30 +02:00
Started work on new unified category selector/editor
This commit is contained in:
@@ -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'),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user