fix(admin): enable touch interaction on TV slider controls

This commit is contained in:
dwindown
2026-04-02 05:52:05 +07:00
parent c8d33f0ca0
commit 4a4fea6c38

View File

@@ -3238,6 +3238,18 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
'Offset Hijriah ${_hijriOffsetDays >= 0 ? '+' : ''}$_hijriOffsetDays hari tersimpan', 'Offset Hijriah ${_hijriOffsetDays >= 0 ? '+' : ''}$_hijriOffsetDays hari tersimpan',
); );
}, },
onProgressChanged: (nextProgress) {
final mapped = (minOffset + ((maxOffset - minOffset) * nextProgress)).round();
final clamped = mapped.clamp(minOffset, maxOffset).toInt();
if (clamped == _hijriOffsetDays) return;
setState(() {
_hijriOffsetDays = clamped;
});
_queueJadwalAutoSave(
message:
'Offset Hijriah ${_hijriOffsetDays >= 0 ? '+' : ''}$_hijriOffsetDays hari tersimpan',
);
},
); );
} }
@@ -3361,6 +3373,8 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
}) { }) {
final value = _parseCtrlInt(controller, fallback); final value = _parseCtrlInt(controller, fallback);
final valueLabel = suffix.isEmpty ? '$value' : '$value $suffix'; final valueLabel = suffix.isEmpty ? '$value' : '$value $suffix';
final denominator = max - min;
final progress = denominator <= 0 ? 0.0 : ((value - min) / denominator);
return _scrollAware( return _scrollAware(
controller: _scrollControllerForTab(_selectedTab), controller: _scrollControllerForTab(_selectedTab),
@@ -3369,7 +3383,7 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
focusNode: focusNode, focusNode: focusNode,
label: label, label: label,
valueLabel: valueLabel, valueLabel: valueLabel,
progress: ((value - min) / (max - min)).clamp(0.0, 1.0), progress: progress.clamp(0.0, 1.0),
helperText: 'Tekan OK untuk mulai ubah. Saat aktif, gunakan ← → untuk menyesuaikan nilai.', helperText: 'Tekan OK untuk mulai ubah. Saat aktif, gunakan ← → untuk menyesuaikan nilai.',
onMoveLeft: onMoveLeft, onMoveLeft: onMoveLeft,
onMoveUp: onMoveUp, onMoveUp: onMoveUp,
@@ -3394,6 +3408,19 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
); );
onValueChanged?.call(); onValueChanged?.call();
}, },
onProgressChanged: denominator <= 0
? null
: (nextProgress) {
final mapped = (min + ((max - min) * nextProgress))
.round()
.clamp(min, max)
.toInt();
if (mapped == _parseCtrlInt(controller, fallback)) return;
setState(() {
controller.text = mapped.toString();
});
onValueChanged?.call();
},
), ),
); );
} }
@@ -3410,6 +3437,7 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
VoidCallback? onMoveDown, VoidCallback? onMoveDown,
required VoidCallback onIncrement, required VoidCallback onIncrement,
required VoidCallback onDecrement, required VoidCallback onDecrement,
ValueChanged<double>? onProgressChanged,
}) { }) {
return _TvAdjustTile( return _TvAdjustTile(
scale: s, scale: s,
@@ -3423,6 +3451,7 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
onMoveDown: onMoveDown, onMoveDown: onMoveDown,
onIncrement: onIncrement, onIncrement: onIncrement,
onDecrement: onDecrement, onDecrement: onDecrement,
onProgressChanged: onProgressChanged,
); );
} }
@@ -3524,6 +3553,11 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
onMoveDown: onMoveDown, onMoveDown: onMoveDown,
onIncrement: () => onChanged((value + step).clamp(0.5, 2.0)), onIncrement: () => onChanged((value + step).clamp(0.5, 2.0)),
onDecrement: () => onChanged((value - step).clamp(0.5, 2.0)), onDecrement: () => onChanged((value - step).clamp(0.5, 2.0)),
onProgressChanged: (nextProgress) {
final mapped = (0.5 + (1.5 * nextProgress)).clamp(0.5, 2.0);
final snapped = (((mapped / step).round() * step).clamp(0.5, 2.0)).toDouble();
onChanged(snapped);
},
); );
} }
@@ -4185,6 +4219,7 @@ class _TvAdjustTile extends StatefulWidget {
final VoidCallback? onMoveDown; final VoidCallback? onMoveDown;
final VoidCallback onIncrement; final VoidCallback onIncrement;
final VoidCallback onDecrement; final VoidCallback onDecrement;
final ValueChanged<double>? onProgressChanged;
const _TvAdjustTile({ const _TvAdjustTile({
required this.scale, required this.scale,
@@ -4198,6 +4233,7 @@ class _TvAdjustTile extends StatefulWidget {
this.onMoveDown, this.onMoveDown,
required this.onIncrement, required this.onIncrement,
required this.onDecrement, required this.onDecrement,
this.onProgressChanged,
}); });
@override @override
@@ -4243,6 +4279,12 @@ class _TvAdjustTileState extends State<_TvAdjustTile> {
FocusNode get _focusNode => widget.focusNode ?? _fallbackFocusNode; FocusNode get _focusNode => widget.focusNode ?? _fallbackFocusNode;
void _updateFromTouchPosition(double x, double width) {
if (widget.onProgressChanged == null || width <= 0) return;
final normalized = (x / width).clamp(0.0, 1.0);
widget.onProgressChanged!(normalized);
}
@override @override
void dispose() { void dispose() {
if (widget.focusNode == null) { if (widget.focusNode == null) {
@@ -4385,72 +4427,114 @@ class _TvAdjustTileState extends State<_TvAdjustTile> {
SizedBox(height: 14 * s), SizedBox(height: 14 * s),
Row( Row(
children: [ children: [
Container( GestureDetector(
width: 36 * s, onTap: () {
height: 36 * s, _focusNode.requestFocus();
alignment: Alignment.center, widget.onDecrement();
decoration: BoxDecoration( },
color: _isEditing
? SacredColors.surfaceContainerHigh
: SacredColors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(SacredRadii.sm),
border: Border.all(
color: _isEditing
? SacredColors.primary.withValues(alpha: 0.8)
: SacredColors.outlineVariant.withValues(alpha: 0.35),
),
),
child: Text(
'',
style: GoogleFonts.manrope(
fontSize: 16 * s,
fontWeight: FontWeight.w800,
color: SacredColors.onSurface,
),
),
),
SizedBox(width: 12 * s),
Expanded(
child: Container( child: Container(
height: 6 * s, width: 36 * s,
height: 36 * s,
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: SacredColors.outlineVariant.withValues(alpha: 0.2), color: _isEditing
borderRadius: BorderRadius.circular(3 * s), ? SacredColors.surfaceContainerHigh
: SacredColors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(SacredRadii.sm),
border: Border.all(
color: _isEditing
? SacredColors.primary.withValues(alpha: 0.8)
: SacredColors.outlineVariant.withValues(alpha: 0.35),
),
), ),
child: FractionallySizedBox( child: Text(
alignment: Alignment.centerLeft, '',
widthFactor: widget.progress.clamp(0.0, 1.0), style: GoogleFonts.manrope(
child: Container( fontSize: 16 * s,
decoration: BoxDecoration( fontWeight: FontWeight.w800,
color: SacredColors.primary, color: SacredColors.onSurface,
borderRadius: BorderRadius.circular(3 * s),
),
), ),
), ),
), ),
), ),
SizedBox(width: 12 * s), SizedBox(width: 12 * s),
Container( Expanded(
width: 36 * s, child: LayoutBuilder(
height: 36 * s, builder: (context, constraints) {
alignment: Alignment.center, final barWidth = constraints.maxWidth;
decoration: BoxDecoration( return GestureDetector(
color: _isEditing behavior: HitTestBehavior.opaque,
? SacredColors.surfaceContainerHigh onTapDown: (details) {
: SacredColors.surfaceContainerHighest, _focusNode.requestFocus();
borderRadius: BorderRadius.circular(SacredRadii.sm), _updateFromTouchPosition(details.localPosition.dx, barWidth);
border: Border.all( },
color: _isEditing onHorizontalDragStart: widget.onProgressChanged == null
? SacredColors.primary.withValues(alpha: 0.8) ? null
: SacredColors.outlineVariant.withValues(alpha: 0.35), : (_) {
), _focusNode.requestFocus();
setState(() => _isEditing = true);
},
onHorizontalDragUpdate: widget.onProgressChanged == null
? null
: (details) => _updateFromTouchPosition(
details.localPosition.dx,
barWidth,
),
onHorizontalDragEnd: widget.onProgressChanged == null
? null
: (_) => setState(() => _isEditing = false),
onHorizontalDragCancel: widget.onProgressChanged == null
? null
: () => setState(() => _isEditing = false),
child: Container(
height: 6 * s,
decoration: BoxDecoration(
color: SacredColors.outlineVariant.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(3 * s),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: widget.progress.clamp(0.0, 1.0),
child: Container(
decoration: BoxDecoration(
color: SacredColors.primary,
borderRadius: BorderRadius.circular(3 * s),
),
),
),
),
);
},
), ),
child: Text( ),
'', SizedBox(width: 12 * s),
style: GoogleFonts.manrope( GestureDetector(
fontSize: 16 * s, onTap: () {
fontWeight: FontWeight.w800, _focusNode.requestFocus();
color: SacredColors.onSurface, widget.onIncrement();
},
child: Container(
width: 36 * s,
height: 36 * s,
alignment: Alignment.center,
decoration: BoxDecoration(
color: _isEditing
? SacredColors.surfaceContainerHigh
: SacredColors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(SacredRadii.sm),
border: Border.all(
color: _isEditing
? SacredColors.primary.withValues(alpha: 0.8)
: SacredColors.outlineVariant.withValues(alpha: 0.35),
),
),
child: Text(
'',
style: GoogleFonts.manrope(
fontSize: 16 * s,
fontWeight: FontWeight.w800,
color: SacredColors.onSurface,
),
), ),
), ),
), ),