feat: add an owner + sell screen
This commit is contained in:
parent
d2cbb43bcb
commit
073f8bd334
15 changed files with 354 additions and 82 deletions
|
|
@ -10,7 +10,7 @@ import "package:seshat/data/services/websocket_client.dart";
|
||||||
List<SingleChildWidget> get providers {
|
List<SingleChildWidget> get providers {
|
||||||
return [
|
return [
|
||||||
Provider(create: (context) => AuthClient()),
|
Provider(create: (context) => AuthClient()),
|
||||||
Provider(create: (context) => ApiClient(authClient: context.read())),
|
Provider(create: (context) => ApiClient()),
|
||||||
Provider(create: (context) => WebsocketClient()),
|
Provider(create: (context) => WebsocketClient()),
|
||||||
Provider(
|
Provider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
|
|
|
||||||
1
lib/data/repositories/book_instance_repository.dart
Normal file
1
lib/data/repositories/book_instance_repository.dart
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
class BookInstanceRepository {}
|
||||||
1
lib/data/repositories/book_repository.dart
Normal file
1
lib/data/repositories/book_repository.dart
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
class BookRepository {}
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:seshat/data/services/api_client.dart';
|
import 'package:seshat/data/services/api_client.dart';
|
||||||
import 'package:seshat/data/services/websocket_client.dart';
|
import 'package:seshat/data/services/websocket_client.dart';
|
||||||
import 'package:seshat/domain/models/owner.dart';
|
import 'package:seshat/domain/models/owner.dart';
|
||||||
|
|
@ -17,22 +14,25 @@ class OwnerRepository {
|
||||||
|
|
||||||
final ApiClient _apiClient;
|
final ApiClient _apiClient;
|
||||||
final WebsocketClient _wsClient;
|
final WebsocketClient _wsClient;
|
||||||
final BehaviorSubject<Owner> _ownersController = BehaviorSubject<Owner>(
|
|
||||||
sync: true,
|
|
||||||
);
|
|
||||||
late final StreamSubscription sub;
|
late final StreamSubscription sub;
|
||||||
List<Owner>? _cachedOwners;
|
List<Owner>? _cachedOwners;
|
||||||
|
|
||||||
Future<Result<Owner>> postOwner(
|
/// Adds an [Owner] to the database, and gets the resulting [Owner].
|
||||||
|
Future<Result<Owner>> addOwner(
|
||||||
String firstName,
|
String firstName,
|
||||||
String lastName,
|
String lastName,
|
||||||
String contact,
|
String contact,
|
||||||
) async {
|
) async {
|
||||||
return Result.ok(
|
var response = await _apiClient.addOwner(firstName, lastName, contact);
|
||||||
Owner(firstName: firstName, lastName: lastName, contact: contact, id: 50),
|
switch (response) {
|
||||||
);
|
case Ok():
|
||||||
|
return Result.ok(response.value);
|
||||||
|
case Error():
|
||||||
|
return Result.error(response.error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches all the [Owner]s from the database, and subscribes to updates
|
||||||
Future<Result<List<Owner>>> getOwners() async {
|
Future<Result<List<Owner>>> getOwners() async {
|
||||||
if (_cachedOwners == null) {
|
if (_cachedOwners == null) {
|
||||||
final result = await _apiClient.getOwners();
|
final result = await _apiClient.getOwners();
|
||||||
|
|
@ -43,9 +43,7 @@ class OwnerRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub = _wsClient.owners.listen((owner) {
|
sub = _wsClient.owners.listen((owner) {
|
||||||
debugPrint("\n\n\n\n[3] Added : $owner\n\n\n\n");
|
|
||||||
_cachedOwners!.add(owner);
|
_cachedOwners!.add(owner);
|
||||||
_ownersController.add(owner);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -54,8 +52,6 @@ class OwnerRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Owner> get liveOwners => _ownersController.stream;
|
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
sub.cancel();
|
sub.cancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:seshat/config/constants.dart';
|
import 'package:seshat/config/constants.dart';
|
||||||
import 'package:seshat/data/services/auth_client.dart';
|
|
||||||
import 'package:seshat/domain/models/owner.dart';
|
import 'package:seshat/domain/models/owner.dart';
|
||||||
import 'package:seshat/utils/command.dart';
|
import 'package:seshat/utils/command.dart';
|
||||||
import 'package:seshat/utils/result.dart';
|
import 'package:seshat/utils/result.dart';
|
||||||
|
|
@ -12,14 +10,8 @@ import 'package:seshat/utils/result.dart';
|
||||||
typedef AuthHeaderProvider = String? Function();
|
typedef AuthHeaderProvider = String? Function();
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
ApiClient({
|
ApiClient({String? host, int? port});
|
||||||
String? host,
|
|
||||||
int? port,
|
|
||||||
HttpClient Function()? clientFactory,
|
|
||||||
required AuthClient authClient,
|
|
||||||
}) : _authClient = authClient;
|
|
||||||
|
|
||||||
final AuthClient _authClient;
|
|
||||||
late final Command0 load;
|
late final Command0 load;
|
||||||
String? token;
|
String? token;
|
||||||
bool isReady = false;
|
bool isReady = false;
|
||||||
|
|
@ -31,26 +23,65 @@ class ApiClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ====================
|
||||||
|
* =====[ OWNERS ]=====
|
||||||
|
* ====================
|
||||||
|
*/
|
||||||
|
|
||||||
Future<Result<List<Owner>>> getOwners() async {
|
Future<Result<List<Owner>>> getOwners() async {
|
||||||
final client = HttpClient();
|
final client = Client();
|
||||||
try {
|
try {
|
||||||
await _initStore();
|
await _initStore();
|
||||||
final request = await client.getUrl(
|
|
||||||
Uri.parse("https://$apiBasePath/owners"),
|
|
||||||
);
|
|
||||||
final token = await _secureStorage!.read(key: "token");
|
final token = await _secureStorage!.read(key: "token");
|
||||||
debugPrint("\n\n\n\nFOUND TOKEN : $token\n\n\n\n");
|
final headers = {"Authorization": "Bearer $token"};
|
||||||
// await _authHeader(request.headers);
|
final response = await client.get(
|
||||||
request.headers.add(HttpHeaders.authorizationHeader, "Bearer $token");
|
Uri.parse("https://$apiBasePath/owners"),
|
||||||
final response = await request.close();
|
headers: headers,
|
||||||
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final stringData = await response.transform(Utf8Decoder()).join();
|
final json = jsonDecode(response.body) as List<dynamic>;
|
||||||
final json = jsonDecode(stringData) as List<dynamic>;
|
|
||||||
return Result.ok(
|
return Result.ok(
|
||||||
json.map((element) => Owner.fromJSON(element)).toList(),
|
json.map((element) => Owner.fromJSON(element)).toList(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Result.error(HttpException("Invalid response"));
|
return Result.error(Exception("Invalid request"));
|
||||||
|
}
|
||||||
|
} on Exception catch (error) {
|
||||||
|
return Result.error(error);
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<Owner>> addOwner(
|
||||||
|
String firstName,
|
||||||
|
String lastName,
|
||||||
|
String contact,
|
||||||
|
) async {
|
||||||
|
final client = Client();
|
||||||
|
try {
|
||||||
|
await _initStore();
|
||||||
|
final token = await _secureStorage!.read(key: "token");
|
||||||
|
final headers = {
|
||||||
|
"Authorization": "Bearer $token",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
final body = {
|
||||||
|
"first_name": firstName,
|
||||||
|
"last_name": lastName,
|
||||||
|
"contact": contact,
|
||||||
|
};
|
||||||
|
final response = await client.post(
|
||||||
|
Uri.parse("https://$apiBasePath/owner"),
|
||||||
|
headers: headers,
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 201) {
|
||||||
|
final json = jsonDecode(response.body);
|
||||||
|
return Result.ok(Owner.fromJSON(json));
|
||||||
|
} else {
|
||||||
|
return Result.error(Exception("Invalid request"));
|
||||||
}
|
}
|
||||||
} on Exception catch (error) {
|
} on Exception catch (error) {
|
||||||
return Result.error(error);
|
return Result.error(error);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import 'package:seshat/ui/add_page/widgets/add_page.dart';
|
||||||
import 'package:seshat/ui/auth/viewmodel/login_view_model.dart';
|
import 'package:seshat/ui/auth/viewmodel/login_view_model.dart';
|
||||||
import 'package:seshat/ui/auth/widgets/login_page.dart';
|
import 'package:seshat/ui/auth/widgets/login_page.dart';
|
||||||
import 'package:seshat/ui/home_page/home_page.dart';
|
import 'package:seshat/ui/home_page/home_page.dart';
|
||||||
import 'package:seshat/ui/sell_page/sell_page.dart';
|
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/widgets/sell_page.dart';
|
||||||
|
|
||||||
GoRouter router(AuthRepository authRepository) => GoRouter(
|
GoRouter router(AuthRepository authRepository) => GoRouter(
|
||||||
initialLocation: Routes.add,
|
initialLocation: Routes.add,
|
||||||
|
|
@ -45,7 +46,10 @@ GoRouter router(AuthRepository authRepository) => GoRouter(
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: Routes.sell,
|
path: Routes.sell,
|
||||||
pageBuilder: (context, state) => NoTransitionPage(child: SellPage()),
|
pageBuilder: (context, state) {
|
||||||
|
final viewModel = SellViewModel();
|
||||||
|
return NoTransitionPage(child: SellPage(viewModel: viewModel));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: Routes.login,
|
path: Routes.login,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ abstract final class Routes {
|
||||||
|
|
||||||
// ==[ ADD ]==
|
// ==[ ADD ]==
|
||||||
static const add = '/add';
|
static const add = '/add';
|
||||||
static const addOwner = '/add/owner';
|
|
||||||
static const addPrice = '/add/price';
|
|
||||||
static const addForm = '/add/form';
|
|
||||||
|
|
||||||
// ==[ SELL ]==
|
// ==[ SELL ]==
|
||||||
static const sell = '/sell';
|
static const sell = '/sell';
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,14 @@ class AddViewModel extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Owner> _owners = [];
|
List<Owner> _owners = [];
|
||||||
|
|
||||||
List<Owner>? get owners => _owners;
|
List<Owner>? get owners => _owners;
|
||||||
|
|
||||||
Future<Result<Owner>> addOwner(
|
Future<Result<Owner>> addOwner(
|
||||||
String firstName,
|
String firstName,
|
||||||
String lastName,
|
String lastName,
|
||||||
String contact,
|
String contact,
|
||||||
) async {
|
) async {
|
||||||
final result = await _ownerRepository.postOwner(
|
debugPrint("\n\n\n\n(2) TRANFERRING\n\n\n\n");
|
||||||
|
final result = await _ownerRepository.addOwner(
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
contact,
|
contact,
|
||||||
|
|
@ -52,7 +51,9 @@ class AddViewModel extends ChangeNotifier {
|
||||||
|
|
||||||
switch (secondResult) {
|
switch (secondResult) {
|
||||||
case Ok():
|
case Ok():
|
||||||
|
debugPrint("\n\n\n${secondResult.value.length}");
|
||||||
_owners = secondResult.value;
|
_owners = secondResult.value;
|
||||||
|
debugPrint("\n\n\n${_owners.length}");
|
||||||
_currentOwner = result.value;
|
_currentOwner = result.value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return Result.ok(result.value);
|
return Result.ok(result.value);
|
||||||
|
|
@ -130,16 +131,7 @@ class AddViewModel extends ChangeNotifier {
|
||||||
debugPrint("Oupsie daysie, ${result.error}");
|
debugPrint("Oupsie daysie, ${result.error}");
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
sub = _ownerRepository.liveOwners.listen((Owner owner) {
|
|
||||||
debugPrint("\n\n\n\n[5] Updated UI : $owner\n\n\n\n");
|
|
||||||
_owners.add(owner);
|
|
||||||
_owners.sort(
|
|
||||||
(a, b) => "${a.firstName} ${a.lastName}".compareTo(
|
|
||||||
"${b.firstName} ${b.lastName}",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
notifyListeners();
|
|
||||||
});
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ class _AddPageState extends State<AddPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
SizedBox(height: 5),
|
||||||
Center(
|
Center(
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 50),
|
margin: EdgeInsets.symmetric(horizontal: 50),
|
||||||
|
|
@ -132,12 +133,11 @@ class _AddPageState extends State<AddPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 100),
|
|
||||||
SvgPicture.asset('assets/scan-overlay.svg'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Center(child: SvgPicture.asset('assets/scan-overlay.svg')),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
|
@ -157,6 +157,7 @@ class _AddPageState extends State<AddPage> {
|
||||||
child: Text("Enregistrer manuellement"),
|
child: Text("Enregistrer manuellement"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class OwnerPopup extends StatefulWidget {
|
||||||
|
|
||||||
class _OwnerPopupState extends State<OwnerPopup> {
|
class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
bool showNewOwner = false;
|
bool showNewOwner = false;
|
||||||
String? firstName;
|
String? firstName;
|
||||||
String? lastName;
|
String? lastName;
|
||||||
|
|
@ -25,6 +26,9 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
searchController.text = (widget.viewModel.currentOwner == null)
|
||||||
|
? ""
|
||||||
|
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}";
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: widget.viewModel,
|
listenable: widget.viewModel,
|
||||||
|
|
@ -35,19 +39,14 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
(widget.viewModel.currentOwner == null)
|
|
||||||
? "Choix actuel : aucun"
|
|
||||||
: "Choix actuel : ${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
(showNewOwner || widget.viewModel.owners!.isEmpty)
|
(showNewOwner || widget.viewModel.owners!.isEmpty)
|
||||||
? SizedBox()
|
? SizedBox()
|
||||||
: DropdownMenu<Owner>(
|
: DropdownMenu<Owner>(
|
||||||
enableFilter: true,
|
enableFilter: true,
|
||||||
|
controller: searchController,
|
||||||
label: Text("Rechercher un·e propriétaire"),
|
label: Text("Rechercher un·e propriétaire"),
|
||||||
|
requestFocusOnTap: true,
|
||||||
dropdownMenuEntries: [
|
dropdownMenuEntries: [
|
||||||
for (var owner in widget.viewModel.owners!)
|
for (var owner in widget.viewModel.owners!)
|
||||||
DropdownMenuEntry(
|
DropdownMenuEntry(
|
||||||
|
|
@ -65,7 +64,6 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
initialSelection: widget.viewModel.currentOwner,
|
|
||||||
onSelected: (Owner? owner) {
|
onSelected: (Owner? owner) {
|
||||||
widget.viewModel.currentOwner = owner;
|
widget.viewModel.currentOwner = owner;
|
||||||
},
|
},
|
||||||
|
|
@ -151,6 +149,7 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
if (showNewOwner) {
|
if (showNewOwner) {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
|
debugPrint("\n\n\n\n(1) SENDING REQUEST\n\n\n\n");
|
||||||
await widget.viewModel.addOwner(
|
await widget.viewModel.addOwner(
|
||||||
firstName!,
|
firstName!,
|
||||||
lastName!,
|
lastName!,
|
||||||
|
|
@ -160,9 +159,8 @@ class _OwnerPopupState extends State<OwnerPopup> {
|
||||||
showNewOwner = false;
|
showNewOwner = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
widget.onPressAccept(context);
|
|
||||||
}
|
}
|
||||||
|
widget.onPressAccept(context);
|
||||||
},
|
},
|
||||||
child: Text("Valider"),
|
child: Text("Valider"),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
39
lib/ui/sell_page/view_model/sell_view_model.dart
Normal file
39
lib/ui/sell_page/view_model/sell_view_model.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import 'package:seshat/domain/models/book_instance.dart';
|
||||||
|
|
||||||
|
class SellViewModel extends ChangeNotifier {
|
||||||
|
SellViewModel();
|
||||||
|
|
||||||
|
bool _showScan = false;
|
||||||
|
bool get showScan => _showScan;
|
||||||
|
set showScan(bool newValue) {
|
||||||
|
_showScan = newValue;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<BookInstance> _scannedBooks = [];
|
||||||
|
get scannedBooks => _scannedBooks;
|
||||||
|
void scanBook(BarcodeCapture barcode) {
|
||||||
|
final addedBook = BookInstance(
|
||||||
|
balId: 5,
|
||||||
|
bookId: 5,
|
||||||
|
id: _scannedBooks.length,
|
||||||
|
ownerId: 5,
|
||||||
|
price: 5,
|
||||||
|
status: true,
|
||||||
|
);
|
||||||
|
_scannedBooks.add(addedBook);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendSell() {
|
||||||
|
_scannedBooks.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteBook(int id) {
|
||||||
|
_scannedBooks.removeWhere((book) => book.id == id);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
lib/ui/sell_page/widgets/manual_scan_popup.dart
Normal file
39
lib/ui/sell_page/widgets/manual_scan_popup.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||||
|
|
||||||
|
class ManualScanPopup extends StatelessWidget {
|
||||||
|
ManualScanPopup({required this.viewModel});
|
||||||
|
|
||||||
|
final SellViewModel viewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text("Recherche manuelle"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Rechercher",
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: Icon(Icons.arrow_forward),
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 200),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text("Annuler"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
lib/ui/sell_page/widgets/scan_screen.dart
Normal file
73
lib/ui/sell_page/widgets/scan_screen.dart
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/widgets/manual_scan_popup.dart';
|
||||||
|
|
||||||
|
class ScanScreen extends StatefulWidget {
|
||||||
|
ScanScreen({super.key, required this.viewModel});
|
||||||
|
|
||||||
|
SellViewModel viewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScanScreen> createState() => _ScanScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScanScreenState extends State<ScanScreen> {
|
||||||
|
final MobileScannerController controller = MobileScannerController(
|
||||||
|
formats: [BarcodeFormat.ean13],
|
||||||
|
detectionTimeoutMs: 1000,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
MobileScanner(
|
||||||
|
controller: controller,
|
||||||
|
onDetect: (barcodes) async {
|
||||||
|
widget.viewModel.showScan = false;
|
||||||
|
widget.viewModel.scanBook(barcodes);
|
||||||
|
controller.dispose();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.viewModel.showScan = false;
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(child: SvgPicture.asset('assets/scan-overlay.svg')),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(theme.cardColor),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) =>
|
||||||
|
ManualScanPopup(viewModel: widget.viewModel),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text("Vendre un livre sans scanner"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
116
lib/ui/sell_page/widgets/sell_page.dart
Normal file
116
lib/ui/sell_page/widgets/sell_page.dart
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:seshat/domain/models/book_instance.dart';
|
||||||
|
import 'package:seshat/ui/core/ui/navigation_bar.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/view_model/sell_view_model.dart';
|
||||||
|
import 'package:seshat/ui/sell_page/widgets/scan_screen.dart';
|
||||||
|
|
||||||
|
class SellPage extends StatefulWidget {
|
||||||
|
const SellPage({super.key, required this.viewModel});
|
||||||
|
|
||||||
|
final SellViewModel viewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SellPage> createState() => _SellPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SellPageState extends State<SellPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
bottomNavigationBar: AppNavigationBar(startIndex: 2),
|
||||||
|
body: ListenableBuilder(
|
||||||
|
listenable: widget.viewModel,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 6),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
for (BookInstance bookInstance
|
||||||
|
in widget.viewModel.scannedBooks)
|
||||||
|
Card(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Text(
|
||||||
|
"${bookInstance.price.toString()}€",
|
||||||
|
style: TextStyle(fontSize: 30),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"Les chiens et la charrue · Patrick K. Dewdney ${bookInstance.id}",
|
||||||
|
),
|
||||||
|
subtitle: Text("Union Étudiante Auvergne"),
|
||||||
|
trailing: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.viewModel.deleteBook(
|
||||||
|
bookInstance.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.delete),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 40),
|
||||||
|
Text("Somme minimum requise : 20€"),
|
||||||
|
SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Argent reçu",
|
||||||
|
helperText:
|
||||||
|
"L'argent reçu sera réparti automatiquement",
|
||||||
|
suffixText: "€",
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.viewModel.sendSell();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.check),
|
||||||
|
style: ButtonStyle(
|
||||||
|
iconSize: WidgetStatePropertyAll(70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 70),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.viewModel.showScan = true;
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
style: ButtonStyle(
|
||||||
|
iconSize: WidgetStatePropertyAll(70),
|
||||||
|
elevation: WidgetStatePropertyAll(50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
(widget.viewModel.showScan)
|
||||||
|
? ScanScreen(viewModel: widget.viewModel)
|
||||||
|
: SizedBox(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// return Center(child: Text("Sell page."));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in a new issue