From 44500835d751da2726b858274f3b1392200cfa93 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brule Date: Sun, 23 Nov 2025 12:39:23 -0500 Subject: [PATCH] Add Android OAuth support and fix map camera crash - Add appAuthRedirectScheme manifest placeholder for flutter_appauth on Android - Fix Google Maps camera animation crash on Android ("No valid view found") - Add safety checks and retry mechanism for camera initialization - Make action buttons always visible regardless of delivery selection Co-Authored-By: Claude --- android/app/build.gradle.kts | 5 ++- lib/components/dark_mode_map.dart | 53 +++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f5edf6b..1a35234 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -24,10 +24,13 @@ android { applicationId = "com.goutezplanb.planb_logistic" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 23 // Required for Google Navigation Flutter + minSdk = flutter.minSdkVersion // Required for Google Navigation Flutter targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + + // OAuth redirect scheme for flutter_appauth + manifestPlaceholders["appAuthRedirectScheme"] = "com.goutezplanb.delivery" } packagingOptions { diff --git a/lib/components/dark_mode_map.dart b/lib/components/dark_mode_map.dart index 324af12..ac58144 100644 --- a/lib/components/dark_mode_map.dart +++ b/lib/components/dark_mode_map.dart @@ -312,7 +312,7 @@ class _DarkModeMapComponentState extends State { // Calculate dynamic padding for bottom button bar final topPadding = 0.0; - final bottomPadding = widget.selectedDelivery != null ? 110.0 : 0.0; + final bottomPadding = 110.0; return Stack( children: [ @@ -327,10 +327,34 @@ class _DarkModeMapComponentState extends State { _navigationController = controller; // Apply dark mode style with a small delay to ensure map is ready await Future.delayed(const Duration(milliseconds: 500)); + + // Safety check: ensure widget is still mounted before proceeding + if (!mounted) return; + await _applyDarkModeStyle(); - controller.animateCamera( - CameraUpdate.newLatLngZoom(initialPosition, 12), - ); + + // Wrap camera animation in try-catch to handle "No valid view found" errors + // This can happen on Android when the view isn't fully ready + try { + if (mounted && _navigationController != null) { + await controller.animateCamera( + CameraUpdate.newLatLngZoom(initialPosition, 12), + ); + } + } catch (e) { + debugPrint('Camera animation error (view may not be ready): $e'); + // Retry once after a longer delay + await Future.delayed(const Duration(milliseconds: 1000)); + if (mounted && _navigationController != null) { + try { + await controller.animateCamera( + CameraUpdate.newLatLngZoom(initialPosition, 12), + ); + } catch (e2) { + debugPrint('Camera animation retry failed: $e2'); + } + } + } }, initialCameraPosition: CameraPosition( target: initialPosition, @@ -338,9 +362,8 @@ class _DarkModeMapComponentState extends State { ), ), ), - // Bottom action button bar - 4 equal-width buttons - if (widget.selectedDelivery != null) - Positioned( + // Bottom action button bar - 4 equal-width buttons (always visible) + Positioned( bottom: 0, left: 0, right: 0, @@ -366,7 +389,7 @@ class _DarkModeMapComponentState extends State { child: _buildBottomActionButton( label: _isNavigating ? 'Stop' : 'Start', icon: _isNavigating ? Icons.stop : Icons.navigation, - onPressed: _isStartingNavigation || _isInitializing + onPressed: _isStartingNavigation || _isInitializing || (widget.selectedDelivery == null && !_isNavigating) ? null : (_isNavigating ? _stopNavigation : _startNavigation), isDanger: _isNavigating, @@ -394,12 +417,14 @@ class _DarkModeMapComponentState extends State { // Completed button Expanded( child: _buildBottomActionButton( - label: widget.selectedDelivery!.delivered ? 'Undo' : 'Completed', - icon: widget.selectedDelivery!.delivered ? Icons.undo : Icons.check_circle, - onPressed: () => widget.onAction?.call( - widget.selectedDelivery!.delivered ? 'uncomplete' : 'complete', - ), - isPrimary: !widget.selectedDelivery!.delivered, + label: widget.selectedDelivery?.delivered == true ? 'Undo' : 'Completed', + icon: widget.selectedDelivery?.delivered == true ? Icons.undo : Icons.check_circle, + onPressed: widget.selectedDelivery != null + ? () => widget.onAction?.call( + widget.selectedDelivery!.delivered ? 'uncomplete' : 'complete', + ) + : null, + isPrimary: widget.selectedDelivery != null && !widget.selectedDelivery!.delivered, ), ), ],