Files
Obtainium/lib/components/settings_widgets.dart
Imran Remtulla 7d6ad09910 feat: expressive Add FAB, broader haptics, and progress-spinner consistency
- Use an extended (icon + label) FAB for the primary Add action in the
  bottom layout; keep a compact FAB in the navigation rail so it fits.
- Add tactile feedback (respecting the existing setting) to interactions
  that lacked it: download cancel buttons, Add/more/view FABs, list-tile
  update and swipe install/remove, settings segmented buttons, and all
  settings toggles (centralized in SettingsToggleRow).
- Drop manual strokeWidth overrides so inline spinners inherit the themed
  Material 3 look.
2026-07-02 21:41:34 +01:00

187 lines
5.0 KiB
Dart

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/theme.dart';
import 'package:obtainium/components/ui_widgets.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:provider/provider.dart';
bool _isKnownTileType(Widget w) =>
w is SettingsTile || w is SettingsToggleRow;
Widget _wrapChildWithRadius(Widget w, BorderRadius radius) {
if (w is SettingsTile) {
return SettingsTile(
key: w.key,
padding: w.padding,
borderRadius: radius,
color: w.color,
child: w.child,
);
}
if (w is SettingsToggleRow) {
return SettingsToggleRow(
key: w.key,
label: w.label,
value: w.value,
onChanged: w.onChanged,
subtitle: w.subtitle,
borderRadius: radius,
helpWidgets: w.helpWidgets,
);
}
return w;
}
List<Widget> shapeSettingsTiles(List<Widget> children) {
final result = <Widget>[];
for (var i = 0; i < children.length; i++) {
final w = children[i];
if (!_isKnownTileType(w)) {
result.add(w);
continue;
}
final prevIsTile = i > 0 && _isKnownTileType(children[i - 1]);
final nextIsTile =
i < children.length - 1 && _isKnownTileType(children[i + 1]);
result.add(
_wrapChildWithRadius(
w,
positionalTileRadius(isFirst: !prevIsTile, isLast: !nextIsTile),
),
);
}
return result;
}
class SettingsTile extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final BorderRadius? borderRadius;
final Color? color;
const SettingsTile({
super.key,
required this.child,
this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
this.borderRadius,
this.color,
});
@override
Widget build(BuildContext context) {
return Material(
color: color ?? Theme.of(context).colorScheme.surfaceContainerLow,
shape: RoundedSuperellipseBorder(
borderRadius:
borderRadius ?? BorderRadius.circular(connectedTileBigRadius),
),
clipBehavior: Clip.antiAlias,
child: Padding(padding: padding, child: child),
);
}
}
class SettingsToggleRow extends StatelessWidget {
final String label;
final bool value;
final ValueChanged<bool>? onChanged;
final Widget? subtitle;
final BorderRadius? borderRadius;
final List<Widget> helpWidgets;
const SettingsToggleRow({
super.key,
required this.label,
required this.value,
required this.onChanged,
this.subtitle,
this.borderRadius,
this.helpWidgets = const [],
});
@override
Widget build(BuildContext context) {
final ValueChanged<bool>? hapticOnChanged = onChanged == null
? null
: (v) {
context.read<SettingsProvider>().selectionClick();
onChanged!(v);
};
final Widget tileChild = helpWidgets.isEmpty
? SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
title: Text(label),
subtitle: subtitle,
value: value,
onChanged: hapticOnChanged,
)
: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
title: Text(label),
subtitle: subtitle,
onTap: hapticOnChanged == null
? null
: () => hapticOnChanged(!value),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: tr('about'),
onPressed: () => showHelpDialog(
context,
title: label,
content: helpWidgets,
),
),
Switch(value: value, onChanged: hapticOnChanged),
],
),
);
return SettingsTile(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0),
borderRadius: borderRadius,
child: tileChild,
);
}
}
class SettingsSectionHeader extends StatelessWidget {
final String title;
const SettingsSectionHeader({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(4, 8, 4, 8),
child: Text(
title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
}
class SettingsGroup extends StatelessWidget {
final String title;
final List<Widget> children;
const SettingsGroup({super.key, required this.title, required this.children});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 3,
children: [
SettingsSectionHeader(title: title),
...shapeSettingsTiles(children),
],
);
}
}