mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
refactor controllers
This commit is contained in:
parent
ec520c330a
commit
9603651e05
7
git-authors.toml
Normal file
7
git-authors.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[authors.mtk]
|
||||
name = "Mikkel Troels Kongsted"
|
||||
email = "mtkongsted@gmail.com"
|
||||
|
||||
[authors.sfj]
|
||||
name = "SFJ"
|
||||
email = "simonfromjakobsen@gmail.com"
|
@ -119,9 +119,9 @@ class CartControllerMemory extends CartController {
|
||||
Future<Result<Null, String>> purchase(String token) async {
|
||||
final res = await server.purchaseCart(token, cart);
|
||||
switch (res) {
|
||||
case Success<Null>():
|
||||
case Ok<Null, String>():
|
||||
return const Ok(null);
|
||||
case Error<Null>(message: final message):
|
||||
case Err<Null, String>(value: final message):
|
||||
return Err(message);
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,10 @@ class ProductController extends ChangeNotifier {
|
||||
Future<void> fetchProductsFromServer() async {
|
||||
final res = await server.allProducts();
|
||||
switch (res) {
|
||||
case Success<List<Product>>(data: final data):
|
||||
case Ok<List<Product>, String>(value: final data):
|
||||
products = data;
|
||||
notifyListeners();
|
||||
case Error<List<Product>>():
|
||||
case Err<List<Product>, String>():
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
163
mobile/lib/controllers/session.dart
Normal file
163
mobile/lib/controllers/session.dart
Normal file
@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/models/user.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/server/server.dart';
|
||||
|
||||
class SessionController {
|
||||
final Server server;
|
||||
|
||||
String? _sessionToken;
|
||||
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):
|
||||
_sessionToken = sessionToken;
|
||||
notifySessionChangeListeners();
|
||||
return const Ok(null);
|
||||
case Err<String, String>(value: final message):
|
||||
return Err(message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<Null, Null>> loadUser() async {
|
||||
// TODO: retrieve session from cache, if exists
|
||||
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();
|
||||
|
||||
// The mechanism for checking that a user is logged in, only listens on
|
||||
// the session provider. There we also notify sessions listeners, to
|
||||
// account for this one specific case. Is this smart? idk.
|
||||
notifySessionChangeListeners();
|
||||
|
||||
return const Ok(null);
|
||||
case Err<User, String>():
|
||||
return const Err(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Null> logout() async {
|
||||
final sessionToken = _sessionToken;
|
||||
if (sessionToken == null) {
|
||||
return;
|
||||
}
|
||||
await server.logout(sessionToken);
|
||||
_sessionToken = null;
|
||||
notifySessionChangeListeners();
|
||||
}
|
||||
|
||||
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 {
|
||||
final sessionToken = _sessionToken;
|
||||
if (sessionToken == null) {
|
||||
return const Err("unathorized");
|
||||
}
|
||||
final result = await func(server, sessionToken);
|
||||
if (result case Err<T, String>(value: final message)) {
|
||||
if (message == "unauthorized") {
|
||||
_sessionToken = null;
|
||||
_sessionUser = null;
|
||||
notifySessionChangeListeners();
|
||||
notifyUserChangeListeners();
|
||||
return const Err("unathorized");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Package private.
|
||||
void _addSessionChangeListener(_ChangeListener listener) {
|
||||
_sessionChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
/// Package private.
|
||||
void _addUserChangeListener(_ChangeListener listener) {
|
||||
_userChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
/// Class private.
|
||||
void notifySessionChangeListeners() {
|
||||
for (final listener in _sessionChangeListeners) {
|
||||
listener.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/// Class private.
|
||||
void notifyUserChangeListeners() {
|
||||
for (final listener in _userChangeListeners) {
|
||||
listener.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,132 +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 UserController extends ChangeNotifier {
|
||||
final Server server;
|
||||
String? _sessionToken;
|
||||
User? _user;
|
||||
|
||||
Future<Result<User, Null>> userLoad = Future.error(Null);
|
||||
|
||||
UserController({required this.server});
|
||||
|
||||
/// Make sure a user exists before calling using `.loadUser()`.
|
||||
User get user {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
final token = _sessionToken;
|
||||
if (token == null) {
|
||||
return const Err("No token");
|
||||
}
|
||||
final res = await server.addBalance(token);
|
||||
notifyListeners();
|
||||
switch (res) {
|
||||
case Success<Null>():
|
||||
return const Ok(null);
|
||||
case Error<Null>(message: final message):
|
||||
return Err(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoUserExcept implements Exception {}
|
@ -10,9 +10,9 @@ class UsersController {
|
||||
String name, String email, String password) async {
|
||||
final res = await server.register(name, email, password);
|
||||
switch (res) {
|
||||
case Success<Null>():
|
||||
case Ok<Null, String>():
|
||||
return const Ok(null);
|
||||
case Error<Null>(message: final message):
|
||||
case Err<Null, String>(value: final message):
|
||||
return Err(message);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/pages/dashboard.dart';
|
||||
import 'package:mobile/pages/log_in_page.dart';
|
||||
import 'package:mobile/controllers/add_to_cart_state.dart';
|
||||
@ -10,6 +10,7 @@ import 'package:mobile/controllers/paying_state.dart';
|
||||
import 'package:mobile/controllers/product.dart';
|
||||
import 'package:mobile/controllers/receipt.dart';
|
||||
import 'package:mobile/controllers/users.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/server/backend_server.dart';
|
||||
import 'package:mobile/server/server.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -17,29 +18,38 @@ import 'package:mobile/controllers/routing.dart';
|
||||
|
||||
void main() {
|
||||
final server = BackendServer();
|
||||
final users = UsersController(server: server);
|
||||
final usersController = UsersController(server: server);
|
||||
final sessionController = SessionController(server: server);
|
||||
|
||||
final user = UserController(server: server);
|
||||
user.loadUser().ignore();
|
||||
sessionController.loadUser();
|
||||
|
||||
runApp(MyApp(
|
||||
users: users,
|
||||
usersController: usersController,
|
||||
sessionController: sessionController,
|
||||
server: server,
|
||||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
final UsersController users;
|
||||
final UsersController usersController;
|
||||
final SessionController sessionController;
|
||||
|
||||
final Server server;
|
||||
|
||||
const MyApp({super.key, required this.users, required this.server});
|
||||
const MyApp(
|
||||
{super.key,
|
||||
required this.usersController,
|
||||
required this.sessionController,
|
||||
required this.server});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => UserController(server: server)),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => SessionProvider(controller: sessionController)),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => CurrentUserProvider(controller: sessionController)),
|
||||
ChangeNotifierProvider(create: (_) => RoutingController()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => ProductController(server: server)),
|
||||
@ -49,7 +59,7 @@ class MyApp extends StatelessWidget {
|
||||
ChangeNotifierProvider(create: (_) => PayingStateController()),
|
||||
ChangeNotifierProvider(create: (_) => AddToCartStateController()),
|
||||
ChangeNotifierProvider(create: (_) => LocationImageController()),
|
||||
Provider(create: (_) => users),
|
||||
Provider(create: (_) => usersController),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: 'Fresh Plaza',
|
||||
@ -61,12 +71,24 @@ class MyApp extends StatelessWidget {
|
||||
GoogleFonts.merriweatherTextTheme(Theme.of(context).textTheme),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: Consumer<UserController>(
|
||||
builder: (_, sessionController, __) {
|
||||
if (sessionController.sessionToken is String) {
|
||||
home: Consumer<SessionProvider>(
|
||||
builder: (_, provider, ___) {
|
||||
if (provider.controller.hasUser) {
|
||||
return Dashboard();
|
||||
}
|
||||
return const LogInPage();
|
||||
return FutureBuilder(
|
||||
future: provider.controller.loadUser(),
|
||||
builder: (_, snapshot) {
|
||||
final error = snapshot.error;
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
if (snapshot.data != null &&
|
||||
snapshot.data is Err<Null, Null>) {
|
||||
return const LoginPage();
|
||||
}
|
||||
return const Scaffold(body: CircularProgressIndicator());
|
||||
});
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import 'package:mobile/controllers/routing.dart';
|
||||
import 'package:mobile/controllers/cart.dart';
|
||||
import 'package:mobile/controllers/paying_state.dart';
|
||||
import 'package:mobile/controllers/receipt.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/utils/price.dart';
|
||||
import 'package:mobile/widgets/primary_button.dart';
|
||||
@ -57,42 +57,42 @@ class FinishShoppingPage extends StatelessWidget {
|
||||
child: Center(
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
final session = context.read<UserController>();
|
||||
payingStateRepo.next();
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (cartController.purchase(session.sessionToken!)
|
||||
is Err) {
|
||||
if (context.mounted) {
|
||||
showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AlertDialog(
|
||||
content: const Text(
|
||||
'Du har desværre ikke råd til at købe dette'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.pop(context, 'OK'),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
payingStateRepo.reset();
|
||||
return;
|
||||
}
|
||||
receiptRepo.createReceipt(cart);
|
||||
payingStateRepo.next();
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
cartController.clearCart();
|
||||
payingStateRepo.reset();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
final RoutingController routing =
|
||||
context.read<RoutingController>();
|
||||
routing.routeTo(PageSelector.homePage);
|
||||
}
|
||||
// final session = context.read<SessionController>();
|
||||
// payingStateRepo.next();
|
||||
// await Future.delayed(const Duration(seconds: 1));
|
||||
// if (cartController.purchase(session.sessionToken!)
|
||||
// is Err) {
|
||||
// if (context.mounted) {
|
||||
// showDialog<String>(
|
||||
// context: context,
|
||||
// builder: (BuildContext context) =>
|
||||
// AlertDialog(
|
||||
// content: const Text(
|
||||
// 'Du har desværre ikke råd til at købe dette'),
|
||||
// actions: <Widget>[
|
||||
// TextButton(
|
||||
// onPressed: () =>
|
||||
// Navigator.pop(context, 'OK'),
|
||||
// child: const Text('OK'),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// payingStateRepo.reset();
|
||||
// return;
|
||||
// }
|
||||
// receiptRepo.createReceipt(cart);
|
||||
// payingStateRepo.next();
|
||||
// await Future.delayed(const Duration(seconds: 1));
|
||||
// cartController.clearCart();
|
||||
// payingStateRepo.reset();
|
||||
// if (context.mounted) {
|
||||
// Navigator.pop(context);
|
||||
// final RoutingController routing =
|
||||
// context.read<RoutingController>();
|
||||
// routing.routeTo(PageSelector.homePage);
|
||||
// }
|
||||
},
|
||||
child: const Text("Betal"))),
|
||||
),
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/pages/settings_page.dart';
|
||||
import 'package:mobile/utils/build_if_session_exists.dart';
|
||||
import 'package:mobile/utils/price.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -10,8 +9,6 @@ class HomePage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userController = context.watch<UserController>();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
@ -30,29 +27,33 @@ class HomePage extends StatelessWidget {
|
||||
),
|
||||
Card(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: BuildIfSessionUserExists(
|
||||
sessionController: userController,
|
||||
placeholder: const CircularProgressIndicator(),
|
||||
builder: (context, user) => Text(
|
||||
"Saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.headlineSmall))),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
color: Colors.white,
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Consumer<CurrentUserProvider>(
|
||||
builder: (_, provider, ___) {
|
||||
final user = provider.controller.user;
|
||||
return Text("Saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.headlineSmall);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
BuildIfSessionUserExists(
|
||||
sessionController: userController,
|
||||
placeholder: const CircularProgressIndicator(),
|
||||
builder: (context, user) => Text(
|
||||
"Velkommen ${user.name}",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
))
|
||||
Consumer<CurrentUserProvider>(
|
||||
builder: (_, provider, ___) {
|
||||
final user = provider.controller.user;
|
||||
return Text(
|
||||
"Velkommen ${user.name}",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/pages/register_page.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/widgets/error_box.dart';
|
||||
@ -7,30 +7,30 @@ import 'package:mobile/widgets/primary_button.dart';
|
||||
import 'package:mobile/widgets/primary_input.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LogInPage extends StatelessWidget {
|
||||
const LogInPage({super.key});
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [LogInForm()]));
|
||||
children: [LoginForm()]));
|
||||
}
|
||||
}
|
||||
|
||||
class LogInForm extends StatefulWidget {
|
||||
const LogInForm({super.key});
|
||||
class LoginForm extends StatefulWidget {
|
||||
const LoginForm({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => LogInFormState();
|
||||
State<StatefulWidget> createState() => LoginFormState();
|
||||
}
|
||||
|
||||
class LogInFormState extends State<LogInForm> {
|
||||
class LoginFormState extends State<LoginForm> {
|
||||
bool loginError = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userController = context.read<UserController>();
|
||||
final sessionProvider = context.read<SessionProvider>();
|
||||
|
||||
final mailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
@ -63,8 +63,8 @@ class LogInFormState extends State<LogInForm> {
|
||||
),
|
||||
PrimaryButton(
|
||||
onPressed: () async {
|
||||
final loginResult = await userController.login(
|
||||
mailController.text, passwordController.text);
|
||||
final loginResult = await sessionProvider.controller
|
||||
.loginUser(mailController.text, passwordController.text);
|
||||
switch (loginResult) {
|
||||
case Ok<Null, String>():
|
||||
setState(() => loginError = false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/pages/settings_pages/saldo.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -30,9 +30,11 @@ class SettingsPage extends StatelessWidget {
|
||||
icon: Icons.door_back_door,
|
||||
title: "Log ud",
|
||||
action: (context) async {
|
||||
final sessionsController = context.read<UserController>();
|
||||
Navigator.pop(context);
|
||||
await sessionsController.logout();
|
||||
final sessionProvider = context.read<SessionProvider>();
|
||||
await sessionProvider.controller.logout();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/utils/build_if_session_exists.dart';
|
||||
import 'package:mobile/utils/price.dart';
|
||||
@ -10,7 +10,6 @@ class SaldoSettingsPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sessionController = context.watch<UserController>();
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SafeArea(
|
||||
@ -26,22 +25,30 @@ class SaldoSettingsPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
BuildIfSessionUserExists(
|
||||
sessionController: sessionController,
|
||||
placeholder: const CircularProgressIndicator(),
|
||||
builder: (context, user) {
|
||||
return Text(
|
||||
"Nuværende saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.bodyLarge);
|
||||
}),
|
||||
Consumer<CurrentUserProvider>(builder: (_, provider, ___) {
|
||||
final user = provider.controller.user;
|
||||
return Text(
|
||||
"Nuværende saldo: ${formatDkkCents(user.balanceDkkCents)}",
|
||||
style: Theme.of(context).textTheme.bodyLarge);
|
||||
}),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
final res = await sessionController.addBalance();
|
||||
switch (res) {
|
||||
case Ok<Null, String>():
|
||||
print("yay");
|
||||
case Err<Null, String>(value: final message):
|
||||
print("Womp womp fejled er: $message");
|
||||
final currentUserProvider = context.read<CurrentUserProvider>();
|
||||
final res = await currentUserProvider.controller.addBalance();
|
||||
if (res case Err<Null, String>(value: final message)) {
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text('Serverfejl: $message'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'OK'),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
|
@ -4,6 +4,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:mobile/models/cart_item.dart';
|
||||
import 'package:mobile/models/product.dart';
|
||||
import 'package:mobile/models/user.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/server/server.dart';
|
||||
|
||||
class BackendServer implements Server {
|
||||
@ -21,24 +22,23 @@ class BackendServer implements Server {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<List<Product>>> allProducts() async {
|
||||
Future<Result<List<Product>, String>> allProducts() async {
|
||||
final res = await http
|
||||
.get(
|
||||
Uri.parse("$_apiUrl/products/all"),
|
||||
)
|
||||
.then((res) => json.decode(res.body));
|
||||
if (res["ok"]) {
|
||||
return Success(
|
||||
data: (res["products"] as List<dynamic>)
|
||||
.map(((product) => Product.fromJson(product)))
|
||||
.toList());
|
||||
return Ok((res["products"] as List<dynamic>)
|
||||
.map(((product) => Product.fromJson(product)))
|
||||
.toList());
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> register(
|
||||
Future<Result<Null, String>> register(
|
||||
String name,
|
||||
String email,
|
||||
String password,
|
||||
@ -49,14 +49,14 @@ class BackendServer implements Server {
|
||||
).then((res) => json.decode(res.body));
|
||||
|
||||
if (res["ok"]) {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<String>> login(
|
||||
Future<Result<String, String>> login(
|
||||
String email,
|
||||
String password,
|
||||
) async {
|
||||
@ -66,41 +66,41 @@ class BackendServer implements Server {
|
||||
).then((res) => json.decode(res.body));
|
||||
|
||||
if (res["ok"]) {
|
||||
return Success(data: res["token"]);
|
||||
return Ok(res["token"]);
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> logout(String token) async {
|
||||
Future<Result<Null, String>> logout(String token) async {
|
||||
final res = await _post(
|
||||
endpoint: "sessions/logout",
|
||||
).then((res) => json.decode(res.body));
|
||||
|
||||
if (res["ok"]) {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<User>> sessionUser(String token) async {
|
||||
Future<Result<User, String>> sessionUser(String token) async {
|
||||
("sending request fr with token $token");
|
||||
final res = await http.get(
|
||||
Uri.parse("$_apiUrl/sessions/user"),
|
||||
headers: {"Session-Token": token},
|
||||
).then((res) => json.decode(res.body));
|
||||
if (res["ok"]) {
|
||||
return Success(data: User.fromJson(res["user"]));
|
||||
return Ok(User.fromJson(res["user"]));
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> purchaseCart(
|
||||
Future<Result<Null, String>> purchaseCart(
|
||||
String token, List<CartItem> cartItems) async {
|
||||
final res = await http.post(Uri.parse("$_apiUrl/carts/purchase"), headers: {
|
||||
"Content-Type": "application/json",
|
||||
@ -113,14 +113,14 @@ class BackendServer implements Server {
|
||||
}).then((res) => json.decode(res.body));
|
||||
|
||||
if (res["ok"]) {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> addBalance(String token) async {
|
||||
Future<Result<Null, String>> addBalance(String token) async {
|
||||
print("$_apiUrl/api/users/balance/add");
|
||||
final res = await http.post(
|
||||
Uri.parse("$_apiUrl/users/balance/add"),
|
||||
@ -132,9 +132,9 @@ class BackendServer implements Server {
|
||||
).then((res) => json.decode(res.body));
|
||||
|
||||
if (res["ok"]) {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
} else {
|
||||
return Error(message: res["msg"]);
|
||||
return Err(res["msg"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ import 'package:mobile/models/cart_item.dart';
|
||||
import 'package:mobile/models/coordinate.dart';
|
||||
import 'package:mobile/models/product.dart';
|
||||
import 'package:mobile/models/user.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
import 'package:mobile/server/server.dart';
|
||||
|
||||
class MockServer implements Server {
|
||||
@override
|
||||
Future<Response<List<Product>>> allProducts() async {
|
||||
Future<Result<List<Product>, String>> allProducts() async {
|
||||
var nextId = 0;
|
||||
return Success(data: <Product>[
|
||||
return Ok(<Product>[
|
||||
Product(
|
||||
id: nextId++,
|
||||
name: "Minimælk",
|
||||
@ -91,45 +92,44 @@ class MockServer implements Server {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> register(
|
||||
Future<Result<Null, String>> register(
|
||||
String name,
|
||||
String email,
|
||||
String password,
|
||||
) async {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<String>> login(
|
||||
Future<Result<String, String>> login(
|
||||
String email,
|
||||
String password,
|
||||
) async {
|
||||
return Success(data: "asdsadasdsad");
|
||||
return Ok("asdsadasdsad");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> logout(String token) async {
|
||||
return Success(data: null);
|
||||
Future<Result<Null, String>> logout(String token) async {
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<User>> sessionUser(String token) async {
|
||||
return Success(
|
||||
data: User(
|
||||
id: 0,
|
||||
email: "test@test.com",
|
||||
name: "testuser",
|
||||
balanceDkkCents: 10000));
|
||||
Future<Result<User, String>> sessionUser(String token) async {
|
||||
return Ok(User(
|
||||
id: 0,
|
||||
email: "test@test.com",
|
||||
name: "testuser",
|
||||
balanceDkkCents: 10000));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> purchaseCart(
|
||||
Future<Result<Null, String>> purchaseCart(
|
||||
String token, List<CartItem> cartItems) async {
|
||||
return Success(data: null);
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<Null>> addBalance(String token) async {
|
||||
return Success(data: null);
|
||||
Future<Result<Null, String>> addBalance(String token) async {
|
||||
return Ok(null);
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,27 @@
|
||||
import 'package:mobile/models/cart_item.dart';
|
||||
import 'package:mobile/models/product.dart';
|
||||
import 'package:mobile/models/user.dart';
|
||||
import 'package:mobile/results.dart';
|
||||
|
||||
abstract class Server {
|
||||
Future<Response<List<Product>>> allProducts();
|
||||
Future<Result<List<Product>, String>> allProducts();
|
||||
|
||||
Future<Response<Null>> register(
|
||||
Future<Result<Null, String>> register(
|
||||
String name,
|
||||
String email,
|
||||
String password,
|
||||
);
|
||||
|
||||
Future<Response<String>> login(
|
||||
Future<Result<String, String>> login(
|
||||
String email,
|
||||
String password,
|
||||
);
|
||||
Future<Response<Null>> logout(String token);
|
||||
Future<Result<Null, String>> logout(String token);
|
||||
|
||||
Future<Response<User>> sessionUser(String token);
|
||||
Future<Result<User, String>> sessionUser(String token);
|
||||
|
||||
Future<Response<Null>> purchaseCart(String token, List<CartItem> cartItems);
|
||||
Future<Result<Null, String>> purchaseCart(
|
||||
String token, List<CartItem> cartItems);
|
||||
|
||||
Future<Response<Null>> addBalance(String token);
|
||||
}
|
||||
|
||||
sealed class Response<Data> {}
|
||||
|
||||
class Success<Data> extends Response<Data> {
|
||||
Data data;
|
||||
Success({required this.data});
|
||||
}
|
||||
|
||||
class Error<Data> extends Response<Data> {
|
||||
String message;
|
||||
Error({required this.message});
|
||||
Future<Result<Null, String>> addBalance(String token);
|
||||
}
|
||||
|
@ -1,28 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile/controllers/user.dart';
|
||||
import 'package:mobile/controllers/session.dart';
|
||||
import 'package:mobile/models/user.dart';
|
||||
|
||||
class BuildIfSessionUserExists extends StatelessWidget {
|
||||
final UserController sessionController;
|
||||
final Widget placeholder;
|
||||
final Widget Function(BuildContext, User) builder;
|
||||
|
||||
const BuildIfSessionUserExists(
|
||||
{super.key,
|
||||
required this.sessionController,
|
||||
required this.placeholder,
|
||||
required this.builder});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: sessionController.loadUserOld(),
|
||||
builder: (context, snapshot) {
|
||||
final user = sessionController.userOld;
|
||||
if (user == null) {
|
||||
return placeholder;
|
||||
}
|
||||
return builder(context, user);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:mobile/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user