feat: start of pending screen

This commit is contained in:
alzalia1 2025-08-14 00:27:39 +02:00
parent 019a21f00e
commit ee9c4c3801
12 changed files with 425 additions and 67 deletions

View file

@ -22,6 +22,17 @@ class BalRepository {
} }
} }
Future<Result<List<Bal>>> _getBalsNoCache() async {
final result = await _apiClient.getBals();
switch (result) {
case Ok():
_bals = result.value;
return Result.ok(result.value);
case Error():
return result;
}
}
Future<Result<Bal>> balById(int id) async { Future<Result<Bal>> balById(int id) async {
if (_bals == null) { if (_bals == null) {
await getBals(); await getBals();
@ -39,13 +50,20 @@ class BalRepository {
} }
} }
Future<Result<void>> addBal(String name) async { Future<Result<Bal>> editBal(
final result = await _apiClient.addBal(name); int id,
switch (result) { String name,
case Ok(): DateTime start,
return result; DateTime end,
case Error(): ) async {
return result; final result = await _apiClient.editBal(id, name, start, end);
} _getBalsNoCache();
return result;
}
Future<Result<void>> addBal(String name, DateTime start, DateTime end) async {
final result = await _apiClient.addBal(name, start, end);
_getBalsNoCache();
return result;
} }
} }

View file

@ -42,6 +42,38 @@ class ApiClient {
* ================= * =================
*/ */
Future<Result<Bal>> editBal(
int id,
String name,
DateTime start,
DateTime end,
) async {
final client = Client();
try {
final headers = await _getHeaders({"Content-Type": "application/json"});
final body = {
"name": name,
"start_timestamp": (start.millisecondsSinceEpoch / 1000).round(),
"end_timestamp": (end.millisecondsSinceEpoch / 1000).round(),
};
final response = await client.patch(
Uri.parse("https://$apiBasePath/bal/${id.toString()}"),
headers: headers,
body: jsonEncode(body),
);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
return Result.ok(Bal.fromJSON(json));
} else {
throw Exception("Something went wrong");
}
} catch (e) {
return Result.error(Exception(e));
} finally {
client.close();
}
}
Future<Result<Bal>> getBalById(int id) async { Future<Result<Bal>> getBalById(int id) async {
final client = Client(); final client = Client();
try { try {
@ -54,7 +86,7 @@ class ApiClient {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return Result.ok(Bal.fromJSON(json)); return Result.ok(Bal.fromJSON(json));
} else if (response.statusCode == 403) { } else if (response.statusCode == 403) {
return Result.error(Exception("You don't own the specified bal")); throw Exception("You don't own the specified bal");
} else { } else {
return Result.error( return Result.error(
Exception("No bal wirth this id exists the database"), Exception("No bal wirth this id exists the database"),
@ -67,11 +99,15 @@ class ApiClient {
} }
} }
Future<Result<Bal>> addBal(String name) async { Future<Result<Bal>> addBal(String name, DateTime start, DateTime end) async {
final client = Client(); final client = Client();
try { try {
final headers = await _getHeaders({"Content-Type": "application/json"}); final headers = await _getHeaders({"Content-Type": "application/json"});
final body = {"name": name}; final body = {
"name": name,
"start_timestamp": (start.millisecondsSinceEpoch / 1000).round(),
"end_timestamp": (end.millisecondsSinceEpoch / 1000).round(),
};
final response = await client.post( final response = await client.post(
Uri.parse("https://$apiBasePath/bal"), Uri.parse("https://$apiBasePath/bal"),
headers: headers, headers: headers,
@ -81,10 +117,9 @@ class ApiClient {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return Result.ok(Bal.fromJSON(json)); return Result.ok(Bal.fromJSON(json));
} else { } else {
return Result.error(Exception("Something went wrong")); throw Exception("Something went wrong");
} }
} catch (e) { } catch (e) {
debugPrint("\n\n\n\n${e.toString()}\n\n\n\n");
return Result.error(Exception(e)); return Result.error(Exception(e));
} finally { } finally {
client.close(); client.close();
@ -101,12 +136,17 @@ class ApiClient {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
final json = jsonDecode(response.body) as List<dynamic>; final json = jsonDecode(response.body) as List<dynamic>;
debugPrint("\n\n\n\nRECEIVED $json\n\n\n\n"); debugPrint("\n\n\n\nRECEIVED : $json\n\n\n\n");
debugPrint(
"\n\n\n\nFORMATTED : ${json.map((element) => Bal.fromJSON(element)).toList()}\n\n\n\n",
);
return Result.ok(json.map((element) => Bal.fromJSON(element)).toList()); return Result.ok(json.map((element) => Bal.fromJSON(element)).toList());
} else { } else {
return Result.error(Exception("Something wrong happened")); throw Exception("Something wrong happened");
} }
} catch (e) { } catch (e) {
debugPrint("ERROR: ${e.toString()}");
return Result.error(Exception(e)); return Result.error(Exception(e));
} finally { } finally {
client.close(); client.close();
@ -127,7 +167,7 @@ class ApiClient {
} else if (response.statusCode == 404) { } else if (response.statusCode == 404) {
return Result.ok(null); return Result.ok(null);
} else { } else {
return Result.error(Exception("Something went wrong")); throw Exception("Something went wrong");
} }
} catch (e) { } catch (e) {
return Result.error(Exception(e)); return Result.error(Exception(e));
@ -154,7 +194,7 @@ class ApiClient {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return Result.ok(Book.fromJSON(json)); return Result.ok(Book.fromJSON(json));
} else { } else {
return Result.error(Exception("The book was not found")); throw Exception("The book was not found");
} }
} catch (e) { } catch (e) {
return Result.error(Exception("API $e")); return Result.error(Exception("API $e"));
@ -193,9 +233,9 @@ class ApiClient {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return Result.ok(BookInstance.fromJSON(json)); return Result.ok(BookInstance.fromJSON(json));
} else if (response.statusCode == 403) { } else if (response.statusCode == 403) {
return Result.error(Exception("You don't own that book instance")); throw Exception("You don't own that book instance");
} else { } else {
return Result.error(Exception("Something wrong happened")); throw Exception("Something wrong happened");
} }
} catch (e) { } catch (e) {
return Result.error(Exception(e)); return Result.error(Exception(e));
@ -225,7 +265,7 @@ class ApiClient {
json.map((element) => Owner.fromJSON(element)).toList(), json.map((element) => Owner.fromJSON(element)).toList(),
); );
} else { } else {
return Result.error(Exception("Invalid request")); throw Exception("Invalid request");
} }
} on Exception catch (error) { } on Exception catch (error) {
return Result.error(error); return Result.error(error);
@ -257,7 +297,7 @@ class ApiClient {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return Result.ok(Owner.fromJSON(json)); return Result.ok(Owner.fromJSON(json));
} else { } else {
return Result.error(Exception("Invalid request")); throw Exception("Invalid request");
} }
} on Exception catch (error) { } on Exception catch (error) {
return Result.error(error); return Result.error(error);

View file

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:seshat/config/dependencies.dart'; import 'package:seshat/config/dependencies.dart';
import 'package:seshat/routing/router.dart'; import 'package:seshat/routing/router.dart';
void main() { void main() async {
Logger.root.level = Level.ALL; Logger.root.level = Level.ALL;
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -19,6 +20,12 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp.router( return MaterialApp.router(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [const Locale("fr")],
routerConfig: router(context.read()), routerConfig: router(context.read()),
theme: ThemeData.dark(), theme: ThemeData.dark(),
); );

View file

@ -16,6 +16,26 @@ class BalViewModel extends ChangeNotifier {
int? id; int? id;
Bal? get bal => _bal; Bal? get bal => _bal;
Future<Result<void>> editBal(
int id,
String name,
DateTime start,
DateTime end,
) async {
final result = await _balRepository.editBal(id, name, start, end);
switch (result) {
case Ok():
debugPrint("\n\n\n\nDID EDIT\n\n\n\n");
_bal = result.value;
notifyListeners();
break;
case Error():
debugPrint("\n\n\n\nERROR: ${result.error}");
break;
}
return result;
}
/* /*
* ================================= * =================================
* =====[ COMMAND AND LOADING ]===== * =====[ COMMAND AND LOADING ]=====

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:seshat/domain/models/bal.dart'; import 'package:seshat/domain/models/bal.dart';
import 'package:seshat/ui/bal_page/view_model/bal_view_model.dart'; import 'package:seshat/ui/bal_page/view_model/bal_view_model.dart';
import 'package:seshat/ui/bal_page/widget/pending/bal_pending_screen.dart';
import 'package:seshat/ui/core/ui/navigation_bar.dart'; import 'package:seshat/ui/core/ui/navigation_bar.dart';
import 'package:seshat/ui/core/ui/await_loading.dart'; import 'package:seshat/ui/core/ui/await_loading.dart';
@ -16,26 +17,29 @@ class BalPage extends StatefulWidget {
class _BalPageState extends State<BalPage> { class _BalPageState extends State<BalPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListenableBuilder(
bottomNavigationBar: AppNavigationBar(startIndex: 0), listenable: widget.viewModel,
body: ListenableBuilder( builder: (context, child) {
listenable: widget.viewModel, return switch (widget.viewModel.isLoaded) {
builder: (context, child) { false => Scaffold(
return switch (widget.viewModel.isLoaded) { bottomNavigationBar: AppNavigationBar(startIndex: 0),
false => AwaitLoading(), body: AwaitLoading(),
true => switch (widget.viewModel.bal == null) { ),
true => Center( true => switch (widget.viewModel.bal == null) {
true => Scaffold(
bottomNavigationBar: AppNavigationBar(startIndex: 0),
body: Center(
child: Text("La BAL référencée n'est pas accessible"), child: Text("La BAL référencée n'est pas accessible"),
), ),
false => switch (widget.viewModel.bal!.state) { ),
BalState.pending => Center(child: Text("Pending")), false => switch (widget.viewModel.bal!.state) {
BalState.ongoing => Center(child: Text("Ongoing")), BalState.pending => BalPendingScreen(viewModel: widget.viewModel),
BalState.ended => Center(child: Text("Ending")), BalState.ongoing => Center(child: Text("Ongoing")),
}, BalState.ended => Center(child: Text("Ending")),
}, },
}; },
}, };
), },
); );
} }
} }

View file

@ -1,11 +1,191 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:seshat/domain/models/bal.dart';
import 'package:seshat/ui/bal_page/view_model/bal_view_model.dart';
import 'package:seshat/ui/core/ui/navigation_bar.dart';
class BalPendingScreen extends StatelessWidget { class BalPendingScreen extends StatelessWidget {
const BalPendingScreen({super.key}); const BalPendingScreen({super.key, required this.viewModel});
final BalViewModel viewModel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO: implement build return Scaffold(
throw UnimplementedError(); bottomNavigationBar: AppNavigationBar(startIndex: 0),
appBar: AppBar(
title: Text(viewModel.bal!.name),
actions: [
IconButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => EditPopup(viewModel: viewModel),
);
},
icon: Icon(Icons.edit),
),
],
),
body: Center(
child: ElevatedButton(
onPressed: () {},
child: Text("Démarrer cette BAL"),
),
),
);
}
}
class EditPopup extends StatefulWidget {
const EditPopup({super.key, required this.viewModel});
final BalViewModel viewModel;
@override
State<EditPopup> createState() => _EditPopup();
}
class _EditPopup extends State<EditPopup> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String? name;
DateTime? start;
Future<void> _selectStart() async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: start ?? widget.viewModel.bal!.startTime,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 2),
locale: Locale("fr", "FR"),
);
setState(() {
start = pickedDate;
});
}
DateTime? end;
Future<void> _selectEnd() async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: end ?? widget.viewModel.bal!.endTime,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 2),
locale: Locale("fr", "FR"),
);
setState(() {
end = pickedDate;
});
}
@override
Widget build(BuildContext context) {
initializeDateFormatting();
var format = DateFormat("dd MMM yyyy", "fr");
return AlertDialog(
title: Text("Créer une BAL"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Nom de la BAL",
border: OutlineInputBorder(),
),
initialValue: widget.viewModel.bal!.name,
validator: (value) {
if (value == null || value.isEmpty) {
return "Veuillez entrer un nom";
}
return null;
},
onSaved: (newValue) {
name = newValue;
},
),
Row(
children: [
Text("Date de début : "),
TextButton(
onPressed: () {
_selectStart();
},
child: Text(
format.format(start ?? widget.viewModel.bal!.startTime),
locale: Locale("fr"),
),
),
],
),
Row(
children: [
Text("Date de fin : "),
TextButton(
onPressed: () {
_selectEnd();
},
child: Text(
format.format(end ?? widget.viewModel.bal!.endTime),
),
),
],
),
Text("Note: Les dates sont à titre purement indicatif."),
],
),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("Annuler"),
),
TextButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
final Bal bal = widget.viewModel.bal!;
final result = await widget.viewModel.editBal(
bal.id,
name ?? bal.name,
start ?? bal.startTime,
end ?? bal.endTime,
);
if (result is Error && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Une erreur est survenue")),
);
}
if (context.mounted) {
Navigator.of(context).pop();
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Veuillez indiquer une date de début et de fin.",
),
),
);
}
},
child: Text("Valider"),
),
],
);
} }
} }

View file

@ -23,10 +23,12 @@ class _AwaitLoadingState extends State<AwaitLoading> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
t = Timer(Duration(seconds: 8), () { t = Timer(Duration(seconds: 8), () {
setState(() { if (context.mounted) {
text = setState(() {
"Il semblerait qu'il y ait un problème. Vérifiez que vous êtes connecté·e à internet."; text =
}); "Il semblerait qu'il y ait un problème. Vérifiez que vous êtes connecté·e à internet.";
});
}
}); });
return Column( return Column(

View file

@ -24,8 +24,12 @@ class HomeViewModel extends ChangeNotifier {
Bal? _currentBal; Bal? _currentBal;
Bal? get currentBal => _currentBal; Bal? get currentBal => _currentBal;
Future<Result<void>> createBal(String name) async { Future<Result<void>> createBal(
final result = await _balRepository.addBal(name); String name,
DateTime start,
DateTime end,
) async {
final result = await _balRepository.addBal(name, start, end);
switch (result) { switch (result) {
case Ok(): case Ok():
final result2 = await _balRepository.getBals(); final result2 = await _balRepository.getBals();
@ -34,7 +38,6 @@ class HomeViewModel extends ChangeNotifier {
_bals = result2.value..sort((a, b) => a.compareTo(b)); _bals = result2.value..sort((a, b) => a.compareTo(b));
break; break;
case Error(): case Error():
debugPrint("\n\n\n\n${result2.error.toString()}\n\n\n\n");
return result2; return result2;
} }
break; break;

View file

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:seshat/ui/home_page/view_model/home_view_model.dart'; import 'package:seshat/ui/home_page/view_model/home_view_model.dart';
class CreateConfirmationPopup extends StatefulWidget { class CreateConfirmationPopup extends StatefulWidget {
@ -14,8 +16,40 @@ class CreateConfirmationPopup extends StatefulWidget {
class _CreateConfirmationPopupState extends State<CreateConfirmationPopup> { class _CreateConfirmationPopupState extends State<CreateConfirmationPopup> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String? name; String? name;
DateTime? start;
Future<void> _selectStart() async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: start ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 2),
locale: Locale("fr"),
);
setState(() {
start = pickedDate;
});
}
DateTime? end;
Future<void> _selectEnd() async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: end ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 2),
locale: Locale("fr"),
);
setState(() {
end = pickedDate;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
initializeDateFormatting();
var format = DateFormat("dd MMM yyyy", "fr");
return AlertDialog( return AlertDialog(
title: Text("Créer une BAL"), title: Text("Créer une BAL"),
content: Column( content: Column(
@ -41,6 +75,29 @@ class _CreateConfirmationPopupState extends State<CreateConfirmationPopup> {
name = newValue; name = newValue;
}, },
), ),
Row(
children: [
Text("Date de début : "),
TextButton(
onPressed: () {
_selectStart();
},
child: Text(format.format(start ?? DateTime.now())),
),
],
),
Row(
children: [
Text("Date de fin : "),
TextButton(
onPressed: () {
_selectEnd();
},
child: Text(format.format(end ?? DateTime.now())),
),
],
),
Text("Note: Les dates sont à titre purement indicatif."),
], ],
), ),
), ),
@ -55,12 +112,23 @@ class _CreateConfirmationPopupState extends State<CreateConfirmationPopup> {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate() &&
start != null &&
end != null) {
_formKey.currentState!.save(); _formKey.currentState!.save();
await widget.viewModel.createBal(name!); await widget.viewModel.createBal(name!, start!, end!);
}
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Veuillez indiquer une date de début et de fin.",
),
),
);
} }
}, },
child: Text("Valider"), child: Text("Valider"),

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:seshat/domain/models/bal.dart'; import 'package:seshat/domain/models/bal.dart';
import 'package:seshat/ui/core/ui/navigation_bar.dart'; import 'package:seshat/ui/core/ui/navigation_bar.dart';
import 'package:seshat/ui/home_page/view_model/home_view_model.dart'; import 'package:seshat/ui/home_page/view_model/home_view_model.dart';
@ -18,6 +20,8 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
initializeDateFormatting();
var format = DateFormat("dd MMM yyyy", "fr");
return Scaffold( return Scaffold(
bottomNavigationBar: AppNavigationBar(startIndex: 0), bottomNavigationBar: AppNavigationBar(startIndex: 0),
appBar: AppBar( appBar: AppBar(
@ -55,30 +59,24 @@ class _HomePageState extends State<HomePage> {
title: Text(bal.name), title: Text(bal.name),
subtitle: switch (bal.state) { subtitle: switch (bal.state) {
BalState.pending => Text( BalState.pending => Text(
"À venir · Débute le ${bal.startTime.toString()}", "À venir · Débute le ${format.format(bal.startTime)}",
), ),
BalState.ongoing => Text("En cours"), BalState.ongoing => Text("En cours"),
BalState.ended => Text("Terminée"), BalState.ended => Text("Terminée"),
}, },
trailing: switch (bal.state) { trailing: switch (bal.state) {
BalState.pending => IconButton(
onPressed: () {
_moveToBal(context, bal.id);
},
icon: Icon(Icons.edit),
),
BalState.ongoing => IconButton(
onPressed: () {
_moveToBal(context, bal.id);
},
icon: Icon(Icons.arrow_forward),
),
BalState.ended => IconButton( BalState.ended => IconButton(
onPressed: () { onPressed: () {
_moveToBal(context, bal.id); _moveToBal(context, bal.id);
}, },
icon: Icon(Icons.analytics), icon: Icon(Icons.analytics),
), ),
_ => IconButton(
onPressed: () {
_moveToBal(context, bal.id);
},
icon: Icon(Icons.arrow_forward),
),
}, },
), ),
), ),
@ -113,6 +111,7 @@ class _HomePageState extends State<HomePage> {
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false,
builder: (context) { builder: (context) {
return CreateConfirmationPopup( return CreateConfirmationPopup(
viewModel: widget.viewModel, viewModel: widget.viewModel,

View file

@ -94,6 +94,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -184,6 +189,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
js: js:
dependency: transitive dependency: transitive
description: description:

View file

@ -30,6 +30,9 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
@ -44,6 +47,7 @@ dependencies:
nested: ^1.0.0 nested: ^1.0.0
flutter_secure_storage: ^9.2.4 flutter_secure_storage: ^9.2.4
rxdart: ^0.28.0 rxdart: ^0.28.0
intl: ^0.20.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: