14 Commits
Author SHA1 Message Date
bastien 5a10b00f84 Merge tag '0.0.4' into develop
0.0.4
2026-07-09 23:09:12 +02:00
bastien 615fe2b937 Merge branch 'release/0.0.4'
Laverie/mobile/pipeline/tag This commit looks good
2026-07-09 23:09:07 +02:00
bastien bb5ac94e8b version 0.0.4 2026-07-09 23:08:51 +02:00
bastien cef740de26 Test validation lancement lavage 2026-07-09 23:08:28 +02:00
bastien eb4c322001 Merge tag '0.0.3' into develop
0.0.3
2026-07-09 22:57:28 +02:00
bastien e0028e10e0 Merge branch 'release/0.0.3'
Laverie/mobile/pipeline/tag This commit looks good
2026-07-09 22:57:22 +02:00
bastien d42163f12f Maj dashboard 2026-07-09 22:57:07 +02:00
bastien 8a888e2ecd Merge tag '0.0.2' into develop
0.0.2
2026-07-05 01:10:36 +02:00
bastien 920bed947e Merge branch 'release/0.0.2'
Laverie/mobile/pipeline/tag This commit looks good
2026-07-05 01:10:29 +02:00
bastien 8b4ea2e1ec version 0.0.2 2026-07-05 01:10:19 +02:00
bastien 409847b4a9 correction prod 2026-07-05 01:09:44 +02:00
bastien fb678bcc03 Merge tag '0.0.1' into develop
0.0.1 test
2026-07-05 00:43:02 +02:00
bastien 69a366c6b5 Merge branch 'release/0.0.1'
Laverie/mobile/pipeline/tag There was a failure building this commit
Laverie/mobile/pipeline/head This commit looks good
2026-07-05 00:42:50 +02:00
bastien 844cd2c8c9 version 0.0.1 2026-07-05 00:42:40 +02:00
9 changed files with 214 additions and 11 deletions
+4
View File
@@ -30,6 +30,10 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}
}
+7
View File
@@ -0,0 +1,7 @@
# Stripe push provisioning (optionnel) SDK privé non disponible sur Maven public
-dontwarn com.google.android.gms.tapandpay.TapAndPayClient
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivity$g
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Args
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Error
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningEphemeralKeyProvider
+1 -1
View File
@@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# Compatibilité plugins Flutter pas encore migrés vers le Kotlin intégré AGP 9
# Compatibilité plugins Flutter (mobile_scanner, stripe_android) pas encore migrés vers le Kotlin intégré AGP 9
android.builtInKotlin=false
# Évite les erreurs de cache Kotlin cross-disques (C: pub cache / D: projet) sous Windows
kotlin.incremental=false
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
+1 -1
View File
@@ -19,7 +19,7 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
}
@@ -0,0 +1,160 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../router/app_router.dart';
import '../theme/app_colors.dart';
/// Fenêtre de confirmation avant le lancement d'un lavage.
class WashStartConfirmationDialog {
static Future<bool> show(
BuildContext context, {
required String machineName,
required double currentBalance,
required double price,
required NumberFormat currency,
}) async {
final remainingBalance = currentBalance - price;
final hasEnoughBalance = remainingBalance >= 0;
final result = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text('Confirmer le lavage'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
machineName,
style: Theme.of(dialogContext).textTheme.bodyMedium?.copyWith(
color: AppColors.textSecondary,
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.background,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.divider),
),
child: Column(
children: [
_AmountRow(
label: 'Solde actuel',
amount: currency.format(currentBalance),
emphasized: true,
),
const SizedBox(height: 12),
_AmountRow(
label: 'Prix du cycle',
amount: ' ${currency.format(price)}',
color: AppColors.textSecondary,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Divider(height: 1),
),
_AmountRow(
label: 'Solde restant',
amount: currency.format(remainingBalance),
emphasized: true,
color: hasEnoughBalance ? AppColors.success : AppColors.error,
),
],
),
),
if (!hasEnoughBalance) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.error.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
const Icon(Icons.warning_amber_rounded, color: AppColors.error, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
'Solde insuffisant. Rechargez votre portefeuille pour continuer.',
style: Theme.of(dialogContext).textTheme.bodySmall?.copyWith(
color: AppColors.error,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
],
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Annuler'),
),
if (!hasEnoughBalance)
TextButton(
onPressed: () {
Navigator.pop(dialogContext, false);
dialogContext.push(AppRoutes.walletTopUp);
},
child: const Text('Recharger'),
)
else
ElevatedButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Démarrer le lavage'),
),
],
),
);
return result ?? false;
}
}
class _AmountRow extends StatelessWidget {
const _AmountRow({
required this.label,
required this.amount,
this.emphasized = false,
this.color,
});
final String label;
final String amount;
final bool emphasized;
final Color? color;
@override
Widget build(BuildContext context) {
final amountColor = color ?? AppColors.textPrimary;
return Row(
children: [
Expanded(
child: Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: emphasized ? FontWeight.w600 : FontWeight.normal,
),
),
),
Text(
amount,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: amountColor,
fontWeight: emphasized ? FontWeight.w700 : FontWeight.w600,
fontSize: emphasized ? 18 : 16,
),
),
],
);
}
}
@@ -60,14 +60,9 @@ class HomeScreen extends ConsumerWidget {
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
children: [
Text(
user?.firstName != null ? 'Bonjour ${user!.firstName} 👋' : 'Bienvenue',
user?.firstName != null ? 'Bonjour ${user!.firstName}' : 'Bienvenue',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(fontSize: 20),
),
const SizedBox(height: 4),
Text(
'Votre laverie, dans votre poche.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
const QuickActionsRow(),
if (activeWash != null) ...[
@@ -8,6 +8,8 @@ import '../../../core/router/app_router.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/machine_status_theme.dart';
import '../../../core/widgets/empty_state.dart';
import '../../../core/widgets/wash_start_confirmation_dialog.dart';
import '../../wallet/data/wallet_repository.dart';
import '../../wash/data/wash_repository.dart';
import '../data/machine_repository.dart';
import '../domain/machine_detail.dart';
@@ -25,11 +27,46 @@ class MachineActionScreen extends ConsumerStatefulWidget {
class _MachineActionScreenState extends ConsumerState<MachineActionScreen> {
bool _isStarting = false;
Future<void> _confirmAndStartWash(MachineDetail detail) async {
final currency = NumberFormat.currency(locale: 'fr_FR', symbol: '');
double currentBalance;
try {
final wallet = await ref.read(walletProvider.future);
currentBalance = wallet.currentBalance;
} catch (_) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Impossible de charger votre solde'),
backgroundColor: AppColors.error,
),
);
}
return;
}
if (!mounted) return;
final confirmed = await WashStartConfirmationDialog.show(
context,
machineName: detail.machine.name,
currentBalance: currentBalance,
price: detail.price,
currency: currency,
);
if (confirmed && mounted) {
await _startWash(detail);
}
}
Future<void> _startWash(MachineDetail detail) async {
setState(() => _isStarting = true);
try {
await ref.read(washRepositoryProvider).startWashFromMachine(detail.machine.uuid);
ref.invalidate(washesProvider);
ref.invalidate(walletProvider);
ref.invalidate(machineDetailProvider(widget.machineUuid));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -74,7 +111,7 @@ class _MachineActionScreenState extends ConsumerState<MachineActionScreen> {
detail: detail,
currency: currency,
isStarting: _isStarting,
onStart: () => _startWash(detail),
onStart: () => _confirmAndStartWash(detail),
onReserve: () => context.push(AppRoutes.machineBooking(widget.machineUuid)),
),
),
+1 -1
View File
@@ -3,7 +3,7 @@ description: >
Application mobile native Laverie Connectée (Android + iOS).
Pas de cible web Flutter — Android en priorité, iOS préparé.
publish_to: 'none'
version: 1.0.0+1
version: 0.0.4
environment:
sdk: '>=3.2.0 <4.0.0'