129 lines
3.5 KiB
Dart
129 lines
3.5 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
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>>;
|
|
|
|
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';
|
|
|
|
SharedPreferences get _prefs => ref.read(prefsProvider);
|
|
|
|
@override
|
|
AvailabilityMap build() {
|
|
final raw = _prefs.getStringList(_key) ?? const <String>[];
|
|
final map = <String, Set<DayBlock>>{};
|
|
for (final entry in raw) {
|
|
// Stored as "2026-08-14:morning,evening".
|
|
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;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
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(',')}',
|
|
]);
|
|
state = map;
|
|
}
|
|
|
|
Set<DayBlock> blocksFor(DateTime day) => state[dayKey(day)] ?? <DayBlock>{};
|
|
|
|
bool isDayOff(DateTime day) => blocksFor(day).isEmpty;
|
|
|
|
void toggleBlock(DateTime day, DayBlock block) {
|
|
final map = {
|
|
for (final e in state.entries) e.key: {...e.value},
|
|
};
|
|
final key = dayKey(day);
|
|
final blocks = map[key] ?? <DayBlock>{};
|
|
if (!blocks.remove(block)) blocks.add(block);
|
|
if (blocks.isEmpty) {
|
|
map.remove(key);
|
|
} else {
|
|
map[key] = blocks;
|
|
}
|
|
_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},
|
|
};
|
|
final key = dayKey(day);
|
|
if (available) {
|
|
map[key] = DayBlock.values.toSet();
|
|
} else {
|
|
map.remove(key);
|
|
}
|
|
_persist(map);
|
|
}
|
|
|
|
/// Presets across the visible week: all days, weekdays only, weekends only.
|
|
void applyPreset(
|
|
List<DateTime> week, {
|
|
required bool Function(DateTime) test,
|
|
}) {
|
|
final map = {
|
|
for (final e in state.entries) e.key: {...e.value},
|
|
};
|
|
for (final day in week) {
|
|
final key = dayKey(day);
|
|
if (test(day)) {
|
|
map[key] = DayBlock.values.toSet();
|
|
} else {
|
|
map.remove(key);
|
|
}
|
|
}
|
|
_persist(map);
|
|
}
|
|
|
|
void clearWeek(List<DateTime> week) {
|
|
final map = {
|
|
for (final e in state.entries) e.key: {...e.value},
|
|
};
|
|
for (final day in week) {
|
|
map.remove(dayKey(day));
|
|
}
|
|
_persist(map);
|
|
}
|
|
}
|
|
|
|
final availabilityProvider =
|
|
NotifierProvider<AvailabilityController, AvailabilityMap>(
|
|
AvailabilityController.new,
|
|
);
|
|
|
|
/// How many days in the coming week have at least one block switched on.
|
|
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++;
|
|
}
|
|
return count;
|
|
});
|