Add SRS app: results, shops, orders and users, wired to srs-api
Flutter web/PWA for the lottery agency, built on Riverpod and go_router,
talking to srs-api over HTTP (HttpApi, token kept in SharedPreferences).
- Results, Shops, Orders and Users tabs (Users for admins only), with
phone, tablet and desktop layouts and shared widgets and theme tokens
- Users: list with search and Active/Inactive filters; user detail with
shops, sign-in details, and an Access card
- WhatsApp results switch on each user (sendResults on
PATCH /api/users/:id). Shows the WhatsApp logo (Simple Icons, CC0,
drawn with flutter_svg), green only when results will really go out.
Can't be turned on without a phone number ("Add a phone number first"
opens the phone sheet), and is paused while the user is inactive. The
users list marks people who get results with the logo.
- Each Access switch has a one-line description; the explanation
paragraphs are gone
- Remove demo mode: the app always uses the real AP
test/support and is used only by tests.
123 lines
3.1 KiB
Dart
123 lines
3.1 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'api.dart';
|
|
import 'format.dart';
|
|
import 'models.dart';
|
|
|
|
final apiProvider = Provider<Api>((ref) => throw UnimplementedError());
|
|
|
|
final startDateProvider = Provider<DateTime>(
|
|
(ref) => throw UnimplementedError(),
|
|
);
|
|
|
|
class DataVersion extends Notifier<int> {
|
|
@override
|
|
int build() => 0;
|
|
void bump() => state++;
|
|
}
|
|
|
|
final dataVersionProvider = NotifierProvider<DataVersion, int>(DataVersion.new);
|
|
|
|
class SessionNotifier extends Notifier<AppUser?> {
|
|
SessionNotifier(this.initial);
|
|
final AppUser? initial;
|
|
|
|
@override
|
|
AppUser? build() {
|
|
final sub = ref.read(apiProvider).sessionEnded.listen((_) => _end());
|
|
ref.onDispose(sub.cancel);
|
|
return initial;
|
|
}
|
|
|
|
Future<void> login(String username, String password) async {
|
|
final api = ref.read(apiProvider);
|
|
final u = await api.login(username, password);
|
|
state = u;
|
|
try {
|
|
ref.read(selectedDateProvider.notifier).set(await api.latestDrawDate());
|
|
} on ApiException {// ignore: empty_catches
|
|
}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
ref.read(apiProvider).logout();
|
|
_end();
|
|
}
|
|
|
|
void _end() {
|
|
state = null;
|
|
ref.invalidate(selectedDateProvider);
|
|
}
|
|
}
|
|
|
|
final sessionProvider = NotifierProvider<SessionNotifier, AppUser?>(
|
|
() => SessionNotifier(null),
|
|
);
|
|
|
|
class SelectedDate extends Notifier<DateTime> {
|
|
@override
|
|
DateTime build() => ref.read(startDateProvider);
|
|
void set(DateTime d) => state = dateOnly(d);
|
|
}
|
|
|
|
final selectedDateProvider = NotifierProvider<SelectedDate, DateTime>(
|
|
SelectedDate.new,
|
|
);
|
|
|
|
final resultsProvider = FutureProvider.family<DrawResults, DateTime>((ref, d) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).results(d);
|
|
});
|
|
|
|
final shopsProvider = FutureProvider.family<List<ShopDay>, DateTime>((ref, d) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).shops(d);
|
|
});
|
|
|
|
final shopDetailProvider = FutureProvider.family<ShopDetail, (int, DateTime)>((
|
|
ref,
|
|
k,
|
|
) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).shopDetail(k.$1, k.$2);
|
|
});
|
|
|
|
final ordersProvider = FutureProvider.family<List<Order>, DateTime>((ref, d) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).orders(d);
|
|
});
|
|
|
|
final orderProvider = FutureProvider.family<Order, int>((ref, id) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).order(id);
|
|
});
|
|
|
|
final monthProvider = FutureProvider.family<List<DrawInfo>, (int, int)>((
|
|
ref,
|
|
k,
|
|
) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).month(k.$1, k.$2);
|
|
});
|
|
|
|
final activeShopsProvider = FutureProvider<List<Shop>>((ref) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).activeShops();
|
|
});
|
|
|
|
final usersProvider = FutureProvider<List<AppUser>>((ref) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).users();
|
|
});
|
|
|
|
final userProvider = FutureProvider.family<AppUser, int>((ref, id) {
|
|
ref.watch(dataVersionProvider);
|
|
return ref.read(apiProvider).user(id);
|
|
});
|
|
|
|
Future<T> mutate<T>(WidgetRef ref, Future<T> Function() fn) async {
|
|
final r = await fn();
|
|
ref.read(dataVersionProvider.notifier).bump();
|
|
return r;
|
|
}
|