Update: 2026-06-26 17:29:23
This commit is contained in:
@@ -300,7 +300,7 @@ class LoginController extends GetxController {
|
||||
if (decoded is! Map || decoded.isEmpty) return;
|
||||
|
||||
if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') {
|
||||
mySnackeBarError("User does not exist.".tr);
|
||||
mySnackbarError("User does not exist.".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ class LoginController extends GetxController {
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
} catch (e) {
|
||||
addError('$e', 'loginUsingCredentials');
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
} finally {
|
||||
isloading = false;
|
||||
update();
|
||||
|
||||
@@ -42,11 +42,11 @@ class PhoneAuthHelper {
|
||||
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
|
||||
return true;
|
||||
} else {
|
||||
mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
|
||||
mySnackbarError(data['message'] ?? 'Failed to send OTP.');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('Server error. Please try again.'.tr);
|
||||
mySnackbarError('Server error. Please try again.'.tr);
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -87,13 +87,13 @@ class PhoneAuthHelper {
|
||||
Get.to(() => RegistrationScreen(phoneNumber: phoneNumber));
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError(data['message']);
|
||||
mySnackbarError(data['message']);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('Server error. Please try again.'.tr);
|
||||
mySnackbarError('Server error. Please try again.'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('An error occurred: $e');
|
||||
mySnackbarError('An error occurred: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ class PhoneAuthHelper {
|
||||
|
||||
await _handleSuccessfulLogin(data['message']['data']);
|
||||
} else {
|
||||
mySnackeBarError(
|
||||
mySnackbarError(
|
||||
"User with this phone number or email already exists.".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('e: ${e}');
|
||||
mySnackeBarError('An error occurred: $e');
|
||||
mySnackbarError('An error occurred: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -308,17 +308,17 @@ class RegisterController extends GetxController {
|
||||
box.read(BoxName.email).toString(),
|
||||
);
|
||||
} else {
|
||||
mySnackeBarError("The email or phone number is already registered.".tr);
|
||||
mySnackbarError("The email or phone number is already registered.".tr);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError("phone not verified".tr);
|
||||
mySnackbarError("phone not verified".tr);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError("you must insert token code".tr);
|
||||
mySnackbarError("you must insert token code".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
addError(e.toString(), 'passenger sign up ');
|
||||
mySnackeBarError("Something went wrong. Please try again.".tr);
|
||||
mySnackbarError("Something went wrong. Please try again.".tr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,10 @@ class OtpVerificationController extends GetxController {
|
||||
isLoading.value = true;
|
||||
// بإمكانك عرض رسالة نجاح هنا
|
||||
} else {
|
||||
mySnackeBarError('Failed to send OTP'.tr);
|
||||
mySnackbarError('Failed to send OTP'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
} finally {
|
||||
// isLoading.value = false;
|
||||
}
|
||||
@@ -108,10 +108,10 @@ class OtpVerificationController extends GetxController {
|
||||
|
||||
Get.offAll(() => const MapPagePassenger());
|
||||
} else {
|
||||
mySnackeBarError('OTP is incorrect or expired');
|
||||
mySnackbarError('OTP is incorrect or expired');
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
} finally {
|
||||
isVerifying.value = false;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class CRUD {
|
||||
} on SocketException catch (_) {
|
||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||
if (attempts >= 3) {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
|
||||
_netGuard.notifyOnce((title, msg) => mySnackbarError(msg));
|
||||
return 'no_internet';
|
||||
}
|
||||
await Future.delayed(Duration(seconds: attempts));
|
||||
|
||||
@@ -198,7 +198,7 @@ class LogOutController extends GetxController {
|
||||
'email': box.read(BoxName.email),
|
||||
});
|
||||
} else {
|
||||
mySnackeBarError('Email you inserted is Wrong.'.tr);
|
||||
mySnackbarError('Email you inserted is Wrong.'.tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class ImageController extends GetxController {
|
||||
link,
|
||||
);
|
||||
} catch (e) {
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
} finally {
|
||||
isloading = false;
|
||||
update();
|
||||
|
||||
@@ -28,6 +28,7 @@ class MapEngineController extends GetxController {
|
||||
|
||||
Set<Marker> markers = {};
|
||||
Set<Polyline> polyLines = {};
|
||||
int markerRevision = 0;
|
||||
List<LatLng> polylineCoordinates = [];
|
||||
Set<Polygon> polygons = {};
|
||||
Set<Circle> circles = {};
|
||||
|
||||
@@ -1935,7 +1935,7 @@ class RideLifecycleController extends GetxController {
|
||||
Get.back();
|
||||
await Future.delayed(const Duration(milliseconds: 120));
|
||||
} catch (e) {
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2734,7 +2734,7 @@ class RideLifecycleController extends GetxController {
|
||||
throw Exception('Failed to save trip');
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('Failed to book trip: $e'.tr);
|
||||
mySnackbarError('Failed to book trip: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3494,20 +3494,13 @@ class RideLifecycleController extends GetxController {
|
||||
minutes = (durationToAdd.inMinutes % 60).round();
|
||||
|
||||
if (polyLines.isNotEmpty) {
|
||||
mapEngine.clearPolyline();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
mapEngine.polyLines.clear();
|
||||
}
|
||||
|
||||
rideConfirm = false;
|
||||
isMarkersShown = true;
|
||||
update();
|
||||
|
||||
await bottomSheet();
|
||||
|
||||
await mapEngine.playRouteAnimation(
|
||||
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
|
||||
|
||||
// بناء Set كاملة دفعة واحدة ثم تعيينها
|
||||
// بناء الـ markers أولاً ثم وضعها معاً مع polylines في تحديث واحد
|
||||
final Set<Marker> newMarkers = {
|
||||
Marker(
|
||||
markerId: const MarkerId('start'),
|
||||
@@ -3542,11 +3535,33 @@ class RideLifecycleController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// تعيين الـ markers مباشرة في mapEngine (بدون setter لتجنب update() مضاعف)
|
||||
mapEngine.markers = newMarkers;
|
||||
mapEngine.markerRevision++;
|
||||
|
||||
Log.print(
|
||||
'✅ FIX P1 v2: ${newMarkers.length} markers placed — start: $startLoc, end: $endLoc');
|
||||
mapEngine.update();
|
||||
|
||||
// إضافة الـ markers مباشرة عبر الـ controller لضمان ظهورها
|
||||
try {
|
||||
final ctrl = mapEngine.mapController;
|
||||
if (ctrl != null) {
|
||||
for (final m in newMarkers) {
|
||||
await ctrl.addMarker(m);
|
||||
}
|
||||
Log.print('✅ Added ${newMarkers.length} markers via controller.addMarker()');
|
||||
} else {
|
||||
Log.print('⚠️ mapController is null, relying on declarative markers');
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('⚠️ addMarker via controller failed: $e');
|
||||
}
|
||||
|
||||
await bottomSheet();
|
||||
|
||||
await mapEngine.playRouteAnimation(
|
||||
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
|
||||
|
||||
update();
|
||||
} catch (e, stackTrace) {
|
||||
if (isDrawingRoute) {
|
||||
isDrawingRoute = false;
|
||||
|
||||
@@ -41,7 +41,7 @@ class ComplaintController extends GetxController {
|
||||
void _showCustomSnackbar(String title, String message,
|
||||
{bool isError = false}) {
|
||||
if (isError) {
|
||||
mySnackeBarError(message.tr);
|
||||
mySnackbarError(message.tr);
|
||||
} else {
|
||||
mySnackbarSuccess(message.tr);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error picking contacts: $e');
|
||||
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
|
||||
mySnackbarError('An error occurred while picking contacts: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider
|
||||
void sendInviteToPassenger() async {
|
||||
if (invitePhoneController.text.isEmpty ||
|
||||
invitePhoneController.text.length < 9) {
|
||||
mySnackeBarError('Please enter a correct phone'.tr);
|
||||
mySnackbarError('Please enter a correct phone'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error sending invite: $e");
|
||||
mySnackeBarError('An unexpected error occurred. Please try again.'.tr);
|
||||
mySnackbarError('An unexpected error occurred. Please try again.'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider
|
||||
);
|
||||
fetchDriverStatsPassengers(); // Refresh list
|
||||
} else {
|
||||
mySnackeBarError(response['message'] ?? 'Claim failed'.tr);
|
||||
mySnackbarError(response['message'] ?? 'Claim failed'.tr);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -80,11 +80,11 @@ class InvitesRewardsController extends GetxController {
|
||||
mySnackbarWarning(response['message'] ?? "Could not add invite".tr);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError("Network error occurred".tr);
|
||||
mySnackbarError("Network error occurred".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only
|
||||
mySnackeBarError("Network error occurred".tr);
|
||||
mySnackbarError("Network error occurred".tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,15 +57,15 @@ class VipOrderController extends GetxController {
|
||||
tripData.value = res['message'];
|
||||
} else {
|
||||
tripData.clear(); // Ensure empty list if no data
|
||||
// mySnackeBarError('No trip data found');
|
||||
// mySnackbarError('No trip data found');
|
||||
}
|
||||
} else {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('Failed to fetch trip data');
|
||||
// mySnackbarError('Failed to fetch trip data');
|
||||
}
|
||||
} catch (e) {
|
||||
tripData.clear();
|
||||
// mySnackeBarError('An error occurred: $e');
|
||||
// mySnackbarError('An error occurred: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ class PaymentController extends GetxController {
|
||||
try {
|
||||
final phone = box.read(BoxName.phoneWallet) ?? walletphoneController.text.trim();
|
||||
if (phone.isEmpty) {
|
||||
mySnackeBarError('Please enter a phone number'.tr);
|
||||
mySnackbarError('Please enter a phone number'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,11 +465,11 @@ class PaymentController extends GetxController {
|
||||
amount: double.parse(amount),
|
||||
));
|
||||
} else {
|
||||
mySnackeBarError(resMap['message']?.toString() ?? 'Failed to create invoice'.tr);
|
||||
mySnackbarError(resMap['message']?.toString() ?? 'Failed to create invoice'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
mySnackeBarError(e.toString());
|
||||
mySnackbarError(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver {
|
||||
final permissionStatus = await Permission.microphone.request();
|
||||
if (!permissionStatus.isGranted) {
|
||||
_endCallInternal("permission_denied");
|
||||
mySnackeBarError("Microphone permission is required for voice calls".tr);
|
||||
mySnackbarError("Microphone permission is required for voice calls".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver {
|
||||
response == 'failure' ||
|
||||
response['status'] != 'success') {
|
||||
_endCallInternal("session_creation_failed");
|
||||
mySnackeBarError("Microphone permission is required for voice calls".tr);
|
||||
mySnackbarError("Microphone permission is required for voice calls".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver {
|
||||
final permissionStatus = await Permission.microphone.request();
|
||||
if (!permissionStatus.isGranted) {
|
||||
declineCall();
|
||||
mySnackeBarError("Microphone permission is required for voice calls".tr);
|
||||
mySnackbarError("Microphone permission is required for voice calls".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -830,7 +830,7 @@ class ShareAppPage extends StatelessWidget {
|
||||
rewardsController.linkInviteCode(manualCodeController.text.trim());
|
||||
manualCodeController.clear();
|
||||
} else {
|
||||
mySnackeBarError('Please enter a referral code'.tr);
|
||||
mySnackbarError('Please enter a referral code'.tr);
|
||||
}
|
||||
},
|
||||
child: Text('Link'.tr, style: const TextStyle(color: Colors.white, fontSize: 14)),
|
||||
|
||||
@@ -56,8 +56,8 @@ class GoogleMapPassengerWidget extends StatelessWidget {
|
||||
Log.print('⚠️ onCameraIdle: mapController is NULL');
|
||||
}
|
||||
},
|
||||
markers: controller.markers,
|
||||
polylines: controller.polyLines,
|
||||
markers: Set.of(controller.markers),
|
||||
polylines: Set.of(controller.polyLines),
|
||||
polygons: controller.polygons,
|
||||
circles: controller.circles,
|
||||
initialCameraPosition: CameraPosition(
|
||||
|
||||
@@ -387,10 +387,10 @@ class MapMenuWidget extends StatelessWidget {
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
} else {
|
||||
mySnackeBarError('Could not launch driver app store.');
|
||||
mySnackbarError('Could not launch driver app store.');
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('Could not open the link.');
|
||||
mySnackbarError('Could not open the link.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
|
||||
|
||||
Future<void> _submitProof() async {
|
||||
if (_proofController.text.trim().isEmpty) {
|
||||
mySnackeBarError('Please paste the transfer message'.tr);
|
||||
mySnackbarError('Please paste the transfer message'.tr);
|
||||
return;
|
||||
}
|
||||
setState(() => _status = 'verifying');
|
||||
|
||||
@@ -65,7 +65,7 @@ class _PaymentScreenMtnState extends State<PaymentScreenMtn> with SingleTickerPr
|
||||
|
||||
Future<void> _submitProof() async {
|
||||
if (_proofController.text.trim().isEmpty) {
|
||||
mySnackeBarError('Please paste the transfer message'.tr);
|
||||
mySnackbarError('Please paste the transfer message'.tr);
|
||||
return;
|
||||
}
|
||||
setState(() => _status = 'verifying');
|
||||
|
||||
@@ -100,7 +100,7 @@ class _PassengerProfilePageState extends State<PassengerProfilePage> {
|
||||
}
|
||||
|
||||
void _showUploadError() {
|
||||
mySnackeBarError('Failed to upload photo'.tr);
|
||||
mySnackbarError('Failed to upload photo'.tr);
|
||||
}
|
||||
|
||||
Future<ImageSource?> _showImageSourceSheet() async {
|
||||
|
||||
@@ -2,11 +2,7 @@ import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/colors.dart';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Snackbar variant definition
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
enum _SnackVariant { success, error, info, warning }
|
||||
|
||||
extension _VariantProps on _SnackVariant {
|
||||
@@ -37,20 +33,8 @@ extension _VariantProps on _SnackVariant {
|
||||
_SnackVariant.info => 'Info',
|
||||
_SnackVariant.warning => 'Warning',
|
||||
};
|
||||
|
||||
HapticFeedbackType get haptic => switch (this) {
|
||||
_SnackVariant.error => HapticFeedbackType.medium,
|
||||
_SnackVariant.warning => HapticFeedbackType.medium,
|
||||
_SnackVariant.success => HapticFeedbackType.light,
|
||||
_SnackVariant.info => HapticFeedbackType.selection,
|
||||
};
|
||||
}
|
||||
|
||||
enum HapticFeedbackType { light, medium, selection }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Core snackbar widget
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
class _SnackContent extends StatefulWidget {
|
||||
final String message;
|
||||
final _SnackVariant variant;
|
||||
@@ -74,21 +58,17 @@ class _SnackContentState extends State<_SnackContent>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ctrl = AnimationController(vsync: this, duration: _displayDuration);
|
||||
|
||||
_scaleCtrl = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
|
||||
_scaleAnim = CurvedAnimation(
|
||||
parent: _scaleCtrl,
|
||||
curve: Curves.elasticOut,
|
||||
);
|
||||
|
||||
_progressAnim = Tween<double>(begin: 1.0, end: 0.0).animate(
|
||||
CurvedAnimation(parent: _ctrl, curve: Curves.linear),
|
||||
);
|
||||
|
||||
_scaleCtrl.forward();
|
||||
_ctrl.forward();
|
||||
}
|
||||
@@ -111,16 +91,16 @@ class _SnackContentState extends State<_SnackContent>
|
||||
decoration: BoxDecoration(
|
||||
color: surface,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: accent.withOpacity(0.18), width: 1.2),
|
||||
border: Border.all(color: accent.withAlpha(46), width: 1.2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: accent.withOpacity(0.12),
|
||||
color: accent.withAlpha(31),
|
||||
blurRadius: 20,
|
||||
spreadRadius: -2,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.06),
|
||||
color: Colors.black.withAlpha(15),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@@ -131,28 +111,24 @@ class _SnackContentState extends State<_SnackContent>
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// ── Main row ──────────────────────────────────────────────────
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 10, 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Animated icon badge
|
||||
ScaleTransition(
|
||||
scale: _scaleAnim,
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withOpacity(0.12),
|
||||
color: accent.withAlpha(31),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(v.icon, color: accent, size: 22),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Text content
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -170,8 +146,8 @@ class _SnackContentState extends State<_SnackContent>
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
widget.message,
|
||||
style: TextStyle(
|
||||
color: Colors.grey[800],
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF424242),
|
||||
fontSize: 13.5,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -182,19 +158,17 @@ class _SnackContentState extends State<_SnackContent>
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Close button
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
Get.closeCurrentSnackbar();
|
||||
_closeSnackbar(context);
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
margin: const EdgeInsets.only(left: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
color: Colors.grey.withAlpha(25),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
@@ -207,24 +181,17 @@ class _SnackContentState extends State<_SnackContent>
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ── Thin progress strip ───────────────────────────────────────
|
||||
AnimatedBuilder(
|
||||
animation: _progressAnim,
|
||||
builder: (_, __) => Stack(
|
||||
children: [
|
||||
// Track
|
||||
Container(
|
||||
height: 3,
|
||||
color: accent.withOpacity(0.08),
|
||||
),
|
||||
// Fill
|
||||
Container(height: 3, color: accent.withAlpha(20)),
|
||||
FractionallySizedBox(
|
||||
widthFactor: _progressAnim.value,
|
||||
child: Container(
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withOpacity(0.45),
|
||||
color: accent.withAlpha(115),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(4),
|
||||
bottomRight: Radius.circular(4),
|
||||
@@ -240,84 +207,57 @@ class _SnackContentState extends State<_SnackContent>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _closeSnackbar(BuildContext context) {
|
||||
ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar();
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Internal dispatcher — single source of truth
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
int _retryCount = 0;
|
||||
|
||||
SnackbarController? _show(_SnackVariant variant, String message) {
|
||||
// Prevent crash if Navigator or Overlay context is not yet initialized at early startup
|
||||
if (Get.context == null || Get.overlayContext == null || Get.key.currentState?.overlay == null) {
|
||||
debugPrint("⚠️ Cannot show snackbar: Overlay/Navigator is not ready yet. Message: $message");
|
||||
// Retry up to 3 times after the next frame (handles race condition during route transitions)
|
||||
void _show(_SnackVariant variant, String message) {
|
||||
if (Get.context == null) {
|
||||
if (_retryCount < 3) {
|
||||
_retryCount++;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message));
|
||||
}
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
_retryCount = 0;
|
||||
|
||||
try {
|
||||
// Removed Get.closeCurrentSnackbar() because it causes async LateInitializationError in GetX at early startup
|
||||
final context = Get.context;
|
||||
if (context == null) return;
|
||||
|
||||
// We use ScaffoldMessenger instead of Get.snackbar because GetX's snackbar
|
||||
// throws synchronous "No Overlay widget found" FlutterErrors when the
|
||||
// Overlay isn't perfectly mounted, crashing the app globally.
|
||||
final context = Get.context;
|
||||
if (context == null) return null;
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
if (messenger == null) return;
|
||||
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
if (messenger == null) {
|
||||
debugPrint("⚠️ Cannot show snackbar: ScaffoldMessenger not found. Message: $message");
|
||||
return null;
|
||||
}
|
||||
messenger.clearSnackBars();
|
||||
|
||||
messenger.clearSnackBars(); // Prevent stacking
|
||||
|
||||
switch (variant.haptic) {
|
||||
case HapticFeedbackType.light:
|
||||
HapticFeedback.lightImpact();
|
||||
case HapticFeedbackType.medium:
|
||||
HapticFeedback.mediumImpact();
|
||||
case HapticFeedbackType.selection:
|
||||
HapticFeedback.selectionClick();
|
||||
}
|
||||
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: _SnackContent(message: message, variant: variant),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 4),
|
||||
dismissDirection: DismissDirection.up,
|
||||
),
|
||||
);
|
||||
|
||||
return null; // We return null since we no longer use Get.snackbar's controller
|
||||
} catch (e) {
|
||||
debugPrint("⚠️ Exception caught showing snackbar: $e");
|
||||
return null;
|
||||
switch (variant) {
|
||||
case _SnackVariant.error:
|
||||
case _SnackVariant.warning:
|
||||
HapticFeedback.mediumImpact();
|
||||
case _SnackVariant.success:
|
||||
HapticFeedback.lightImpact();
|
||||
case _SnackVariant.info:
|
||||
HapticFeedback.selectionClick();
|
||||
}
|
||||
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: _SnackContent(message: message, variant: variant),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 4),
|
||||
dismissDirection: DismissDirection.up,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Public API — drop-in replacements for the old functions
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
SnackbarController? mySnackbarSuccess(String message) =>
|
||||
_show(_SnackVariant.success, message);
|
||||
|
||||
SnackbarController? mySnackeBarError(String message) =>
|
||||
_show(_SnackVariant.error, message);
|
||||
|
||||
SnackbarController? mySnackbarInfo(String message) =>
|
||||
_show(_SnackVariant.info, message);
|
||||
|
||||
SnackbarController? mySnackbarWarning(String message) =>
|
||||
_show(_SnackVariant.warning, message);
|
||||
|
||||
void mySnackbarSuccess(String message) => _show(_SnackVariant.success, message);
|
||||
void mySnackbarError(String message) => _show(_SnackVariant.error, message);
|
||||
void mySnackbarInfo(String message) => _show(_SnackVariant.info, message);
|
||||
void mySnackbarWarning(String message) => _show(_SnackVariant.warning, message);
|
||||
|
||||
Reference in New Issue
Block a user