commit fonctionnalite impec
This commit is contained in:
parent
2bef06a2fe
commit
9eafda610f
BIN
assets/Orange_money.png
Normal file
BIN
assets/Orange_money.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/airtel_money.png
Normal file
BIN
assets/airtel_money.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/mvola.jpg
Normal file
BIN
assets/mvola.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
7
lib/Components/paymentType.dart
Normal file
7
lib/Components/paymentType.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
enum PaymentType {
|
||||||
|
cash,
|
||||||
|
card,
|
||||||
|
mvola,
|
||||||
|
orange,
|
||||||
|
airtel
|
||||||
|
}
|
||||||
@ -1563,7 +1563,12 @@ class _CommandeActions extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PaymentType { cash, card }
|
enum PaymentType { cash, card ,
|
||||||
|
|
||||||
|
mvola,
|
||||||
|
orange,
|
||||||
|
airtel
|
||||||
|
}
|
||||||
|
|
||||||
class PaymentMethod {
|
class PaymentMethod {
|
||||||
final PaymentType type;
|
final PaymentType type;
|
||||||
@ -1584,7 +1589,28 @@ class PaymentMethodDialog extends StatefulWidget {
|
|||||||
class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
|
class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
|
||||||
PaymentType _selectedPayment = PaymentType.cash;
|
PaymentType _selectedPayment = PaymentType.cash;
|
||||||
final _amountController = TextEditingController();
|
final _amountController = TextEditingController();
|
||||||
|
void _validatePayment() {
|
||||||
|
if (_selectedPayment == PaymentType.cash) {
|
||||||
|
final amountGiven = double.tryParse(_amountController.text) ?? 0;
|
||||||
|
if (amountGiven < widget.commande.montantTotal) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Erreur',
|
||||||
|
'Le montant donné est insuffisant',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigator.pop(context, PaymentMethod(
|
||||||
|
type: _selectedPayment,
|
||||||
|
amountGiven: _selectedPayment == PaymentType.cash
|
||||||
|
? double.parse(_amountController.text)
|
||||||
|
: widget.commande.montantTotal,
|
||||||
|
));
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -1598,52 +1624,91 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final amount = double.tryParse(_amountController.text) ?? 0;
|
final amount = double.tryParse(_amountController.text) ?? 0;
|
||||||
final change = amount - widget.commande.montantTotal;
|
final change = amount - widget.commande.montantTotal;
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Méthode de paiement'),
|
title: const Text('Méthode de paiement', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
content: Column(
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
RadioListTile<PaymentType>(
|
// Section Paiement mobile
|
||||||
title: const Text('Paiement en liquide'),
|
const Align(
|
||||||
value: PaymentType.cash,
|
alignment: Alignment.centerLeft,
|
||||||
groupValue: _selectedPayment,
|
child: Text('Mobile Money', style: TextStyle(fontWeight: FontWeight.w500)),
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_selectedPayment = value!;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (_selectedPayment == PaymentType.cash)
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildMobileMoneyTile(
|
||||||
|
title: 'Mvola',
|
||||||
|
imagePath: 'assets/mvola.jpg',
|
||||||
|
value: PaymentType.mvola,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: _buildMobileMoneyTile(
|
||||||
|
title: 'Orange Money',
|
||||||
|
imagePath: 'assets/Orange_money.png',
|
||||||
|
value: PaymentType.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: _buildMobileMoneyTile(
|
||||||
|
title: 'Airtel Money',
|
||||||
|
imagePath: 'assets/airtel_money.png',
|
||||||
|
value: PaymentType.airtel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Section Carte bancaire
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text('Carte Bancaire', style: TextStyle(fontWeight: FontWeight.w500)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildPaymentMethodTile(
|
||||||
|
title: 'Carte bancaire',
|
||||||
|
icon: Icons.credit_card,
|
||||||
|
value: PaymentType.card,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Section Paiement en liquide
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text('Espèces', style: TextStyle(fontWeight: FontWeight.w500)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildPaymentMethodTile(
|
||||||
|
title: 'Paiement en liquide',
|
||||||
|
icon: Icons.money,
|
||||||
|
value: PaymentType.cash,
|
||||||
|
),
|
||||||
|
if (_selectedPayment == PaymentType.cash) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _amountController,
|
controller: _amountController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Montant donné',
|
labelText: 'Montant donné',
|
||||||
prefixText: 'MGA ',
|
prefixText: 'MGA ',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
onChanged: (value) {
|
onChanged: (value) => setState(() {}),
|
||||||
setState(() {}); // Rafraîchir l'UI pour calculer la monnaie
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
RadioListTile<PaymentType>(
|
const SizedBox(height: 8),
|
||||||
title: const Text('Carte bancaire'),
|
Text(
|
||||||
value: PaymentType.card,
|
|
||||||
groupValue: _selectedPayment,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_selectedPayment = value!;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (_selectedPayment == PaymentType.cash)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(
|
|
||||||
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
|
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@ -1651,41 +1716,97 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
|
|||||||
color: change >= 0 ? Colors.green : Colors.red,
|
color: change >= 0 ? Colors.green : Colors.red,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('Annuler'),
|
child: const Text('Annuler', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
style: ElevatedButton.styleFrom(
|
||||||
final amount = _selectedPayment == PaymentType.cash
|
backgroundColor: Colors.blue.shade800,
|
||||||
? double.tryParse(_amountController.text) ?? 0
|
foregroundColor: Colors.white,
|
||||||
: widget.commande.montantTotal;
|
|
||||||
|
|
||||||
if (_selectedPayment == PaymentType.cash && amount < widget.commande.montantTotal) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Le montant donné est insuffisant'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
),
|
||||||
);
|
onPressed: _validatePayment,
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Navigator.pop(
|
|
||||||
context,
|
|
||||||
PaymentMethod(
|
|
||||||
type: _selectedPayment,
|
|
||||||
amountGiven: amount,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Confirmer'),
|
child: const Text('Confirmer'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildMobileMoneyTile({
|
||||||
|
required String title,
|
||||||
|
required String imagePath,
|
||||||
|
required PaymentType value,
|
||||||
|
}) {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(
|
||||||
|
color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => setState(() => _selectedPayment = value),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
imagePath,
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (context, error, stackTrace) =>
|
||||||
|
const Icon(Icons.mobile_friendly, size: 30),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPaymentMethodTile({
|
||||||
|
required String title,
|
||||||
|
required IconData icon,
|
||||||
|
required PaymentType value,
|
||||||
|
}) {
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(
|
||||||
|
color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => setState(() => _selectedPayment = value),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 24),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(title),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,12 +260,29 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
void _showClientFormDialog() {
|
void _showClientFormDialog() {
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title: const Text('Informations Client'),
|
title: Row(
|
||||||
content: SingleChildScrollView(
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.person_add, color: Colors.blue.shade700),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Text('Informations Client'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Container(
|
||||||
|
width: 600,
|
||||||
|
constraints: const BoxConstraints(maxHeight: 600),
|
||||||
|
child: SingleChildScrollView(
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildTextFormField(
|
_buildTextFormField(
|
||||||
controller: _nomController,
|
controller: _nomController,
|
||||||
@ -311,6 +328,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
@ -320,20 +338,16 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue.shade800,
|
backgroundColor: Colors.blue.shade800,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
Get.back();
|
Get.back();
|
||||||
Get.snackbar(
|
// Au lieu d'afficher juste un message, on valide directement la commande
|
||||||
'Succès',
|
_submitOrder();
|
||||||
'Informations client enregistrées',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Enregistrer'),
|
child: const Text('Valider la commande'), // Changement de texte ici
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -464,7 +478,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'${product.price.toStringAsFixed(2)} DA',
|
'${product.price.toStringAsFixed(2)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.green.shade700,
|
color: Colors.green.shade700,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -626,9 +640,9 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
child: const Icon(Icons.shopping_bag, size: 20),
|
child: const Icon(Icons.shopping_bag, size: 20),
|
||||||
),
|
),
|
||||||
title: Text(product.name),
|
title: Text(product.name),
|
||||||
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'),
|
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} MGA'),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
'${(entry.value * product.price).toStringAsFixed(2)} DA',
|
'${(entry.value * product.price).toStringAsFixed(2)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.blue.shade800,
|
color: Colors.blue.shade800,
|
||||||
@ -658,7 +672,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${total.toStringAsFixed(2)} DA',
|
'${total.toStringAsFixed(2)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -708,23 +722,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _submitOrder() async {
|
Future<void> _submitOrder() async {
|
||||||
if (_nomController.text.isEmpty ||
|
// Vérifier d'abord si le panier est vide
|
||||||
_prenomController.text.isEmpty ||
|
|
||||||
_emailController.text.isEmpty ||
|
|
||||||
_telephoneController.text.isEmpty ||
|
|
||||||
_adresseController.text.isEmpty) {
|
|
||||||
Get.back(); // Ferme le bottom sheet
|
|
||||||
Get.snackbar(
|
|
||||||
'Informations manquantes',
|
|
||||||
'Veuillez remplir les informations client',
|
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
_showClientFormDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
|
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
|
||||||
if (itemsInCart.isEmpty) {
|
if (itemsInCart.isEmpty) {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
@ -734,6 +732,24 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
|
_showCartBottomSheet(); // Ouvrir le panier pour montrer qu'il est vide
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensuite vérifier les informations client
|
||||||
|
if (_nomController.text.isEmpty ||
|
||||||
|
_prenomController.text.isEmpty ||
|
||||||
|
_emailController.text.isEmpty ||
|
||||||
|
_telephoneController.text.isEmpty ||
|
||||||
|
_adresseController.text.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Informations manquantes',
|
||||||
|
'Veuillez remplir les informations client',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
_showClientFormDialog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,14 +797,12 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
|
|||||||
try {
|
try {
|
||||||
await _appDatabase.createCommandeComplete(client, commande, details);
|
await _appDatabase.createCommandeComplete(client, commande, details);
|
||||||
|
|
||||||
Get.back(); // Ferme le bottom sheet
|
|
||||||
|
|
||||||
// Afficher le dialogue de confirmation
|
// Afficher le dialogue de confirmation
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Commande Validée'),
|
title: const Text('Commande Validée'),
|
||||||
content: const Text('Votre commande a été enregistrée avec succès.'),
|
content: const Text('Votre commande a été enregistrée et expédiée avec succès.'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@ -102,6 +102,9 @@ flutter:
|
|||||||
- assets/database/usersdb.db
|
- assets/database/usersdb.db
|
||||||
- assets/database/work.db
|
- assets/database/work.db
|
||||||
- assets/database/roles.db
|
- assets/database/roles.db
|
||||||
|
- assets/airtel_money.png
|
||||||
|
- assets/mvola.jpg
|
||||||
|
- assets/Orange_money.png
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user