fixed designs and flow
This commit is contained in:
@@ -4,94 +4,107 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/availability.dart';
|
||||
import 'session_controller.dart';
|
||||
|
||||
/// Availability is stored as `yyyy-mm-dd` -> the blocks switched on that day.
|
||||
typedef AvailabilityMap = Map<String, Set<DayBlock>>;
|
||||
/// Availability is stored as `yyyy-mm-dd` -> what the worker said about it.
|
||||
///
|
||||
/// Days the worker has not answered are simply absent, and read back as
|
||||
/// unavailable: nobody is on the hook for a day they never opened.
|
||||
typedef AvailabilityMap = Map<String, DayPlan>;
|
||||
|
||||
String dayKey(DateTime d) =>
|
||||
'${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
|
||||
|
||||
class AvailabilityController extends Notifier<AvailabilityMap> {
|
||||
static const _key = 'availability';
|
||||
static const _key = 'availability_v2';
|
||||
|
||||
SharedPreferences get _prefs => ref.read(prefsProvider);
|
||||
|
||||
@override
|
||||
AvailabilityMap build() {
|
||||
final raw = _prefs.getStringList(_key) ?? const <String>[];
|
||||
final map = <String, Set<DayBlock>>{};
|
||||
final map = <String, DayPlan>{};
|
||||
for (final entry in raw) {
|
||||
// Stored as "2026-08-14:morning,evening".
|
||||
// Stored as "2026-10-05:prefers:9-17", or "…:available", or
|
||||
// "…:prefers:any" when no hours were given.
|
||||
final parts = entry.split(':');
|
||||
if (parts.length != 2) continue;
|
||||
final blocks = parts[1]
|
||||
.split(',')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.map(
|
||||
(name) => DayBlock.values.firstWhere(
|
||||
(b) => b.name == name,
|
||||
orElse: () => DayBlock.morning,
|
||||
),
|
||||
)
|
||||
.toSet();
|
||||
if (blocks.isNotEmpty) map[parts[0]] = blocks;
|
||||
if (parts.length < 2) continue;
|
||||
final status = DayAvailability.values.firstWhere(
|
||||
(s) => s.name == parts[1],
|
||||
orElse: () => DayAvailability.unavailable,
|
||||
);
|
||||
if (status == DayAvailability.unavailable) continue;
|
||||
map[parts[0]] = DayPlan(
|
||||
status: status,
|
||||
window: parts.length > 2 ? _parseWindow(parts[2]) : PreferredWindow.any,
|
||||
);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
static PreferredWindow _parseWindow(String raw) {
|
||||
if (raw == 'any') return PreferredWindow.any;
|
||||
final ends = raw.split('-');
|
||||
if (ends.length != 2) return PreferredWindow.any;
|
||||
return PreferredWindow(
|
||||
from: int.tryParse(ends[0]),
|
||||
to: int.tryParse(ends[1]),
|
||||
);
|
||||
}
|
||||
|
||||
static String _encodeWindow(PreferredWindow w) =>
|
||||
w.isAnyTime ? 'any' : '${w.from ?? ''}-${w.to ?? ''}';
|
||||
|
||||
void _persist(AvailabilityMap map) {
|
||||
_prefs.setStringList(_key, [
|
||||
for (final e in map.entries)
|
||||
if (e.value.isNotEmpty)
|
||||
'${e.key}:${e.value.map((b) => b.name).join(',')}',
|
||||
if (e.value.isSet)
|
||||
'${e.key}:${e.value.status.name}:${_encodeWindow(e.value.window)}',
|
||||
]);
|
||||
state = map;
|
||||
}
|
||||
|
||||
Set<DayBlock> blocksFor(DateTime day) => state[dayKey(day)] ?? <DayBlock>{};
|
||||
AvailabilityMap get _copy => {for (final e in state.entries) e.key: e.value};
|
||||
|
||||
bool isDayOff(DateTime day) => blocksFor(day).isEmpty;
|
||||
DayPlan planFor(DateTime day) => state[dayKey(day)] ?? const DayPlan();
|
||||
|
||||
void toggleBlock(DateTime day, DayBlock block) {
|
||||
final map = {
|
||||
for (final e in state.entries) e.key: {...e.value},
|
||||
};
|
||||
bool isDayOff(DateTime day) => !planFor(day).isSet;
|
||||
|
||||
void setStatus(DateTime day, DayAvailability status) {
|
||||
final map = _copy;
|
||||
final key = dayKey(day);
|
||||
final blocks = map[key] ?? <DayBlock>{};
|
||||
if (!blocks.remove(block)) blocks.add(block);
|
||||
if (blocks.isEmpty) {
|
||||
if (status == DayAvailability.unavailable) {
|
||||
map.remove(key);
|
||||
} else {
|
||||
map[key] = blocks;
|
||||
// The window rides along, so a worker who flips to Unavailable and back
|
||||
// does not have to set their hours a second time.
|
||||
map[key] = (map[key] ?? const DayPlan()).copyWith(status: status);
|
||||
}
|
||||
_persist(map);
|
||||
}
|
||||
|
||||
/// The day toggle: all three blocks on, or the day cleared.
|
||||
void setDayAvailable(DateTime day, bool available) {
|
||||
final map = {
|
||||
for (final e in state.entries) e.key: {...e.value},
|
||||
};
|
||||
void setWindow(DateTime day, PreferredWindow window) {
|
||||
final key = dayKey(day);
|
||||
if (available) {
|
||||
map[key] = DayBlock.values.toSet();
|
||||
} else {
|
||||
map.remove(key);
|
||||
}
|
||||
final current = state[key];
|
||||
// A window without a status to hang it on is not a day the worker has
|
||||
// answered, so there is nothing to store.
|
||||
if (current == null) return;
|
||||
final map = _copy..[key] = current.copyWith(window: window);
|
||||
_persist(map);
|
||||
}
|
||||
|
||||
/// Presets across the visible week: all days, weekdays only, weekends only.
|
||||
/// Days outside the preset are cleared, which is what makes the four
|
||||
/// buttons read as four answers rather than four additive filters.
|
||||
void applyPreset(
|
||||
List<DateTime> week, {
|
||||
required bool Function(DateTime) test,
|
||||
}) {
|
||||
final map = {
|
||||
for (final e in state.entries) e.key: {...e.value},
|
||||
};
|
||||
final map = _copy;
|
||||
for (final day in week) {
|
||||
final key = dayKey(day);
|
||||
if (test(day)) {
|
||||
map[key] = DayBlock.values.toSet();
|
||||
map[key] = (map[key] ?? const DayPlan()).copyWith(
|
||||
status: DayAvailability.available,
|
||||
);
|
||||
} else {
|
||||
map.remove(key);
|
||||
}
|
||||
@@ -100,9 +113,7 @@ class AvailabilityController extends Notifier<AvailabilityMap> {
|
||||
}
|
||||
|
||||
void clearWeek(List<DateTime> week) {
|
||||
final map = {
|
||||
for (final e in state.entries) e.key: {...e.value},
|
||||
};
|
||||
final map = _copy;
|
||||
for (final day in week) {
|
||||
map.remove(dayKey(day));
|
||||
}
|
||||
@@ -115,14 +126,13 @@ final availabilityProvider =
|
||||
AvailabilityController.new,
|
||||
);
|
||||
|
||||
/// How many days in the coming week have at least one block switched on.
|
||||
/// How many days in the coming week the worker has said yes to, either way.
|
||||
final daysSetProvider = Provider<int>((ref) {
|
||||
final map = ref.watch(availabilityProvider);
|
||||
final now = DateTime.now();
|
||||
var count = 0;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
final d = now.add(Duration(days: i));
|
||||
if ((map[dayKey(d)] ?? const <DayBlock>{}).isNotEmpty) count++;
|
||||
if (map[dayKey(now.add(Duration(days: i)))]?.isSet ?? false) count++;
|
||||
}
|
||||
return count;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user