chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل
نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_service/constant/colors.dart';
|
||||
import 'package:siro_service/controller/auth/register_controller.dart';
|
||||
import 'package:siro_service/views/widgets/my_textField.dart';
|
||||
|
||||
class RegisterPage extends StatelessWidget {
|
||||
const RegisterPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final RegisterController controller = Get.put(RegisterController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FD),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black87),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
title: Text(
|
||||
'إنشاء حساب موظف'.tr,
|
||||
style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Center(
|
||||
child: Icon(
|
||||
Icons.person_add_rounded,
|
||||
size: 80,
|
||||
color: AppColor.blueColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
_buildFieldTitle('الاسم الأول'),
|
||||
MyTextForm(
|
||||
controller: controller.firstName,
|
||||
label: 'الاسم الأول',
|
||||
hint: 'أدخل الاسم الأول',
|
||||
type: TextInputType.name,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildFieldTitle('الاسم الأخير'),
|
||||
MyTextForm(
|
||||
controller: controller.lastName,
|
||||
label: 'الاسم الأخير',
|
||||
hint: 'أدخل الاسم الأخير',
|
||||
type: TextInputType.name,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildFieldTitle('البريد الإلكتروني'),
|
||||
MyTextForm(
|
||||
controller: controller.email,
|
||||
label: 'البريد الإلكتروني',
|
||||
hint: 'example@mail.com',
|
||||
type: TextInputType.emailAddress,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildFieldTitle('رقم الهاتف'),
|
||||
MyTextForm(
|
||||
controller: controller.phone,
|
||||
label: 'رقم الهاتف',
|
||||
hint: '09xxxxxxxx',
|
||||
type: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildFieldTitle('كلمة المرور'),
|
||||
MyTextForm(
|
||||
controller: controller.password,
|
||||
label: 'كلمة المرور',
|
||||
hint: '********',
|
||||
type: TextInputType.visiblePassword,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
Obx(() => controller.isLoading.value
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container(
|
||||
height: 55,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppColor.blueColor, Color(0xFF1A237E)],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.blueColor.withOpacity(0.3),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () => controller.register(),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Submit registration request'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'لديك حساب بالفعل؟ '.tr,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'Login'.tr,
|
||||
style: const TextStyle(
|
||||
color: AppColor.blueColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFieldTitle(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0, right: 4.0),
|
||||
child: Text(
|
||||
title.tr,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_service/constant/colors.dart';
|
||||
import 'package:siro_service/constant/links.dart';
|
||||
import 'package:siro_service/controller/functions/crud.dart';
|
||||
import 'package:siro_service/controller/mainController/main_controller.dart';
|
||||
import 'package:siro_service/controller/mainController/pages/best_driver_page.dart';
|
||||
import 'package:siro_service/controller/mainController/pages/complaint.dart';
|
||||
import 'package:siro_service/controller/mainController/pages/edit_car_plate.dart';
|
||||
import 'package:siro_service/controller/mainController/pages/passengers_cant_regster.dart';
|
||||
import 'package:siro_service/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_service/views/widgets/my_dialog.dart';
|
||||
import 'package:siro_service/views/widgets/my_textField.dart';
|
||||
|
||||
import '../../constant/style.dart';
|
||||
import '../../controller/mainController/pages/ride_monitor_page.dart';
|
||||
import '../widgets/mycircular.dart';
|
||||
import '../../controller/mainController/pages/add_car.dart';
|
||||
import '../../controller/mainController/pages/drivers_cant_register.dart';
|
||||
import '../../controller/mainController/pages/new_driver.dart';
|
||||
import '../../controller/mainController/pages/welcome_call.dart';
|
||||
import '../../main.dart';
|
||||
import '../widgets/my_scafold.dart';
|
||||
import '../../login_page.dart';
|
||||
|
||||
class Main extends StatelessWidget {
|
||||
Main({super.key});
|
||||
|
||||
final MainController mainController = Get.put(MainController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyScaffold(title: 'Siro Service'.tr, isleading: false, body: [
|
||||
Container(
|
||||
color: AppColor.surfaceColor,
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- Header Section ---
|
||||
_buildHeader(),
|
||||
|
||||
// --- Statistics Section ---
|
||||
_buildStatsSection(),
|
||||
|
||||
// --- Actions Section ---
|
||||
_buildCategoryTitle('🔍 Search & Inquiries'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: 'passenger details by phone'.tr,
|
||||
icon: Icons.person_search_rounded,
|
||||
color: Colors.blue,
|
||||
onTap: () => _showSearchDialog(
|
||||
title: 'insert passenger phone'.tr,
|
||||
controller: mainController.passengerPhoneController,
|
||||
onSearch: () => mainController.searchPassengerByPhone(),
|
||||
),
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Driver details by phone'.tr,
|
||||
icon: Icons.contact_phone_rounded,
|
||||
color: Colors.indigo,
|
||||
onTap: () => _showSearchDialog(
|
||||
title: 'insert Driver phone'.tr,
|
||||
controller: mainController.driverPhoneController,
|
||||
onSearch: () => mainController.searchDriverByPhone(),
|
||||
),
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Driver details by national number'.tr,
|
||||
icon: Icons.badge_rounded,
|
||||
color: Colors.teal,
|
||||
onTap: () => _showSearchDialog(
|
||||
title: 'insert Driver national'.tr,
|
||||
controller: mainController.driverPhoneController,
|
||||
type: TextInputType.number,
|
||||
onSearch: () => mainController.searchDriverByNational(),
|
||||
),
|
||||
),
|
||||
]),
|
||||
|
||||
_buildCategoryTitle('⏳ Approval Queue'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: 'Drivers waitting Register'.tr,
|
||||
icon: Icons.pending_actions_rounded,
|
||||
color: Colors.orange,
|
||||
onTap: () async {
|
||||
await mainController.getDriverWantCompleteRegistration();
|
||||
Get.to(() => DriversCantRegister());
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Drivers phones not register'.tr,
|
||||
icon: Icons.phone_disabled_rounded,
|
||||
color: Colors.blueGrey,
|
||||
onTap: () async {
|
||||
await mainController.getDriversPhoneNotComplete();
|
||||
Get.to(() => DriversCantRegister());
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Drivers Activity'.tr,
|
||||
icon: Icons.assessment_rounded,
|
||||
color: Colors.blueAccent,
|
||||
onTap: () async {
|
||||
await mainController.getDriversPhoneNotComplete();
|
||||
Get.to(() => DriversCantRegister());
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Drivers Cant Register'.tr,
|
||||
icon: Icons.car_crash_rounded,
|
||||
color: Colors.redAccent,
|
||||
onTap: () async {
|
||||
await mainController.getDriverNotCompleteRegistration();
|
||||
Get.to(() => DriversCantRegister());
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Passengers Cant Register'.tr,
|
||||
icon: Icons.group_off_rounded,
|
||||
color: Colors.deepOrange,
|
||||
onTap: () async {
|
||||
await mainController.getPassengerNotCompleteRegistration();
|
||||
Get.to(() => PassengersCantRegister());
|
||||
},
|
||||
),
|
||||
]),
|
||||
|
||||
_buildCategoryTitle('⚡ Quick Actions'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: 'Register new driver'.tr,
|
||||
icon: Icons.person_add_rounded,
|
||||
color: Colors.green,
|
||||
onTap: () => Get.to(() => RegisterCaptain()),
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Add Driver Who Wants to Work'.tr,
|
||||
icon: Icons.handshake_rounded,
|
||||
color: Colors.lightGreen,
|
||||
onTap: () => _showAddDriverDialog(),
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Add Car Who Wants to Work'.tr,
|
||||
icon: Icons.add_road_rounded,
|
||||
color: Colors.cyan,
|
||||
onTap: () => _showAddCarDialog(),
|
||||
),
|
||||
]),
|
||||
|
||||
_buildCategoryTitle('🚗 Vehicle Management'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: 'Add car'.tr,
|
||||
icon: Icons.add_circle_outline_rounded,
|
||||
color: Colors.purple,
|
||||
onTap: () async {
|
||||
await mainController.getdriverWithoutCar();
|
||||
if (mainController.driverWithoutCar.isNotEmpty) {
|
||||
Get.to(() => const AddCar());
|
||||
}
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: 'Edit car plate'.tr,
|
||||
icon: Icons.edit_attributes_rounded,
|
||||
color: Colors.amber,
|
||||
onTap: () async {
|
||||
await mainController.getCarPlateNotEdit();
|
||||
if (mainController.carPlateNotEdit.isNotEmpty) {
|
||||
Get.to(() => const EditCarPlate());
|
||||
}
|
||||
},
|
||||
),
|
||||
]),
|
||||
|
||||
_buildCategoryTitle('🚗 متابعة الرحلات'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: 'Follow up ride'.tr,
|
||||
icon: Icons.map_rounded,
|
||||
color: const Color(0xFF4318FF),
|
||||
onTap: () => Get.to(() => const RideMonitorServicePage()),
|
||||
),
|
||||
]),
|
||||
|
||||
_buildCategoryTitle('📊 Reporting & Quality'.tr),
|
||||
_buildGridSection([
|
||||
ServiceItem(
|
||||
title: "View complaint".tr,
|
||||
icon: Icons.report_problem_rounded,
|
||||
color: Colors.deepOrange,
|
||||
onTap: () => Get.to(() => const Complaint()),
|
||||
),
|
||||
ServiceItem(
|
||||
title: "Welcome call".tr,
|
||||
icon: Icons.ring_volume_rounded,
|
||||
color: Colors.pink,
|
||||
onTap: () async {
|
||||
await mainController.getNewDriverRegister();
|
||||
Get.to(() => const WelcomeCall());
|
||||
},
|
||||
),
|
||||
ServiceItem(
|
||||
title: "best driver".tr,
|
||||
icon: Icons.emoji_events_rounded,
|
||||
color: Colors.orangeAccent,
|
||||
onTap: () async {
|
||||
await mainController.getNewDriverRegister();
|
||||
Get.to(() => DriverTheBest());
|
||||
},
|
||||
),
|
||||
]),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColor.primaryColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32),
|
||||
bottomRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Welcome back,'.tr,
|
||||
style: AppStyle.title.copyWith(color: Colors.white70),
|
||||
),
|
||||
Text(
|
||||
'Service Agent'.tr,
|
||||
style: AppStyle.headTitle2.copyWith(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon:
|
||||
const Icon(Icons.refresh_rounded, color: Colors.white),
|
||||
onPressed: () => mainController.refreshDashboardStats(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: Colors.white24,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.logout, color: Colors.white),
|
||||
onPressed: () async {
|
||||
box.erase();
|
||||
await storage.deleteAll();
|
||||
Get.offAll(() => const LoginPage());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// --- Custom Search Bar ---
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: AppStyle.boxDecoration.copyWith(
|
||||
color: Colors.white.withValues(alpha: 0.95),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Quick Search...'.tr,
|
||||
border: InputBorder.none,
|
||||
icon: const Icon(Icons.search, color: AppColor.primaryColor),
|
||||
),
|
||||
onSubmitted: (val) {
|
||||
// Global search logic
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatsSection() {
|
||||
return GetBuilder<MainController>(
|
||||
builder: (controller) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildStatCard(
|
||||
'Pending'.tr,
|
||||
controller.isLoading
|
||||
? '...'
|
||||
: controller.driverWantCompleteRegistration.length
|
||||
.toString(),
|
||||
Icons.timer_rounded,
|
||||
Colors.orange,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildStatCard(
|
||||
'New'.tr,
|
||||
controller.isLoading
|
||||
? '...'
|
||||
: controller.newDriverRegister.length.toString(),
|
||||
Icons.person_add_alt_1_rounded,
|
||||
Colors.green,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildStatCard(
|
||||
'Issues'.tr,
|
||||
controller.isLoading
|
||||
? '...'
|
||||
: controller.driverNotCompleteRegistration.length
|
||||
.toString(),
|
||||
Icons.warning_rounded,
|
||||
Colors.red,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(
|
||||
String title, String count, IconData icon, Color color) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: AppStyle.boxDecoration,
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24),
|
||||
const SizedBox(height: 8),
|
||||
Text(count, style: AppStyle.headTitle2.copyWith(fontSize: 20)),
|
||||
Text(title,
|
||||
style: AppStyle.subtitle.copyWith(color: AppColor.accentColor)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryTitle(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 12),
|
||||
child: Text(
|
||||
title,
|
||||
style: AppStyle.headTitle2.copyWith(fontSize: 18),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridSection(List<ServiceItem> items) {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
childAspectRatio: 1.15,
|
||||
),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return _buildServiceCard(item);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildServiceCard(ServiceItem item) {
|
||||
return InkWell(
|
||||
onTap: item.onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: item.color.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(item.icon, color: item.color, size: 30),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
item.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title
|
||||
.copyWith(fontSize: 13, fontWeight: FontWeight.w600),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSearchDialog({
|
||||
required String title,
|
||||
required TextEditingController controller,
|
||||
required VoidCallback onSearch,
|
||||
TextInputType type = TextInputType.phone,
|
||||
}) {
|
||||
MyDialog().getDialog(
|
||||
title,
|
||||
'Search Details'.tr,
|
||||
Form(
|
||||
key: mainController.formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MyTextForm(
|
||||
controller: controller,
|
||||
label: title,
|
||||
hint: title,
|
||||
type: type,
|
||||
validator: (val) {
|
||||
if (val == null || val.isEmpty) {
|
||||
return 'Please enter a value'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onSearch,
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddDriverDialog() {
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: "Add Driver Who Wants to Work".tr,
|
||||
titleStyle: AppStyle.headTitle2,
|
||||
content: SizedBox(
|
||||
width: Get.width * .8,
|
||||
height: 350,
|
||||
child: Form(
|
||||
key: mainController.formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
MyTextForm(
|
||||
controller: mainController.driverNameController,
|
||||
label: 'Insert Name of Driver'.tr,
|
||||
hint: 'Insert Name of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.nationalIdController,
|
||||
label: 'Insert national ID of Driver'.tr,
|
||||
hint: 'Insert national ID of Driver'.tr,
|
||||
type: TextInputType.number),
|
||||
MyTextForm(
|
||||
controller: mainController.phoneController,
|
||||
label: 'Insert phone of Driver'.tr,
|
||||
hint: 'Insert phone of Driver'.tr,
|
||||
type: TextInputType.phone),
|
||||
MyTextForm(
|
||||
controller: mainController.licenseTypeController,
|
||||
label: 'Insert license type of Driver'.tr,
|
||||
hint: 'Insert license type of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.siteDriverController,
|
||||
label: 'Insert site of Driver'.tr,
|
||||
hint: 'Insert site of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.birthDateController,
|
||||
label: 'Insert birth_date of Driver'.tr,
|
||||
hint: 'YYYY-MM-DD'.tr,
|
||||
type: TextInputType.datetime),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Add'.tr,
|
||||
onPressed: () async {
|
||||
var res =
|
||||
await CRUD().post(link: AppLink.addDriverWantWork, payload: {
|
||||
"driver_name": mainController.driverNameController.text,
|
||||
"national_id": mainController.nationalIdController.text,
|
||||
"birth_date": mainController.birthDateController.text,
|
||||
"site": mainController.siteDriverController.text,
|
||||
"license_type": mainController.licenseTypeController.text,
|
||||
"phone": mainController.phoneController.text,
|
||||
});
|
||||
if (res != 'failure' && res['status'] == 'success') {
|
||||
Get.back();
|
||||
mainController.driverNameController.clear();
|
||||
mainController.nationalIdController.clear();
|
||||
mainController.birthDateController.clear();
|
||||
mainController.licenseTypeController.clear();
|
||||
mainController.siteDriverController.clear();
|
||||
mainController.phoneController.clear();
|
||||
mySnackbarSuccess('Added successfully'.tr);
|
||||
}
|
||||
},
|
||||
kolor: AppColor.greenColor,
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () => Get.back()),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddCarDialog() {
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: "Add Car Who Wants to Work".tr,
|
||||
titleStyle: AppStyle.headTitle2,
|
||||
content: SizedBox(
|
||||
width: Get.width * .8,
|
||||
height: 350,
|
||||
child: Form(
|
||||
key: mainController.formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
MyTextForm(
|
||||
controller: mainController.carOwnerWorkController,
|
||||
label: 'Insert Name of Owner'.tr,
|
||||
hint: 'Insert Name of Owner'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.carNumberController,
|
||||
label: 'Insert car_number of Driver'.tr,
|
||||
hint: 'Insert car_number of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.phoneCarController,
|
||||
label: 'Insert phone of Owner'.tr,
|
||||
hint: 'Insert phone of Owner'.tr,
|
||||
type: TextInputType.phone),
|
||||
MyTextForm(
|
||||
controller: mainController.manufactureYearController,
|
||||
label: 'Insert year of Car'.tr,
|
||||
hint: 'Insert year of Car'.tr,
|
||||
type: TextInputType.number),
|
||||
MyTextForm(
|
||||
controller: mainController.carModelController,
|
||||
label: 'Insert car_model of Driver'.tr,
|
||||
hint: 'Insert car_model of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.siteCarController,
|
||||
label: 'Insert site of Owner'.tr,
|
||||
hint: 'Insert site of Owner'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.carTypeController,
|
||||
label: 'Insert car_type of Driver'.tr,
|
||||
hint: 'Insert car_type of Driver'.tr,
|
||||
type: TextInputType.name),
|
||||
MyTextForm(
|
||||
controller: mainController.registrationDateController,
|
||||
label: 'Insert registration_date of Car'.tr,
|
||||
hint: 'YYYY-MM-DD'.tr,
|
||||
type: TextInputType.datetime),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Add'.tr,
|
||||
onPressed: () async {
|
||||
var res = await CRUD().post(link: AppLink.addCarWantWork, payload: {
|
||||
"owner_name": mainController.carOwnerWorkController.text,
|
||||
"car_number": mainController.carNumberController.text,
|
||||
"manufacture_year": mainController.manufactureYearController.text,
|
||||
"car_model": mainController.carModelController.text,
|
||||
"car_type": mainController.carTypeController.text,
|
||||
"site": mainController.siteCarController.text,
|
||||
"registration_date": mainController.registrationDateController.text,
|
||||
"phone": mainController.phoneCarController.text,
|
||||
});
|
||||
if (res != 'failure' && res['status'] == 'success') {
|
||||
Get.back();
|
||||
mainController.carOwnerWorkController.clear();
|
||||
mainController.carNumberController.clear();
|
||||
mainController.manufactureYearController.clear();
|
||||
mainController.carModelController.clear();
|
||||
mainController.siteCarController.clear();
|
||||
mainController.carTypeController.clear();
|
||||
mainController.registrationDateController.clear();
|
||||
mainController.phoneCarController.clear();
|
||||
mySnackbarSuccess('Added successfully'.tr);
|
||||
}
|
||||
},
|
||||
kolor: AppColor.greenColor,
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () => Get.back()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceItem {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
ServiceItem({
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.onTap,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
|
||||
class MyCircleContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color backgroundColor;
|
||||
final Color borderColor;
|
||||
|
||||
MyCircleContainer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.backgroundColor = AppColor.secondaryColor,
|
||||
this.borderColor = AppColor.accentColor,
|
||||
}) : super(key: key);
|
||||
|
||||
final controller = Get.put(CircleController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<CircleController>(
|
||||
builder: ((controller) => GestureDetector(
|
||||
onTap: () {
|
||||
controller.changeColor();
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
onEnd: () {
|
||||
controller.onEnd();
|
||||
},
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: controller.size,
|
||||
height: controller.size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: controller.backgroundColor,
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(child: child),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
class CircleController extends GetxController {
|
||||
Color backgroundColor = AppColor.secondaryColor;
|
||||
double size = 40;
|
||||
void changeColor() {
|
||||
backgroundColor = backgroundColor == AppColor.secondaryColor
|
||||
? AppColor.accentColor
|
||||
: AppColor.secondaryColor;
|
||||
size = 60;
|
||||
update();
|
||||
}
|
||||
|
||||
void onEnd() {
|
||||
size = 40;
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
import '../../main.dart';
|
||||
|
||||
class MyElevatedButton extends StatelessWidget {
|
||||
final String title;
|
||||
final VoidCallback onPressed;
|
||||
final Color kolor;
|
||||
final int vibrateDuration;
|
||||
final bool loading;
|
||||
final Widget? child;
|
||||
|
||||
const MyElevatedButton({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.onPressed,
|
||||
this.kolor = AppColor.primaryColor,
|
||||
this.vibrateDuration = 100,
|
||||
this.loading = false,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool vibrate = box.read(BoxName.isvibrate) ?? true;
|
||||
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: kolor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
onPressed: loading
|
||||
? null
|
||||
: () async {
|
||||
if (vibrate == true) {
|
||||
if (Platform.isIOS) {
|
||||
HapticFeedback.selectionClick();
|
||||
} else if (Platform.isAndroid) {
|
||||
await Vibration.vibrate(duration: vibrateDuration);
|
||||
} else {}
|
||||
}
|
||||
onPressed();
|
||||
},
|
||||
child: loading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: child ??
|
||||
Text(
|
||||
title.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
|
||||
class IconWidgetMenu extends StatelessWidget {
|
||||
const IconWidgetMenu({
|
||||
Key? key,
|
||||
required this.onpressed,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback onpressed;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onpressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 1),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColor.secondaryColor,
|
||||
shape: BoxShape.circle,
|
||||
// boxShadow: [
|
||||
// BoxShadow(
|
||||
// color: AppColor.secondaryColor,
|
||||
// offset: Offset(-2, -2),
|
||||
// blurRadius: 0,
|
||||
// spreadRadius: 0,
|
||||
// blurStyle: BlurStyle.outer,
|
||||
// ),
|
||||
// BoxShadow(
|
||||
// color: AppColor.accentColor,
|
||||
// offset: Offset(3, 3),
|
||||
// blurRadius: 0,
|
||||
// spreadRadius: 0,
|
||||
// blurStyle: BlurStyle.outer,
|
||||
// ),
|
||||
// ],
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 30,
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.subtitle.copyWith(color: AppColor.secondaryColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
import 'elevated_btn.dart';
|
||||
|
||||
class MyDialog extends GetxController {
|
||||
void getDialog(
|
||||
String title, String? midTitle, Widget widget, VoidCallback onPressed) {
|
||||
// final textToSpeechController = Get.put(TextToSpeechController());
|
||||
Get.defaultDialog(
|
||||
title: title,
|
||||
titleStyle: AppStyle.title,
|
||||
middleTextStyle: AppStyle.title,
|
||||
content: Column(
|
||||
children: [
|
||||
// IconButton(
|
||||
// onPressed: () async {
|
||||
// // await textToSpeechController.speakText(title ?? midTitle!);
|
||||
// },
|
||||
// icon: const Icon(Icons.headphones)),
|
||||
widget
|
||||
],
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok'.tr,
|
||||
onPressed: onPressed,
|
||||
kolor: AppColor.greenColor,
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
|
||||
class MyScaffold extends StatelessWidget {
|
||||
const MyScaffold({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.action = const Icon(
|
||||
Icons.clear,
|
||||
color: AppColor.secondaryColor,
|
||||
),
|
||||
required this.isleading,
|
||||
required this.body,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<Widget> body;
|
||||
final Widget action;
|
||||
final bool isleading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
elevation: 0,
|
||||
leading: isleading
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new,
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
actions: [action],
|
||||
title: Text(
|
||||
title,
|
||||
style: AppStyle.title
|
||||
.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
body: SafeArea(child: Stack(children: body)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/style.dart';
|
||||
|
||||
class MyTextForm extends StatelessWidget {
|
||||
const MyTextForm({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.label,
|
||||
required this.hint,
|
||||
required this.type,
|
||||
this.validator,
|
||||
});
|
||||
final TextEditingController controller;
|
||||
final String label, hint;
|
||||
final TextInputType type;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: SizedBox(
|
||||
width: Get.width * .8,
|
||||
child: TextFormField(
|
||||
keyboardType: type,
|
||||
cursorColor: AppColor.accentColor,
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(
|
||||
color: AppColor.primaryColor,
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
focusColor: AppColor.accentColor,
|
||||
fillColor: AppColor.accentColor,
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
labelText: label.tr,
|
||||
hintText: hint.tr,
|
||||
hintStyle: AppStyle.title,
|
||||
labelStyle: AppStyle.title,
|
||||
),
|
||||
validator: validator ?? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '${'Please enter'.tr} $label.'.tr;
|
||||
}
|
||||
|
||||
if (type == TextInputType.emailAddress) {
|
||||
if (!value.contains('@')) {
|
||||
return 'Please enter a valid email.'.tr;
|
||||
}
|
||||
} else if (type == TextInputType.phone) {
|
||||
if (value.length > 14) {
|
||||
return 'Please enter a valid phone number.'.tr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/colors.dart';
|
||||
|
||||
class MyCircularProgressIndicator extends StatelessWidget {
|
||||
final Color backgroundColor;
|
||||
|
||||
const MyCircularProgressIndicator({
|
||||
super.key,
|
||||
this.backgroundColor = Colors.transparent,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 110,
|
||||
height: 110,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Image.asset('assets/images/logo.png'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum _SnackVariant { success, error, info, warning }
|
||||
|
||||
extension _VariantProps on _SnackVariant {
|
||||
Color get baseColor => switch (this) {
|
||||
_SnackVariant.success => const Color(0xFF1A9E5C),
|
||||
_SnackVariant.error => const Color(0xFFD93025),
|
||||
_SnackVariant.info => const Color(0xFF1A73E8),
|
||||
_SnackVariant.warning => const Color(0xFFF29900),
|
||||
};
|
||||
|
||||
Color get surfaceColor => switch (this) {
|
||||
_SnackVariant.success => const Color(0xFFF0FBF5),
|
||||
_SnackVariant.error => const Color(0xFFFEF2F1),
|
||||
_SnackVariant.info => const Color(0xFFF0F6FF),
|
||||
_SnackVariant.warning => const Color(0xFFFFF8E6),
|
||||
};
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
_SnackVariant.success => Icons.check_circle_rounded,
|
||||
_SnackVariant.error => Icons.error_rounded,
|
||||
_SnackVariant.info => Icons.info_rounded,
|
||||
_SnackVariant.warning => Icons.warning_amber_rounded,
|
||||
};
|
||||
|
||||
String get label => switch (this) {
|
||||
_SnackVariant.success => 'Success',
|
||||
_SnackVariant.error => 'Error',
|
||||
_SnackVariant.info => 'Info',
|
||||
_SnackVariant.warning => 'Warning',
|
||||
};
|
||||
}
|
||||
|
||||
class _SnackContent extends StatefulWidget {
|
||||
final String message;
|
||||
final _SnackVariant variant;
|
||||
|
||||
const _SnackContent({required this.message, required this.variant});
|
||||
|
||||
@override
|
||||
State<_SnackContent> createState() => _SnackContentState();
|
||||
}
|
||||
|
||||
class _SnackContentState extends State<_SnackContent>
|
||||
with TickerProviderStateMixin {
|
||||
late final AnimationController _ctrl;
|
||||
late final AnimationController _scaleCtrl;
|
||||
late final Animation<double> _scaleAnim;
|
||||
late final Animation<double> _progressAnim;
|
||||
|
||||
static const Duration _displayDuration = Duration(seconds: 4);
|
||||
|
||||
@override
|
||||
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();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ctrl.dispose();
|
||||
_scaleCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final v = widget.variant;
|
||||
final accent = v.baseColor;
|
||||
final surface = v.surfaceColor;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: surface,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: accent.withAlpha(46), width: 1.2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: accent.withAlpha(31),
|
||||
blurRadius: 20,
|
||||
spreadRadius: -2,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(15),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 10, 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ScaleTransition(
|
||||
scale: _scaleAnim,
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withAlpha(31),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(v.icon, color: accent, size: 22),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
v.label.tr,
|
||||
style: TextStyle(
|
||||
color: accent,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
widget.message,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF424242),
|
||||
fontSize: 13.5,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
_closeSnackbar(context);
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
margin: const EdgeInsets.only(left: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withAlpha(25),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.close_rounded,
|
||||
size: 16,
|
||||
color: Colors.grey[500],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedBuilder(
|
||||
animation: _progressAnim,
|
||||
builder: (_, __) => Stack(
|
||||
children: [
|
||||
Container(height: 3, color: accent.withAlpha(20)),
|
||||
FractionallySizedBox(
|
||||
widthFactor: _progressAnim.value,
|
||||
child: Container(
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withAlpha(115),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(4),
|
||||
bottomRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _closeSnackbar(BuildContext context) {
|
||||
ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar();
|
||||
}
|
||||
}
|
||||
|
||||
int _retryCount = 0;
|
||||
|
||||
void _show(_SnackVariant variant, String message) {
|
||||
if (Get.context == null) {
|
||||
if (_retryCount < 3) {
|
||||
_retryCount++;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
_retryCount = 0;
|
||||
|
||||
final context = Get.context;
|
||||
if (context == null) return;
|
||||
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
if (messenger == null) return;
|
||||
|
||||
messenger.clearSnackBars();
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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