first commit
This commit is contained in:
commit
faf67cc6d8
148 changed files with 6580 additions and 0 deletions
8
lib/config/dependencies.dart
Normal file
8
lib/config/dependencies.dart
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import "package:flutter/widgets.dart";
|
||||
import "package:nested/nested.dart";
|
||||
import "package:provider/provider.dart";
|
||||
import "package:seshat/data/services/websocket_client.dart";
|
||||
|
||||
List<SingleChildWidget> get providers {
|
||||
return [Provider(create: (context) => WebsocketClient())];
|
||||
}
|
||||
47
lib/data/repositories/owner_repository.dart
Normal file
47
lib/data/repositories/owner_repository.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:seshat/data/services/api_client.dart';
|
||||
import 'package:seshat/data/services/websocket_client.dart';
|
||||
import 'package:seshat/domain/models/owner.dart';
|
||||
import 'package:seshat/utils/result.dart';
|
||||
|
||||
class OwnerRepository {
|
||||
OwnerRepository({
|
||||
required ApiClient apiClient,
|
||||
required WebsocketClient wsClient,
|
||||
}) : _apiClient = apiClient,
|
||||
_wsClient = wsClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
final WebsocketClient _wsClient;
|
||||
List<Owner>? _cachedData;
|
||||
|
||||
Future<Result<List<Owner>>> getOwners() async {
|
||||
if (_cachedData == null) {
|
||||
final result = await _apiClient.getOwners();
|
||||
|
||||
if (result is Ok<List<Owner>>) {
|
||||
_cachedData = result.value;
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return Result.ok(_cachedData!);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Owner> liveOwners() async* {
|
||||
await for (String data in _wsClient.connect()) {
|
||||
Map<String, dynamic> decodedData = jsonDecode(
|
||||
data,
|
||||
).cast<Map<String, dynamic>>();
|
||||
Owner owner = Owner.fromJSON(decodedData);
|
||||
if (_cachedData == null) {
|
||||
getOwners();
|
||||
} else {
|
||||
_cachedData!.add(owner);
|
||||
yield* Stream.value(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
lib/data/services/api_client.dart
Normal file
8
lib/data/services/api_client.dart
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import 'package:seshat/domain/models/owner.dart';
|
||||
import 'package:seshat/utils/result.dart';
|
||||
|
||||
class ApiClient {
|
||||
Future<Result<List<Owner>>> getOwners() async {
|
||||
return Result.ok(<Owner>[]);
|
||||
}
|
||||
}
|
||||
10
lib/data/services/websocket_client.dart
Normal file
10
lib/data/services/websocket_client.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
class WebsocketClient {
|
||||
static const String _host = "ws://bal.ninjdai.fr:3000";
|
||||
|
||||
Stream<dynamic> connect() {
|
||||
final channel = WebSocketChannel.connect(Uri.parse("$_host/ws"));
|
||||
return channel.stream;
|
||||
}
|
||||
}
|
||||
22
lib/domain/models/owner.dart
Normal file
22
lib/domain/models/owner.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class Owner extends ChangeNotifier {
|
||||
Owner({
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.contact,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
String firstName;
|
||||
String lastName;
|
||||
String contact;
|
||||
int id;
|
||||
|
||||
factory Owner.fromJSON(Map<String, dynamic> json) => Owner(
|
||||
contact: json["contact"],
|
||||
firstName: json["first_name"],
|
||||
lastName: json["last_name"],
|
||||
id: json["id"],
|
||||
);
|
||||
}
|
||||
81
lib/main.dart
Normal file
81
lib/main.dart
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:seshat/config/dependencies.dart';
|
||||
import 'package:seshat/routing/router.dart';
|
||||
|
||||
// TODO: In router, make it so that the navbar is integrated in everyscreen that needs it -> consistancy (should be at least, hope so) so it stops jumping. Then, make it so that the pages are children of scaffold. That's all ?
|
||||
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
|
||||
// runApp(MultiProvider(providers: providers, child: const MyApp()));
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(routerConfig: router());
|
||||
}
|
||||
}
|
||||
|
||||
// class Root extends StatefulWidget {
|
||||
// const Root({super.key});
|
||||
|
||||
// @override
|
||||
// State<Root> createState() => _RootState();
|
||||
// }
|
||||
|
||||
// class _RootState extends State<Root> {
|
||||
// var selectedIndex = 1;
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final theme = Theme.of(context);
|
||||
// return AnnotatedRegion(
|
||||
// value: SystemUiOverlayStyle(
|
||||
// statusBarBrightness: Brightness.light,
|
||||
// statusBarIconBrightness: Brightness.light,
|
||||
// ),
|
||||
// child: Scaffold(
|
||||
// appBar: null,
|
||||
// bottomNavigationBar: NavigationBar(
|
||||
// destinations: const <Widget>[
|
||||
// NavigationDestination(icon: Icon(Icons.home), label: "Home"),
|
||||
// NavigationDestination(
|
||||
// icon: Icon(Icons.plus_one_outlined),
|
||||
// label: "Ajout",
|
||||
// ),
|
||||
// NavigationDestination(icon: Icon(Icons.sell), label: "Vente"),
|
||||
// ],
|
||||
// selectedIndex: selectedIndex,
|
||||
// onDestinationSelected: (int index) {
|
||||
// setState(() {
|
||||
// selectedIndex = index;
|
||||
// });
|
||||
// },
|
||||
// indicatorColor: theme.highlightColor,
|
||||
// ),
|
||||
// body: switch (selectedIndex) {
|
||||
// 0 => Add(title: "Home Page"),
|
||||
// 1 => MultiProvider(
|
||||
// providers: [
|
||||
// ChangeNotifierProvider(
|
||||
// create: (context) => TabScreen("scanPage"),
|
||||
// ),
|
||||
// ChangeNotifierProvider(
|
||||
// create: (context) =>
|
||||
// Owner("UEAuvergne", "unionetudianteauvergne@gmail.com"),
|
||||
// ),
|
||||
// ],
|
||||
// child: Add(title: "Ajout"),
|
||||
// ),
|
||||
// _ => Add(title: "Wildcard"),
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
34
lib/routing/router.dart
Normal file
34
lib/routing/router.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:seshat/routing/routes.dart';
|
||||
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
||||
import 'package:seshat/ui/add_page/widgets/add_page.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
import 'package:seshat/ui/home_page/home_page.dart';
|
||||
import 'package:seshat/ui/sell_page/sell_page.dart';
|
||||
|
||||
GoRouter router() => GoRouter(
|
||||
initialLocation: Routes.add,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.home,
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: HomePage()),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.add,
|
||||
pageBuilder: (context, state) =>
|
||||
NoTransitionPage(child: AddPage(viewModel: AddViewModel())),
|
||||
// routes: [
|
||||
// GoRoute(path: Routes.addForm),
|
||||
// GoRoute(path: Routes.addOwner),
|
||||
// GoRoute(path: Routes.addPrice),
|
||||
// ],
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.sell,
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: SellPage()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
13
lib/routing/routes.dart
Normal file
13
lib/routing/routes.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
abstract final class Routes {
|
||||
// ==[ HOME ]==
|
||||
static const home = '/';
|
||||
|
||||
// ==[ ADD ]==
|
||||
static const add = '/add';
|
||||
static const addOwner = '/add/owner';
|
||||
static const addPrice = '/add/price';
|
||||
static const addForm = '/add/form';
|
||||
|
||||
// ==[ SELL ]==
|
||||
static const sell = '/sell';
|
||||
}
|
||||
75
lib/ui/add_page/view_model/add_view_model.dart
Normal file
75
lib/ui/add_page/view_model/add_view_model.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:seshat/domain/models/owner.dart';
|
||||
|
||||
class AddViewModel extends ChangeNotifier {
|
||||
AddViewModel();
|
||||
|
||||
final _log = Logger("AddViewModel");
|
||||
|
||||
Owner? _currentOwner;
|
||||
Owner? get currentOwner => _currentOwner;
|
||||
set currentOwner(Owner? owner) {
|
||||
_currentOwner = owner;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<Owner>? _owners = [
|
||||
Owner(
|
||||
firstName: "Jean",
|
||||
lastName: "Henri",
|
||||
contact: "contact@gmail.com",
|
||||
id: 1,
|
||||
),
|
||||
Owner(
|
||||
firstName: "Jeanette",
|
||||
lastName: "Henriette",
|
||||
contact: "contact@gmail.com",
|
||||
id: 2,
|
||||
),
|
||||
Owner(
|
||||
firstName: "Jacques",
|
||||
lastName: "Gerard",
|
||||
contact: "contact@gmail.com",
|
||||
id: 3,
|
||||
),
|
||||
Owner(
|
||||
firstName: "Jacquelines",
|
||||
lastName: "Geraldine",
|
||||
contact: "contact@gmail.com",
|
||||
id: 4,
|
||||
),
|
||||
Owner(
|
||||
firstName: "Louis",
|
||||
lastName: "Valentin",
|
||||
contact: "contact@gmail.com",
|
||||
id: 5,
|
||||
),
|
||||
Owner(
|
||||
firstName: "Louise",
|
||||
lastName: "Valentine",
|
||||
contact: "contact@gmail.com",
|
||||
id: 6,
|
||||
),
|
||||
];
|
||||
|
||||
List<Owner>? get owners => _owners;
|
||||
Owner addOwner(String firstName, String lastName, String contact) {
|
||||
_owners!.add(
|
||||
Owner(
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
contact: contact,
|
||||
id: _owners!.last.id + 1,
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
return Owner(
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
contact: contact,
|
||||
id: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
120
lib/ui/add_page/widgets/add_page.dart
Normal file
120
lib/ui/add_page/widgets/add_page.dart
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
||||
import 'package:seshat/ui/add_page/widgets/owner_popup.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
|
||||
class AddPage extends StatefulWidget {
|
||||
const AddPage({super.key, required this.viewModel});
|
||||
|
||||
final AddViewModel viewModel;
|
||||
|
||||
@override
|
||||
State<AddPage> createState() => _AddPageState();
|
||||
}
|
||||
|
||||
class _AddPageState extends State<AddPage> {
|
||||
bool askPrice = true;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MobileScannerController controller = MobileScannerController(
|
||||
formats: [BarcodeFormat.ean13],
|
||||
detectionTimeoutMs: 1000,
|
||||
);
|
||||
// return Consumer<TabScreen>(
|
||||
// builder: (context, screen, child) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: AppNavigationBar(startIndex: 1),
|
||||
body: Stack(
|
||||
children: [
|
||||
ColoredBox(color: Colors.black),
|
||||
MobileScanner(
|
||||
controller: controller,
|
||||
onDetect: (barcodes) {
|
||||
onBarcodeScan(barcodes, controller);
|
||||
},
|
||||
),
|
||||
SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 50),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(Icons.person),
|
||||
title: TextButton(
|
||||
child: Text("No"),
|
||||
onPressed: () => _ownerDialogBuilder(
|
||||
context,
|
||||
controller,
|
||||
widget.viewModel,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.attach_money),
|
||||
title: TextButton(
|
||||
child: Text(
|
||||
(askPrice)
|
||||
? "Demander à chaque fois"
|
||||
: "Prix libre toujours",
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
askPrice = !askPrice;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: SizedBox()),
|
||||
SvgPicture.asset('assets/scan-overlay.svg'),
|
||||
Expanded(child: SizedBox()),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text("Enregistrer manuellement"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
// },
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
void onBarcodeScan(
|
||||
BarcodeCapture barcodes,
|
||||
MobileScannerController controller,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> _ownerDialogBuilder(
|
||||
BuildContext context,
|
||||
MobileScannerController controller,
|
||||
AddViewModel viewModel,
|
||||
) {
|
||||
controller.stop();
|
||||
|
||||
void onPressAccept(BuildContext localContext) {
|
||||
controller.start();
|
||||
Navigator.of(localContext).pop();
|
||||
}
|
||||
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) =>
|
||||
OwnerPopup(viewModel: viewModel, onPressAccept: onPressAccept),
|
||||
);
|
||||
}
|
||||
0
lib/ui/add_page/widgets/form_screen.dart
Normal file
0
lib/ui/add_page/widgets/form_screen.dart
Normal file
172
lib/ui/add_page/widgets/owner_popup.dart
Normal file
172
lib/ui/add_page/widgets/owner_popup.dart
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:seshat/domain/models/owner.dart';
|
||||
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
|
||||
|
||||
class OwnerPopup extends StatefulWidget {
|
||||
const OwnerPopup({
|
||||
super.key,
|
||||
required this.viewModel,
|
||||
required this.onPressAccept,
|
||||
});
|
||||
|
||||
final AddViewModel viewModel;
|
||||
final Function(BuildContext) onPressAccept;
|
||||
|
||||
@override
|
||||
State<OwnerPopup> createState() => _OwnerPopupState();
|
||||
}
|
||||
|
||||
class _OwnerPopupState extends State<OwnerPopup> {
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
bool showNewOwner = false;
|
||||
String? firstName;
|
||||
String? lastName;
|
||||
String? contact;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return ListenableBuilder(
|
||||
listenable: widget.viewModel,
|
||||
builder: (context, child) => AlertDialog(
|
||||
title: Center(child: Text("Propriétaire du livre")),
|
||||
content: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
(widget.viewModel.currentOwner == null)
|
||||
? "Choix actuel : aucun"
|
||||
: "Choix actuel : ${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
(showNewOwner)
|
||||
? SizedBox()
|
||||
: DropdownMenu<Owner>(
|
||||
enableFilter: true,
|
||||
label: Text("Rechercher un·e propriétaire"),
|
||||
dropdownMenuEntries: [
|
||||
for (var owner in widget.viewModel.owners!)
|
||||
DropdownMenuEntry(
|
||||
value: owner,
|
||||
label: "${owner.firstName} ${owner.lastName}",
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
(widget.viewModel.currentOwner == owner)
|
||||
? WidgetStatePropertyAll<Color>(
|
||||
theme.highlightColor,
|
||||
)
|
||||
: WidgetStatePropertyAll<Color>(
|
||||
theme.canvasColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
initialSelection: widget.viewModel.currentOwner,
|
||||
onSelected: (Owner? owner) {
|
||||
widget.viewModel.currentOwner = owner;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
showNewOwner = !showNewOwner;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
(showNewOwner) ? "Annuler" : "Ajouter un propriétaire",
|
||||
),
|
||||
),
|
||||
(!showNewOwner)
|
||||
? SizedBox()
|
||||
: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Nom",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSaved: (newValue) {
|
||||
setState(() {
|
||||
lastName = newValue;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Indiquez un nom";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Prénom",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSaved: (newValue) {
|
||||
setState(() {
|
||||
firstName = newValue;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Indiquez un prénom";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Contact",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSaved: (newValue) {
|
||||
setState(() {
|
||||
contact = newValue;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "Indiquez un moyen de contact";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_formKey.currentState!.save();
|
||||
setState(() {
|
||||
widget.viewModel.currentOwner = widget.viewModel
|
||||
.addOwner(firstName!, lastName!, contact!);
|
||||
showNewOwner = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text("Créer"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
widget.onPressAccept(context);
|
||||
},
|
||||
child: Text("Valider"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
75
lib/ui/add_page/widgets/scan_screen.dart
Normal file
75
lib/ui/add_page/widgets/scan_screen.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// class ScanPage extends StatefulWidget {
|
||||
// const ScanPage({super.key});
|
||||
|
||||
// @override
|
||||
// State<ScanPage> createState() => _ScanPage();
|
||||
// }
|
||||
|
||||
// class _ScanPage extends State<ScanPage> {
|
||||
// final MobileScannerController controller = MobileScannerController(
|
||||
// detectionTimeoutMs: 1000,
|
||||
// );
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Stack(
|
||||
// children: <Widget>[
|
||||
// MobileScanner(
|
||||
// controller: controller,
|
||||
// onDetect: (result) {
|
||||
// print(result.barcodes.first.rawValue);
|
||||
// },
|
||||
// ),
|
||||
// SafeArea(
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// children: [
|
||||
// Center(
|
||||
// child: Card(
|
||||
// margin: EdgeInsets.symmetric(horizontal: 50),
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Consumer<TabScreen>(
|
||||
// builder: (context, screen, child) {
|
||||
// return ListTile(
|
||||
// leading: Icon(Icons.person),
|
||||
// title: TextButton(
|
||||
// child: Text("No"),
|
||||
// onPressed: () {
|
||||
// screen.change("ownerPage");
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ListTile(
|
||||
// leading: Icon(Icons.attach_money),
|
||||
// title: TextButton(
|
||||
// child: Text("Demander à chaque fois"),
|
||||
// onPressed: () {
|
||||
// return;
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Expanded(child: SizedBox()),
|
||||
// SvgPicture.asset('assets/scan-overlay.svg'),
|
||||
// Expanded(child: SizedBox()),
|
||||
// TextButton(
|
||||
// onPressed: () {},
|
||||
// child: Text("Enregistrer manuellement"),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
48
lib/ui/core/ui/navigation_bar.dart
Normal file
48
lib/ui/core/ui/navigation_bar.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class AppNavigationBar extends StatefulWidget {
|
||||
const AppNavigationBar({super.key, required this.startIndex});
|
||||
|
||||
final int startIndex;
|
||||
|
||||
@override
|
||||
State<AppNavigationBar> createState() => _AppNavigationBarState();
|
||||
}
|
||||
|
||||
class _AppNavigationBarState extends State<AppNavigationBar> {
|
||||
final Logger _log = Logger("chose");
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var selectedIndex = widget.startIndex;
|
||||
_log.info("We at $selectedIndex");
|
||||
return NavigationBar(
|
||||
destinations: [
|
||||
NavigationDestination(icon: Icon(Icons.home), label: "Home"),
|
||||
NavigationDestination(icon: Icon(Icons.add), label: "Ajout"),
|
||||
NavigationDestination(icon: Icon(Icons.remove), label: "Vente"),
|
||||
],
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: (index) {
|
||||
setState(() {
|
||||
selectedIndex = index;
|
||||
});
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/');
|
||||
break;
|
||||
case 1:
|
||||
context.go('/add');
|
||||
break;
|
||||
case 2:
|
||||
context.go('/sell');
|
||||
break;
|
||||
default:
|
||||
context.go("/");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
16
lib/ui/home_page/home_page.dart
Normal file
16
lib/ui/home_page/home_page.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: AppNavigationBar(startIndex: 0),
|
||||
body: Center(child: Text("Home page.")),
|
||||
);
|
||||
// return Center(child: Text("Home page;"));
|
||||
}
|
||||
}
|
||||
16
lib/ui/sell_page/sell_page.dart
Normal file
16
lib/ui/sell_page/sell_page.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||
|
||||
class SellPage extends StatelessWidget {
|
||||
const SellPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: AppNavigationBar(startIndex: 2),
|
||||
body: Center(child: Text("Sell page.")),
|
||||
);
|
||||
// return Center(child: Text("Sell page."));
|
||||
}
|
||||
}
|
||||
97
lib/utils/command.dart
Normal file
97
lib/utils/command.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'result.dart';
|
||||
|
||||
typedef CommandAction0<T> = Future<Result<T>> Function();
|
||||
typedef CommandAction1<T, A> = Future<Result<T>> Function(A);
|
||||
|
||||
/// Facilitates interaction with a ViewModel.
|
||||
///
|
||||
/// Encapsulates an action,
|
||||
/// exposes its running and error states,
|
||||
/// and ensures that it can't be launched again until it finishes.
|
||||
///
|
||||
/// Use [Command0] for actions without arguments.
|
||||
/// Use [Command1] for actions with one argument.
|
||||
///
|
||||
/// Actions must return a [Result].
|
||||
///
|
||||
/// Consume the action result by listening to changes,
|
||||
/// then call to [clearResult] when the state is consumed.
|
||||
abstract class Command<T> extends ChangeNotifier {
|
||||
Command();
|
||||
|
||||
bool _running = false;
|
||||
|
||||
/// True when the action is running.
|
||||
bool get running => _running;
|
||||
|
||||
Result<T>? _result;
|
||||
|
||||
/// true if action completed with error
|
||||
bool get error => _result is Error;
|
||||
|
||||
/// true if action completed successfully
|
||||
bool get completed => _result is Ok;
|
||||
|
||||
/// Get last action result
|
||||
Result? get result => _result;
|
||||
|
||||
/// Clear last action result
|
||||
void clearResult() {
|
||||
_result = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Internal execute implementation
|
||||
Future<void> _execute(CommandAction0<T> action) async {
|
||||
// Ensure the action can't launch multiple times.
|
||||
// e.g. avoid multiple taps on button
|
||||
if (_running) return;
|
||||
|
||||
// Notify listeners.
|
||||
// e.g. button shows loading state
|
||||
_running = true;
|
||||
_result = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_result = await action();
|
||||
} finally {
|
||||
_running = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [Command] without arguments.
|
||||
/// Takes a [CommandAction0] as action.
|
||||
class Command0<T> extends Command<T> {
|
||||
Command0(this._action);
|
||||
|
||||
final CommandAction0<T> _action;
|
||||
|
||||
/// Executes the action.
|
||||
Future<void> execute() async {
|
||||
await _execute(_action);
|
||||
}
|
||||
}
|
||||
|
||||
/// [Command] with one argument.
|
||||
/// Takes a [CommandAction1] as action.
|
||||
class Command1<T, A> extends Command<T> {
|
||||
Command1(this._action);
|
||||
|
||||
final CommandAction1<T, A> _action;
|
||||
|
||||
/// Executes the action with the argument.
|
||||
Future<void> execute(A argument) async {
|
||||
await _execute(() => _action(argument));
|
||||
}
|
||||
}
|
||||
48
lib/utils/result.dart
Normal file
48
lib/utils/result.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2024 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// Utility class to wrap result data
|
||||
///
|
||||
/// Evaluate the result using a switch statement:
|
||||
/// ```dart
|
||||
/// switch (result) {
|
||||
/// case Ok(): {
|
||||
/// print(result.value);
|
||||
/// }
|
||||
/// case Error(): {
|
||||
/// print(result.error);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
sealed class Result<T> {
|
||||
const Result();
|
||||
|
||||
/// Creates a successful [Result], completed with the specified [value].
|
||||
const factory Result.ok(T value) = Ok._;
|
||||
|
||||
/// Creates an error [Result], completed with the specified [error].
|
||||
const factory Result.error(Exception error) = Error._;
|
||||
}
|
||||
|
||||
/// Subclass of Result for values
|
||||
final class Ok<T> extends Result<T> {
|
||||
const Ok._(this.value);
|
||||
|
||||
/// Returned value in result
|
||||
final T value;
|
||||
|
||||
@override
|
||||
String toString() => 'Result<$T>.ok($value)';
|
||||
}
|
||||
|
||||
/// Subclass of Result for errors
|
||||
final class Error<T> extends Result<T> {
|
||||
const Error._(this.error);
|
||||
|
||||
/// Returned error in result
|
||||
final Exception error;
|
||||
|
||||
@override
|
||||
String toString() => 'Result<$T>.error($error)';
|
||||
}
|
||||
Reference in a new issue