Files
venera/lib/pages/settings/setting_components.dart
2025-01-25 16:50:04 +08:00

608 lines
15 KiB
Dart

part of 'settings_page.dart';
class _SwitchSetting extends StatefulWidget {
const _SwitchSetting({
required this.title,
required this.settingKey,
this.onChanged,
this.subtitle,
});
final String title;
final String settingKey;
final VoidCallback? onChanged;
final String? subtitle;
@override
State<_SwitchSetting> createState() => _SwitchSettingState();
}
class _SwitchSettingState extends State<_SwitchSetting> {
@override
Widget build(BuildContext context) {
assert(appdata.settings[widget.settingKey] is bool);
return ListTile(
title: Text(widget.title),
subtitle: widget.subtitle == null ? null : Text(widget.subtitle!),
trailing: Switch(
value: appdata.settings[widget.settingKey],
onChanged: (value) {
setState(() {
appdata.settings[widget.settingKey] = value;
});
appdata.saveData().then((_) {
widget.onChanged?.call();
});
},
),
);
}
}
class SelectSetting extends StatelessWidget {
const SelectSetting({
super.key,
required this.title,
required this.settingKey,
required this.optionTranslation,
this.onChanged,
this.help,
});
final String title;
final String settingKey;
final Map<String, String> optionTranslation;
final VoidCallback? onChanged;
final String? help;
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 450) {
return _DoubleLineSelectSettings(
title: title,
settingKey: settingKey,
optionTranslation: optionTranslation,
onChanged: onChanged,
help: help,
);
} else {
return _EndSelectorSelectSetting(
title: title,
settingKey: settingKey,
optionTranslation: optionTranslation,
onChanged: onChanged,
help: help,
);
}
},
),
);
}
}
class _DoubleLineSelectSettings extends StatefulWidget {
const _DoubleLineSelectSettings({
required this.title,
required this.settingKey,
required this.optionTranslation,
this.onChanged,
this.help,
});
final String title;
final String settingKey;
final Map<String, String> optionTranslation;
final VoidCallback? onChanged;
final String? help;
@override
State<_DoubleLineSelectSettings> createState() =>
_DoubleLineSelectSettingsState();
}
class _DoubleLineSelectSettingsState extends State<_DoubleLineSelectSettings> {
@override
Widget build(BuildContext context) {
return ListTile(
title: Row(
children: [
Text(widget.title),
const SizedBox(width: 4),
if (widget.help != null)
Button.icon(
size: 18,
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ContentDialog(
title: "Help".tl,
content: Text(widget.help!)
.paddingHorizontal(16)
.fixWidth(double.infinity),
actions: [
Button.filled(
onPressed: context.pop,
child: Text("OK".tl),
),
],
);
},
);
},
),
],
),
subtitle: Text(
widget.optionTranslation[appdata.settings[widget.settingKey]] ??
"None"),
trailing: const Icon(Icons.arrow_drop_down),
onTap: () {
var renderBox = context.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
var size = renderBox.size;
var rect = offset & size;
showMenu(
elevation: 3,
color: context.brightness == Brightness.light
? const Color(0xFFF6F6F6)
: const Color(0xFF1E1E1E),
context: context,
position: RelativeRect.fromRect(
rect,
Offset.zero & MediaQuery.of(context).size,
),
items: widget.optionTranslation.keys
.map((key) => PopupMenuItem(
value: key,
height: App.isMobile ? 46 : 40,
child: Text(widget.optionTranslation[key]!),
))
.toList(),
).then((value) {
if (value != null) {
setState(() {
appdata.settings[widget.settingKey] = value;
});
appdata.saveData();
widget.onChanged?.call();
}
});
},
);
}
}
class _EndSelectorSelectSetting extends StatefulWidget {
const _EndSelectorSelectSetting({
required this.title,
required this.settingKey,
required this.optionTranslation,
this.onChanged,
this.help,
});
final String title;
final String settingKey;
final Map<String, String> optionTranslation;
final VoidCallback? onChanged;
final String? help;
@override
State<_EndSelectorSelectSetting> createState() =>
_EndSelectorSelectSettingState();
}
class _EndSelectorSelectSettingState extends State<_EndSelectorSelectSetting> {
@override
Widget build(BuildContext context) {
var options = widget.optionTranslation;
return ListTile(
title: Row(
children: [
Text(widget.title),
const SizedBox(width: 4),
if (widget.help != null)
Button.icon(
size: 18,
icon: const Icon(Icons.help_outline),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ContentDialog(
title: "Help".tl,
content: Text(widget.help!)
.paddingHorizontal(16)
.fixWidth(double.infinity),
actions: [
Button.filled(
onPressed: context.pop,
child: Text("OK".tl),
),
],
);
},
);
},
),
],
),
trailing: Select(
current: options[appdata.settings[widget.settingKey]],
values: options.values.toList(),
minWidth: 64,
onTap: (index) {
setState(() {
appdata.settings[widget.settingKey] = options.keys.elementAt(index);
});
appdata.saveData();
widget.onChanged?.call();
},
),
);
}
}
class _SliderSetting extends StatefulWidget {
const _SliderSetting({
required this.title,
required this.settingsIndex,
required this.interval,
required this.min,
required this.max,
this.onChanged,
});
final String title;
final String settingsIndex;
final double interval;
final double min;
final double max;
final VoidCallback? onChanged;
@override
State<_SliderSetting> createState() => _SliderSettingState();
}
class _SliderSettingState extends State<_SliderSetting> {
@override
Widget build(BuildContext context) {
return ListTile(
title: Row(
children: [
Text(widget.title),
const Spacer(),
Text(
appdata.settings[widget.settingsIndex].toString(),
style: ts.s12,
),
],
),
subtitle: Slider(
value: appdata.settings[widget.settingsIndex].toDouble(),
onChanged: (value) {
if (value.toInt() == value) {
setState(() {
appdata.settings[widget.settingsIndex] = value.toInt();
appdata.saveData();
});
} else {
setState(() {
appdata.settings[widget.settingsIndex] = value;
appdata.saveData();
});
}
widget.onChanged?.call();
},
divisions: ((widget.max - widget.min) / widget.interval).toInt(),
min: widget.min,
max: widget.max,
),
);
}
}
class _PopupWindowSetting extends StatelessWidget {
const _PopupWindowSetting({required this.title, required this.builder});
final Widget Function() builder;
final String title;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
trailing: const Icon(Icons.arrow_right),
onTap: () {
showPopUpWidget(App.rootContext, builder());
},
);
}
}
class _MultiPagesFilter extends StatefulWidget {
const _MultiPagesFilter({
required this.title,
required this.settingsIndex,
required this.pages,
});
final String title;
final String settingsIndex;
// key - name
final Map<String, String> pages;
@override
State<_MultiPagesFilter> createState() => _MultiPagesFilterState();
}
class _MultiPagesFilterState extends State<_MultiPagesFilter> {
late List<String> keys;
@override
void initState() {
keys = List.from(appdata.settings[widget.settingsIndex]);
keys.remove("");
super.initState();
}
@override
void dispose() {
super.dispose();
Future.microtask(() {
updateSetting();
});
}
var reorderWidgetKey = UniqueKey();
var scrollController = ScrollController();
final _key = GlobalKey();
@override
Widget build(BuildContext context) {
var tiles = keys.map((e) => buildItem(e)).toList();
var view = ReorderableBuilder<String>(
key: reorderWidgetKey,
scrollController: scrollController,
longPressDelay: App.isDesktop
? const Duration(milliseconds: 100)
: const Duration(milliseconds: 500),
dragChildBoxDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 5,
offset: Offset(0, 2),
spreadRadius: 2)
],
),
onReorder: (reorderFunc) {
setState(() {
keys = List.from(reorderFunc(keys));
});
},
children: tiles,
builder: (children) {
return GridView(
key: _key,
controller: scrollController,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
mainAxisExtent: 48,
),
children: children,
);
},
);
return PopUpWidgetScaffold(
title: widget.title,
tailing: [
if (keys.length < widget.pages.length)
TextButton.icon(
label: Text("Add".tl),
icon: const Icon(Icons.add),
onPressed: showAddDialog,
)
],
body: view,
);
}
Widget buildItem(String key) {
Widget removeButton = Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
onPressed: () {
setState(() {
keys.remove(key);
});
},
icon: const Icon(Icons.delete_outline)),
);
return ListTile(
title: Text(widget.pages[key] ?? "(Invalid) $key"),
key: Key(key),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
removeButton,
const Icon(Icons.drag_handle),
],
),
);
}
void showAddDialog() {
var canAdd = <String, String>{};
widget.pages.forEach((key, value) {
if (!keys.contains(key)) {
canAdd[key] = value;
}
});
var selected = <String>[];
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "Add".tl,
content: Column(
mainAxisSize: MainAxisSize.min,
children: canAdd.entries
.map(
(e) => CheckboxListTile(
value: selected.contains(e.key),
title: Text(e.value),
key: Key(e.key),
onChanged: (value) {
setState(() {
if (value!) {
selected.add(e.key);
} else {
selected.remove(e.key);
}
});
},
),
)
.toList(),
),
actions: [
if (selected.length < canAdd.length)
TextButton(
child: Text("Select All".tl),
onPressed: () {
setState(() {
selected = canAdd.keys.toList();
});
},
)
else
TextButton(
child: Text("Deselect All".tl),
onPressed: () {
setState(() {
selected.clear();
});
},
),
const SizedBox(width: 8),
FilledButton(
onPressed: selected.isNotEmpty
? () {
this.setState(() {
keys.addAll(selected);
});
Navigator.pop(context);
}
: null,
child: Text("Add".tl),
),
],
);
});
},
);
}
void updateSetting() {
appdata.settings[widget.settingsIndex] = keys;
appdata.saveData();
}
}
class _CallbackSetting extends StatelessWidget {
const _CallbackSetting({
required this.title,
required this.callback,
required this.actionTitle,
this.subtitle,
});
final String title;
final String? subtitle;
final VoidCallback callback;
final String actionTitle;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
subtitle: subtitle == null ? null : Text(subtitle!),
trailing: Button.normal(
onPressed: callback,
child: Text(actionTitle),
).fixHeight(28),
onTap: callback,
);
}
}
class _SettingPartTitle extends StatelessWidget {
const _SettingPartTitle({required this.title, required this.icon});
final String title;
final IconData icon;
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: context.colorScheme.onSurface.withValues(alpha: 0.1),
),
),
),
child: Row(
children: [
Icon(icon, size: 24),
const SizedBox(width: 8),
Text(title, style: ts.s18),
],
),
),
);
}
}