first commit

This commit is contained in:
Abdullah Salah
2024-12-25 11:09:55 +03:00
commit 216efb8a83
125 changed files with 6752 additions and 0 deletions

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
class AppTheme {
static const primaryColor = Color(0xFF668D7B);
static const secondaryColor = Color(0xFFBEDDCC);
static const cardColor = Color(0xFF93B5A4);
static const yellowColor = Color(0xFFF1CC83);
static const redColor = Colors.red;
static const brownColor = Color(0xFF6B6148);
static const blackColor = Color(0xFF444444);
static const textColor = Color(0xFFFFFFFF);
static get theme => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: primaryColor,
surface: primaryColor,
),
scaffoldBackgroundColor: primaryColor,
textTheme: const TextTheme(
bodySmall: bodySmall,
bodyMedium: bodyMedium,
bodyLarge: bodyLarge,
titleLarge: titleLarge,
headlineLarge: headlineLarge,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
elevation: WidgetStateProperty.all(0),
backgroundColor: WidgetStateProperty.all(textColor),
padding: WidgetStateProperty.all(const EdgeInsets.symmetric(vertical: 0, horizontal: 4)),
foregroundColor: WidgetStateProperty.all(primaryColor),
textStyle: WidgetStateProperty.all(
bodyMedium
)
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
elevation: WidgetStateProperty.all(0),
backgroundColor: WidgetStateProperty.all(primaryColor),
padding: WidgetStateProperty.all(const EdgeInsets.symmetric(vertical: 0, horizontal: 4)),
foregroundColor: WidgetStateProperty.all(textColor),
textStyle: WidgetStateProperty.all(
bodyMedium
)
),
),
cardColor: cardColor,
);
static const bodySmall = TextStyle(
color: textColor,
fontSize: 14,
fontWeight: FontWeight.w300,
fontFamily: 'NotoSansArabic',
);
static const bodyMedium = TextStyle(
color: textColor,
fontSize: 16,
fontWeight: FontWeight.w400,
fontFamily: 'NotoSansArabic',
);
static const bodyLarge = TextStyle(
color: textColor,
fontSize: 18,
fontWeight: FontWeight.w400,
fontFamily: 'NotoSansArabic',
);
static const titleLarge = TextStyle(
color: textColor,
fontSize: 22,
fontWeight: FontWeight.w600,
fontFamily: 'NotoSansArabic',
);
static const headlineLarge = TextStyle(
color: textColor,
fontSize: 32,
fontWeight: FontWeight.w700,
fontFamily: 'NotoSansArabic',
);
}

View File

@ -0,0 +1,47 @@
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:google_mlkit_commons/google_mlkit_commons.dart';
extension MLKitUtils on AnalysisImage {
InputImage toInputImage() {
return when(
nv21: (image) {
return InputImage.fromBytes(
bytes: image.bytes,
metadata: InputImageMetadata(
rotation: inputImageRotation,
format: InputImageFormat.nv21,
size: image.size,
bytesPerRow: image.planes.first.bytesPerRow,
),
);
},
bgra8888: (image) {
return InputImage.fromBytes(
bytes: image.bytes,
metadata: InputImageMetadata(
size: size,
rotation: inputImageRotation,
format: InputImageFormat.nv21,
bytesPerRow: image.planes.first.bytesPerRow,
),
);
}
)!;
}
InputImageRotation get inputImageRotation =>
InputImageRotation.values.byName(rotation.name);
InputImageFormat get inputImageFormat {
switch (format) {
case InputAnalysisImageFormat.bgra8888:
return InputImageFormat.bgra8888;
case InputAnalysisImageFormat.nv21:
return InputImageFormat.nv21;
default:
return InputImageFormat.yuv420;
}
}
}

37
lib/main.dart Normal file
View File

@ -0,0 +1,37 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/log_in_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
const List<Locale> supportedLocales = [Locale("ar")];
runApp(EasyLocalization(
supportedLocales: supportedLocales,
path: 'assets/languages',
startLocale: const Locale("ar"),
fallbackLocale: const Locale("ar"),
saveLocale: true,
child: const MyApp(),
));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gascom',
debugShowCheckedModeBanner: false,
theme: AppTheme.theme,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
home: const LogInScreen(),
);
}
}

View File

@ -0,0 +1,118 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/screens/success_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/app_text_field.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class ChangeEngineScreen extends StatefulWidget {
const ChangeEngineScreen({super.key});
@override
State<ChangeEngineScreen> createState() => _ChangeEngineScreenState();
}
class _ChangeEngineScreenState extends State<ChangeEngineScreen> {
TextEditingController reasonController = TextEditingController();
TextEditingController countryController = TextEditingController();
TextEditingController engineNumberController = TextEditingController();
TextEditingController enginePowerController = TextEditingController();
TextEditingController engineTypeController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
title: "تبديل المحرك",
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(20.0),
children: [
AutoSizeText(
"لتبديل المحرك يرجى تزويدنا بمعلومات المحرك الجديد الجديد.",
maxLines: 3,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 20.0),
AutoSizeText(
"سبب التبديل",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: reasonController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"منشأ المحرك الجديد",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: countryController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"رقم المحرك",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: engineNumberController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"القدرة التوليدية (KV.A)",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: enginePowerController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"نوع المحرك",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: engineTypeController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 25.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: handleSendRequest,
label: "تاكيد",
isElevated: true),
),
],
)
],
),
),
);
}
void handleSendRequest() {
// handle request
pushScreenWithoutNavBar(
context,
const SuccessScreen(
title: "تم تقديم الطلب بنجاح",
subtitle: "سيتم مراجعة طلبك، واعلامك بالموافقة قريباً"
)
);
}
}

View File

@ -0,0 +1,127 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/screens/document_camera_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/app_text_field.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class ChangeLocationScreen extends StatefulWidget {
const ChangeLocationScreen({super.key});
@override
State<ChangeLocationScreen> createState() => _ChangeLocationScreenState();
}
class _ChangeLocationScreenState extends State<ChangeLocationScreen> {
TextEditingController reasonController = TextEditingController();
TextEditingController districtController = TextEditingController();
TextEditingController mahalaController = TextEditingController();
TextEditingController streetController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
title: "تحويل موقع المولدة",
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(20.0),
children: [
AutoSizeText(
"لتحويل الموقع يرجى تزويدنا بمعلومات دقيقة للموقع الجديد بالاضافة الى سند ملكية للارض الجديدة.",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 2,
),
const SizedBox(height: 20.0),
AutoSizeText(
"سبب التحويل",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: reasonController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"المنطقة",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: districtController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"المحلة",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: mahalaController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
AutoSizeText(
"الزقاق",
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 5.0),
AppTextField(
controller: streetController,
keyboardType: TextInputType.multiline,
),
const SizedBox(height: 15.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: handleSetLocationOnMap,
label: "تحديد على الخريطة",
isElevated: false
),
),
],
),
const SizedBox(height: 25.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: handleSendRequest,
label: "تاكيد",
isElevated: true
),
),
],
)
],
),
),
);
}
void handleSendRequest() {
// handle request
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => const DocumentCameraScreen(title: "سند ملكية", description: "ضع سند ملكية الارض الجديدة في المربع، وتاكد من وجود اضاءة مناسبة",),
));
}
void handleSetLocationOnMap() {
}
}

View File

@ -0,0 +1,126 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/success_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class DocumentCameraScreen extends StatefulWidget {
const DocumentCameraScreen({
super.key,
required this.title,
required this.description,
});
final String title;
final String description;
@override
State<DocumentCameraScreen> createState() => _DocumentCameraScreenState();
}
class _DocumentCameraScreenState extends State<DocumentCameraScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: widget.title,
),
body: SafeArea(
child: CameraAwesomeBuilder.custom(
sensorConfig: SensorConfig.single(
aspectRatio: CameraAspectRatios.ratio_16_9,
flashMode: FlashMode.none,
sensor: Sensor.position(SensorPosition.back),
zoom: 0.0,
),
saveConfig: SaveConfig.photo(
),
builder: (state, previewSize) {
return Stack(
children: [
Center(
child: ClipPath(
clipper: HoleClipper(),
child: Container(
color: AppTheme.primaryColor,
),
),
),
Positioned(
bottom: 130,
left: 20,
right: 20,
child: AutoSizeText(
widget.description,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
maxLines: 2,
)
),
Positioned(
bottom: 70,
left: 20,
right: 20,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
// save image as bytes in a variable using the state object
// state.when(
// onPhotoMode:(s) => s.takePhoto(
// onPhoto: (request) => request.when(
// single: (r) => r.path,
// ),
// ),
// );
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SuccessScreen(title: 'تم تقديم الطلب بنجاح', subtitle: 'سيتم مراجعة طلبك، واعلامك بموعد الكشف الموقعي',),
),
);
},
label: "التقاط",
isElevated: true
),
),
],
),
)
],
);
}
),
),
);
}
}
class HoleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path()
..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
..addRRect(RRect.fromRectAndRadius(
Rect.fromCenter(
center: Offset(size.width / 2, size.height * 0.38),
width: size.width - (size.width * 0.2), // Adjust the width of the square
height: size.height - (size.height * 0.3), // Adjust the height of the square to match the width for a square shape
),
const Radius.circular(20), // Adjust the border radius as needed
))
..fillType = PathFillType.evenOdd;
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}

View File

@ -0,0 +1,276 @@
import 'dart:io';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/extensions/face_detection_extension.dart';
import 'package:gascom/screens/order_details_screen.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:path_provider/path_provider.dart';
class FaceDetectionScreen extends StatefulWidget {
const FaceDetectionScreen({super.key});
@override
State<FaceDetectionScreen> createState() => _FaceDetectionScreenState();
}
class _FaceDetectionScreenState extends State<FaceDetectionScreen> {
String message = "ضع وجهك في الدائرة المخصصة وتاكد من وجود اضاءة مناسبة";
final options = FaceDetectorOptions(
enableContours: true,
enableClassification: true,
enableLandmarks: true,
performanceMode: FaceDetectorMode.accurate,
minFaceSize: 0.5,
);
late final faceDetector = FaceDetector(options: options);
// Face stability tracking
DateTime? _stableStartTime;
Rect? _lastFacePosition;
// Constants for stability detection
final double _movementThreshold = 40;
final Duration _requiredStableTime = const Duration(seconds: 2);
@override
void dispose() {
faceDetector.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
title: "تحقق الوجه",
),
body: SafeArea(
child: Stack(
children: [
CameraAwesomeBuilder.previewOnly(
builder: (state, preview) => const SizedBox(),
imageAnalysisConfig: AnalysisConfig(
maxFramesPerSecond: 10,
),
onImageForAnalysis: handleImageAnalysis,
sensorConfig: SensorConfig.single(
aspectRatio: CameraAspectRatios.ratio_4_3,
flashMode: FlashMode.none,
sensor: Sensor.position(SensorPosition.front),
zoom: 0.0,
),
),
Center(
child: ClipPath(
clipper: HoleClipper(),
child: Container(
color: AppTheme.primaryColor,
),
),
),
Positioned(
bottom: 50,
left: 20,
right: 20,
child: Text(
message,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
)
],
),
),
);
}
bool _isFaceStable(Rect currentFace) {
if (_lastFacePosition == null) {
_lastFacePosition = currentFace;
return false;
}
final double movement =
(currentFace.center.dx - _lastFacePosition!.center.dx).abs()
+ (currentFace.center.dy - _lastFacePosition!.center.dy).abs();
_lastFacePosition = currentFace;
return movement < _movementThreshold;
}
bool _isFaceCovered(Face face) {
// Check if essential face contours are present and visible
final requiredContours = [
FaceContourType.face,
FaceContourType.leftEye,
FaceContourType.rightEye,
FaceContourType.noseBridge,
FaceContourType.noseBottom,
FaceContourType.leftCheek,
FaceContourType.rightCheek,
FaceContourType.upperLipTop,
FaceContourType.upperLipBottom,
FaceContourType.lowerLipTop,
FaceContourType.lowerLipBottom,
];
// Count visible contours
int visibleContours = 0;
for (var contourType in requiredContours) {
if (face.contours[contourType]?.points.isNotEmpty ?? false) {
visibleContours++;
}
}
// Check landmarks visibility
final hasEssentialLandmarks =
face.landmarks[FaceLandmarkType.leftEye] != null &&
face.landmarks[FaceLandmarkType.rightEye] != null &&
face.landmarks[FaceLandmarkType.noseBase] != null &&
face.landmarks[FaceLandmarkType.bottomMouth] != null &&
face.landmarks[FaceLandmarkType.leftMouth] != null &&
face.landmarks[FaceLandmarkType.rightMouth] != null &&
face.landmarks[FaceLandmarkType.leftEar] != null &&
face.landmarks[FaceLandmarkType.rightEar] != null;
// Check if eyes are open (existing check enhanced)
final eyesOpen = (face.rightEyeOpenProbability ?? 0) > 0.7 && (face.leftEyeOpenProbability ?? 0) > 0.7;
print("@@@@@@@@@@@@@@@@@@");
print(visibleContours);
print(hasEssentialLandmarks);
print(eyesOpen);
return !hasEssentialLandmarks ||
visibleContours != requiredContours.length ||
!eyesOpen;
}
Future<void> handleImageAnalysis(AnalysisImage img) async {
final inputImage = img.toInputImage();
try {
final faces = await faceDetector.processImage(inputImage);
if (!context.mounted || !mounted) return;
if (faces.length == 1) {
var face = faces.first;
var rect = faces.first.boundingBox;
final bool isStable = _isFaceStable(rect);
// Check if face is covered
if (_isFaceCovered(face)) {
setState(() {
message = "الرجاء إزالة أي غطاء عن الوجه";
});
_stableStartTime = null;
return;
}
if (!isStable) {
setState(() {
message = "ثبت وجهك في المكان المخصص وتأكد من أنه وجه حقيقي";
});
_stableStartTime = null;
return;
}
if (!(
rect.left > (inputImage.metadata?.size.width ?? 0) * 0.1
&& rect.right < (inputImage.metadata?.size.width ?? 0) * 0.9
&& rect.top > (inputImage.metadata?.size.height ?? 0) * 0.1
&& rect.bottom < (inputImage.metadata?.size.height ?? 0) * 0.9
&& (faces.first.rightEyeOpenProbability ?? 0) > 0.3
&& (faces.first.leftEyeOpenProbability ?? 0) > 0.3
)) {
setState(() {
message = "ثبت وجهك في المكان المخصص";
});
return;
}
_stableStartTime ??= DateTime.now();
final stableDuration = DateTime.now().difference(_stableStartTime!);
if (stableDuration >= _requiredStableTime) {
img.when(
nv21: (image) {
faceDetector.close();
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const OrderDetailsScreen()),
);
},
bgra8888: (image) {
faceDetector.close();
Navigator.pop(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const OrderDetailsScreen()),
);
},
);
return;
} else {
setState(() {
message = "ثبت وجهك في المكان المخصص";
});
}
}
} catch (error) {
debugPrint("...sending image resulted error $error");
}
}
Future<CaptureRequest> handleBuildPath(List<Sensor> sensors) async {
final Directory extDir = await getTemporaryDirectory();
final Directory testDir =
await Directory('${extDir.path}/camerawesome')
.create(recursive: true);
if (sensors.length == 1) {
final String filePath =
'${testDir.path}/${DateTime.now().millisecondsSinceEpoch}.jpg';
return SingleCaptureRequest(filePath, sensors.first);
} else {
return MultipleCaptureRequest(
{
for (final sensor in sensors)
sensor:
'${testDir.path}/${sensor.position == SensorPosition.front ? 'front_' : "back_"}${DateTime.now().millisecondsSinceEpoch}.jpg',
},
);
}
}
}
class HoleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path()
..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
..addRRect(RRect.fromRectAndRadius(
Rect.fromCenter(
center: Offset(size.width / 2, size.height * 0.45),
width: size.width - (size.width * 0.2), // Adjust the width of the square
height: size.height - (size.height * 0.3), // Adjust the height of the square to match the width for a square shape
),
const Radius.circular(20), // Adjust the border radius as needed
))
..fillType = PathFillType.evenOdd;
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
import 'package:gascom/widgets/fine_container.dart';
class FinesScreen extends StatelessWidget {
const FinesScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
title: "غرامات",
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 30),
Text(
"غرامات غير مدفوعة",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) => FineContainer(),
separatorBuilder: (context, index) => const SizedBox(height: 10),
),
const SizedBox(height: 15),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Row(
children: [
Divider(
color: AppTheme.textColor,
height: 1,
thickness: 1,
),
],
),
),
const SizedBox(height: 15),
Text(
"غرامات مدفوعة",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) => const FineContainer(),
separatorBuilder: (context, index) => const SizedBox(height: 10),
),
const SizedBox(height: 30),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:gascom/widgets/order_container.dart';
class FollowOrderScreen extends StatefulWidget {
const FollowOrderScreen({super.key});
@override
State<FollowOrderScreen> createState() => _FollowOrderScreenState();
}
class _FollowOrderScreenState extends State<FollowOrderScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 30),
Text(
"طلبات حالية",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) => OrderContainer(),
separatorBuilder: (context, index) => const SizedBox(height: 10),
),
const SizedBox(height: 30),
Text(
"طلبات سابقة",
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 20),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) => OrderContainer(),
separatorBuilder: (context, index) => const SizedBox(height: 10),
),
const SizedBox(height: 30),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,195 @@
import 'package:animated_custom_dropdown/custom_dropdown.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/app_text_field.dart';
class GeneratorInfoScreen extends StatefulWidget {
const GeneratorInfoScreen({super.key});
@override
State<GeneratorInfoScreen> createState() => _GeneratorInfoScreenState();
}
class _GeneratorInfoScreenState extends State<GeneratorInfoScreen> {
final TextEditingController ownerNameController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
final TextEditingController areaController = TextEditingController();
final TextEditingController districtController = TextEditingController();
final TextEditingController laneController = TextEditingController();
final TextEditingController generatorBrandController = TextEditingController();
final TextEditingController generatorPowerController = TextEditingController();
final TextEditingController authorizedNameController = TextEditingController();
String selectedGeneratorType = 'حكومية';
List<String> availableGeneratorType = ['حكومية', 'اهلية'];
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 20),
children: [
const SizedBox(height: 45,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"معلومات المولدة",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
"#10023432",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textDirection: TextDirection.ltr,
),
],
),
const SizedBox(height: 30,),
Text(
"اسم صاحب المولدة",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: ownerNameController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"رقم الهاتف",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: phoneController,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 15,),
Text(
"فئة المولدة",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
CustomDropdown<String>(
hintText: "فئة المولدة",
items: availableGeneratorType,
closedHeaderPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
expandedHeaderPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: CustomDropdownDecoration(
closedFillColor: AppTheme.primaryColor,
expandedFillColor: AppTheme.primaryColor,
closedBorder: Border.all(
color: AppTheme.textColor,
width: 1
),
expandedBorder: Border.all(
color: AppTheme.textColor,
width: 1
),
closedBorderRadius: const BorderRadius.all(Radius.circular(30)),
expandedBorderRadius: const BorderRadius.all(Radius.circular(20)),
hintStyle: Theme.of(context).textTheme.bodySmall,
listItemStyle: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w400,
),
headerStyle: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w400,
),
expandedSuffixIcon: const Icon(CupertinoIcons.chevron_up, color: AppTheme.textColor, size: 18,),
closedSuffixIcon: const Icon(CupertinoIcons.chevron_down, color: AppTheme.textColor, size: 18,),
),
onChanged: (value) {
selectedGeneratorType = value ?? "";
},
),
const SizedBox(height: 15,),
Text(
"المنطقة",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: areaController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"المحلة",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: districtController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"الزقاق",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: laneController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"نوع المولدة",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: generatorBrandController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"راس التوليد",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: generatorPowerController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 15,),
Text(
"مخول استلام الحصة الوقودية",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 6,),
AppTextField(
controller: authorizedNameController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 40,),
Center(
child: SizedBox(
width: 200,
height: 45,
child: AppButton(
onPressed: handleUpdateInfo,
label: "تحديث المعلومات",
isElevated: false
)
),
),
const SizedBox(height: 50,),
],
),
),
);
}
void handleUpdateInfo() {
}
}

View File

@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/change_engine_screen.dart';
import 'package:gascom/screens/change_location.dart';
import 'package:gascom/screens/document_camera_screen.dart';
import 'package:gascom/screens/fines_screen.dart';
import 'package:gascom/screens/notifications_screen.dart';
import 'package:gascom/screens/pay_monthly_gas.dart';
import 'package:gascom/screens/service_fees_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/home_grid_item.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: SafeArea(
child: Column(
children: [
const SizedBox(height: 50,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgPicture.asset(
width: 160,
"assets/svgs/logo.svg",
semanticsLabel: 'Logo',
),
InkWell(
onTap: () => pushScreenWithoutNavBar(context, const NotificationsScreen()),
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: SvgPicture.asset(
"assets/svgs/notification.svg",
semanticsLabel: 'Menu',
),
),
),
],
),
),
const SizedBox(height: 40,),
Row(
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.65,
child: const Divider(
color: AppTheme.yellowColor,
thickness: 3,
height: 0,
),
),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.35,
child: const Divider(
color: AppTheme.secondaryColor,
thickness: 1,
height: 0,
),
),
],
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 30,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"الخدمات",
style: Theme.of(context).textTheme.bodyLarge,
),
SizedBox(
width: 140,
height: 32,
child: AppButton(
onPressed: () {
pushScreenWithoutNavBar(context, const ServiceFeesScreen());
},
label: "رسوم الخدمات",
isElevated: false
),
),
],
),
),
const SizedBox(height: 30,),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GridView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 20,
mainAxisSpacing: 5,
childAspectRatio: MediaQuery.sizeOf(context).width / (MediaQuery.sizeOf(context).height / 1.3),
),
children: [
HomeGridItem(
title: "طلب حصة",
svgPath: "assets/svgs/gas_station.svg",
onPressed: () {
pushScreenWithoutNavBar(context, PayMonthlyGas());
}
),
HomeGridItem(
title: "غرامات",
svgPath: "assets/svgs/dollar_flag.svg",
onPressed: () {
pushScreenWithoutNavBar(context, const FinesScreen());
}
),
HomeGridItem(
title: "تحويل موقع",
svgPath: "assets/svgs/location_pin.svg",
onPressed: () {
pushScreenWithoutNavBar(context, const ChangeLocationScreen());
}
),
HomeGridItem(
title: "تبديل محرك",
svgPath: "assets/svgs/settings.svg",
onPressed: () {
pushScreenWithoutNavBar(context, const ChangeEngineScreen());
}
),
HomeGridItem(
title: "تجديد بطاقة الحصة الوقودية",
svgPath: "assets/svgs/profile_paper.svg",
onPressed: () {
pushScreenWithoutNavBar(context, const DocumentCameraScreen(title: "تجديد دفتر", description: "لتجديد الدفتر يرجى ارفاق صورة للدفتر الحالي, ضع الدفتر في المربع، وتاكد من وجود اضاءة جيدة ثم اضغط التقاط.",));
}
),
HomeGridItem(
title: "تغيير مخول",
svgPath: "assets/svgs/user_sync.svg",
onPressed: () {
}
),
HomeGridItem(
title: "الدعم الفني",
svgPath: "assets/svgs/headphone.svg",
onPressed: () {}
),
],
),
)
],
),
),
)
],
)
)
);
}
}

View File

@ -0,0 +1,145 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/nfc_screen.dart';
import 'package:gascom/screens/otp_provider_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:pinput/pinput.dart';
class LogInScreen extends StatefulWidget {
const LogInScreen({super.key});
@override
State<LogInScreen> createState() => _LogInScreenState();
}
class _LogInScreenState extends State<LogInScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 150),
Center(
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.6,
child: SvgPicture.asset(
"assets/svgs/logo.svg",
semanticsLabel: 'Home',
),
),
),
const SizedBox(height: 50),
Center(
child: AutoSizeText(
"مرحبا بكم في كازكوم، يرجى ادخال رقم الترخيص الخاص بصاحب المولدة",
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
Center(
child: AutoSizeText(
"أو قم بمسح رمز QR الموجود على البطاقة",
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 35),
Row(
children: [
InkWell(
borderRadius: BorderRadius.circular(11),
onTap: () {
// TODO: Implement QR Code Scanner
},
child: Container(
width: 60,
height: 60,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11),
border: Border.all(
color: AppTheme.textColor,
width: 2,
),
),
child: SvgPicture.asset(
"assets/svgs/camera.svg",
semanticsLabel: 'QR Code',
),
),
),
const SizedBox(width: 15),
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11),
border: Border.all(
color: AppTheme.textColor,
width: 2,
),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Pinput(
length: 5,
defaultPinTheme: PinTheme(
width: 30,
height: 35,
textStyle: Theme.of(context).textTheme.bodyLarge,
margin: const EdgeInsets.symmetric(horizontal: 5),
decoration: const BoxDecoration(
// color: AppTheme.textColor,
border: Border(
bottom: BorderSide(
color: AppTheme.textColor,
width: 1,
),
),
),
),
onCompleted: (value) {
Navigator.push(context, MaterialPageRoute(builder: (context) => OtpProviderScreen(
cardNumber: int.parse(value),
)));
},
),
),
),
),
],
),
const SizedBox(height: 50),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 180,
child: AppButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
// builder: (context) => NewGeneratorScreen(),
builder: (context) => NfcScreen(),
));
},
label: "مولـدة جـــديــدة",
isElevated: false
),
),
],
),
],
),
)
);
}
}

View File

@ -0,0 +1,120 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class NationalIdCameraScreen extends StatefulWidget {
const NationalIdCameraScreen({
super.key,
required this.title,
required this.subtitle,
required this.description,
required this.onScanComplete,
});
final String title;
final String subtitle;
final String description;
final void Function() onScanComplete;
@override
State<NationalIdCameraScreen> createState() => _NationalIdCameraScreenState();
}
class _NationalIdCameraScreenState extends State<NationalIdCameraScreen> {
String? error;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(),
body: SafeArea(
child: CameraAwesomeBuilder.custom(
sensorConfig: SensorConfig.single(
aspectRatio: CameraAspectRatios.ratio_16_9,
flashMode: FlashMode.none,
sensor: Sensor.position(SensorPosition.back),
zoom: 0.0,
),
saveConfig: SaveConfig.photo(
),
builder: (state, previewSize) {
return Stack(
children: [
Center(
child: ClipPath(
clipper: HoleClipper(),
child: Container(
color: AppTheme.primaryColor,
),
),
),
Positioned(
top: 30,
left: 20,
right: 20,
child: AutoSizeText(
widget.title,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
maxLines: 2,
)
),
Positioned(
top: 100,
left: 20,
right: 20,
child: AutoSizeText(
widget.subtitle,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
maxLines: 1,
)
),
Positioned(
bottom: 200,
left: 20,
right: 20,
child: AutoSizeText(
error ?? widget.description,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
maxLines: 2,
)
),
],
);
}
),
),
);
}
}
class HoleClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path()
..addRect(Rect.fromLTWH(0, 0, size.width, size.height))
..addRRect(RRect.fromRectAndRadius(
Rect.fromCenter(
center: Offset(size.width / 2, size.height * 0.38),
width: size.width - (size.width * 0.1), // Adjust the width of the square
height: size.height - (size.height * 0.7), // Adjust the height of the square to match the width for a square shape
),
const Radius.circular(20), // Adjust the border radius as needed
))
..fillType = PathFillType.evenOdd;
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}

View File

@ -0,0 +1,112 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/screens/national_id_camera_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class NewGeneratorScreen extends StatefulWidget {
const NewGeneratorScreen({super.key});
@override
State<NewGeneratorScreen> createState() => _NewGeneratorScreenState();
}
class _NewGeneratorScreenState extends State<NewGeneratorScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 150),
// SizedBox(
// width: 40,
// child: SvgPicture.asset(
// "assets/svgs/x.svg",
// semanticsLabel: 'Cancel',
// ),
// ),
// const SizedBox(height: 50),
Center(
child: AutoSizeText(
"طلب تخصيص حصة كاز للمولدة",
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 50),
Center(
child: AutoSizeText(
"يوفر تطبيق كاز كوم خدمة طلب حصة الكاز للمولدات الاهلية الكترونياً وبسهولة تامة.",
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 25),
Center(
child: AutoSizeText(
"لتسجيل حساب في كازكوم يرجى ملئ المعلومات المطلوبة والتأكد من صحة ودقة المعلومات.",
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 75),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => NationalIdCameraScreen(
title: "الهوية الوطنية",
subtitle: "الوجه الامامي",
description: "ضع البطاقة الموحدة الخاصة بصاحب المولد في المربع وتاكد من وجود اضاءة مناسبة",
onScanComplete: () {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => NationalIdCameraScreen(
title: "الهوية الوطنية",
subtitle: "الوجه الخلفي",
description: "ضع البطاقة الموحدة الخاصة بصاحب المولد في المربع وتاكد من وجود اضاءة مناسبة",
onScanComplete: () {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => NationalIdCameraScreen(
title: "بطاقة السكن",
subtitle: "",
description: "ضع بطاقة السكن الخاصة بصاحب المولد في المربع وتاكد من وجود اضاءة مناسبة",
onScanComplete: () {
}
)
));
}
)
));
},
)
)
);
},
label: "التالي",
isElevated: false
),
),
],
),
]
)
)
);
}
}

585
lib/screens/nfc_screen.dart Normal file
View File

@ -0,0 +1,585 @@
import 'dart:typed_data';
import 'package:dmrtd/dmrtd.dart';
import 'package:dmrtd/src/proto/can_key.dart';
import 'package:flutter/material.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class NfcScreen extends StatefulWidget {
const NfcScreen({super.key});
@override
State<NfcScreen> createState() => _NfcScreenState();
}
class _NfcScreenState extends State<NfcScreen> {
final NfcProvider _nfc = NfcProvider();
MrtdData? _mrtdData = MrtdData();
String _alertMessage = "";
bool _isReading = false;
String id = ''; // should change based on user id card (it's the string below the image)
DateTime dateOfBirth = DateTime(2000, 1, 1); // should change based on user id card
DateTime dateOfExpiry = DateTime(2029, 1, 1); // should change based on user id card
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
try {
_buttonPressed();
} catch (e) {
print(e);
}
});
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return const Scaffold(
appBar: CustomAppBar(),
body: SafeArea(
child: Center(
child: Text("NFC Screen, Nfc has ti be turned on."),
),
),
);
}
String formatProgressMsg(String message, int percentProgress) {
final p = (percentProgress / 20).round();
final full = "🟢 " * p;
final empty = "⚪️ " * (5 - p);
return "$message\n\n$full$empty";
}
void _readMRTD({required AccessKey accessKey, bool isPace = false}) async {
try {
setState(() {
_mrtdData = null;
_alertMessage = "Waiting for Passport tag ...";
_isReading = true;
});
try {
bool demo = false;
print("-------------- FIRST -------------");
if (!demo)
await _nfc.connect(
iosAlertMessage: "Hold your phone near Biometric Passport");
print("-------------- SECOND -------------");
final passport = Passport(_nfc);
print("-------------- THIRD -------------");
setState(() {
_alertMessage = "Reading Passport ...";
});
print("-------------- 4 -------------");
_nfc.setIosAlertMessage("Trying to read EF.CardAccess ...");
final mrtdData = MrtdData();
print("-------------- 5 -------------");
try {
mrtdData.cardAccess = await passport.readEfCardAccess();
} on PassportError {
print("-------------- 6 -------------");
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Trying to read EF.CardSecurity ...");
try {
//mrtdData.cardSecurity = await passport.readEfCardSecurity();
} on PassportError {
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Initiating session with PACE...");
//set MrtdData
mrtdData.isPACE = isPace;
mrtdData.isDBA = accessKey.PACE_REF_KEY_TAG == 0x01 ? true : false;
if (isPace) {
//PACE session
print("-------------- 7 -------------");
await passport.startSessionPACE(accessKey, mrtdData.cardAccess!);
} else {
print("-------------- 8 -------------");
//BAC session
await passport.startSession(accessKey as DBAKey);
}
print("-------------- 9 -------------");
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.COM ...", 0));
mrtdData.com = await passport.readEfCOM();
_nfc.setIosAlertMessage(
formatProgressMsg("Reading Data Groups ...", 20));
print("-------------- 10 -------------");
if (mrtdData.com!.dgTags.contains(EfDG1.TAG)) {
mrtdData.dg1 = await passport.readEfDG1();
print("DG1 mrz country: ${mrtdData.dg1?.mrz.country}");
print("DG1 mrz dateOfBirth: ${mrtdData.dg1?.mrz.dateOfBirth}");
print("DG1 mrz dateOfExpiry: ${mrtdData.dg1?.mrz.dateOfExpiry}");
print("DG1 mrz documentCode: ${mrtdData.dg1?.mrz.documentCode}");
print("DG1 mrz documentNumber: ${mrtdData.dg1?.mrz.documentNumber}");
print("DG1 mrz firstName: ${mrtdData.dg1?.mrz.firstName}");
print("DG1 mrz gender: ${mrtdData.dg1?.mrz.gender}");
print("DG1 mrz lastName: ${mrtdData.dg1?.mrz.lastName}");
print("DG1 mrz nationality: ${mrtdData.dg1?.mrz.nationality}");
print("DG1 mrz optionalData: ${mrtdData.dg1?.mrz.optionalData}");
print("DG1 mrz optionalData2: ${mrtdData.dg1?.mrz.optionalData2}");
print("DG1 mrz version: ${mrtdData.dg1?.mrz.version}");
}
print("-------------- 11 -------------");
if (mrtdData.com!.dgTags.contains(EfDG2.TAG)) {
mrtdData.dg2 = await passport.readEfDG2();
print("DG2 deviceType: ${mrtdData.dg2?.deviceType}");
print("DG2 expression: ${mrtdData.dg2?.expression}");
print("DG2 eyeColor: ${mrtdData.dg2?.eyeColor}");
print("DG2 faceImageType: ${mrtdData.dg2?.faceImageType}");
print("DG2 facialRecordDataLength: ${mrtdData.dg2?.facialRecordDataLength}");
print("DG2 featureMask: ${mrtdData.dg2?.featureMask}");
print("DG2 gender: ${mrtdData.dg2?.gender}");
print("DG2 hairColor: ${mrtdData.dg2?.hairColor}");
print("DG2 imageColorSpace: ${mrtdData.dg2?.imageColorSpace}");
print("DG2 imageData: ${mrtdData.dg2?.imageData}");
print("DG2 imageHeight: ${mrtdData.dg2?.imageHeight}");
print("DG2 imageType: ${mrtdData.dg2?.imageType}");
print("DG2 imageWidth: ${mrtdData.dg2?.imageWidth}");
print("DG2 lengthOfRecord: ${mrtdData.dg2?.lengthOfRecord}");
print("DG2 nrFeaturePoints: ${mrtdData.dg2?.nrFeaturePoints}");
print("DG2 numberOfFacialImages: ${mrtdData.dg2?.numberOfFacialImages}");
print("DG2 poseAngle: ${mrtdData.dg2?.poseAngle}");
print("DG2 poseAngleUncertainty: ${mrtdData.dg2?.poseAngleUncertainty}");
print("DG2 quality: ${mrtdData.dg2?.quality}");
print("DG2 sourceType: ${mrtdData.dg2?.sourceType}");
print("DG2 versionNumber: ${mrtdData.dg2?.versionNumber}");
}
// To read DG3 and DG4 session has to be established with CVCA certificate (not supported).
// if(mrtdData.com!.dgTags.contains(EfDG3.TAG)) {
// mrtdData.dg3 = await passport.readEfDG3();
// }
// if(mrtdData.com!.dgTags.contains(EfDG4.TAG)) {
// mrtdData.dg4 = await passport.readEfDG4();
// }
if (mrtdData.com!.dgTags.contains(EfDG5.TAG)) {
mrtdData.dg5 = await passport.readEfDG5();
}
if (mrtdData.com!.dgTags.contains(EfDG6.TAG)) {
mrtdData.dg6 = await passport.readEfDG6();
}
if (mrtdData.com!.dgTags.contains(EfDG7.TAG)) {
mrtdData.dg7 = await passport.readEfDG7();
}
if (mrtdData.com!.dgTags.contains(EfDG8.TAG)) {
mrtdData.dg8 = await passport.readEfDG8();
}
if (mrtdData.com!.dgTags.contains(EfDG9.TAG)) {
mrtdData.dg9 = await passport.readEfDG9();
}
if (mrtdData.com!.dgTags.contains(EfDG10.TAG)) {
mrtdData.dg10 = await passport.readEfDG10();
}
if (mrtdData.com!.dgTags.contains(EfDG11.TAG)) {
mrtdData.dg11 = await passport.readEfDG11();
print("DG11 custodyInformation: ${mrtdData.dg11?.custodyInformation}");
print("DG11 ersonalSummary: ${mrtdData.dg11?.ersonalSummary}");
print("DG11 fullDateOfBirth: ${mrtdData.dg11?.fullDateOfBirth}");
print("DG11 nameOfHolder: ${mrtdData.dg11?.nameOfHolder}");
print("DG11 otherNames: ${mrtdData.dg11?.otherNames}");
print("DG11 otherValidTDNumbers: ${mrtdData.dg11?.otherValidTDNumbers}");
print("DG11 permanentAddress: ${mrtdData.dg11?.permanentAddress}");
print("DG11 personalNumber: ${mrtdData.dg11?.personalNumber}");
print("DG11 placeOfBirth: ${mrtdData.dg11?.placeOfBirth}");
print("DG11 profession: ${mrtdData.dg11?.profession}");
print("DG11 proofOfCitizenship: ${mrtdData.dg11?.proofOfCitizenship}");
print("DG11 telephone: ${mrtdData.dg11?.telephone}");
print("DG11 title: ${mrtdData.dg11?.title}");
}
if (mrtdData.com!.dgTags.contains(EfDG12.TAG)) {
mrtdData.dg12 = await passport.readEfDG12();
print("DG12 dateOfIssue: ${mrtdData.dg12?.dateOfIssue}");
print("DG12 dateOfIssue: ${mrtdData.dg12?.issuingAuthority}");
}
if (mrtdData.com!.dgTags.contains(EfDG13.TAG)) {
mrtdData.dg13 = await passport.readEfDG13();
}
if (mrtdData.com!.dgTags.contains(EfDG14.TAG)) {
mrtdData.dg14 = await passport.readEfDG14();
}
if (mrtdData.com!.dgTags.contains(EfDG15.TAG)) {
mrtdData.dg15 = await passport.readEfDG15();
_nfc.setIosAlertMessage(formatProgressMsg("Doing AA ...", 60));
print("DG15 aaPublicKey: ${mrtdData.dg15?.aaPublicKey}");
mrtdData.aaSig = await passport.activeAuthenticate(Uint8List(8));
}
if (mrtdData.com!.dgTags.contains(EfDG16.TAG)) {
mrtdData.dg16 = await passport.readEfDG16();
}
print("-------------- 12 -------------");
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.SOD ...", 80));
mrtdData.sod = await passport.readEfSOD();
setState(() {
_mrtdData = mrtdData;
});
setState(() {
_alertMessage = "";
});
} on Exception catch (e) {
final se = e.toString().toLowerCase();
String alertMsg = "An error has occurred while reading Passport!";
if (e is PassportError) {
if (se.contains("security status not satisfied")) {
alertMsg =
"Failed to initiate session with passport.\nCheck input data!";
}
print("PassportError: ${e.message}");
} else {
print(
"An exception was encountered while trying to read Passport: $e");
}
if (se.contains('timeout')) {
alertMsg = "Timeout while waiting for Passport tag";
} else if (se.contains("tag was lost")) {
alertMsg = "Tag was lost. Please try again!";
} else if (se.contains("invalidated by user")) {
alertMsg = "";
}
setState(() {
_alertMessage = alertMsg;
});
} finally {
if (_alertMessage.isNotEmpty) {
await _nfc.disconnect(iosErrorMessage: _alertMessage);
} else {
await _nfc.disconnect(
iosAlertMessage: formatProgressMsg("Finished", 100));
}
setState(() {
_isReading = false;
});
}
} on Exception catch (e) {
print("Read MRTD error: $e");
}
}
void _readMRTDOld() async {
try {
setState(() {
_mrtdData = null;
_alertMessage = "Waiting for Passport tag ...";
_isReading = true;
});
await _nfc.connect(
iosAlertMessage: "Hold your phone near Biometric Passport");
final passport = Passport(_nfc);
setState(() {
_alertMessage = "Reading Passport ...";
});
_nfc.setIosAlertMessage("Trying to read EF.CardAccess ...");
final mrtdData = MrtdData();
try {
mrtdData.cardAccess = await passport.readEfCardAccess();
} on PassportError {
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Trying to read EF.CardSecurity ...");
try {
mrtdData.cardSecurity = await passport.readEfCardSecurity();
} on PassportError {
//if (e.code != StatusWord.fileNotFound) rethrow;
}
_nfc.setIosAlertMessage("Initiating session ...");
final bacKeySeed = DBAKey(id, dateOfBirth, dateOfExpiry);
await passport.startSession(bacKeySeed);
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.COM ...", 0));
mrtdData.com = await passport.readEfCOM();
_nfc.setIosAlertMessage(formatProgressMsg("Reading Data Groups ...", 20));
if (mrtdData.com!.dgTags.contains(EfDG1.TAG)) {
mrtdData.dg1 = await passport.readEfDG1();
print(mrtdData.dg1?.mrz);
}
if (mrtdData.com!.dgTags.contains(EfDG2.TAG)) {
mrtdData.dg2 = await passport.readEfDG2();
print("DG2 deviceType: ${mrtdData.dg2?.deviceType}");
print("DG2 expression: ${mrtdData.dg2?.expression}");
print("DG2 eyeColor: ${mrtdData.dg2?.eyeColor}");
print("DG2 faceImageType: ${mrtdData.dg2?.faceImageType}");
print("DG2 facialRecordDataLength: ${mrtdData.dg2?.facialRecordDataLength}");
print("DG2 featureMask: ${mrtdData.dg2?.featureMask}");
print("DG2 fid: ${mrtdData.dg2?.fid}");
print("DG2 gender: ${mrtdData.dg2?.gender}");
print("DG2 hairColor: ${mrtdData.dg2?.hairColor}");
print("DG2 imageColorSpace: ${mrtdData.dg2?.imageColorSpace}");
print("DG2 imageData: ${mrtdData.dg2?.imageData}");
print("DG2 imageHeight: ${mrtdData.dg2?.imageHeight}");
print("DG2 imageType: ${mrtdData.dg2?.imageType}");
print("DG2 imageWidth: ${mrtdData.dg2?.imageWidth}");
print("DG2 nrFeaturePoints: ${mrtdData.dg2?.nrFeaturePoints}");
print("DG2 numberOfFacialImages: ${mrtdData.dg2?.numberOfFacialImages}");
print("DG2 lengthOfRecord: ${mrtdData.dg2?.lengthOfRecord}");
print("DG2 poseAngle: ${mrtdData.dg2?.poseAngle}");
print("DG2 poseAngleUncertainty: ${mrtdData.dg2?.poseAngleUncertainty}");
print("DG2 quality: ${mrtdData.dg2?.quality}");
print("DG2 sourceType: ${mrtdData.dg2?.sourceType}");
print("DG2 versionNumber: ${mrtdData.dg2?.versionNumber}");
print("DG2 tag: ${mrtdData.dg2?.tag}");
print("DG2 sfi: ${mrtdData.dg2?.sfi}");
}
// To read DG3 and DG4 session has to be established with CVCA certificate (not supported).
// if(mrtdData.com!.dgTags.contains(EfDG3.TAG)) {
// mrtdData.dg3 = await passport.readEfDG3();
// }
// if(mrtdData.com!.dgTags.contains(EfDG4.TAG)) {
// mrtdData.dg4 = await passport.readEfDG4();
// }
if (mrtdData.com!.dgTags.contains(EfDG5.TAG)) {
mrtdData.dg5 = await passport.readEfDG5();
}
if (mrtdData.com!.dgTags.contains(EfDG6.TAG)) {
mrtdData.dg6 = await passport.readEfDG6();
}
if (mrtdData.com!.dgTags.contains(EfDG7.TAG)) {
mrtdData.dg7 = await passport.readEfDG7();
}
if (mrtdData.com!.dgTags.contains(EfDG8.TAG)) {
mrtdData.dg8 = await passport.readEfDG8();
}
if (mrtdData.com!.dgTags.contains(EfDG9.TAG)) {
mrtdData.dg9 = await passport.readEfDG9();
}
if (mrtdData.com!.dgTags.contains(EfDG10.TAG)) {
mrtdData.dg10 = await passport.readEfDG10();
}
if (mrtdData.com!.dgTags.contains(EfDG11.TAG)) {
mrtdData.dg11 = await passport.readEfDG11();
print("DG11 custodyInformation: ${mrtdData.dg11?.custodyInformation}");
print("DG11 ersonalSummary: ${mrtdData.dg11?.ersonalSummary}");
print("DG11 fullDateOfBirth: ${mrtdData.dg11?.fullDateOfBirth}");
print("DG11 nameOfHolder: ${mrtdData.dg11?.nameOfHolder}");
print("DG11 otherNames: ${mrtdData.dg11?.otherNames}");
print("DG11 otherValidTDNumbers: ${mrtdData.dg11?.otherValidTDNumbers}");
print("DG11 permanentAddress: ${mrtdData.dg11?.permanentAddress}");
print("DG11 personalNumber: ${mrtdData.dg11?.personalNumber}");
print("DG11 placeOfBirth: ${mrtdData.dg11?.placeOfBirth}");
print("DG11 profession: ${mrtdData.dg11?.profession}");
print("DG11 proofOfCitizenship: ${mrtdData.dg11?.proofOfCitizenship}");
print("DG11 telephone: ${mrtdData.dg11?.telephone}");
print("DG11 title: ${mrtdData.dg11?.title}");
}
if (mrtdData.com!.dgTags.contains(EfDG12.TAG)) {
mrtdData.dg12 = await passport.readEfDG12();
print("DG12 dateOfIssue: ${mrtdData.dg12?.dateOfIssue}");
print("DG12 issuingAuthority: ${mrtdData.dg12?.issuingAuthority}");
}
if (mrtdData.com!.dgTags.contains(EfDG13.TAG)) {
mrtdData.dg13 = await passport.readEfDG13();
}
if (mrtdData.com!.dgTags.contains(EfDG14.TAG)) {
mrtdData.dg14 = await passport.readEfDG14();
}
if (mrtdData.com!.dgTags.contains(EfDG15.TAG)) {
mrtdData.dg15 = await passport.readEfDG15();
print("DG12 issuingAuthority: ${mrtdData.dg15?.aaPublicKey}");
_nfc.setIosAlertMessage(formatProgressMsg("Doing AA ...", 60));
mrtdData.aaSig = await passport.activeAuthenticate(Uint8List(8));
}
if (mrtdData.com!.dgTags.contains(EfDG16.TAG)) {
mrtdData.dg16 = await passport.readEfDG16();
}
_nfc.setIosAlertMessage(formatProgressMsg("Reading EF.SOD ...", 80));
mrtdData.sod = await passport.readEfSOD();
setState(() {
_mrtdData = mrtdData;
});
setState(() {
_alertMessage = "";
});
} on Exception catch (e) {
final se = e.toString().toLowerCase();
String alertMsg = "An error has occurred while reading Passport!";
if (e is PassportError) {
if (se.contains("security status not satisfied")) {
alertMsg =
"Failed to initiate session with passport.\nCheck input data!";
}
print("PassportError: ${e.message}");
} else {
print("An exception was encountered while trying to read Passport: $e");
}
if (se.contains('timeout')) {
alertMsg = "Timeout while waiting for Passport tag";
} else if (se.contains("tag was lost")) {
alertMsg = "Tag was lost. Please try again!";
} else if (se.contains("invalidated by user")) {
alertMsg = "";
}
setState(() {
_alertMessage = alertMsg;
});
} finally {
if (_alertMessage.isNotEmpty) {
await _nfc.disconnect(iosErrorMessage: _alertMessage);
} else {
await _nfc.disconnect(
iosAlertMessage: formatProgressMsg("Finished", 100));
}
setState(() {
_isReading = false;
});
}
}
void _buttonPressed() async {
String _can = "";
print("Button pressed");
//Check on what tab we are
if (true) {
//DBA tab
// String errorText = "";
// if (_doe.text.isEmpty) {
// errorText += "Please enter date of expiry!\n";
// }
// if (_dob.text.isEmpty) {
// errorText += "Please enter date of birth!\n";
// }
// if (_docNumber.text.isEmpty) {
// errorText += "Please enter passport number!";
// }
// setState(() {
// _alertMessage = errorText;
// });
// //If there is an error, just jump out of the function
// if (errorText.isNotEmpty) return;
final bacKeySeed = DBAKey(id, dateOfBirth, dateOfExpiry, paceMode: true);
_readMRTD(accessKey: bacKeySeed, isPace: true);
} else {
// PACE tab
// String errorText = "";
// if (_can.isEmpty) {
// errorText = "Please enter CAN number!";
// } else if (_can.text.length != 6) {
// errorText = "CAN number must be exactly 6 digits long!";
// }
// setState(() {
// _alertMessage = errorText;
// });
// //If there is an error, just jump out of the function
// if (errorText.isNotEmpty) return;
// final canKeySeed = CanKey(_can.text);
final canKeySeed = CanKey(id);
_readMRTD(accessKey: canKeySeed, isPace: true);
}
}
}
class MrtdData {
EfCardAccess? cardAccess;
EfCardSecurity? cardSecurity;
EfCOM? com;
EfSOD? sod;
EfDG1? dg1;
EfDG2? dg2;
EfDG3? dg3;
EfDG4? dg4;
EfDG5? dg5;
EfDG6? dg6;
EfDG7? dg7;
EfDG8? dg8;
EfDG9? dg9;
EfDG10? dg10;
EfDG11? dg11;
EfDG12? dg12;
EfDG13? dg13;
EfDG14? dg14;
EfDG15? dg15;
EfDG16? dg16;
Uint8List? aaSig;
bool? isPACE;
bool? isDBA;
}
final Map<DgTag, String> dgTagToString = {
EfDG1.TAG: 'EF.DG1',
EfDG2.TAG: 'EF.DG2',
EfDG3.TAG: 'EF.DG3',
EfDG4.TAG: 'EF.DG4',
EfDG5.TAG: 'EF.DG5',
EfDG6.TAG: 'EF.DG6',
EfDG7.TAG: 'EF.DG7',
EfDG8.TAG: 'EF.DG8',
EfDG9.TAG: 'EF.DG9',
EfDG10.TAG: 'EF.DG10',
EfDG11.TAG: 'EF.DG11',
EfDG12.TAG: 'EF.DG12',
EfDG13.TAG: 'EF.DG13',
EfDG14.TAG: 'EF.DG14',
EfDG15.TAG: 'EF.DG15',
EfDG16.TAG: 'EF.DG16'
};

View File

@ -0,0 +1,57 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class NotificationsScreen extends StatelessWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
title: "الإشعارات",
),
body: SafeArea(
child: ListView.separated(
padding: const EdgeInsets.all(20),
itemCount: 5,
separatorBuilder: (context, index) => const SizedBox(height: 10),
itemBuilder: (context, index) => InkWell(
child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppTheme.textColor,
width: 1,
),
),
child: ListTile(
title: AutoSizeText(
"تم تحديث حالة الطلب",
style: Theme.of(context).textTheme.bodyLarge,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AutoSizeText(
"تم تحديث حالة الطلب الخاص بك الى قيد المراجعة",
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.right,
),
AutoSizeText(
"2024/12/12",
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.right,
),
],
),
)
),
),
),
),
);
}
}

View File

@ -0,0 +1,400 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/face_detection_screen.dart';
import 'package:gascom/screens/payment_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class OrderDetailsScreen extends StatefulWidget {
const OrderDetailsScreen({super.key});
@override
State<OrderDetailsScreen> createState() => _OrderDetailsScreenState();
}
class _OrderDetailsScreenState extends State<OrderDetailsScreen> {
int activeStep = 3;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: "رقم الطلب: 67895435",
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 20),
children: [
const SizedBox(height: 20),
AutoSizeText(
"حالة الطلب",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 10),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
activeStep > 0 ? const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.check_rounded,
size: 26,
color: AppTheme.yellowColor,
),
) : const SizedBox(width: 40,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"مراجعة الطلب من قبل لجنة الطاقة",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: activeStep == 0
? AppTheme.textColor
: activeStep > 0
? AppTheme.yellowColor
: AppTheme.textColor.withOpacity(0.6)
),
),
if (activeStep > 0) ...[
const SizedBox(height: 5),
AutoSizeText(
"2024/02/06",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.yellowColor,
),
),
]
],
),
),
]
),
const SizedBox(height: 5),
SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(width: 40),
VerticalDivider(
color: activeStep > 0 ? AppTheme.textColor : AppTheme.textColor.withOpacity(0.6),
thickness: 1,
width: 20,
),
],
),
),
const SizedBox(height: 5),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
activeStep > 1 ? const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.check_rounded,
size: 26,
color: AppTheme.yellowColor,
),
) : const SizedBox(width: 40,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"مراجعة الطلب من قبل لجنة الطاقة",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: activeStep == 1
? AppTheme.textColor
: activeStep > 1
? AppTheme.yellowColor
: AppTheme.textColor.withOpacity(0.6)
),
),
if (activeStep > 1) ...[
const SizedBox(height: 5),
AutoSizeText(
"2024/02/06",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.yellowColor,
),
),
]
],
),
),
]
),
const SizedBox(height: 5),
SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(width: 40),
VerticalDivider(
color: activeStep > 1 ? AppTheme.textColor : AppTheme.textColor.withOpacity(0.6),
thickness: 1,
width: 20,
),
],
),
),
const SizedBox(height: 5),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
activeStep > 2 ? const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.check_rounded,
size: 26,
color: AppTheme.yellowColor,
),
) : const SizedBox(width: 40,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"مراجعة الطلب من قبل لجنة الطاقة",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: activeStep == 2
? AppTheme.textColor
: activeStep > 2
? AppTheme.yellowColor
: AppTheme.textColor.withOpacity(0.6)
),
),
if (activeStep > 2) ...[
const SizedBox(height: 5),
AutoSizeText(
"2024/02/06",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.yellowColor,
),
),
]
],
),
),
]
),
const SizedBox(height: 5),
SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(width: 40),
VerticalDivider(
color: activeStep > 2 ? AppTheme.textColor : AppTheme.textColor.withOpacity(0.6),
thickness: 1,
width: 20,
),
],
),
),
const SizedBox(height: 5),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
activeStep > 3 ? const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.check_rounded,
size: 26,
color: AppTheme.yellowColor,
),
) : const SizedBox(width: 40,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"الدفع",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: activeStep == 3
? AppTheme.textColor
: activeStep > 3
? AppTheme.yellowColor
: AppTheme.textColor.withOpacity(0.6)
),
),
const SizedBox(height: 10),
if (activeStep > 3) ...[
AutoSizeText(
"2024/02/06",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.yellowColor,
),
),
const SizedBox(height: 5),
],
if (activeStep >= 3) ...[
AutoSizeText(
"مبلغ الحصة",
minFontSize: 10,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: activeStep == 3
? AppTheme.textColor
: AppTheme.yellowColor
),
),
AutoSizeText(
"5000 لتر / كاز اويل",
minFontSize: 10,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: activeStep == 3
? AppTheme.textColor
: AppTheme.yellowColor
),
),
AutoSizeText(
"5,000,000 د.ع",
minFontSize: 10,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: activeStep == 3
? AppTheme.textColor
: AppTheme.yellowColor
),
),
],
if (activeStep == 3) ...[
const SizedBox(height: 10),
SizedBox(
width: 180,
child: AppButton(
onPressed: () {
pushScreenWithoutNavBar(context, PaymentScreen(
title: "دفع مبلغ الحصة",
onPaymentComplete: () {
pushScreenWithoutNavBar(context, FaceDetectionScreen());
},
));
},
label: "ادفع",
isElevated: true
),
)
]
],
),
),
]
),
const SizedBox(height: 10),
SizedBox(
height: 40,
child: Row(
children: [
const SizedBox(width: 40),
VerticalDivider(
color: activeStep > 3 ? AppTheme.textColor : AppTheme.textColor.withOpacity(0.6),
thickness: 1,
width: 20,
),
],
),
),
const SizedBox(height: 5),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
activeStep > 4 ? const Padding(
padding: EdgeInsets.symmetric(horizontal: 6),
child: Icon(
Icons.check_rounded,
size: 26,
color: AppTheme.yellowColor,
),
) : const SizedBox(width: 40,),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"الاستلام",
minFontSize: 10,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: activeStep == 4
? AppTheme.textColor
: activeStep > 4
? AppTheme.yellowColor
: AppTheme.textColor.withOpacity(0.6)
),
),
const SizedBox(height: 5),
if (activeStep == 4) AutoSizeText(
"الطلب قيد التسليم",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: activeStep == 4
? AppTheme.textColor
: AppTheme.yellowColor,
),
),
if (activeStep > 4) ...[
AutoSizeText(
"2024/02/13",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: activeStep == 4
? AppTheme.textColor
: AppTheme.yellowColor,
),
),
AutoSizeText(
"تم الاستلام",
minFontSize: 10,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: activeStep == 4
? AppTheme.textColor
: AppTheme.yellowColor,
),
),
],
const SizedBox(height: 5),
if (activeStep == 4) ...[
const SizedBox(height: 5),
SizedBox(
width: 180,
child: AppButton(
onPressed: () {},
label: "تتبع الطلب",
isElevated: true
),
)
]
],
),
),
]
),
],
),
);
}
}

View File

@ -0,0 +1,88 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gascom/screens/otp_screen.dart';
import 'package:gascom/widgets/app_button.dart';
class OtpProviderScreen extends StatelessWidget {
const OtpProviderScreen({
super.key,
required this.cardNumber,
});
final int cardNumber;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 150),
Center(
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.6,
child: SvgPicture.asset(
"assets/svgs/logo.svg",
semanticsLabel: 'Home',
),
),
),
const SizedBox(height: 50),
Center(
child: AutoSizeText(
"طريقة تاكيد الهوية",
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
Center(
child: AutoSizeText(
"اختر طريقة ارسال رمز OTP لتسجيل الدخول",
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 35),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
// TODO: Navigate to OTP Screen
Navigator.push(context, MaterialPageRoute(builder: (context) => const OtpScreen(isSms: true,)));
},
label: "SMS",
isElevated: false
),
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
// TODO: Navigate to OTP Screen
Navigator.push(context, MaterialPageRoute(builder: (context) => const OtpScreen(isSms: false,)));
},
label: "Whatsapp",
isElevated: false
),
),
],
),
],
),
)
);
}
}

View File

@ -0,0 +1,77 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/widgets/bottom_nav.dart';
import 'package:pinput/pinput.dart';
class OtpScreen extends StatelessWidget {
const OtpScreen({
super.key,
required this.isSms,
});
final bool isSms;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 25),
children: [
const SizedBox(height: 150),
Center(
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.6,
child: SvgPicture.asset(
"assets/svgs/logo.svg",
semanticsLabel: 'Home',
),
),
),
const SizedBox(height: 80),
Center(
child: AutoSizeText(
"يرجى ادخال رمز OTP",
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
minFontSize: 8,
),
),
const SizedBox(height: 35),
Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 6),
decoration: BoxDecoration(
// add a rounded border radius to the container
borderRadius: BorderRadius.circular(40),
border: Border.all(
color: AppTheme.textColor,
width: 2,
),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Pinput(
length: 6,
defaultPinTheme: PinTheme(
width: 30,
height: 35,
textStyle: Theme.of(context).textTheme.bodyLarge,
margin: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
border: Border.all(width: 0, color: Colors.transparent),
),
),
onCompleted: (value) {
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (context) => const BottomNav()), (_) => false);
},
),
),
),
],
),
));
}
}

View File

@ -0,0 +1,136 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/payment_screen.dart';
import 'package:gascom/screens/success_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class PayMonthlyGas extends StatelessWidget {
const PayMonthlyGas({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: "دفع رسوم تقديم الطلب",
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(20),
children: [
AutoSizeText(
"طلب حصة كاز اويل ",
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: AutoSizeText(
"حجم الحصة",
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(width: 5,),
AutoSizeText(
"1000 لتر",
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: AutoSizeText(
"تاريخ الطلب",
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(width: 5,),
AutoSizeText(
"03/08/2024",
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: AutoSizeText(
"رسوم تقديم الطلب",
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(width: 5,),
AutoSizeText(
"25,000 د.ع",
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 15),
Row(
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.5,
child: const Divider(
color: AppTheme.yellowColor,
thickness: 1,
height: 15,
),
),
],
),
const SizedBox(height: 15),
Row(
children: [
Expanded(
child: AutoSizeText(
"دفع رسوم تقديم الطلب",
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(width: 5,),
AutoSizeText(
"25,000 د.ع",
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 50),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
pushScreenWithoutNavBar(context, PaymentScreen(
title: "",
onPaymentComplete: () {
pushScreenWithoutNavBar(context, const SuccessScreen(title: "تم تقديم الطلب بنجاح", subtitle: "يمكنك متابعة اجراءات المراجعة والتدقيق من خلال نافذة تتبع الطلبات"));
},
));
},
label: "ادفع",
isElevated: true,
),
)
],
)
],
)
),
);
}
}

View File

@ -0,0 +1,252 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/utils/text_field_4_space_formatter.dart';
import 'package:gascom/utils/text_field_date_formatter.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/app_text_field.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class PaymentScreen extends StatefulWidget {
const PaymentScreen({
super.key,
required this.title,
required this.onPaymentComplete,
});
final String title;
final void Function() onPaymentComplete;
@override
State<PaymentScreen> createState() => _PaymentScreenState();
}
class _PaymentScreenState extends State<PaymentScreen> {
TextEditingController nameController = TextEditingController();
TextEditingController cardNumberController = TextEditingController();
TextEditingController expireController = TextEditingController();
TextEditingController cvvController = TextEditingController();
bool checkBoxValue = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(20),
children: [
AutoSizeText(
widget.title,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 20),
Container(
margin: const EdgeInsets.symmetric(horizontal: 5),
padding: const EdgeInsets.all(15),
// width: double.infinity,
decoration: BoxDecoration(
color: AppTheme.textColor,
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"5,000,000 د.ع",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.blackColor),
),
const SizedBox(
height: 2,
),
AutoSizeText(
"مبلغ حصة كاز اويل",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppTheme.blackColor,
fontWeight: FontWeight.w400),
),
const SizedBox(height: 2),
const Divider(
color: Colors.grey,
thickness: 1,
height: 30,
),
AutoSizeText(
"الاسم على البطاقة",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: AppTheme.blackColor,
),
),
const SizedBox(height: 5),
AppTextField(
fillColor: Color(0xFFCBCBCB),
isFilled: true,
controller: nameController,
keyboardType: TextInputType.text,
),
const SizedBox(height: 10),
AutoSizeText(
"رقم البطاقة",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: AppTheme.blackColor,
),
),
const SizedBox(height: 5),
Directionality(
textDirection: TextDirection.ltr,
child: AppTextField(
fillColor: Color(0xFFCBCBCB),
isFilled: true,
controller: cardNumberController,
keyboardType: TextInputType.number,
isCentered: true,
maxCharacters: 19,
inputFormatters: [
TextField4SpaceFormatter(),
],
),
),
const SizedBox(height: 10),
SizedBox(
height: 80,
width: double.infinity,
child: Row(
children: [
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"انتهاء الصلاحية",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: AppTheme.blackColor,
),
),
const SizedBox(height: 5),
Directionality(
textDirection: TextDirection.ltr,
child: AppTextField(
fillColor: Color(0xFFCBCBCB),
isFilled: true,
controller: expireController,
keyboardType: TextInputType.number,
isCentered: true,
maxCharacters: 5,
inputFormatters: [
TextFieldDateFormatter(),
],
),
),
],
),
),
const SizedBox(
width: 10,
),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
"رقم CVV",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: AppTheme.blackColor,
),
),
const SizedBox(height: 5),
Directionality(
textDirection: TextDirection.ltr,
child: AppTextField(
fillColor: Color(0xFFCBCBCB),
isFilled: true,
controller: cvvController,
keyboardType: TextInputType.number,
isCentered: true,
maxCharacters: 3,
),
),
],
),
),
],
),
),
const SizedBox(height: 10),
Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: Colors.grey,
width: 1,
),
),
child: Container(
margin: const EdgeInsets.all(6),
width: 12,
height: 12,
child: Checkbox(
value: checkBoxValue,
onChanged: handleCheckBoxChange,
checkColor: Colors.grey,
activeColor: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
side: const BorderSide(
color: Colors.grey,
width: 1,
),
),
),
),
const SizedBox(
width: 8,
),
AutoSizeText(
"اوافق على القوانين والاحكام",
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: AppTheme.blackColor,
),
),
],
),
const SizedBox(height: 15),
Row(children: [
Expanded(
child: AppButton(
onPressed: widget.onPaymentComplete,
label: "تاكيد",
isElevated: true,
color: AppTheme.yellowColor,
),
),
]),
],
),
)
],
),
));
}
void handleCheckBoxChange(bool? value) {
setState(() {
checkBoxValue = value!;
});
}
}

View File

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:gascom/widgets/custom_app_bar.dart';
class ServiceFeesScreen extends StatelessWidget {
const ServiceFeesScreen({super.key});
final List<Map<String, String>> fees = const [
{
"service": "رسوم طلب حصة كاز اويل",
"price": "25,000 د.ع",
},
{
"service": "رسوم توصيل الحصة",
"price": "60,000 د.ع",
},
{
"service": "سعر 1000 لتر كاز اويل",
"price": "400,000 د.ع",
},
{
"service": "رسوم تحويل موقع",
"price": "مجاناً",
},
{
"service": "رسوم تبديل محرك",
"price": "مجاناً",
},
{
"service": "رسوم تجديد دفتر",
"price": "مجاناً",
},
{
"service": "رسوم تغيير مخول",
"price": "مجاناً",
},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: " رسوم خدمات كازكم",
),
body: SafeArea(
child: ListView.separated(
itemCount: fees.length,
padding: const EdgeInsets.all(20),
separatorBuilder: (context, index) => const SizedBox(height: 10),
itemBuilder: (context, index) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
fees[index]["service"]!,
style: Theme.of(context).textTheme.bodyMedium,
),
),
Text(
fees[index]["price"]!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:gascom/widgets/bottom_nav.dart';
class SuccessScreen extends StatelessWidget {
const SuccessScreen({
super.key,
required this.title,
required this.subtitle,
});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_rounded,
size: 100.0,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
const SizedBox(height: 20.0),
AutoSizeText(
title,
maxLines: 2,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 10.0),
AutoSizeText(
subtitle,
maxLines: 2,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 40.0),
SizedBox(
width: 200,
child: AppButton(
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const BottomNav()),
(_) => false
);
},
label: "تم",
isElevated: true,
),
),
const SizedBox(height: 40.0),
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,23 @@
import 'package:flutter/services.dart';
class TextField4SpaceFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text.replaceAll(' ', '');
final buffer = StringBuffer();
for (int i = 0; i < text.length; i++) {
buffer.write(text[i]);
if ((i + 1) % 4 == 0 && i + 1 != text.length) {
buffer.write(' ');
}
}
final formattedText = buffer.toString();
return newValue.copyWith(
text: formattedText,
selection: TextSelection.collapsed(offset: formattedText.length),
);
}
}

View File

@ -0,0 +1,23 @@
import 'package:flutter/services.dart';
class TextFieldDateFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text.replaceAll('/', '');
final buffer = StringBuffer();
for (int i = 0; i < text.length; i++) {
buffer.write(text[i]);
if (i == 1 && i + 1 != text.length) {
buffer.write('/');
}
}
final formattedText = buffer.toString();
return newValue.copyWith(
text: formattedText,
selection: TextSelection.collapsed(offset: formattedText.length),
);
}
}

100
lib/widgets/app_button.dart Normal file
View File

@ -0,0 +1,100 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
class AppButton extends StatelessWidget {
const AppButton({
super.key,
required this.onPressed,
required this.label,
required this.isElevated,
this.isLoading = false,
this.textStyle,
this.color,
});
final void Function() onPressed;
final String label;
final bool isLoading;
final bool isElevated;
final TextStyle? textStyle;
final Color? color;
@override
Widget build(BuildContext context) {
return isElevated
? ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: Theme.of(context).elevatedButtonTheme.style?.copyWith(
backgroundColor: WidgetStatePropertyAll(color ?? AppTheme.textColor),
),
child: isLoading
? const Spinner(color: AppTheme.primaryColor)
: Row(
children: [
Expanded(
child: AutoSizeText(
label,
minFontSize: 8,
maxFontSize: 14,
maxLines: 1,
textAlign: TextAlign.center,
style: textStyle ?? Theme.of(context).textTheme.bodyLarge?.copyWith(
color: AppTheme.primaryColor,
)
),
),
],
),
)
: OutlinedButton(
onPressed: isLoading ? null : onPressed,
style: Theme.of(context).elevatedButtonTheme.style?.copyWith(
backgroundColor: WidgetStatePropertyAll(Theme.of(context).scaffoldBackgroundColor),
side: WidgetStatePropertyAll(BorderSide(
width: 1.5,
color: color ?? AppTheme.textColor,
))
),
child: isLoading
? Spinner(color: color ?? AppTheme.textColor,)
: Row(
children: [
Expanded(
child: AutoSizeText(
label,
minFontSize: 8,
maxFontSize: 14,
maxLines: 1,
textAlign: TextAlign.center,
style: textStyle ?? Theme.of(context).textTheme.bodyLarge?.copyWith(
color: AppTheme.textColor,
)
),
),
],
),
);
}
}
class Spinner extends StatelessWidget {
const Spinner({
super.key,
required this.color,
});
final Color? color;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
color: color ?? Colors.white,
),
);
}
}

View File

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gascom/constants/app_theme.dart';
class AppTextField extends StatelessWidget {
const AppTextField(
{super.key,
required this.controller,
required this.keyboardType,
this.onChanged,
this.errorText,
this.fillColor,
this.textColor,
this.isFilled,
this.isCentered,
this.maxCharacters,
this.inputFormatters,});
final TextEditingController controller;
final TextInputType keyboardType;
final void Function(String)? onChanged;
final String? errorText;
final Color? fillColor;
final Color? textColor;
final bool? isFilled;
final bool? isCentered;
final int? maxCharacters;
final List<TextInputFormatter>? inputFormatters;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 40,
child: TextField(
controller: controller,
keyboardType: keyboardType,
onChanged: onChanged,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w500,
color: textColor ?? const Color.fromARGB(255, 20, 20, 20),
),
textAlign: isCentered ?? false ? TextAlign.center : TextAlign.start,
autocorrect: false,
inputFormatters: inputFormatters,
cursorColor: textColor ?? const Color.fromARGB(255, 20, 20, 20),
cursorRadius: const Radius.circular(10),
maxLength: maxCharacters,
decoration: InputDecoration(
counterText: "",
fillColor: fillColor ?? const Color.fromARGB(255, 20, 20, 20),
filled: isFilled ?? false,
contentPadding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide:
const BorderSide(color: AppTheme.textColor, width: 1)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30),
borderSide:
const BorderSide(color: AppTheme.textColor, width: 1)),
errorText: errorText,
errorStyle: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: AppTheme.redColor),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: const BorderSide(color: AppTheme.redColor, width: 1)),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: const BorderSide(color: AppTheme.redColor, width: 1)),
),
),
);
}
}

104
lib/widgets/bottom_nav.dart Normal file
View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/follow_order_screen.dart';
import 'package:gascom/screens/generator_info_screen.dart';
import 'package:gascom/screens/home_screen.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class BottomNav extends StatelessWidget {
const BottomNav({
super.key,
});
@override
Widget build(BuildContext context) {
return PersistentTabView(
controller: PersistentTabController(initialIndex: 0),
resizeToAvoidBottomInset: false,
navBarHeight: 70,
tabs: [
tabConfig(
context,
const HomeScreen(),
"home",
"assets/svgs/home_filled.svg",
"assets/svgs/home.svg",
"Home Active",
"Home Inactive",
"الرئيسية",
),
tabConfig(
context,
const GeneratorInfoScreen(),
"generator_info",
"assets/svgs/generator_filled.svg",
"assets/svgs/generator.svg",
"Generator Info Active",
"Generator Info Inactive",
"معلومات المولدة",
),
tabConfig(
context,
const FollowOrderScreen(),
"follow_order",
"assets/svgs/van_filled.svg",
"assets/svgs/van.svg",
"Follow Order Active",
"Follow Order Inactive",
"تتبع الطلب",
),
],
navBarBuilder: (navBarConfig) => Style6BottomNavBar(
navBarConfig: navBarConfig,
navBarDecoration: NavBarDecoration(
color: AppTheme.secondaryColor,
padding: EdgeInsets.all(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 10,
spreadRadius: 1,
offset: const Offset(0, 0),
),
],
),
),
);
}
PersistentTabConfig tabConfig(
BuildContext context,
Widget screen,
String initialRoute,
String activeIcon,
String inactiveIcon,
String activeSemanticsLabel,
String inactiveSemanticsLabel,
String title,
) {
return PersistentTabConfig(
screen: screen,
navigatorConfig: NavigatorConfig(
initialRoute: initialRoute,
),
item: ItemConfig(
icon: Padding(
padding: const EdgeInsets.only(top: 4, left: 4, right: 4),
child: SvgPicture.asset(
activeIcon,
semanticsLabel: activeSemanticsLabel,
),
),
inactiveIcon: SvgPicture.asset(
inactiveIcon,
semanticsLabel: inactiveSemanticsLabel,
),
activeForegroundColor: AppTheme.primaryColor,
inactiveForegroundColor: AppTheme.primaryColor,
textStyle: Theme.of(context).textTheme.bodySmall ?? TextStyle(),
title: title,
)
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
const CustomAppBar({super.key, this.title});
final String? title;
@override
Size get preferredSize => const Size.fromHeight(80);
@override
Widget build(BuildContext context) {
return AppBar(
toolbarHeight: 80,
titleSpacing: 0,
backgroundColor: AppTheme.primaryColor,
surfaceTintColor: AppTheme.primaryColor,
title: Text(
title ?? "",
style: Theme.of(context).textTheme.titleLarge,
),
leading: IconButton(
icon: const Icon(CupertinoIcons.back, color: AppTheme.textColor,),
onPressed: () {
Navigator.pop(context);
},
),
bottom: title == null ? null : PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(
color: AppTheme.yellowColor,
height: 1,
margin: const EdgeInsets.symmetric(horizontal: 20),
),
),
);
}
}

View File

@ -0,0 +1,97 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/payment_screen.dart';
import 'package:gascom/screens/success_screen.dart';
import 'package:gascom/widgets/app_button.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class FineContainer extends StatelessWidget {
const FineContainer({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppTheme.textColor,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: AutoSizeText(
"غرامة",
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
AutoSizeText(
"2024/12/12",
style: Theme.of(context).textTheme.bodySmall,
),
],
),
const SizedBox(height: 10),
AutoSizeText(
"200,000 د.ع",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
const Divider(
color: AppTheme.textColor,
height: 1,
thickness: 1,
),
const SizedBox(height: 15),
AutoSizeText(
"مخالفة ساعات التشغيل والاطفاء",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
AutoSizeText(
"يجب دفع الغرامة قبل تاريخ 01/09/2024 وبخلافه، لن يتم تزويدك باي حصة بعد هذا التاريخ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 2,
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.45,
child: AppButton(
onPressed: () {
pushScreenWithoutNavBar(context,
PaymentScreen(
title: "غرامات",
onPaymentComplete: () {
pushScreenWithoutNavBar(context, const SuccessScreen(title: "تم دفع الغرامة بنجاح", subtitle: ""));
}
)
);
},
label: "دفع الغرامة",
isElevated: true,
),
),
],
)
],
),
);
}
}

View File

@ -0,0 +1,55 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gascom/constants/app_theme.dart';
class HomeGridItem extends StatelessWidget {
const HomeGridItem({
super.key,
required this.title,
required this.svgPath,
required this.onPressed,
});
final String title;
final String svgPath;
final void Function() onPressed;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(11),
border: Border.all(
color: AppTheme.secondaryColor,
width: 2,
),
),
child: SvgPicture.asset(
svgPath,
semanticsLabel: title,
),
),
),
// const SizedBox(height: 10,),
AutoSizeText(
title,
style: Theme.of(context).textTheme.bodyMedium,
minFontSize: 12,
maxLines: 2,
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@ -0,0 +1,70 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
import 'package:gascom/screens/order_details_screen.dart';
import 'package:gascom/widgets/order_state_badge.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class OrderContainer extends StatelessWidget {
const OrderContainer({
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: AppTheme.cardColor,
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: AutoSizeText(
"2024/12/12",
style: Theme.of(context).textTheme.bodySmall,
),
),
const OrderStateBadge(state: "قيد المراجعة"),
],
),
const SizedBox(height: 8),
AutoSizeText(
"5000 لتر",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
AutoSizeText(
"رقم الطلب: 67895435",
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 4),
const Divider(
color: AppTheme.primaryColor,
thickness: 1,
height: 25,
),
InkWell(
splashColor: AppTheme.primaryColor,
onTap: () {
pushScreenWithoutNavBar(context, OrderDetailsScreen());
},
child: Center(
child: AutoSizeText(
"اظهر التفاصيل",
style: Theme.of(context).textTheme.bodySmall,
),
),
)
],
),
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
class OrderStateBadge extends StatelessWidget {
const OrderStateBadge({
super.key,
required this.state,
});
final String state;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: state == "قيد المراجعة" ? AppTheme.yellowColor : AppTheme.primaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Text(
state,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: state == "قيد المراجعة" ? AppTheme.brownColor : AppTheme.textColor,
),
),
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:gascom/constants/app_theme.dart';
class TextContainer extends StatelessWidget {
const TextContainer({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppTheme.textColor,
width: 1,
),
),
child: AutoSizeText(
text,
style: Theme.of(context).textTheme.bodySmall,
minFontSize: 8,
),
);
}
}