Add selectionClick to settings switches, discrete slider steps, bottom nav tabs, and expandable section headers. Fix intensity levels for cache clear and random color (medium → light). Add lightImpact to thumbnail long-press. Fix inconsistent search result long-press level. Replace blanket lightImpact in updateNotification with targeted haptics at error/warning call sites to eliminate double-firing and match severity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.1 KiB
Dart
84 lines
2.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
/// A custom-styled adaptive [Slider].
|
|
class SettingsListSlider extends StatefulWidget {
|
|
final String title;
|
|
final String trailing;
|
|
final String? subtitle;
|
|
final double value;
|
|
final ValueChanged<double>? onChanged;
|
|
final double min;
|
|
final double max;
|
|
final int? divisions;
|
|
|
|
const SettingsListSlider({
|
|
super.key,
|
|
required this.title,
|
|
required this.trailing,
|
|
this.subtitle,
|
|
required this.value,
|
|
this.onChanged,
|
|
this.min = 0.0,
|
|
this.max = 1.0,
|
|
this.divisions,
|
|
});
|
|
|
|
@override
|
|
State<SettingsListSlider> createState() => _SettingsListSliderState();
|
|
}
|
|
|
|
class _SettingsListSliderState extends State<SettingsListSlider> {
|
|
int? _previousStep;
|
|
|
|
void _handleChanged(double newValue) {
|
|
if (widget.divisions != null) {
|
|
final step =
|
|
((newValue - widget.min) /
|
|
(widget.max - widget.min) *
|
|
widget.divisions!)
|
|
.round();
|
|
if (_previousStep != null && step != _previousStep) {
|
|
HapticFeedback.selectionClick();
|
|
}
|
|
_previousStep = step;
|
|
}
|
|
widget.onChanged?.call(newValue);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListTile(
|
|
title: Row(
|
|
children: [
|
|
Text(widget.title),
|
|
const Spacer(),
|
|
Text(
|
|
widget.trailing,
|
|
style: const TextStyle(
|
|
fontFeatures: [FontFeature.tabularFigures()],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
subtitle: Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Slider.adaptive(
|
|
value: widget.value,
|
|
min: widget.min,
|
|
max: widget.max,
|
|
divisions: widget.divisions,
|
|
onChanged:
|
|
widget.onChanged != null ? _handleChanged : null,
|
|
),
|
|
if (widget.subtitle != null) Text(widget.subtitle!),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|