feat: implement core architecture, design system, localization, and trip features for both driver and rider apps.
This commit is contained in:
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user