feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.

This commit is contained in:
Hamza-Ayed
2026-07-19 16:21:58 +03:00
parent 959e2fa758
commit c4dfeb4c6f
207 changed files with 14017 additions and 5567 deletions
@@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:driver/core/l10n/app_localizations.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:driver/core/design/tokens.dart';
import 'package:driver/core/design/tripz_colors.dart';
import 'package:driver/features/trip/bloc/driver_trip_bloc.dart';
/// زر الإجراء التالي في شاشة تنفيذ الرحلة.
///
/// الحالة → الزر:
/// - assigned → "في الطريق" (driver_arriving)
/// - driver_arriving → "وصلت" (driver_arrived)
/// - driver_arrived → "بدء الرحلة" (in_progress)
/// - in_progress → "إتمام" (completed)
class TripActionButton extends StatelessWidget {
final dynamic trip;
const TripActionButton({super.key, required this.trip});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final cs = Theme.of(context).colorScheme;
final bloc = context.watch<DriverTripBloc>();
final state = bloc.state;
final nextStatus = state.nextStatus;
if (nextStatus == null) return const SizedBox.shrink();
final label = _buttonLabel(l10n, nextStatus);
final color = nextStatus == 'completed'
? context.tripzColors.success
: cs.primary;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// أزرار ثانوية
Row(
children: [
if (trip.clientPhone != null) ...[
Expanded(
child: _SecondaryButton(
icon: Icons.phone,
label: l10n.callClient,
onTap: () => _launchPhone(trip.clientPhone!),
),
),
const SizedBox(width: T.s8),
],
Expanded(
child: _SecondaryButton(
icon: Icons.chat_bubble_outline,
label: l10n.chat,
onTap: () {
// TODO: Q4 chat screen
},
),
),
],
),
const SizedBox(height: T.s12),
// زر الإجراء الرئيسي
SizedBox(
width: double.infinity,
height: T.buttonHeight,
child: ElevatedButton(
onPressed: state.loading
? null
: () {
bloc.add(DriverTripStatusChanged(nextStatus));
},
style: ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(T.r12),
),
elevation: 0,
),
child: state.loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
);
}
String _buttonLabel(AppLocalizations l10n, String status) {
switch (status) {
case 'driver_arriving':
return l10n.onTheWay;
case 'driver_arrived':
return l10n.arrivedAtPickup;
case 'in_progress':
return l10n.startTrip;
case 'completed':
return l10n.completeTrip;
default:
return status;
}
}
void _launchPhone(String phone) async {
final uri = Uri(scheme: 'tel', path: phone);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
}
/// زر ثانوي صغير.
class _SecondaryButton extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
const _SecondaryButton({
required this.icon,
required this.label,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: onTap,
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(T.r12),
),
minimumSize: const Size(0, T.buttonHeight),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 18),
const SizedBox(width: T.s4),
Text(label),
],
),
);
}
}