implement auth - almost works

This commit is contained in:
Mikkel Troels Kongsted 2025-03-12 15:56:04 +01:00
parent 28f9bb46e2
commit be00f1c965
19 changed files with 312 additions and 221 deletions

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/models/product.dart'; import 'package:mobile/models/product.dart';
import 'package:mobile/results.dart';
class ProductIdException implements Exception {} class ProductIdException implements Exception {}
@ -82,6 +83,10 @@ class CartController extends ChangeNotifier {
cart.clear(); cart.clear();
notifyListeners(); notifyListeners();
} }
Result<Null, String> pay() {
return const Err("Not implemented");
}
} }
class CartItem { class CartItem {

View File

@ -1,15 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/models/product.dart'; import 'package:mobile/models/product.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/server/mock_server.dart';
import 'package:mobile/server/server.dart'; import 'package:mobile/server/server.dart';
class ProductController extends ChangeNotifier { class ProductController extends ChangeNotifier {
final server = MockServer(); final Server server;
List<Product> products = []; List<Product> products = [];
String query = ""; String query = "";
ProductController() { ProductController({required this.server}) {
fetchProductsFromServer(); fetchProductsFromServer();
} }

View File

@ -0,0 +1,59 @@
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;
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<Result<User, Null>> user() async {
final token = _sessionToken;
if (token == null) {
notifyListeners();
return const Err(null);
}
final res = await server.sessionUser(token);
switch (res) {
case Success<User>(data: final user):
return Ok(user);
case Error<User>():
_sessionToken = null;
notifyListeners();
return const Err(null);
}
}
get sessionToken {
return _sessionToken;
}
Future<void> logout() async {
final token = _sessionToken;
if (token != null) {
server.logout(token);
_sessionToken = null;
}
print(_sessionToken);
print("notifying listeners");
notifyListeners();
}
Result<int, String> pay(int userId, int amount) {
return const Err("not implemented");
}
}

View File

@ -1,136 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/results.dart'; import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart';
class UsersControllerOld extends ChangeNotifier { class UsersController extends ChangeNotifier {
int nextId = 0; Server server;
final List<User> users = [];
User? _loggedInUser; UsersController({required this.server});
UsersControllerOld() { Future<Result<Null, String>> register(
addTestUsers(); String name, String email, String password) async {
} final res = await server.register(name, email, password);
switch (res) {
Result<User, String> getUserById(int id) { case Success<Null>():
for (var i = 0; i < users.length; i++) { return const Ok(null);
if (users[i].id == id) { case Error<Null>(message: final message):
return Ok(users[i]); return Err(message);
}
} }
return Err("User with id $id doesn't exist");
}
Result<User, String> getUserByMail(String mail) {
for (var i = 0; i < users.length; i++) {
if (users[i].mail == mail) {
return Ok(users[i]);
}
}
return Err("User with mail $mail doesn't exist");
}
Result<User, String> addUser(String name, String mail, String password) {
if (getUserByMail(mail) is Ok) {
return Err("User with mail $mail already exists");
}
final user = User(
id: nextId++,
name: name,
mail: mail,
password: password,
balanceInDkkCents: 0);
users.add(user);
return Ok(user);
}
Result<User, String> login(String mail, String password) {
User? user;
for (var i = 0; i < users.length; i++) {
if (users[i].mail == mail) {
user = users[i];
}
}
if (user == null) {
return Err("User with mail $mail doesn't exist");
}
if (user.password != password) {
return Err("Wrong password for user with mail $mail");
}
_loggedInUser = user;
notifyListeners();
return Ok(user);
}
void logout() {
_loggedInUser = null;
notifyListeners();
}
User? loggedInUser() {
return _loggedInUser;
}
Result<int, String> pay(int userId, int amount) {
final user = getUserById(userId);
if (user is Ok) {
return (user as User).pay(amount);
}
return Err("User with id $userId doesn't exist");
}
void addTestUsers() {
users
..add(User(
id: nextId++,
mail: "test@test.com",
name: "test",
password: "test",
balanceInDkkCents: 10000))
..add(User(
id: nextId++,
mail: "",
name: "",
password: "",
balanceInDkkCents: 100000));
}
void veryBadNotifyAll() {
//
// TODO: THIS SHOULD BE FIXED
// FIXME: DO SOMETHING ELSE PLEASE!!!!!
//
notifyListeners();
}
}
class User {
final int id;
final String mail;
final String name;
final String password;
// balance is in øre
int balanceInDkkCents;
User({
required this.id,
required this.mail,
required this.name,
required this.password,
required this.balanceInDkkCents,
});
void addBalanceFounds(int amount) {
balanceInDkkCents += amount;
}
Result<int, String> pay(int amount) {
if (balanceInDkkCents < amount) {
return Err("User can not afford paying amount $amount");
}
balanceInDkkCents -= amount;
return Ok(balanceInDkkCents);
} }
} }

View File

@ -1,5 +1,8 @@
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/models/user.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';
import 'package:mobile/controllers/cart.dart'; import 'package:mobile/controllers/cart.dart';
@ -8,6 +11,7 @@ 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/user.dart';
import 'package:mobile/server/mock_server.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:mobile/controllers/routing.dart'; import 'package:mobile/controllers/routing.dart';
@ -20,29 +24,37 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final server = MockServer();
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (_) => RoutingController()), ChangeNotifierProvider(create: (_) => RoutingController()),
ChangeNotifierProvider(create: (_) => ProductController()), ChangeNotifierProvider(
create: (_) => ProductController(server: server)),
ChangeNotifierProvider(create: (_) => CartController()), ChangeNotifierProvider(create: (_) => CartController()),
ChangeNotifierProvider(create: (_) => ReceiptController()), ChangeNotifierProvider(create: (_) => ReceiptController()),
ChangeNotifierProvider(create: (_) => PayingStateController()), ChangeNotifierProvider(create: (_) => PayingStateController()),
ChangeNotifierProvider(create: (_) => AddToCartStateController()), ChangeNotifierProvider(create: (_) => AddToCartStateController()),
ChangeNotifierProvider(create: (_) => LocationImageController()), ChangeNotifierProvider(create: (_) => LocationImageController()),
ChangeNotifierProvider(create: (_) => UsersControllerOld()), ChangeNotifierProvider(create: (_) => UsersController(server: server)),
ChangeNotifierProvider(
create: (_) => SessionController(server: server)),
], ],
child: MaterialApp( child: MaterialApp(
title: 'Fresh Plaza', title: 'Fresh Plaza',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 149, 92, 255)), seedColor: const Color.fromARGB(255, 149, 92, 255)),
scaffoldBackgroundColor: const Color(0xFFFAFAFF), scaffoldBackgroundColor: const Color(0xFFFAFAFF),
textTheme: textTheme:
GoogleFonts.merriweatherTextTheme(Theme.of(context).textTheme), GoogleFonts.merriweatherTextTheme(Theme.of(context).textTheme),
useMaterial3: true, useMaterial3: true,
), ),
home: const LogInPage(), home: Consumer<SessionController>(
), builder: (_, sessionController, __) {
if (sessionController.sessionToken is String) return Dashboard();
return const LogInPage();
},
)),
); );
} }
} }

View File

@ -0,0 +1,35 @@
import 'package:mobile/results.dart';
class User {
final int id;
final String email;
final String name;
// balance is in øre
int balanceInDkkCents;
User({
required this.id,
required this.email,
required this.name,
required this.balanceInDkkCents,
});
User.fromJson(Map<String, dynamic> json)
: id = json["id"],
email = json["email"],
name = json["name"],
balanceInDkkCents = json["balanceInDkkCents"];
void addBalanceFounds(int amount) {
balanceInDkkCents += amount;
}
Result<int, String> pay(int amount) {
if (balanceInDkkCents < amount) {
return Err("User can not afford paying amount $amount");
}
balanceInDkkCents -= amount;
return Ok(balanceInDkkCents);
}
}

View File

@ -6,7 +6,6 @@ import 'package:mobile/models/product.dart';
import 'package:mobile/pages/finish_shopping_page.dart'; import 'package:mobile/pages/finish_shopping_page.dart';
import 'package:mobile/controllers/cart.dart'; import 'package:mobile/controllers/cart.dart';
import 'package:mobile/controllers/product.dart'; import 'package:mobile/controllers/product.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';
@ -144,8 +143,7 @@ class CartItemView extends StatelessWidget {
} }
class CartPage extends StatelessWidget { class CartPage extends StatelessWidget {
final User user; const CartPage({super.key});
const CartPage({super.key, required this.user});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -316,7 +314,7 @@ class CartPage extends StatelessWidget {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
FinishShoppingPage(user: user))); const FinishShoppingPage()));
}, },
child: const Text("Afslut indkøb")), child: const Text("Afslut indkøb")),
), ),

View File

@ -5,21 +5,16 @@ import 'package:mobile/pages/all_receipts_page.dart';
import 'package:mobile/pages/home_page.dart'; import 'package:mobile/pages/home_page.dart';
import 'package:mobile/controllers/routing.dart'; import 'package:mobile/controllers/routing.dart';
import 'package:mobile/controllers/cart.dart'; import 'package:mobile/controllers/cart.dart';
import 'package:mobile/controllers/user.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class Dashboard extends StatelessWidget { class Dashboard extends StatelessWidget {
final User user;
final List<StatelessWidget> pages = []; final List<StatelessWidget> pages = [];
Dashboard({super.key, required this.user}) { Dashboard({super.key}) {
pages.addAll([ pages.addAll([
HomePage( const HomePage(),
user: user,
),
const AllProductsPage(), const AllProductsPage(),
CartPage(user: user), const CartPage(),
const AllReceiptsPage(), const AllReceiptsPage(),
]); ]);
} }

View File

@ -3,7 +3,6 @@ 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/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';
@ -11,17 +10,15 @@ import 'package:mobile/widgets/receipt_item.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class FinishShoppingPage extends StatelessWidget { class FinishShoppingPage extends StatelessWidget {
final User user; const FinishShoppingPage({super.key});
const FinishShoppingPage({super.key, required this.user});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final CartController cartRepo = context.read<CartController>(); final CartController cartController = context.read<CartController>();
final ReceiptController receiptRepo = context.read<ReceiptController>(); final ReceiptController receiptRepo = context.read<ReceiptController>();
final PayingStateController payingStateRepo = final PayingStateController payingStateRepo =
context.watch<PayingStateController>(); context.watch<PayingStateController>();
final cart = cartRepo.allCartItems(); final cart = cartController.allCartItems();
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
@ -50,7 +47,7 @@ class FinishShoppingPage extends StatelessWidget {
"Total:", "Total:",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
Text(formatDkkCents(cartRepo.totalPrice())), Text(formatDkkCents(cartController.totalPrice())),
], ],
), ),
), ),
@ -60,7 +57,8 @@ class FinishShoppingPage extends StatelessWidget {
onPressed: () async { onPressed: () async {
payingStateRepo.next(); payingStateRepo.next();
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
if (user.pay(cartRepo.totalPrice()) is Err) { // TODO: implement paying for user
if (cartController.pay() is Err) {
if (context.mounted) { if (context.mounted) {
showDialog<String>( showDialog<String>(
context: context, context: context,
@ -84,7 +82,7 @@ class FinishShoppingPage extends StatelessWidget {
receiptRepo.createReceipt(cart); receiptRepo.createReceipt(cart);
payingStateRepo.next(); payingStateRepo.next();
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
cartRepo.clearCart(); cartController.clearCart();
payingStateRepo.reset(); payingStateRepo.reset();
if (context.mounted) { if (context.mounted) {
Navigator.pop(context); Navigator.pop(context);

View File

@ -1,15 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart';
import 'package:mobile/pages/settings_page.dart'; import 'package:mobile/pages/settings_page.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/utils/build_if_session_exists.dart';
import 'package:mobile/utils/price.dart'; import 'package:mobile/utils/price.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HomePage extends StatelessWidget { class HomePage extends StatelessWidget {
final User user; const HomePage({super.key});
const HomePage({super.key, required this.user});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sessionController = context.read<SessionController>();
return Column( return Column(
children: [ children: [
Row( Row(
@ -28,25 +29,29 @@ class HomePage extends StatelessWidget {
), ),
Card( Card(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10)),
color: Color(0xFFFFFFFF), color: Color(0xFFFFFFFF),
), ),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Consumer<UsersControllerOld>( child: BuildIfSessionExists(
builder: (context, usersRepo, _) => Text( sessionController: sessionController,
"Saldo: ${formatDkkCents(user.balanceInDkkCents)}", placeholder: const CircularProgressIndicator(),
style: Theme.of(context).textTheme.headlineSmall)), builder: (context, user) => Text(
), "Saldo: ${formatDkkCents(user.balanceInDkkCents)}",
style: Theme.of(context).textTheme.headlineSmall))),
), ),
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( BuildIfSessionExists(
"Velkommen ${user.name}", sessionController: sessionController,
style: Theme.of(context).textTheme.headlineMedium, placeholder: const CircularProgressIndicator(),
), builder: (context, user) => Text(
"Velkommen ${user.name}",
style: Theme.of(context).textTheme.headlineMedium,
))
], ],
), ),
), ),

View File

@ -1,12 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart';
import 'package:mobile/pages/register_page.dart'; import 'package:mobile/pages/register_page.dart';
import 'package:mobile/controllers/user.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';
import 'package:mobile/widgets/primary_input.dart'; import 'package:mobile/widgets/primary_input.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'dashboard.dart';
class LogInPage extends StatelessWidget { class LogInPage extends StatelessWidget {
const LogInPage({super.key}); const LogInPage({super.key});
@ -61,19 +60,15 @@ class LogInFormState extends State<LogInForm> {
controller: passwordController, controller: passwordController,
), ),
PrimaryButton( PrimaryButton(
onPressed: () { onPressed: () async {
final usersRepo = context.read<UsersControllerOld>(); final sessionController = context.read<SessionController>();
final loginResult = final loginResult = await sessionController.login(
usersRepo.login(mailController.text, passwordController.text); mailController.text, passwordController.text);
switch (loginResult) {
if (loginResult is Ok) { case Ok<Null, String>():
setState(() => loginError = false); setState(() => loginError = false);
Navigator.of(context).popUntil((_) => false); case Err<Null, String>():
Navigator.of(context).push(MaterialPageRoute( setState(() => loginError = true);
builder: (context) =>
Dashboard(user: (loginResult as Ok).value)));
} else {
setState(() => loginError = true);
} }
}, },
child: const Text("Log ind")), child: const Text("Log ind")),

View File

@ -71,9 +71,9 @@ class RegisterFormState extends State<RegisterForm> {
obscure: true), obscure: true),
PrimaryButton( PrimaryButton(
onPressed: () { onPressed: () {
final usersRepo = context.read<UsersControllerOld>(); final sessionsRepo = context.read<UsersController>();
if (usersRepo.addUser(nameController.text, mailController.text, if (sessionsRepo.register(nameController.text,
passwordController.text) is Ok) { mailController.text, passwordController.text) is Ok) {
setState(() => registerError = false); setState(() => registerError = false);
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/pages/log_in_page.dart'; import 'package:mobile/controllers/session.dart';
import 'package:mobile/pages/settings_pages/saldo.dart'; import 'package:mobile/pages/settings_pages/saldo.dart';
import 'package:mobile/controllers/user.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class _Page { class _Page {
@ -31,11 +30,9 @@ class SettingsPage extends StatelessWidget {
icon: Icons.door_back_door, icon: Icons.door_back_door,
title: "Log ud", title: "Log ud",
action: (context) { action: (context) {
final users = context.read<UsersControllerOld>(); final sessionsController = context.read<SessionController>();
users.logout();
Navigator.popUntil(context, (_) => false); Navigator.popUntil(context, (_) => false);
Navigator.of(context) sessionsController.logout();
.push(MaterialPageRoute(builder: (context) => const LogInPage()));
}), }),
]; ];

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/controllers/user.dart'; import 'package:mobile/controllers/session.dart';
import 'package:mobile/utils/build_if_session_exists.dart';
import 'package:mobile/utils/price.dart'; import 'package:mobile/utils/price.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -8,8 +9,7 @@ class SaldoSettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final usersRepo = context.watch<UsersControllerOld>(); final sessionController = context.watch<SessionController>();
final user = usersRepo.loggedInUser()!;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
body: SafeArea( body: SafeArea(
@ -25,12 +25,17 @@ class SaldoSettingsPage extends StatelessWidget {
), ),
], ],
), ),
Text("Nuværende saldo: ${formatDkkCents(user.balanceInDkkCents)}", BuildIfSessionExists(
style: Theme.of(context).textTheme.bodyLarge), sessionController: sessionController,
placeholder: const CircularProgressIndicator(),
builder: (context, user) => Text(
"Nuværende saldo: ${formatDkkCents(user.balanceInDkkCents)}",
style: Theme.of(context).textTheme.bodyLarge),
),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
user.addBalanceFounds(10000); // TODO: implement add balance
usersRepo.veryBadNotifyAll(); throw Exception("not implemented: Adding funds");
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text("Tilføj 100,00 kr"), label: const Text("Tilføj 100,00 kr"),

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mobile/models/product.dart'; import 'package:mobile/models/product.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/server/server.dart'; import 'package:mobile/server/server.dart';
class BackendServer implements Server { class BackendServer implements Server {
@ -51,16 +52,61 @@ class BackendServer implements Server {
} }
@override @override
Future<Response<Null>> login( Future<Response<String>> login(
String name,
String email, String email,
String password, String password,
) async { ) async {
final res = await _post( final res = await _post(
endpoint: "auth/login", endpoint: "sessions/login",
body: {"email": email, "password": password}, body: {"email": email, "password": password},
).then((res) => json.decode(res.body)); ).then((res) => json.decode(res.body));
if (res["ok"]) {
return Success(data: res["token"]);
} else {
return Error(message: res["message"]);
}
}
@override
Future<Response<Null>> logout(String token) async {
final res = await _post(
endpoint: "sessions/logout",
body: {
"token": token,
},
).then((res) => json.decode(res.body));
if (res["ok"]) {
return Success(data: null);
} else {
return Error(message: res["message"]);
}
}
@override
Future<Response<User>> sessionUser(String token) async {
final res = await http
.get(
Uri.parse("$_apiUrl/sessions/user/$token"),
)
.then((res) => json.decode(res.body));
if (res["ok"]) {
return Error(message: res["message"]);
} else {
return Success(data: User.fromJson(res));
}
}
@override
Future<Response<Null>> payForCart(String token) async {
final res = await _post(
endpoint: "cart/pay",
body: {
"token": token,
},
).then((res) => json.decode(res.body));
if (res["ok"]) { if (res["ok"]) {
return Success(data: null); return Success(data: null);
} else { } else {

View File

@ -1,5 +1,6 @@
import 'package:mobile/models/coordinate.dart'; import 'package:mobile/models/coordinate.dart';
import 'package:mobile/models/product.dart'; import 'package:mobile/models/product.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/server/server.dart'; import 'package:mobile/server/server.dart';
class MockServer implements Server { class MockServer implements Server {
@ -108,11 +109,30 @@ class MockServer implements Server {
} }
@override @override
Future<Response<Null>> login( Future<Response<String>> login(
String name,
String email, String email,
String password, String password,
) async { ) async {
return Success(data: "asdsadasdsad");
}
@override
Future<Response<Null>> logout(String token) async {
return Success(data: null);
}
@override
Future<Response<User>> sessionUser(String token) async {
return Success(
data: User(
id: 0,
email: "test@test.com",
name: "testuser",
balanceInDkkCents: 10000));
}
@override
Future<Response<Null>> payForCart(String token) async {
return Success(data: null); return Success(data: null);
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:mobile/models/product.dart'; import 'package:mobile/models/product.dart';
import 'package:mobile/models/user.dart';
abstract class Server { abstract class Server {
Future<Response<List<Product>>> allProducts(); Future<Response<List<Product>>> allProducts();
@ -9,11 +10,15 @@ abstract class Server {
String password, String password,
); );
Future<Response<Null>> login( Future<Response<String>> login(
String name,
String email, String email,
String password, String password,
); );
Future<Response<Null>> logout(String token);
Future<Response<User>> sessionUser(String token);
Future<Response<Null>> payForCart(String token);
} }
sealed class Response<Data> {} sealed class Response<Data> {}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:mobile/controllers/session.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart';
class BuildIfSessionExists extends StatelessWidget {
final SessionController sessionController;
final Widget placeholder;
final Widget Function(BuildContext, User) builder;
const BuildIfSessionExists(
{super.key,
required this.sessionController,
required this.placeholder,
required this.builder});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: sessionController.user(),
builder: (context, snapshot) {
final data = snapshot.data;
if (data == null) {
return placeholder;
}
if (data is Ok<User, Null>) {
final user = data.value;
return builder(context, user);
}
return Container();
});
}
}

View File

@ -13,7 +13,7 @@ import 'package:mobile/main.dart';
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame. // Build our app and trigger a frame.
await tester.pumpWidget(MyApp()); await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0. // Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget); expect(find.text('0'), findsOneWidget);