Files
krow-worker-app/lib/state/profile_controller.dart
2026-09-17 22:26:11 +05:30

169 lines
4.8 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/compliance.dart';
import '../models/shift.dart';
import '../models/worker.dart';
import 'session_controller.dart';
import 'shifts_controller.dart';
class ProfileState {
const ProfileState({
required this.worker,
required this.settings,
required this.skills,
required this.contacts,
this.bank,
this.newsletter = false,
});
final Worker worker;
/// Settings the worker has worked in, and the trades they can do.
final Set<String> settings;
final Set<String> skills;
final List<EmergencyContact> contacts;
final BankAccount? bank;
final bool newsletter;
ProfileState copyWith({
Worker? worker,
Set<String>? settings,
Set<String>? skills,
List<EmergencyContact>? contacts,
BankAccount? bank,
bool? newsletter,
}) => ProfileState(
worker: worker ?? this.worker,
settings: settings ?? this.settings,
skills: skills ?? this.skills,
contacts: contacts ?? this.contacts,
bank: bank ?? this.bank,
newsletter: newsletter ?? this.newsletter,
);
}
class ProfileController extends Notifier<ProfileState> {
static const _kSettings = 'profile_settings';
static const _kSkills = 'profile_skills';
static const _kNewsletter = 'profile_newsletter';
static const _kName = 'profile_name';
SharedPreferences get _prefs => ref.read(prefsProvider);
@override
ProfileState build() {
final p = _prefs;
final phone = ref.watch(sessionProvider).phone;
return ProfileState(
worker: Worker(
name: p.getString(_kName) ?? 'Iliana Ortega',
role: 'Barista',
city: 'San Jose',
phone: phone ?? '+1 408 555 0148',
onTimePercent: 50,
noShows: 1,
cancellations: 1,
),
settings:
(p.getStringList(_kSettings) ??
const ['Hospitality', 'Events', 'Catering'])
.toSet(),
skills:
(p.getStringList(_kSkills) ??
const ['Barista', 'Customer service', 'Food service'])
.toSet(),
contacts: [
EmergencyContact(
name: 'Carlos Ortega',
phone: '+1 555 001 0006',
relationship: 'Spouse',
),
],
newsletter: p.getBool(_kNewsletter) ?? false,
);
}
void updateName(String name) {
_prefs.setString(_kName, name);
state = state.copyWith(worker: state.worker.copyWith(name: name));
}
void toggleSetting(String value) {
final next = {...state.settings};
if (!next.remove(value)) next.add(value);
_prefs.setStringList(_kSettings, next.toList());
state = state.copyWith(settings: next);
}
void toggleSkill(String value) {
final next = {...state.skills};
if (!next.remove(value)) next.add(value);
_prefs.setStringList(_kSkills, next.toList());
state = state.copyWith(skills: next);
}
void addSkill(String value) {
final trimmed = value.trim();
if (trimmed.isEmpty) return;
final next = {...state.skills, trimmed};
_prefs.setStringList(_kSkills, next.toList());
state = state.copyWith(skills: next);
}
void saveContact(EmergencyContact contact, {int? index}) {
final next = [...state.contacts];
if (index != null && index < next.length) {
next[index] = contact;
} else {
next.add(contact);
}
state = state.copyWith(contacts: next);
}
void removeContact(int index) {
final next = [...state.contacts]..removeAt(index);
state = state.copyWith(contacts: next);
}
void saveBank(BankAccount account) {
state = state.copyWith(bank: account);
}
void setNewsletter(bool value) {
_prefs.setBool(_kNewsletter, value);
state = state.copyWith(newsletter: value);
}
}
final profileProvider = NotifierProvider<ProfileController, ProfileState>(
ProfileController.new,
);
/// Live stats derived from the shift list, so the profile reflects what the
/// worker has actually done in this session.
final workerStatsProvider = Provider<Worker>((ref) {
final base = ref.watch(profileProvider).worker;
final shifts = ref.watch(shiftsProvider);
final completed = shifts
.where((s) => s.status == ShiftStatus.completed)
.toList();
final noShows = shifts.where((s) => s.status == ShiftStatus.noShow).length;
final cancels = shifts.where((s) => s.status == ShiftStatus.cancelled).length;
final onTime = completed
.where(
(s) =>
s.clockIn != null &&
!s.clockIn!.isAfter(s.start.add(const Duration(minutes: 5))),
)
.length;
return base.copyWith(
shiftsWorked: completed.length,
onTimePercent: completed.isEmpty
? 100
: (onTime * 100 / completed.length).round(),
noShows: noShows,
cancellations: cancels,
);
});