refactor(legacy): remove remaining setState usages + tap-to-center on device banner

This commit is contained in:
2026-04-23 03:06:32 +02:00
parent 773312d5f9
commit 53edf0b7e1
5 changed files with 130 additions and 98 deletions

View File

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -18,28 +18,38 @@ class EditPeriodSheet extends ConsumerStatefulWidget {
}
class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
late TimeOfDay _start;
late TimeOfDay _end;
late List<bool> _weekDays;
late final ValueNotifier<TimeOfDay> _start;
late final ValueNotifier<TimeOfDay> _end;
late final ValueNotifier<List<bool>> _weekDays;
@override
void initState() {
super.initState();
if (widget.initial != null) {
_start = _parseTime(widget.initial!.start);
_end = _parseTime(widget.initial!.end);
_weekDays = widget.initial!.week
.padRight(7, '0')
.split('')
.map((c) => c == '1')
.toList();
_start = ValueNotifier(_parseTime(widget.initial!.start));
_end = ValueNotifier(_parseTime(widget.initial!.end));
_weekDays = ValueNotifier(
widget.initial!.week
.padRight(7, '0')
.split('')
.map((c) => c == '1')
.toList(),
);
} else {
_start = const TimeOfDay(hour: 22, minute: 0);
_end = const TimeOfDay(hour: 7, minute: 0);
_weekDays = [true, true, true, true, true, true, true];
_start = ValueNotifier(const TimeOfDay(hour: 22, minute: 0));
_end = ValueNotifier(const TimeOfDay(hour: 7, minute: 0));
_weekDays = ValueNotifier([true, true, true, true, true, true, true]);
}
}
@override
void dispose() {
_start.dispose();
_end.dispose();
_weekDays.dispose();
super.dispose();
}
TimeOfDay _parseTime(String time) {
final parts = time.split(':');
return TimeOfDay(
@@ -54,13 +64,13 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
String _formatForDisplay(TimeOfDay t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
String _weekToString() => _weekDays.map((d) => d ? '1' : '0').join();
String _weekToString(List<bool> days) =>
days.map((d) => d ? '1' : '0').join();
@override
Widget build(BuildContext context) {
final primaryColor = context.sfColors.legacyPrimary;
final isEditing = widget.initial != null;
final hasSelectedDays = _weekDays.any((d) => d);
return Padding(
padding: EdgeInsets.only(
@@ -89,32 +99,38 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
Row(
children: [
Expanded(
child: _TimePickerTile(
label: context.translate(I18n.doNotDisturbStart),
value: _formatForDisplay(_start),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: _start,
);
if (picked != null) setState(() => _start = picked);
},
child: ValueListenableBuilder<TimeOfDay>(
valueListenable: _start,
builder: (context, start, _) => _TimePickerTile(
label: context.translate(I18n.doNotDisturbStart),
value: _formatForDisplay(start),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: start,
);
if (picked != null) _start.value = picked;
},
),
),
),
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
Expanded(
child: _TimePickerTile(
label: context.translate(I18n.doNotDisturbEnd),
value: _formatForDisplay(_end),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: _end,
);
if (picked != null) setState(() => _end = picked);
},
child: ValueListenableBuilder<TimeOfDay>(
valueListenable: _end,
builder: (context, end, _) => _TimePickerTile(
label: context.translate(I18n.doNotDisturbEnd),
value: _formatForDisplay(end),
color: primaryColor,
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: end,
);
if (picked != null) _end.value = picked;
},
),
),
),
],
@@ -129,29 +145,41 @@ class _EditPeriodSheetState extends ConsumerState<EditPeriodSheet> {
),
),
SizedBox(height: SizeUtils.getByScreen(small: 10, big: 8)),
WeekDayRow(
week: _weekToString(),
activeColor: primaryColor,
readOnly: false,
onToggle: (index) =>
setState(() => _weekDays[index] = !_weekDays[index]),
ValueListenableBuilder<List<bool>>(
valueListenable: _weekDays,
builder: (context, days, _) => WeekDayRow(
week: _weekToString(days),
activeColor: primaryColor,
readOnly: false,
onToggle: (index) {
final updated = List<bool>.from(days);
updated[index] = !updated[index];
_weekDays.value = updated;
},
),
),
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 20)),
PrimaryButton(
onPressed: hasSelectedDays
? () {
Navigator.pop(
context,
DoNotDisturbPeriod(
start: _formatForApi(_start),
end: _formatForApi(_end),
week: _weekToString(),
),
);
}
: null,
text: context.translate(I18n.doNotDisturbConfirm),
color: primaryColor,
ValueListenableBuilder<List<bool>>(
valueListenable: _weekDays,
builder: (context, days, _) {
final hasSelectedDays = days.any((d) => d);
return PrimaryButton(
onPressed: hasSelectedDays
? () {
Navigator.pop(
context,
DoNotDisturbPeriod(
start: _formatForApi(_start.value),
end: _formatForApi(_end.value),
week: _weekToString(days),
),
);
}
: null,
text: context.translate(I18n.doNotDisturbConfirm),
color: primaryColor,
);
},
),
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
],

View File

@@ -44,20 +44,14 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.activity?.name ?? '');
_nameController.addListener(_onNameChanged);
}
@override
void dispose() {
_nameController.removeListener(_onNameChanged);
_nameController.dispose();
super.dispose();
}
void _onNameChanged() {
if (mounted) setState(() {});
}
Future<void> _submit({
required ActivityFormState formState,
required List<ScheduledActivityEntity> currentActivities,
@@ -110,9 +104,6 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
: (ref.watch(scheduledActivitiesProvider(device.id)).value ??
const []);
final canSubmit = _nameController.text.trim().isNotEmpty &&
formState.isTimeValid;
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
return Padding(
@@ -209,30 +200,39 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
SizedBox(height: SizeUtils.getByScreen(small: 24, big: 22)),
SizedBox(
height: SizeUtils.getByScreen(small: 48, big: 46),
child: ElevatedButton(
onPressed: canSubmit
? () => _submit(
formState: formState,
currentActivities: currentActivities,
)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
disabledBackgroundColor: Colors.grey[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
child: AnimatedBuilder(
animation: _nameController,
builder: (context, _) {
final canSubmit =
_nameController.text.trim().isNotEmpty &&
formState.isTimeValid;
return ElevatedButton(
onPressed: canSubmit
? () => _submit(
formState: formState,
currentActivities: currentActivities,
)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
disabledBackgroundColor: Colors.grey[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
SizeUtils.getByScreen(small: 12, big: 10),
),
),
),
),
),
child: Text(
context.translate(I18n.save),
style: TextStyle(
color: canSubmit ? Colors.white : Colors.grey,
fontSize: SizeUtils.getByScreen(small: 16, big: 15),
fontWeight: FontWeight.w600,
),
),
child: Text(
context.translate(I18n.save),
style: TextStyle(
color: canSubmit ? Colors.white : Colors.grey,
fontSize:
SizeUtils.getByScreen(small: 16, big: 15),
fontWeight: FontWeight.w600,
),
),
);
},
),
),
],

View File

@@ -85,7 +85,6 @@ class _LegacyOtpCodeFieldsState extends State<LegacyOtpCodeFields> {
_focusNodes[nextIndex].requestFocus();
_emit();
setState(() {});
}
void _onChanged(int index, String value) {
@@ -101,7 +100,6 @@ class _LegacyOtpCodeFieldsState extends State<LegacyOtpCodeFields> {
}
_emit();
setState(() {});
}
KeyEventResult _onKey(int index, KeyEvent event) {
@@ -114,7 +112,6 @@ class _LegacyOtpCodeFieldsState extends State<LegacyOtpCodeFields> {
_controllers[index - 1].text = '';
_focusNodes[index - 1].requestFocus();
_emit();
setState(() {});
return KeyEventResult.handled;
}
}

View File

@@ -11,6 +11,7 @@ class DeviceBanner extends ConsumerStatefulWidget {
final List<DeviceEntity> devices;
final List<PositionEntity> positions;
final ValueChanged<DeviceEntity> onDeviceChanged;
final VoidCallback? onTap;
const DeviceBanner({
super.key,
@@ -18,6 +19,7 @@ class DeviceBanner extends ConsumerStatefulWidget {
required this.devices,
required this.positions,
required this.onDeviceChanged,
this.onTap,
});
@override
@@ -94,10 +96,14 @@ class _DeviceBannerState extends ConsumerState<DeviceBanner> {
final pos = widget.positions
.where((p) => p.deviceIdentificator == dev.identificator)
.firstOrNull;
return _DeviceCard(
device: dev,
position: pos,
primaryColor: primaryColor,
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: widget.onTap,
child: _DeviceCard(
device: dev,
position: pos,
primaryColor: primaryColor,
),
);
},
),

View File

@@ -496,6 +496,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
devices: widget.devices,
positions: widget.positions,
onDeviceChanged: widget.onDeviceChanged,
onTap: _centerOnDevice,
),
),
];