Intégration fonctionnalites V1
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user