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 get _cacheFile async { final directory = await getApplicationCacheDirectory(); return File("${directory.path}/cookies.txt").create(); } Future clear() async { (await _cacheFile).writeAsString("", mode: FileMode.write); } Future save(String token) async { (await _cacheFile).writeAsString(token, mode: FileMode.write); } Future> 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> loginUser(String email, String password) async { final loginResult = await server.login(email, password); switch (loginResult) { case Ok(value: final sessionToken): await cookieController.save(sessionToken); notifySessionChangeListeners(); return const Ok(null); case Err(value: final message): return Err(message); } } Future> loadCachedUser() async { switch (await cookieController.load()) { case Ok(): return _loadCurrentUser(); case Err(): notifyUserChangeListeners(); return const Err(null); } } Future> loadUpdatedUser() async { return _loadCurrentUser(); } Future> _loadCurrentUser() async { final sessionUserResult = await requestWithSession( (server, sessionToken) => server.sessionUser(sessionToken)); switch (sessionUserResult) { case Ok(value: final sessionUser): _sessionUser = sessionUser; notifyUserChangeListeners(); return const Ok(null); case Err(): return const Err(null); } } Future logout() async { switch (await cookieController.load()) { case Ok(value: final sessionToken): await server.logout(sessionToken); await cookieController.clear(); _sessionUser = null; notifySessionChangeListeners(); case Err(): notifySessionChangeListeners(); return; } } User get user { final user = _sessionUser; if (user == null) { throw NoUser(); } return user; } bool get hasUser { return _sessionUser != null; } Future> addBalance() async { final addBalanceResult = await requestWithSession( (server, sessionToken) => server.addBalance(sessionToken)); if (addBalanceResult case Err(value: final message)) { return Err(message); } if (await _loadCurrentUser() case Err()) { return const Err("could not fetch user"); } return const Ok(null); } /// Package private. Future> requestWithSession( Future> Function(Server server, String sessionToken) func) async { switch (await cookieController.load()) { case Err(): return const Err("unathorized"); case Ok(value: final sessionToken): final result = await func(server, sessionToken); if (result case Err(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(); } }