195 lines
5.2 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart';
import 'package:path_provider/path_provider.dart';
class CookieController {
CookieController();
static Future<File> get _cacheFile async {
final directory = await getApplicationCacheDirectory();
return File("${directory.path}/cookies.txt").create();
}
Future<void> clear() async {
(await _cacheFile).writeAsString("", mode: FileMode.write);
}
Future<void> save(String token) async {
(await _cacheFile).writeAsString(token, mode: FileMode.write);
}
Future<Result<String, Null>> load() async {
final token = await (await _cacheFile).readAsString();
if (token.isEmpty) {
return const Err(null);
}
return Ok(token);
}
}
class SessionController {
final Server server;
final CookieController cookieController = CookieController();
User? _sessionUser;
final List<_ChangeListener> _sessionChangeListeners = [];
final List<_ChangeListener> _userChangeListeners = [];
SessionController({required this.server});
Future<Result<Null, String>> loginUser(String email, String password) async {
final loginResult = await server.login(email, password);
switch (loginResult) {
case Ok<String, String>(value: final sessionToken):
await cookieController.save(sessionToken);
notifySessionChangeListeners();
return const Ok(null);
case Err<String, String>(value: final message):
return Err(message);
}
}
Future<Result<Null, Null>> loadCachedUser() async {
switch (await cookieController.load()) {
case Ok<String, Null>():
return _loadCurrentUser();
case Err<String, Null>():
notifyUserChangeListeners();
return const Err(null);
}
}
Future<Result<Null, Null>> loadUpdatedUser() async {
return _loadCurrentUser();
}
Future<Result<Null, Null>> _loadCurrentUser() async {
final sessionUserResult = await requestWithSession<User>(
(server, sessionToken) => server.sessionUser(sessionToken));
switch (sessionUserResult) {
case Ok<User, String>(value: final sessionUser):
_sessionUser = sessionUser;
notifyUserChangeListeners();
return const Ok(null);
case Err<User, String>():
return const Err(null);
}
}
Future<Null> logout() async {
switch (await cookieController.load()) {
case Ok<String, Null>(value: final sessionToken):
await server.logout(sessionToken);
await cookieController.clear();
_sessionUser = null;
notifySessionChangeListeners();
case Err<String, Null>():
notifySessionChangeListeners();
return;
}
}
User get user {
final user = _sessionUser;
if (user == null) {
throw NoUser();
}
return user;
}
bool get hasUser {
return _sessionUser != null;
}
Future<Result<Null, String>> addBalance() async {
final addBalanceResult = await requestWithSession(
(server, sessionToken) => server.addBalance(sessionToken));
if (addBalanceResult case Err<Null, String>(value: final message)) {
return Err(message);
}
if (await _loadCurrentUser() case Err<Null, Null>()) {
return const Err("could not fetch user");
}
return const Ok(null);
}
/// Package private.
Future<Result<T, String>> requestWithSession<T>(
Future<Result<T, String>> Function(Server server, String sessionToken)
func) async {
switch (await cookieController.load()) {
case Err<String, Null>():
return const Err("unathorized");
case Ok<String, Null>(value: final sessionToken):
final result = await func(server, sessionToken);
if (result case Err<T, String>(value: final message)) {
if (message == "unauthorized") {
cookieController.clear();
_sessionUser = null;
notifySessionChangeListeners();
notifyUserChangeListeners();
return const Err("unathorized");
}
}
return result;
}
}
void notifySessionChangeListeners() {
for (final listener in _sessionChangeListeners) {
listener.notify();
}
}
void notifyUserChangeListeners() {
for (final listener in _userChangeListeners) {
listener.notify();
}
}
void _addSessionChangeListener(_ChangeListener listener) {
_sessionChangeListeners.add(listener);
}
void _addUserChangeListener(_ChangeListener listener) {
_userChangeListeners.add(listener);
}
}
abstract class _ChangeListener {
void notify();
}
class NoUser implements Exception {}
class SessionProvider extends ChangeNotifier implements _ChangeListener {
final SessionController controller;
SessionProvider({required this.controller}) {
controller._addSessionChangeListener(this);
}
@override
void notify() {
notifyListeners();
}
}
class CurrentUserProvider extends ChangeNotifier implements _ChangeListener {
final SessionController controller;
CurrentUserProvider({required this.controller}) {
controller._addUserChangeListener(this);
}
@override
void notify() {
notifyListeners();
}
}