Intégration fonctionnalites V1

This commit is contained in:
bastien
2026-07-04 22:46:14 +02:00
parent bf191d6396
commit 9f352b91d2
25 changed files with 779 additions and 184 deletions
+7
View File
@@ -38,3 +38,10 @@ abstract final class AuthStorageKeys {
static const accessToken = 'laverie_access_token';
static const refreshToken = 'laverie_refresh_token';
}
/// Clé publique Stripe (test) — surcharge via `--dart-define=STRIPE_PUBLISHABLE_KEY=...`
const String _envStripePublishableKey = String.fromEnvironment('STRIPE_PUBLISHABLE_KEY');
final String kStripePublishableKey = _envStripePublishableKey.isNotEmpty
? _envStripePublishableKey
: 'pk_test_51TpANRJRUgjTIwfBR9PoU4Lu201yD5R0JzvOv8Nmyva7ISX3GJPJ3IX4lSqnkg13siYwi3B9Qq0tIpEj6VCzeVFB00PSt9o0OA';
+6
View File
@@ -9,6 +9,7 @@ import '../../features/auth/presentation/splash_screen.dart';
import '../../features/home/presentation/home_screen.dart';
import '../../features/establishments/presentation/establishment_detail_screen.dart';
import '../../features/wallet/presentation/wallet_screen.dart';
import '../../features/wallet/presentation/wallet_top_up_screen.dart';
import '../../features/booking/presentation/booking_modify_screen.dart';
import '../../features/booking/presentation/bookings_screen.dart';
import '../../features/machines/presentation/machine_action_screen.dart';
@@ -25,6 +26,7 @@ abstract final class AppRoutes {
static const register = '/register';
static const home = '/';
static const wallet = '/wallet';
static const walletTopUp = '/wallet/top-up';
static const bookings = '/bookings';
static const washes = '/washes';
static const washScan = '/washes/scan';
@@ -118,6 +120,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
path: AppRoutes.washScan,
builder: (context, state) => const QrScannerScreen(),
),
GoRoute(
path: AppRoutes.walletTopUp,
builder: (context, state) => const WalletTopUpScreen(),
),
GoRoute(
path: '/machines/:uuid/action',
builder: (context, state) {
+14 -1
View File
@@ -70,8 +70,21 @@ class AppTheme {
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(48),
elevation: 0,
backgroundColor: AppColors.primary,
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.success.withValues(alpha: 0.4),
disabledForegroundColor: Colors.white70,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
textStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(48),
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.success.withValues(alpha: 0.5),
disabledForegroundColor: Colors.white.withValues(alpha: 0.8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
textStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
+71
View File
@@ -0,0 +1,71 @@
import 'package:intl/intl.dart';
import 'package:timezone/data/latest.dart' as tz_data;
import 'package:timezone/timezone.dart' as tz;
/// Heure métier alignée sur le fuseau du serveur API (pas l'UTC ni le téléphone).
abstract final class ServerTime {
static const defaultTimezone = 'Europe/Paris';
static bool _initialized = false;
static tz.Location _location = tz.UTC;
static Future<void> initialize({String timezone = defaultTimezone}) async {
if (!_initialized) {
tz_data.initializeTimeZones();
_initialized = true;
}
setTimezone(timezone);
}
static void setTimezone(String timezone) {
if (!_initialized) {
tz_data.initializeTimeZones();
_initialized = true;
}
_location = tz.getLocation(timezone);
}
static String get timezone => _location.name;
static tz.TZDateTime now() => tz.TZDateTime.now(_location);
static DateTime startOfToday() {
final current = now();
return DateTime(current.year, current.month, current.day);
}
static DateTime? parse(String? iso) {
if (iso == null || iso.isEmpty) {
return null;
}
return DateTime.tryParse(iso);
}
static tz.TZDateTime toServerTime(DateTime dateTime) {
return tz.TZDateTime.from(dateTime.toUtc(), _location);
}
static String format(
DateTime? dateTime, {
required String pattern,
String locale = 'fr_FR',
}) {
if (dateTime == null) {
return '';
}
final server = toServerTime(dateTime);
return DateFormat(pattern, locale).format(
DateTime(
server.year,
server.month,
server.day,
server.hour,
server.minute,
server.second,
server.millisecond,
server.microsecond,
),
);
}
}
+29
View File
@@ -0,0 +1,29 @@
import 'package:dio/dio.dart';
import '../api/api_endpoints.dart';
import '../api/api_response.dart';
import '../config/app_config.dart';
import 'server_time.dart';
/// Synchronise le fuseau horaire applicatif avec l'API (`/health`).
Future<void> syncServerTimezone() async {
try {
final client = Dio(
BaseOptions(
baseUrl: kApiBaseUrl,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
headers: {'Accept': 'application/json'},
),
);
final response = await client.get(ApiEndpoints.health);
final timezone = ApiResponse.payload(response.data)['timezone'];
if (timezone is String && timezone.isNotEmpty) {
ServerTime.setTimezone(timezone);
}
} catch (_) {
// Conserve le fuseau par défaut (Europe/Paris).
}
}