Files
intaleq_driver/lib/views/home/Captin/home_captain/home_captin.dart

671 lines
22 KiB
Dart
Executable File

import 'dart:io';
import 'package:bubble_head/bubble.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:sefer_driver/views/home/Captin/home_captain/drawer_captain.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/info.dart';
import '../../../../constant/style.dart';
import '../../../../controller/functions/location_background_controller.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/functions/overlay_permisssion.dart';
import '../../../../controller/functions/package_info.dart';
import '../../../../controller/home/captin/home_captain_controller.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
import '../../../../controller/home/navigation/navigation_view.dart';
import '../../../../controller/profile/setting_controller.dart';
import '../../../../env/env.dart';
import '../../../../main.dart';
import '../../../notification/available_rides_page.dart';
import '../driver_map_page.dart';
import 'widget/connect.dart';
import 'widget/left_menu_map_captain.dart';
// ─────────────────────────────────────────────
// Design Tokens (Responsive to Theme)
// ─────────────────────────────────────────────
class _Token {
static Color surface(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF1A1A2E)
: Colors.white;
static Color surfaceCard(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF16213E)
: const Color(0xFFF8F9FA);
static const Color accent = Color(0xFFF0A500);
static Color accentSoft(BuildContext context) =>
const Color(0xFFF0A500).withOpacity(0.12);
static const Color danger = Color(0xFFE53935);
static const Color success = Color(0xFF2ECC71);
static const Color info = Color(0xFF3498DB);
static Color border(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF2A2A4A)
: Colors.grey.withOpacity(0.2);
static Color text(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? Colors.white
: const Color(0xFF2D3436);
static Color textDim(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? Colors.white38
: Colors.black45;
static const double radius = 16.0;
static const double radiusSm = 10.0;
}
// ─────────────────────────────────────────────
// Root Widget
// ─────────────────────────────────────────────
class HomeCaptain extends StatelessWidget {
HomeCaptain({super.key});
final LocationController locationController =
Get.put(LocationController(), permanent: true);
final HomeCaptainController homeCaptainController =
Get.put(HomeCaptainController());
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await closeOverlayIfFound();
await checkForUpdate(context);
await getPermissionOverlay();
await showDriverGiftClaim(context);
await checkForAppliedRide(context);
});
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: _Token.surface(context),
appBar: const _HomeAppBar(),
drawer: AppDrawer(),
body: SafeArea(
top: false,
child: Stack(
children: [
const _MapView(),
leftMainMenuCaptainIcons(),
const _BottomStatusBar(),
const _FloatingControls(),
],
),
),
);
}
}
// ─────────────────────────────────────────────
// AppBar — no blur, solid gradient
// ─────────────────────────────────────────────
class _HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
const _HomeAppBar();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@override
Widget build(BuildContext context) {
final ctrl = Get.find<HomeCaptainController>();
return AppBar(
backgroundColor: _Token.surface(context),
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.light,
titleSpacing: 0,
// ── Logo + App Name ──────────────────────
title: Padding(
padding: const EdgeInsets.only(left: 4),
child: Row(
children: [
_LogoBadge(),
const SizedBox(width: 10),
Text(
AppInformation.appName.split(' ')[0].tr,
style: const TextStyle(
color: _Token.accent,
fontSize: 20,
fontWeight: FontWeight.w800,
letterSpacing: 0.8,
),
),
],
),
),
actions: [
// ── Refuse Counter ───────────────────
GetBuilder<HomeCaptainController>(
builder: (c) => _PillBadge(
icon: Icons.block_rounded,
label: c.countRefuse.toString(),
color: _Token.danger,
),
),
const SizedBox(width: 6),
// ── Map Controls Row ─────────────────
_AppBarControls(ctrl: ctrl),
const SizedBox(width: 8),
],
);
}
}
class _LogoBadge extends StatelessWidget {
@override
Widget build(BuildContext context) => Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: _Token.surfaceCard(context),
shape: BoxShape.circle,
border: Border.all(color: _Token.accent, width: 1.5),
),
child: ClipOval(
child: Image.asset('assets/images/logo.gif', fit: BoxFit.cover),
),
);
}
class _PillBadge extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
const _PillBadge(
{required this.icon, required this.label, required this.color});
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withOpacity(0.12),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.35)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 13),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
color: color, fontWeight: FontWeight.bold, fontSize: 13),
),
],
),
);
}
class _AppBarControls extends StatelessWidget {
final HomeCaptainController ctrl;
const _AppBarControls({required this.ctrl});
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
decoration: BoxDecoration(
color: _Token.surfaceCard(context),
borderRadius: BorderRadius.circular(_Token.radiusSm),
border: Border.all(color: _Token.border(context)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Navigation
_IconBtn(
icon: Icons.map_rounded,
color: _Token.success,
tooltip: 'Navigation'.tr,
onTap: () => Get.to(() => const NavigationView()),
),
// Heatmap
GetBuilder<HomeCaptainController>(
builder: (c) => _IconBtn(
icon: Icons.local_fire_department_rounded,
color:
c.isHeatmapVisible ? Colors.orange : Colors.grey.shade600,
tooltip: 'Heatmap'.tr,
onTap: c.toggleHeatmap,
),
),
// Center on me
_IconBtn(
icon: Icons.my_location_rounded,
color: _Token.accent,
tooltip: 'My Location'.tr,
onTap: () {
ctrl.mapHomeCaptainController?.animateCamera(
CameraUpdate.newLatLngZoom(
Get.find<LocationController>().myLocation,
17.5,
),
);
},
),
],
),
);
}
class _IconBtn extends StatelessWidget {
final IconData icon;
final Color color;
final String tooltip;
final VoidCallback onTap;
const _IconBtn(
{required this.icon,
required this.color,
required this.tooltip,
required this.onTap});
@override
Widget build(BuildContext context) => Tooltip(
message: tooltip,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 6),
child: Icon(icon, color: color, size: 20),
),
),
);
}
// ─────────────────────────────────────────────
// Map View
// ─────────────────────────────────────────────
class _MapView extends StatelessWidget {
const _MapView();
@override
Widget build(BuildContext context) {
return GetBuilder<HomeCaptainController>(
builder: (ctrl) {
if (ctrl.isLoading) return const MyCircularProgressIndicator();
final s = Get.find<SettingController>();
return GetBuilder<LocationController>(
builder: (loc) => IntaleqMap(
apiKey: Env.mapSaasKey,
onMapCreated: ctrl.onMapCreated,
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
initialCameraPosition: CameraPosition(
target: (loc.myLocation.latitude == 0 ||
loc.myLocation.latitude.isNaN)
? ctrl.myLocation
: loc.myLocation,
zoom: 15,
),
mapType:
s.isMapDarkMode ? IntaleqMapType.normal : IntaleqMapType.light,
polygons: ctrl.heatmapPolygons,
markers: {
Marker(
markerId: MarkerId('MyLocation'.tr),
position: loc.myLocation,
rotation: loc.heading,
flat: true,
anchor: const Offset(0.5, 0.5),
icon: ctrl.carIcon,
)
},
myLocationButtonEnabled: false,
myLocationEnabled: false,
compassEnabled: false,
zoomControlsEnabled: false,
),
);
},
);
}
}
// ─────────────────────────────────────────────
// Bottom Status Bar — no blur, solid card
// ─────────────────────────────────────────────
class _BottomStatusBar extends StatelessWidget {
const _BottomStatusBar();
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 12,
left: 12,
right: 12,
child: GestureDetector(
onTap: () => _showDetailsSheet(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: _Token.surfaceCard(context),
borderRadius: BorderRadius.circular(_Token.radius),
border: Border.all(color: _Token.border(context)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.35),
blurRadius: 16,
offset: const Offset(0, 4),
)
],
),
child: Row(
children: [
// Online toggle
const ConnectWidget(),
const Spacer(),
// Ride count
GetBuilder<HomeCaptainController>(
builder: (c) => _StatChip(
icon: Icons.directions_car_rounded,
value: c.countRideToday,
label: 'Rides'.tr,
color: _Token.info,
),
),
const SizedBox(width: 14),
// Today earnings
GetBuilder<HomeCaptainController>(
builder: (c) => _StatChip(
icon: Entypo.wallet,
value: c.totalMoneyToday.toString(),
label: 'Today'.tr,
color: _Token.success,
),
),
// Chevron hint
const SizedBox(width: 8),
const Icon(Icons.keyboard_arrow_up_rounded,
color: Colors.grey, size: 18),
],
),
),
),
);
}
void _showDetailsSheet(BuildContext context) {
final ctrl = Get.find<HomeCaptainController>();
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (_) => _DetailsSheet(ctrl),
);
}
}
class _StatChip extends StatelessWidget {
final IconData icon;
final String value;
final String label;
final Color color;
const _StatChip(
{required this.icon,
required this.value,
required this.label,
required this.color});
@override
Widget build(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 4),
Text(
value,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
),
],
),
Text(label,
style: const TextStyle(color: Colors.white38, fontSize: 11)),
],
);
}
// ─────────────────────────────────────────────
// Details Bottom Sheet — replaces AlertDialog
// ─────────────────────────────────────────────
class _DetailsSheet extends StatelessWidget {
final HomeCaptainController ctrl;
const _DetailsSheet(this.ctrl);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: _Token.surfaceCard(context),
borderRadius: BorderRadius.circular(24),
),
padding: const EdgeInsets.fromLTRB(20, 12, 20, 28),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag handle
Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: _Token.border(context),
borderRadius: BorderRadius.circular(2)),
),
const SizedBox(height: 16),
Text(
'Your Activity'.tr,
style: TextStyle(
color: _Token.text(context),
fontSize: 18,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Divider(color: _Token.border(context), height: 1),
const SizedBox(height: 16),
_SheetRow(
icon: Entypo.wallet,
color: _Token.success,
label: 'Today'.tr,
value: ctrl.totalMoneyToday.toString(),
),
const SizedBox(height: 12),
_SheetRow(
icon: Entypo.wallet,
color: _Token.accent,
label: AppInformation.appName,
value: ctrl.totalMoneyInSEFER.toString(),
),
const SizedBox(height: 12),
Divider(color: _Token.border(context), height: 1),
const SizedBox(height: 12),
_SheetRow(
icon: Icons.timer_outlined,
color: _Token.success,
label: 'Active Duration'.tr,
value: ctrl.stringActiveDuration,
),
const SizedBox(height: 12),
_SheetRow(
icon: Icons.access_time_rounded,
color: _Token.info,
label: 'Total Connection'.tr,
value: ctrl.totalDurationToday,
),
const SizedBox(height: 12),
Divider(color: _Token.border(context), height: 1),
const SizedBox(height: 12),
_SheetRow(
icon: Icons.star_rounded,
color: _Token.accent,
label: 'Total Points'.tr,
value: ctrl.totalPoints.toString(),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
backgroundColor: _Token.accentSoft(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(_Token.radiusSm)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text('Close'.tr,
style: const TextStyle(
color: _Token.accent, fontWeight: FontWeight.bold)),
),
),
],
),
);
}
}
class _SheetRow extends StatelessWidget {
final IconData icon;
final Color color;
final String label;
final String value;
const _SheetRow(
{required this.icon,
required this.color,
required this.label,
required this.value});
@override
Widget build(BuildContext context) => Row(
children: [
Icon(icon, color: color, size: 20),
const SizedBox(width: 12),
Text(label,
style: TextStyle(color: _Token.textDim(context), fontSize: 14)),
const Spacer(),
Text(value,
style: TextStyle(
color: color, fontSize: 16, fontWeight: FontWeight.bold)),
],
);
}
// ─────────────────────────────────────────────
// Floating Action Buttons (right side)
// ─────────────────────────────────────────────
class _FloatingControls extends StatelessWidget {
const _FloatingControls({super.key});
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 88,
right: 12,
child: GetBuilder<HomeCaptainController>(
builder: (ctrl) => Column(
children: [
// Bubble/overlay mode (Android only)
if (Platform.isAndroid) ...[
_Fab(
onTap: () =>
Bubble().startBubbleHead(sendAppToBackground: true),
tooltip: 'Overlay'.tr,
child: Image.asset('assets/images/logo1.png',
width: 26, height: 26),
),
const SizedBox(height: 10),
],
// Available rides
_Fab(
onTap: () => Get.to(() => const AvailableRidesPage()),
icon: Icons.list_alt_rounded,
color: AppColor.primaryColor,
tooltip: 'Available Rides'.tr,
),
// Continue active ride
if (box.read(BoxName.rideStatus) == 'Applied' ||
box.read(BoxName.rideStatus) == 'Begin') ...[
const SizedBox(height: 10),
_Fab(
onTap: () {
if (box.read(BoxName.rideStatus) == 'Applied') {
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Get.put(MapDriverController())
.changeRideToBeginToPassenger();
} else {
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArguments));
Get.put(MapDriverController()).startRideFromStartApp();
}
},
icon: Icons.navigation_rounded,
color: _Token.info,
tooltip: 'Continue Ride'.tr,
),
],
],
),
),
);
}
}
class _Fab extends StatelessWidget {
final VoidCallback onTap;
final IconData? icon;
final Widget? child;
final Color? color;
final String? tooltip;
const _Fab(
{required this.onTap, this.icon, this.child, this.color, this.tooltip});
@override
Widget build(BuildContext context) => Tooltip(
message: tooltip ?? '',
child: Material(
color: color ?? _Token.surfaceCard(context),
shape: const CircleBorder(),
elevation: 6,
shadowColor: (color ?? Colors.black).withOpacity(0.4),
child: InkWell(
onTap: onTap,
customBorder: const CircleBorder(),
child: SizedBox(
width: 52,
height: 52,
child: Center(
child: child ??
Icon(icon,
color: color != null ? Colors.white : _Token.accent,
size: 24),
),
),
),
),
);
}
// ─────────────────────────────────────────────
// Helper
// ─────────────────────────────────────────────
Future<void> checkForAppliedRide(BuildContext context) async {
checkForPendingOrderFromServer();
}