refactoring part one

This commit is contained in:
Mikkel Troels Kongsted 2025-03-17 15:37:16 +01:00
parent e7526452dd
commit a54d94d45e
11 changed files with 172 additions and 133 deletions

View File

@ -1,83 +0,0 @@
import 'package:flutter/material.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart';
class SessionController extends ChangeNotifier {
final Server server;
String? _sessionToken;
User? _user;
SessionController({required this.server});
Future<Result<Null, String>> login(String email, String password) async {
switch (await server.login(email, password)) {
case Success<String>(data: final token):
_sessionToken = token;
notifyListeners();
return const Ok(null);
case Error<String>(message: final message):
notifyListeners();
return Err(message);
}
}
Future<void> _validateToken() async {
final token = _sessionToken;
if (token == null) {
return;
}
final res = await server.sessionUser(token);
switch (res) {
case Success<User>():
return;
case Error<User>():
_sessionToken = null;
return;
}
}
User? get user {
loadUser();
return _user;
}
Future<void> _notifyIfTokenChanged() async {
final prev = _sessionToken;
_validateToken();
if (prev != _sessionToken) {
notifyListeners();
}
}
Future<void> loadUser() async {
final token = _sessionToken;
if (token == null) {
_user = null;
return;
}
final res = await server.sessionUser(token);
switch (res) {
case Success<User>(data: final user):
_user = user;
return;
case Error<User>():
_user = null;
return;
}
}
String? get sessionToken {
_notifyIfTokenChanged();
return _sessionToken;
}
Future<void> logout() async {
final token = _sessionToken;
if (token != null) {
await server.logout(token);
_sessionToken = null;
}
notifyListeners();
}
}

View File

@ -1,27 +1,120 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart'; import 'package:mobile/server/server.dart';
class UsersController extends ChangeNotifier { class UserController extends ChangeNotifier {
Server server; final Server server;
SessionController sessionController; String? _sessionToken;
User? _user;
UsersController({required this.server, required this.sessionController}); Future<Result<User, Null>> userLoad = Future.error(Null);
Future<Result<Null, String>> register( UserController({required this.server});
String name, String email, String password) async {
final res = await server.register(name, email, password); /// Make sure a user exists before calling using `.loadUser()`.
switch (res) { User get user {
case Success<Null>(): final user = _user;
if (user == null) {
throw NoUserExcept();
}
return user;
}
Future<Result<Null, Null>> loadUser() async {
if (_sessionToken == null) {
return const Err(null);
}
final userResult = await server.sessionUser(_sessionToken!);
switch (userResult) {
case Success<User>(data: final user):
_user = user;
return const Ok(null); return const Ok(null);
case Error<Null>(message: final message): case Error<User>():
return const Err(null);
}
}
Future<Result<Null, Null>> loadedUser() async {
throw Exception();
}
Future<Result<Null, String>> login(String email, String password) async {
switch (await server.login(email, password)) {
case Success<String>(data: final token):
_sessionToken = token;
notifyListeners();
return const Ok(null);
case Error<String>(message: final message):
notifyListeners();
return Err(message); return Err(message);
} }
} }
Future<void> _validateToken() async {
final token = _sessionToken;
if (token == null) {
return;
}
final res = await server.sessionUser(token);
switch (res) {
case Success<User>():
return;
case Error<User>():
_sessionToken = null;
return;
}
}
@Deprecated("Use 'user' instead.")
User? get userOld {
loadUserOld();
return _user;
}
Future<void> _notifyIfTokenChanged() async {
final prev = _sessionToken;
_validateToken();
if (prev != _sessionToken) {
notifyListeners();
}
}
@Deprecated("Use 'loadUser' instead.")
Future<void> loadUserOld() async {
final token = _sessionToken;
if (token == null) {
_user = null;
return;
}
final res = await server.sessionUser(token);
switch (res) {
case Success<User>(data: final user):
_user = user;
return;
case Error<User>():
_user = null;
return;
}
}
String? get sessionToken {
_notifyIfTokenChanged();
return _sessionToken;
}
Future<void> logout() async {
final token = _sessionToken;
if (token != null) {
await server.logout(token);
_sessionToken = null;
}
notifyListeners();
}
Future<Result<Null, String>> addBalance() async { Future<Result<Null, String>> addBalance() async {
final token = sessionController.sessionToken; final token = _sessionToken;
if (token == null) { if (token == null) {
return const Err("No token"); return const Err("No token");
} }
@ -35,3 +128,5 @@ class UsersController extends ChangeNotifier {
} }
} }
} }
class NoUserExcept implements Exception {}

View File

@ -0,0 +1,19 @@
import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart';
class UsersController {
Server server;
UsersController({required this.server});
Future<Result<Null, String>> register(
String name, String email, String password) async {
final res = await server.register(name, email, password);
switch (res) {
case Success<Null>():
return const Ok(null);
case Error<Null>(message: final message):
return Err(message);
}
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/pages/dashboard.dart'; import 'package:mobile/pages/dashboard.dart';
import 'package:mobile/pages/log_in_page.dart'; import 'package:mobile/pages/log_in_page.dart';
import 'package:mobile/controllers/add_to_cart_state.dart'; import 'package:mobile/controllers/add_to_cart_state.dart';
@ -9,25 +9,37 @@ import 'package:mobile/controllers/location_image.dart';
import 'package:mobile/controllers/paying_state.dart'; import 'package:mobile/controllers/paying_state.dart';
import 'package:mobile/controllers/product.dart'; import 'package:mobile/controllers/product.dart';
import 'package:mobile/controllers/receipt.dart'; import 'package:mobile/controllers/receipt.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/controllers/users.dart';
import 'package:mobile/server/backend_server.dart'; import 'package:mobile/server/backend_server.dart';
import 'package:mobile/server/server.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:mobile/controllers/routing.dart'; import 'package:mobile/controllers/routing.dart';
void main() { void main() {
runApp(const MyApp()); final server = BackendServer();
final users = UsersController(server: server);
final user = UserController(server: server);
user.loadUser().ignore();
runApp(MyApp(
users: users,
server: server,
));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); final UsersController users;
final Server server;
const MyApp({super.key, required this.users, required this.server});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final server = BackendServer();
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider( ChangeNotifierProvider(create: (_) => UserController(server: server)),
create: (_) => SessionController(server: server)),
ChangeNotifierProvider(create: (_) => RoutingController()), ChangeNotifierProvider(create: (_) => RoutingController()),
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => ProductController(server: server)), create: (_) => ProductController(server: server)),
@ -37,10 +49,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => PayingStateController()), ChangeNotifierProvider(create: (_) => PayingStateController()),
ChangeNotifierProvider(create: (_) => AddToCartStateController()), ChangeNotifierProvider(create: (_) => AddToCartStateController()),
ChangeNotifierProvider(create: (_) => LocationImageController()), ChangeNotifierProvider(create: (_) => LocationImageController()),
ChangeNotifierProvider( Provider(create: (_) => users),
create: (context) => UsersController(
server: server,
sessionController: context.read<SessionController>())),
], ],
child: MaterialApp( child: MaterialApp(
title: 'Fresh Plaza', title: 'Fresh Plaza',
@ -52,7 +61,7 @@ class MyApp extends StatelessWidget {
GoogleFonts.merriweatherTextTheme(Theme.of(context).textTheme), GoogleFonts.merriweatherTextTheme(Theme.of(context).textTheme),
useMaterial3: true, useMaterial3: true,
), ),
home: Consumer<SessionController>( home: Consumer<UserController>(
builder: (_, sessionController, __) { builder: (_, sessionController, __) {
if (sessionController.sessionToken is String) { if (sessionController.sessionToken is String) {
return Dashboard(); return Dashboard();

View File

@ -3,7 +3,7 @@ import 'package:mobile/controllers/routing.dart';
import 'package:mobile/controllers/cart.dart'; import 'package:mobile/controllers/cart.dart';
import 'package:mobile/controllers/paying_state.dart'; import 'package:mobile/controllers/paying_state.dart';
import 'package:mobile/controllers/receipt.dart'; import 'package:mobile/controllers/receipt.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/utils/price.dart'; import 'package:mobile/utils/price.dart';
import 'package:mobile/widgets/primary_button.dart'; import 'package:mobile/widgets/primary_button.dart';
@ -57,7 +57,7 @@ class FinishShoppingPage extends StatelessWidget {
child: Center( child: Center(
child: PrimaryButton( child: PrimaryButton(
onPressed: () async { onPressed: () async {
final session = context.read<SessionController>(); final session = context.read<UserController>();
payingStateRepo.next(); payingStateRepo.next();
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
if (cartController.purchase(session.sessionToken!) if (cartController.purchase(session.sessionToken!)

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/pages/settings_page.dart'; import 'package:mobile/pages/settings_page.dart';
import 'package:mobile/utils/build_if_session_exists.dart'; import 'package:mobile/utils/build_if_session_exists.dart';
@ -11,8 +10,8 @@ class HomePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sessionController = context.watch<SessionController>(); final userController = context.watch<UserController>();
context.watch<UsersController>();
return Column( return Column(
children: [ children: [
Row( Row(
@ -37,7 +36,7 @@ class HomePage extends StatelessWidget {
), ),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: BuildIfSessionUserExists( child: BuildIfSessionUserExists(
sessionController: sessionController, sessionController: userController,
placeholder: const CircularProgressIndicator(), placeholder: const CircularProgressIndicator(),
builder: (context, user) => Text( builder: (context, user) => Text(
"Saldo: ${formatDkkCents(user.balanceDkkCents)}", "Saldo: ${formatDkkCents(user.balanceDkkCents)}",
@ -48,7 +47,7 @@ class HomePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
BuildIfSessionUserExists( BuildIfSessionUserExists(
sessionController: sessionController, sessionController: userController,
placeholder: const CircularProgressIndicator(), placeholder: const CircularProgressIndicator(),
builder: (context, user) => Text( builder: (context, user) => Text(
"Velkommen ${user.name}", "Velkommen ${user.name}",

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/pages/register_page.dart'; import 'package:mobile/pages/register_page.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/widgets/error_box.dart'; import 'package:mobile/widgets/error_box.dart';
@ -30,6 +30,8 @@ class LogInFormState extends State<LogInForm> {
bool loginError = false; bool loginError = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final userController = context.read<UserController>();
final mailController = TextEditingController(); final mailController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@ -61,8 +63,7 @@ class LogInFormState extends State<LogInForm> {
), ),
PrimaryButton( PrimaryButton(
onPressed: () async { onPressed: () async {
final sessionController = context.read<SessionController>(); final loginResult = await userController.login(
final loginResult = await sessionController.login(
mailController.text, passwordController.text); mailController.text, passwordController.text);
switch (loginResult) { switch (loginResult) {
case Ok<Null, String>(): case Ok<Null, String>():

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/controllers/users.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/widgets/error_box.dart'; import 'package:mobile/widgets/error_box.dart';
import 'package:mobile/widgets/primary_button.dart'; import 'package:mobile/widgets/primary_button.dart';
@ -29,11 +29,12 @@ class RegisterFormState extends State<RegisterForm> {
bool registerError = false; bool registerError = false;
String errorText = "Ingen fejlbesked jeg skal ikke vises"; String errorText = "Ingen fejlbesked jeg skal ikke vises";
@override
Widget build(BuildContext context) {
final nameController = TextEditingController(); final nameController = TextEditingController();
final mailController = TextEditingController(); final mailController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Column( return Column(
spacing: 10, spacing: 10,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -72,8 +73,8 @@ class RegisterFormState extends State<RegisterForm> {
obscure: true), obscure: true),
PrimaryButton( PrimaryButton(
onPressed: () async { onPressed: () async {
final sessionsRepo = context.read<UsersController>(); final usersController = context.read<UsersController>();
final res = await sessionsRepo.register(nameController.text, final res = await usersController.register(nameController.text,
mailController.text, passwordController.text); mailController.text, passwordController.text);
if (res is Ok<Null, String>) { if (res is Ok<Null, String>) {
setState(() => registerError = false); setState(() => registerError = false);

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/pages/settings_pages/saldo.dart'; import 'package:mobile/pages/settings_pages/saldo.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -30,7 +30,7 @@ class SettingsPage extends StatelessWidget {
icon: Icons.door_back_door, icon: Icons.door_back_door,
title: "Log ud", title: "Log ud",
action: (context) async { action: (context) async {
final sessionsController = context.read<SessionController>(); final sessionsController = context.read<UserController>();
Navigator.pop(context); Navigator.pop(context);
await sessionsController.logout(); await sessionsController.logout();
}), }),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/utils/build_if_session_exists.dart'; import 'package:mobile/utils/build_if_session_exists.dart';
@ -11,8 +10,7 @@ class SaldoSettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sessionController = context.watch<SessionController>(); final sessionController = context.watch<UserController>();
final userController = context.watch<UsersController>();
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
body: SafeArea( body: SafeArea(
@ -38,7 +36,7 @@ class SaldoSettingsPage extends StatelessWidget {
}), }),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () async { onPressed: () async {
final res = await userController.addBalance(); final res = await sessionController.addBalance();
switch (res) { switch (res) {
case Ok<Null, String>(): case Ok<Null, String>():
print("yay"); print("yay");

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart'; import 'package:mobile/controllers/user.dart';
import 'package:mobile/models/user.dart'; import 'package:mobile/models/user.dart';
class BuildIfSessionUserExists extends StatelessWidget { class BuildIfSessionUserExists extends StatelessWidget {
final SessionController sessionController; final UserController sessionController;
final Widget placeholder; final Widget placeholder;
final Widget Function(BuildContext, User) builder; final Widget Function(BuildContext, User) builder;
@ -16,9 +16,9 @@ class BuildIfSessionUserExists extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: sessionController.loadUser(), future: sessionController.loadUserOld(),
builder: (context, snapshot) { builder: (context, snapshot) {
final user = sessionController.user; final user = sessionController.userOld;
if (user == null) { if (user == null) {
return placeholder; return placeholder;
} }