This commit is contained in:
Mohammed Al-Samarraie
2026-01-13 15:14:30 +03:00
parent 7cbf65e6c1
commit 3b3ed5e640
27 changed files with 36 additions and 36 deletions

View File

@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import '../widgets/app_background.dart';
import '../widgets/settings_bar.dart';
class AboutScreen extends StatelessWidget {
const AboutScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: AppBackground(
child: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
/// -------------------- SETTINGS BAR --------------------
SettingsBar(
selectedIndex: 0,
onTap: (_) {},
showBackButton: true,
onBackTap: () => Navigator.pop(context),
iconPaths: const [],
),
const SizedBox(height: 12),
/// -------------------- CENTER CONTENT --------------------
///
Center(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 10,
),
child: Align(
alignment: Alignment.topCenter,
child: Directionality(
textDirection: TextDirection.rtl,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"عن الشركة",
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 12),
Container(
width: 200,
height: 1,
color: const Color(0x8732C599),
),
const SizedBox(height: 14),
const Text(
"شركة كودا ذ.م.م هي شركة عراقية رائدة تقدم حلولاً متميزة في البرمجيات المخصصة والتطبيقات وإدارة الخوادم والمواقع، ومنظومات الاتصال وخدمات الأمن السيبراني. تأسست عام 2008 ولديها عقود واتفاقيات عديدة مع مؤسسات حكومية عراقية والقطاع الخاص.\n\nوسعت الشركة شراكاتها مع وكالات عالمية مثل CODACS الأمريكية و Laipac الكندية و Teltonika اللتوانية وWolf Team الصينية وLibelium الإسبانية وOdoo العالمية وغيرها. مما أتاح لها تقديم أحدث التقنيات والمنتجات المبتكرة لعملائها.\n\nتوفر كودا حلولاً برمجية مخصصة للمؤسسات الكبيرة والدوائر الحكومية، مما يسهم في تبسيط الإجراءات وتقديم الخدمات للمواطنين بشكل إلكتروني وفعال.\n\nكما تقدم كودا مجموعة واسعة من المنتجات والخدمات الحديثة، بما في ذلك الشاشات الإعلانية، والبوابات الذكية، والمواقف الذكية، والأقفال، والمباني الذكية، وأجهزة الكشك (الخدمة الإلكترونية الذاتية)، وأجهزة محمولة تخصصية، وحساسات، وأجهزة نداء واستغاثة.\n\nبفضل هذه المنتجات والخدمات المبتكرة، تعزز كودا مكانتها الرائدة في السوق المحلي والإقليمي، وتساهم في تطوير التكنولوجيا وتعزيز جودة الحياة في العراق .",
style: TextStyle(
fontSize: 18,
color: Colors.white70,
height:
1.5, // Added line height for better readability
),
),
const SizedBox(height: 20), // Bottom padding
],
),
),
),
),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,269 @@
import 'package:coda_project/presentation/screens/face_screen.dart';
import 'package:coda_project/presentation/screens/notifications_screen.dart';
import 'package:coda_project/presentation/screens/user_settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/settings_bar.dart';
class AttendanceScreen extends StatelessWidget {
const AttendanceScreen({super.key});
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
final screenHeight = MediaQuery.sizeOf(context).height;
return Directionality(
textDirection: TextDirection.ltr,
child: Stack(
children: [
SizedBox(height: MediaQuery.of(context).size.height),
/// ------------------------------
/// SETTINGS BAR (STATIC)
/// ------------------------------
SafeArea(
child: SettingsBar(
selectedIndex: 0,
showBackButton: false,
iconPaths: ['assets/images/user.svg', 'assets/images/ball.svg'],
onTap: (index) {
if (index == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserSettingsScreen(),
),
);
} else if (index == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationsScreen(),
),
);
}
},
),
),
/// ------------------------------
/// GREETING TEXT
/// ------------------------------
Positioned(
top:
screenHeight *
0.14, // moved down because settings bar now exists
left: 0,
right: 0,
child: Center(
child: Text(
"صباح الخير, محمد",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.white,
shadows: [Shadow(color: Color(0x42000000), blurRadius: 6)],
),
),
),
),
/// ------------------------------
/// MAIN CARD AREA
/// ------------------------------
Positioned(
top:
screenHeight *
0.2, // pushed down because of settings bar + greeting
left: 0,
right: 0,
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: screenHeight * 0.05),
child: Stack(
children: [
Container(
height: screenHeight * 0.5,
width: screenWidth * 0.7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
boxShadow: [
BoxShadow(
color: Color(0x1F2B2B2B),
blurRadius: 5,
offset: Offset(10, -10),
),
BoxShadow(
color: Color(0xABCECECE),
blurRadius: 5,
offset: Offset(-2, 5),
),
BoxShadow(
color: Color.fromARGB(148, 2, 70, 35),
blurRadius: 80,
offset: Offset(0, 10),
),
],
),
),
Container(
height: screenHeight * 0.5,
width: screenWidth * 0.7,
decoration: BoxDecoration(
color: Color(0x92757575),
borderRadius: BorderRadius.circular(32),
),
),
],
),
),
),
),
/// ------------------------------
/// LOGIN BUTTON
/// ------------------------------
Positioned(
top: screenHeight * 0.21,
left: screenWidth * 0.05,
child: _ShadowedCard(
shadow: [
BoxShadow(
color: Color(0x62000000),
blurRadius: 10,
spreadRadius: 5,
offset: Offset(5, 5),
),
],
child: _FingerButton(
icon: "assets/images/faceLogin.svg",
label: "تسجيل الدخول",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => OvalCameraCapturePage(isLogin: true),
),
);
},
),
),
),
/// ------------------------------
/// LOGOUT BUTTON
/// ------------------------------
Positioned(
bottom: screenHeight * 0.2,
right: screenWidth * 0.1,
child: _ShadowedCard(
shadow: [
BoxShadow(
color: Color(0xABCECECE),
blurRadius: 5,
spreadRadius: 3,
offset: Offset(-6, -6),
),
BoxShadow(
color: Color(0x92014221),
blurRadius: 10,
offset: Offset(-5, -5),
),
BoxShadow(
color: Color(0x7D1A1A1A),
blurRadius: 10,
spreadRadius: 3,
offset: Offset(5, 5),
),
],
child: _FingerButton(
icon: "assets/images/faceLogout.svg",
label: "تسجيل خروج",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => OvalCameraCapturePage(isLogin: false),
),
);
},
),
),
),
],
),
);
}
}
/// ---------------------------------------------
/// SHADOW WRAPPER
/// ---------------------------------------------
class _ShadowedCard extends StatelessWidget {
final Widget child;
final List<BoxShadow> shadow;
const _ShadowedCard({required this.child, required this.shadow});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 160,
width: 160,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
boxShadow: shadow,
),
),
child,
],
);
}
}
/// ---------------------------------------------
/// BUTTON WIDGET
/// ---------------------------------------------
class _FingerButton extends StatelessWidget {
final String icon;
final String label;
final VoidCallback onTap;
const _FingerButton({
required this.icon,
required this.label,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 160,
width: 160,
decoration: BoxDecoration(
color: Color(0xFFEAFBF3),
borderRadius: BorderRadius.circular(32),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(icon, width: 75, height: 75),
SizedBox(height: 10),
Text(
label,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/app_background.dart';
import '../widgets/auth_form.dart';
import '../../core/di/injection_container.dart';
import '../blocs/login/login_bloc.dart';
class AuthScreen extends StatelessWidget {
const AuthScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => sl<LoginBloc>(),
child: Scaffold(
resizeToAvoidBottomInset: false,
body: AppBackground(
child: SafeArea(
child: Column(
children: [
const SizedBox(height: 60),
// Logo
Center(child: Image.asset("assets/images/logo2.png", width: 200)),
// const SizedBox(height: 15),
// Form - taking remaining space and centered
Expanded(child: Center(child: const AuthForm())),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,351 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'dart:async';
class OvalCameraCapturePage extends StatefulWidget {
final bool isLogin;
const OvalCameraCapturePage({super.key, this.isLogin = true});
@override
State<OvalCameraCapturePage> createState() => _OvalCameraCapturePageState();
}
class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
CameraController? _cameraController;
bool _isCameraInitialized = false;
String? _errorMessage;
bool _isSuccess = false;
Timer? _timer;
@override
void initState() {
super.initState();
_initializeCamera();
}
Future<void> _initializeCamera() async {
try {
// Dispose existing controller if any
await _cameraController?.dispose();
_cameraController = null;
// Get available cameras
final cameras = await availableCameras();
// Check if cameras list is available
if (cameras.isEmpty) {
setState(() {
_errorMessage = "لا توجد كاميرات متاحة";
_isCameraInitialized = false;
});
return;
}
// Try to find front camera, fallback to first available camera
CameraDescription? selectedCamera;
try {
selectedCamera = cameras.firstWhere(
(cam) => cam.lensDirection == CameraLensDirection.front,
);
} catch (e) {
// If no front camera found, use the first available camera
if (cameras.isNotEmpty) {
selectedCamera = cameras.first;
} else {
setState(() {
_errorMessage = "لا توجد كاميرات متاحة";
_isCameraInitialized = false;
});
return;
}
}
_cameraController = CameraController(
selectedCamera,
ResolutionPreset.medium,
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _cameraController!.initialize();
if (!mounted) return;
setState(() {
_isCameraInitialized = true;
_errorMessage = null;
});
_timer = Timer(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
_isSuccess = true;
});
// Auto-close after 2 seconds
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.of(context).pop();
}
});
}
});
} catch (e) {
if (!mounted) return;
setState(() {
_errorMessage = "خطأ في تهيئة الكاميرا: $e";
_isCameraInitialized = false;
});
print("Error initializing camera: $e");
}
}
@override
void dispose() {
_cameraController?.dispose();
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff000000),
body:
_errorMessage != null
? Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.camera_alt_outlined,
size: 64,
color: Colors.white70,
),
SizedBox(height: 16),
Text(
_errorMessage!,
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _initializeCamera,
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xffE8001A),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 32,
vertical: 12,
),
),
child: Text("إعادة المحاولة"),
),
],
),
),
)
: _isCameraInitialized && _cameraController != null
? Stack(
children: [
SizedBox(height: MediaQuery.of(context).size.height),
// Camera Preview
Positioned(
child: Center(child: CameraPreview(_cameraController!)),
),
// Oval overlay with dimmed background
Positioned.fill(
child: CustomPaint(painter: _OvalOverlayPainter()),
),
// Top Text
Positioned(
top: 100,
left: 0,
right: 0,
child: Center(
child: Text(
widget.isLogin ? "تسجيل الدخول" : "تسجيل خروج",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
fontFamily:
'Cairo', // Assuming Cairo font based on Arabic text
),
),
),
),
// Bottom Text and Logo
Positioned(
bottom: 80,
left: 0,
right: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_isSuccess
? (widget.isLogin
? "تم تسجيل دخولك بنجاح"
: "تم تسجيل خروجك بنجاح")
: (widget.isLogin
? "يتم تسجيل الدخول ..."
: "يتم تسجيل الخروج ..."),
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
// Logo
SizedBox(
height: _isSuccess ? 80 : 60,
width: _isSuccess ? 80 : 60,
child: SvgPicture.asset(
_isSuccess
? 'assets/images/logSuccess.svg'
: 'assets/images/logLoading.svg',
// ignore: deprecated_member_use
color: Colors.white,
fit: BoxFit.contain,
),
),
],
),
),
// // Capture button
// Positioned(
// bottom: 60,
// left: 0,
// right: 0,
// child: Center(
// child: GestureDetector(
// onTap: (){},
// child: Container(
// width: 72,
// height: 72,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// color: Colors.white,
// boxShadow: [
// BoxShadow(
// color: Colors.black26,
// blurRadius: 8,
// offset: Offset(0, 4),
// ),
// ],
// ),
// child: Icon(Icons.camera_alt, color: Color(0xffE8001A), size: 36),
// ),
// ),
// ),
// ),
],
)
: Center(
child: CircularProgressIndicator(color: Color(0xffE8001A)),
),
);
}
}
class _OvalOverlayPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final width = size.width * 0.75;
final height = size.height * 0.4;
final center = Offset(size.width / 2, size.height / 2);
final ovalRect = Rect.fromCenter(
center: center,
width: width,
height: height,
);
// Create a path for the whole screen
final screenPath =
Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height));
// Create a path for the oval
final ovalPath = Path()..addOval(ovalRect);
// Subtract the oval from the screen path
final overlayPath = Path.combine(
PathOperation.difference,
screenPath,
ovalPath,
);
// Draw the dimmed area outside the oval with gradient
final gradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromARGB(255, 41, 41, 41), // top dark gray
Color.fromARGB(255, 0, 20, 15), // bottom deep green
],
);
final paint =
Paint()
..shader = gradient.createShader(
Rect.fromLTWH(0, 0, size.width, size.height),
)
..style = PaintingStyle.fill;
canvas.drawPath(overlayPath, paint);
// Draw glowing circles effect (like AppBackground) - drawn after overlay
// Top circle - positioned similar to AppBackground (top: -250, left: 100, right: -200)
final topCircleCenter = Offset(size.width * 0.3, -250);
final topCircleRadius = 150.0;
// Draw multiple circles with different opacities for spread effect (spreadRadius: 160)
for (int i = 0; i < 5; i++) {
final spreadPaint =
Paint()
..color = Color.fromARGB(69 ~/ (i + 1), 62, 254, 190)
..maskFilter = MaskFilter.blur(BlurStyle.normal, 140 - (i * 20));
canvas.drawCircle(
topCircleCenter,
topCircleRadius + (i * 30),
spreadPaint,
);
}
// Bottom circle - positioned similar to AppBackground (bottom: 100, left: -140, right: -120)
final bottomCircleCenter = Offset(size.width * 0.2, size.height + 100);
final bottomCircleRadius = 160.0;
// Draw multiple circles with different opacities for spread effect (spreadRadius: 60)
for (int i = 0; i < 5; i++) {
final spreadPaint =
Paint()
..color = Color.fromARGB(83 ~/ (i + 1), 62, 254, 190)
..maskFilter = MaskFilter.blur(BlurStyle.normal, 180 - (i * 25));
canvas.drawCircle(
bottomCircleCenter,
bottomCircleRadius + (i * 40),
spreadPaint,
);
}
// Draw oval border
final borderPaint =
Paint()
..color = Colors.greenAccent
..style = PaintingStyle.stroke
..strokeWidth = 4;
canvas.drawOval(ovalRect, borderPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,105 @@
import 'package:coda_project/presentation/screens/notifications_screen.dart';
import 'package:coda_project/presentation/screens/user_settings_screen.dart';
import 'package:flutter/material.dart';
import '../widgets/finance_summary_card.dart';
import '../widgets/work_day_card.dart';
import '../widgets/settings_bar.dart';
class FinanceScreen extends StatefulWidget {
final void Function(bool isScrollingDown)? onScrollEvent;
const FinanceScreen({super.key, this.onScrollEvent});
@override
State<FinanceScreen> createState() => _FinanceScreenState();
}
class _FinanceScreenState extends State<FinanceScreen> {
String dropdownValue = "الكل";
late ScrollController scrollController;
@override
void initState() {
super.initState();
scrollController = ScrollController();
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: SafeArea(
child: CustomScrollView(
controller: scrollController,
physics: const BouncingScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: SettingsBar(
selectedIndex: 0,
showBackButton: false,
iconPaths: [
'assets/images/user.svg',
'assets/images/ball.svg',
],
onTap: (index) {
if (index == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserSettingsScreen(),
),
);
} else if (index == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationsScreen(),
),
);
}
},
),
),
const SliverToBoxAdapter(child: SizedBox(height: 5)),
/// SUMMARY CARD
SliverToBoxAdapter(
child: FinanceSummaryCard(
totalAmount: "333,000",
dropdownValue: dropdownValue,
onCalendarTap: () => showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime(2030),
),
onDropdownChanged: (value) {
setState(() => dropdownValue = value!);
},
),
),
/// WORK DAY CARDS
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return const WorkDayCard();
},
childCount: 3,
),
),
const SliverToBoxAdapter(child: SizedBox(height: 120)),
],
),
),
);
}
}

View File

@@ -0,0 +1,536 @@
import 'package:coda_project/presentation/screens/notifications_screen.dart';
import 'package:coda_project/presentation/screens/user_settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/settings_bar.dart';
import 'request_leave_screen.dart';
import 'request_advance_scrren.dart';
import '../../models/leave_request.dart';
import '../../models/advance_request.dart';
import '../../core/services/request_service.dart';
class HolidayScreen extends StatefulWidget {
final void Function(bool isScrollingDown)? onScrollEvent;
const HolidayScreen({super.key, this.onScrollEvent});
@override
State<HolidayScreen> createState() => _HolidayScreenState();
}
class _HolidayScreenState extends State<HolidayScreen> {
int activeTab = 0;
final RequestService _requestService = RequestService();
List<LeaveRequest> _leaveRequests = [];
List<AdvanceRequest> _advanceRequests = [];
final ScrollController _scrollController = ScrollController();
bool _showActions = true;
@override
void initState() {
super.initState();
_initializeData();
_scrollController.addListener(() {
final direction = _scrollController.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
if (_showActions) setState(() => _showActions = false);
widget.onScrollEvent?.call(true);
} else if (direction == ScrollDirection.forward) {
if (!_showActions) setState(() => _showActions = true);
widget.onScrollEvent?.call(false);
}
if (_scrollController.position.pixels <= 5) {
setState(() {
_showActions = true;
});
widget.onScrollEvent?.call(false);
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _initializeData() async {
_leaveRequests = await _requestService.getLeaveRequests();
_advanceRequests = await _requestService.getAdvanceRequests();
_requestService.leaveRequestsStream.listen((requests) {
if (mounted) setState(() => _leaveRequests = requests);
});
_requestService.advanceRequestsStream.listen((requests) {
if (mounted) setState(() => _advanceRequests = requests);
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
// ---------------------------------------------------------
// ⭐ MAIN CONTENT - CUSTOM SCROLL VIEW
// ---------------------------------------------------------
SafeArea(
child: CustomScrollView(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
slivers: [
// SETTINGS BAR
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 8),
// First, make sure you have your screens defined
// Then in your main widget:
child: SettingsBar(
selectedIndex: 0,
showBackButton: false,
iconPaths: [
'assets/images/user.svg',
'assets/images/ball.svg',
],
onTap: (index) {
if (index == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserSettingsScreen(),
),
);
} else if (index == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationsScreen(),
),
);
}
},
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 5)),
// TABS SECTION
SliverToBoxAdapter(
child: SizedBox(
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// الأجازات
GestureDetector(
onTap: () => setState(() => activeTab = 1),
child: Column(
children: [
Text(
"الأجازات",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color:
activeTab == 1
? const Color(0xFF8EFDC2)
: const Color(0x9EFFFFFF),
),
),
if (activeTab == 1)
Container(
width: 60,
height: 2,
margin: const EdgeInsets.only(top: 4),
color: const Color(0xFF8EFDC2),
),
],
),
),
const SizedBox(width: 70),
// السلف
GestureDetector(
onTap: () => setState(() => activeTab = 0),
child: Column(
children: [
Text(
"السلف",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color:
activeTab == 0
? const Color(0xFF8EFDC2)
: const Color(0x9EFFFFFF),
),
),
if (activeTab == 0)
Container(
width: 60,
height: 2,
margin: const EdgeInsets.only(top: 4),
color: const Color(0xFF8EFDC2),
),
],
),
),
],
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 20)),
// CONTENT LISTS
activeTab == 1
? _buildLeaveRequestsSliver()
: _buildAdvanceRequestsSliver(),
const SliverToBoxAdapter(child: SizedBox(height: 120)),
],
),
),
// ---------------------------------------------------------
// ⭐ FLOATING ACTION BUTTONS
// ---------------------------------------------------------
Positioned(
bottom: 150,
right: 20,
child: AnimatedSlide(
offset: _showActions ? Offset.zero : const Offset(0, 1.3),
duration: const Duration(milliseconds: 300),
child: AnimatedOpacity(
opacity: _showActions ? 1 : 0,
duration: const Duration(milliseconds: 250),
child: Column(
children: [
_HolidayActionButton(
label: "طلب سلفة",
svgPath: "assets/images/money2.svg",
iconWidth: 28,
iconHeight: 20,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const RequestAdvanceScreen(),
),
);
},
),
const SizedBox(height: 18),
_HolidayActionButton(
label: "طلب إجازة",
svgPath: "assets/images/plus.svg",
iconWidth: 28,
iconHeight: 20,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const RequestLeaveScreen(),
),
);
},
),
],
),
),
),
),
],
);
}
// ----------------------------------------------------------------
// SLIVERS FOR LISTS
// ----------------------------------------------------------------
Widget _buildLeaveRequestsSliver() {
if (_leaveRequests.isEmpty) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Text(
"لا توجد طلبات أجازة",
style: const TextStyle(color: Colors.white),
),
),
),
);
}
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 25),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildLeaveRequestCard(_leaveRequests[index]),
);
}, childCount: _leaveRequests.length),
),
);
}
Widget _buildAdvanceRequestsSliver() {
if (_advanceRequests.isEmpty) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Text(
"لا توجد طلبات سلف",
style: const TextStyle(color: Colors.white),
),
),
),
);
}
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 25),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildAdvanceRequestCard(_advanceRequests[index]),
);
}, childCount: _advanceRequests.length),
),
);
}
// ----------------------------------------------------------------
// CARD BUILDERS (unchanged)
// ----------------------------------------------------------------
Widget _buildLeaveRequestCard(LeaveRequest request) {
const bgColor = Color(0xFFEAF8F3);
const lineColor = Color(0xFF22A685);
final bool isWaiting = request.status == "waiting";
final bool isApproved = request.status == "approved";
final bool isDenied = request.status == "denied";
final String dateText =
"${request.fromDate.year}.${request.fromDate.month}.${request.fromDate.day}";
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// STATUS ROW
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_statusCircle(
isWaiting,
"قيد الانتظار",
isWaiting ? "assets/images/waiting.svg" : null,
const Color(0xFFA58A1B),
const Color(0xFFFFDC69),
),
const SizedBox(width: 12),
_statusCircle(
isApproved,
"موافقة",
isApproved ? "assets/images/yes.svg" : null,
const Color(0xFF0A8A60),
const Color(0xFF00D7A3),
),
const SizedBox(width: 12),
_statusCircle(
isDenied,
"تم الرفض",
isDenied ? "assets/images/no.svg" : null,
const Color(0xFFE63946),
const Color(0xFFE63946),
),
],
),
const SizedBox(height: 8),
// TITLE + DATE
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
"assets/images/holiday.svg",
width: 32,
height: 32,
color: const Color(0xFF11663C),
),
const SizedBox(width: 8),
Text(
request.leaveType,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
Text(dateText),
],
),
const SizedBox(height: 10),
Container(height: 1.4, width: double.infinity, color: lineColor),
const SizedBox(height: 10),
// REASON ROW
Row(
textDirection: TextDirection.ltr, // Add this
children: [
Expanded(
child: Text(request.reason, textAlign: TextAlign.right),
),
const SizedBox(width: 6),
const Text(
"السبب:",
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
),
],
),
],
),
),
);
}
Widget _statusCircle(
bool active,
String label,
String? svg,
Color color,
Color glow,
) {
return Column(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: active ? color : const Color(0xFFD6D6D6),
boxShadow:
active
? [BoxShadow(color: glow.withOpacity(0.6), blurRadius: 12)]
: [],
),
child:
svg != null
? Center(child: SvgPicture.asset(svg, width: 18, height: 18))
: null,
),
const SizedBox(height: 4),
Text(label, style: const TextStyle(fontSize: 10)),
],
);
}
Widget _buildAdvanceRequestCard(AdvanceRequest request) {
// Same card logic as before (unchanged)
return _buildLeaveRequestCard(
LeaveRequest(
id: request.id,
leaveType: "سلفة ${request.amount}",
isTimedLeave: false,
fromDate: DateTime.now(),
toDate: DateTime.now(),
fromTime: TimeOfDay.now(),
toTime: TimeOfDay.now(),
reason: request.reason,
requestDate: DateTime.now(),
status: request.status,
),
);
}
}
// ----------------------------------------------------------------
// FLOATING BUTTON WIDGET (unchanged)
// ----------------------------------------------------------------
class _HolidayActionButton extends StatelessWidget {
final String label;
final String svgPath;
final VoidCallback onTap;
final double iconWidth;
final double iconHeight;
const _HolidayActionButton({
required this.label,
required this.svgPath,
required this.onTap,
required this.iconWidth,
required this.iconHeight,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 80,
height: 70,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: const [
BoxShadow(
color: Color(0x4B00C68B),
blurRadius: 20,
offset: Offset(0, 6),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: iconWidth,
height: iconHeight,
child: SvgPicture.asset(svgPath, fit: BoxFit.contain),
),
const SizedBox(height: 6),
Text(
label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import '../widgets/app_background.dart';
import '../../widgets/floatingnavbar.dart';
import 'attendence_screen.dart';
import 'finance_screen.dart';
import 'holiday_screen.dart';
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.sizeOf(context).height;
return Scaffold(
body: Stack(
children: [
/// BACKGROUND
const AppBackground(child: SizedBox()),
/// ACTIVE SCREEN (fills entire screen - content will extend behind navbar)
///
Positioned.fill(
child: IndexedStack(
index: _currentIndex,
children: [
const AttendanceScreen(),
const FinanceScreen(),
const HolidayScreen(),
],
),
),
/// FLOATING NAVBAR (positioned above content)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Floatingnavbar(
items: [
NavBarItem(
iconPath: 'assets/images/attendance.svg',
label: 'الحضور',
),
NavBarItem(
iconPath: 'assets/images/finance.svg',
label: 'المالية',
),
NavBarItem(
iconPath: 'assets/images/holiday.svg',
label: 'الإجازة',
),
],
selectedIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
),
],
),
);
}
}

View File

@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import '../widgets/app_background.dart';
import '../widgets/settings_bar.dart';
class NotificationsScreen extends StatelessWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: AppBackground(
child: Column(
children: [
/// -------------------- SETTINGS BAR --------------------
SettingsBar(
selectedIndex: 0,
onTap: (_) {},
showBackButton: true,
onBackTap: () => Navigator.pop(context),
iconPaths: const [],
),
const SizedBox(height: 12),
/// -------------------- CONTENT --------------------
Expanded(
child: Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: [
/// Title
Padding(
padding: const EdgeInsets.only(right: 40),
child: Align(
alignment: Alignment.topRight,
child: const Text(
"الأشعارات",
style: TextStyle(
fontSize: 26,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 20),
/// -------------------- NOTIFICATION CARD --------------------
Padding(
padding: const EdgeInsets.symmetric(horizontal: 22),
child: Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xFFEFFFFA),
borderRadius: BorderRadius.circular(20),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
child: Stack(
children: [
/// ✅ DATE — TOP LEFT
const Positioned(
top: 0,
left: 0,
child: Text(
"2025.12.1",
style: TextStyle(
fontSize: 12,
color: Color(0x9E000000),
fontWeight: FontWeight.w900,
),
),
),
/// ✅ MAIN CONTENT — RTL
Align(
alignment: Alignment.centerRight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"عنوان الاشعار",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
// const SizedBox(height: 6),
/// ✅ DETAILS WITH GREEN LINE
Row(
mainAxisSize: MainAxisSize.min,
children: [
// Green line
Container(
width: 20,
height: 2,
color: Color(0xFF025A42),
),
SizedBox(width: 8),
Text(
"تفاصيل الاشعار",
style: TextStyle(
fontSize: 16,
color: Colors.black87,
),
),
],
),
],
),
),
],
),
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,154 @@
import 'dart:async';
import 'package:coda_project/presentation/screens/auth_screen.dart';
import 'package:flutter/material.dart';
import '../widgets/onboarding_page.dart';
import '../widgets/onboarding_button.dart';
class OnboardingScreen extends StatefulWidget {
const OnboardingScreen({super.key});
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final PageController controller = PageController();
int index = 0;
@override
void initState() {
super.initState();
// auto slide
Timer.periodic(const Duration(seconds: 4), (timer) {
if (!mounted) return;
int next = index == 1 ? 0 : index + 1;
controller.animateToPage(
next,
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
/// BACKGROUND GRADIENT (base layer)
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.0, 0.45, 0.75, 1.0],
colors: [
Color(0xFF2E2E2E),
Color(0xFF00271D),
Color(0xFF005841),
Color.fromARGB(176, 62, 254, 203),
],
),
),
),
/// BLURRED CIRCLE (subtle rounded glow)
Positioned(
bottom: -120,
left: -60,
right: -60,
child: Container(
height: 300,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color.fromARGB(
0,
62,
254,
203,
), // same green with opacity
boxShadow: [
BoxShadow(
color: Color.fromARGB(
69,
62,
254,
142,
), // stronger outer glow
blurRadius: 60,
spreadRadius: 100,
),
],
),
),
),
Column(
children: [
const SizedBox(height: 70),
Image.asset("assets/images/logo2.png", width: 200),
/// PAGEVIEW (SVG + TEXT ONLY)
Expanded(
child: PageView(
physics: BouncingScrollPhysics(),
controller: controller,
onPageChanged: (i) => setState(() => index = i),
children: const [
OnboardingPage(
imagePath: "assets/images/Onboarding1.svg",
text:
"سجل دخولك وخروجك بسهولة وتابع دوامك\nيومياً بدون تعقيد",
),
OnboardingPage(
imagePath: "assets/images/Onboarding2.svg",
text:
"اعرف تفاصيل راتبك وقدّم طلب الإجازة\nوتابع حالته بكل شفافية",
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 40),
child: OnboardingButton(
text: "تسجيل دخول",
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AuthScreen(),
),
);
},
),
),
/// DOTS INDICATOR
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(2, (i) {
bool active = i == index;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 10,
height: 10,
decoration: BoxDecoration(
color: active ? Colors.white : Colors.white60,
borderRadius: BorderRadius.circular(50),
),
);
}),
),
const SizedBox(height: 100),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,258 @@
import 'package:flutter/material.dart';
// import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/app_background.dart';
import '../widgets/settings_bar.dart';
import '../widgets/onboarding_button.dart';
import '../../models/advance_request.dart';
import '../../core/services/request_service.dart';
class RequestAdvanceScreen extends StatefulWidget {
const RequestAdvanceScreen({super.key});
@override
State<RequestAdvanceScreen> createState() => _RequestAdvanceScreenState();
}
class _RequestAdvanceScreenState extends State<RequestAdvanceScreen> {
// Text controller for amount
final TextEditingController amountController = TextEditingController();
// Text controller for reason
final TextEditingController reasonController = TextEditingController();
// Use the singleton instance
final RequestService _requestService = RequestService();
// Method to save the advance request
Future<void> _saveAdvanceRequest() async {
if (amountController.text.isEmpty || reasonController.text.isEmpty) {
// Show an error message if fields are empty
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('الرجاء إدخال جميع الحقول'),
backgroundColor: Colors.red,
),
);
return;
}
// Parse the amount to double
final amount = double.tryParse(amountController.text);
if (amount == null || amount <= 0) {
// Show an error message if amount is invalid
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('الرجاء إدخال مبلغ صحيح'),
backgroundColor: Colors.red,
),
);
return;
}
// Create a new advance request with default status "waiting"
final advanceRequest = AdvanceRequest(
id: DateTime.now().millisecondsSinceEpoch.toString(),
amount: amount,
reason: reasonController.text,
status: "waiting", // Default status
);
try {
// Save the advance request
await _requestService.addAdvanceRequest(advanceRequest);
// Show a success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب السلفة بنجاح'),
backgroundColor: Colors.green,
),
);
// Navigate back to the previous screen
Navigator.pop(context);
} catch (e) {
// Show an error message if something went wrong
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('حدث خطأ: $e'),
backgroundColor: Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AppBackground(
child: SafeArea(
child: Column(
children: [
// Settings bar
SettingsBar(
selectedIndex: -1,
onTap: (_) {},
showBackButton: true,
onBackTap: () => Navigator.pop(context),
iconPaths: const [
"assets/images/user.svg",
"assets/images/bell.svg",
],
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title for advance request
const Align(
alignment: Alignment.topRight,
child: Text(
"طلب سلفة",
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 40),
//=============================
// AMOUNT INPUT
//=============================
Align(
alignment: Alignment.centerRight,
child: const Text(
"المبلغ المطلوب",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 6),
Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
height: 58,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: const [
BoxShadow(
color: Color(0x22000000),
blurRadius: 8,
offset: Offset(0, 3),
),
],
),
child: TextField(
controller: amountController,
textAlign: TextAlign.right,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "دع .000 ",
hintStyle: TextStyle(color: Colors.grey),
),
),
),
),
const SizedBox(height: 25),
// =============================
// REASON TEXTFIELD
// =============================
Align(
alignment: Alignment.centerRight,
child: Directionality(
textDirection: TextDirection.rtl,
child: const Text(
"السبب",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
// OUTER BORDER CONTAINER
Container(
padding: const EdgeInsets.all(
15,
), // border thickness space
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFF00FFAA), // green border
width: 0.5,
),
borderRadius: BorderRadius.circular(14),
),
// INNER TEXTFIELD CONTAINER
child: Container(
height: 100,
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(12),
),
// Added Directionality to fix text direction
child: Directionality(
textDirection: TextDirection.rtl,
child: TextField(
controller: reasonController,
maxLines: 5,
textAlign:
TextAlign
.right, // Added this to align text to the right
decoration: const InputDecoration(
border: InputBorder.none,
hintText:
"اكتب السبب", // Added placeholder text
hintStyle: TextStyle(color: Colors.grey),
),
),
),
),
),
const SizedBox(height: 70),
// CONFIRM BUTTON
Center(
child: OnboardingButton(
text: "تأكيد الطلب",
backgroundColor: const Color(0xFFD1FEF0),
textColor: Colors.black,
onPressed: _saveAdvanceRequest, // Call the save method
),
),
const SizedBox(height: 40),
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,642 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/app_background.dart';
import '../widgets/settings_bar.dart';
import '../widgets/onboarding_button.dart';
import '../../models/leave_request.dart';
import '../../core/services/request_service.dart';
class RequestLeaveScreen extends StatefulWidget {
const RequestLeaveScreen({super.key});
@override
State<RequestLeaveScreen> createState() => _RequestLeaveScreenState();
}
class _RequestLeaveScreenState extends State<RequestLeaveScreen> {
// Dropdown value - initialize with default
String leaveType = "إجازة مرضية ";
// Toggle switch
bool isTimedLeave = false;
// Date & time selectors
DateTime? fromDate = DateTime.now();
DateTime? toDate = DateTime.now();
TimeOfDay? fromTime = const TimeOfDay(hour: 12, minute: 00);
TimeOfDay? toTime = const TimeOfDay(hour: 12, minute: 00);
// Text controller for reason
final TextEditingController reasonController = TextEditingController();
// Use the singleton instance
final RequestService _requestService = RequestService();
/// PICK DATE
Future<void> pickDate(bool isFrom) async {
DateTime initial = isFrom ? fromDate! : toDate!;
DateTime? newDate = await showDatePicker(
context: context,
initialDate: initial,
firstDate: DateTime(2020),
lastDate: DateTime(2035),
builder: (context, child) {
return Theme(data: ThemeData.dark(), child: child!);
},
);
if (newDate != null) {
setState(() {
if (isFrom) {
fromDate = newDate;
} else {
toDate = newDate;
}
});
}
}
/// PICK TIME
Future<void> pickTime(bool isFrom) async {
TimeOfDay initial = isFrom ? fromTime! : toTime!;
TimeOfDay? newTime = await showTimePicker(
context: context,
initialTime: initial,
builder: (context, child) {
return Theme(data: ThemeData.dark(), child: child!);
},
);
if (newTime != null) {
setState(() {
if (isFrom) {
fromTime = newTime;
} else {
toTime = newTime;
}
});
}
}
// Method to save the leave request
Future<void> _saveLeaveRequest() async {
if (reasonController.text.isEmpty) {
// Show an error message if reason is empty
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('الرجاء إدخال السبب'),
backgroundColor: Colors.red,
),
);
return;
}
// Create a new leave request with default status "waiting"
final leaveRequest = LeaveRequest(
id: DateTime.now().millisecondsSinceEpoch.toString(),
leaveType: leaveType, // Use the current leaveType value
isTimedLeave: isTimedLeave,
fromDate: fromDate!,
toDate: toDate!,
fromTime: fromTime!,
toTime: toTime!,
reason: reasonController.text,
requestDate: DateTime.now(),
status: "waiting", // Default status
);
try {
// Save the leave request
await _requestService.addLeaveRequest(leaveRequest);
// Show a success message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم إرسال طلب الأجازة بنجاح'),
backgroundColor: Colors.green,
),
);
// Navigate back to the previous screen
Navigator.pop(context);
} catch (e) {
// Show an error message if something went wrong
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('حدث خطأ: $e'), backgroundColor: Colors.red),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true, // ✅ IMPORTANT
body: AppBackground(
child: SafeArea(
child: Column(
children: [
// In your RequestLeaveScreen's build method, replace the SettingsBar with this:
SettingsBar(
selectedIndex: -1,
onTap: (_) {},
showBackButton: true,
onBackTap: () => Navigator.pop(context),
iconPaths: const [
"assets/images/user.svg",
"assets/images/bell.svg",
],
),
// Title
// const SizedBox(height: 30),
Flexible(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Fixed alignment for "طلب أجازة"
const Align(
alignment: Alignment.topRight,
child: Text(
"طلب أجازة ",
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 10),
//=============================
// DROPDOWN: نوع الإجازة
//=============================
Align(
alignment: Alignment.centerRight,
child: const Text(
"نوع الإجازة",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 5),
// Modified dropdown with disabled state
Directionality(
textDirection: TextDirection.rtl,
child: Opacity(
opacity: isTimedLeave ? 0.45 : 1.0,
child: IgnorePointer(
ignoring: isTimedLeave,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
height: 58,
decoration: BoxDecoration(
color:
isTimedLeave
? Colors.grey[300]
: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: const [
BoxShadow(
color: Color(0x22000000),
blurRadius: 8,
offset: Offset(0, 3),
),
],
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: leaveType,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
color: Colors.black,
size: 28,
),
style: const TextStyle(
color: Colors.black,
fontSize: 17,
),
isExpanded: true,
onChanged: (value) {
setState(() {
leaveType = value!;
// Set toggle based on selected value
isTimedLeave = value == "أجازة زمنية";
});
},
items: [
DropdownMenuItem(
value: "إجازة مرضية ",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة مرضية "),
),
),
),
DropdownMenuItem(
value: "إجازة مدفوعة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة مدفوعة"),
),
),
),
DropdownMenuItem(
value: "إجازة غير مدفوعة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("إجازة غير مدفوعة"),
),
),
),
DropdownMenuItem(
value: "أجازة زمنية",
child: Directionality(
textDirection: TextDirection.rtl,
child: Align(
alignment: Alignment.centerRight,
child: Text("أجازة زمنية"),
),
),
),
],
),
),
),
),
),
),
const SizedBox(height: 25),
//=============================
// PERFECT CUSTOM TOGGLE (NEW)
//=============================
GestureDetector(
onTap: () {
setState(() {
isTimedLeave = !isTimedLeave;
// Set leave type to "أجازة زمنية" when toggle is ON
if (isTimedLeave) {
leaveType = "أجازة زمنية";
}
});
},
child: Row(
children: [
// ---------- TOGGLE ----------
AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: 75,
height: 30,
padding: const EdgeInsets.symmetric(
horizontal: 4,
),
decoration: BoxDecoration(
color:
isTimedLeave
? const Color(
0xFF0A6B4A,
) // ON green track
: const Color(
0xFF9E9E9E,
), // OFF grey track
borderRadius: BorderRadius.circular(40),
),
child: Align(
alignment:
isTimedLeave
? Alignment.centerRight
: Alignment.centerLeft,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: 30,
height: 30,
decoration: BoxDecoration(
color:
isTimedLeave
? const Color(0xFF12BE85) // ON knob
: Colors.white, // OFF knob
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0x2D000000),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
),
),
),
const SizedBox(width: 14),
// ---------- Dot (ALWAYS visible) ----------
Container(
width: 10,
height: 10,
decoration: const BoxDecoration(
color: Color(0xFF32C59A),
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
// ---------- Line (ALWAYS visible) ----------
Expanded(
child: Container(
height: 2,
color: const Color(0xFF32C59A),
),
),
const SizedBox(width: 12),
// ---------- Label ----------
const Text(
"إجازة زمنية",
style: TextStyle(
color: Colors.white,
fontSize: 19,
fontWeight: FontWeight.w600,
),
),
],
),
),
const SizedBox(height: 20),
// =============================
// DATE PICKER BOX
// =============================
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF4F4F4),
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x33000000),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.end, // ALIGN RIGHT
children: [
// TEXT aligned right
const Text(
"فترة الإجازة",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
// 🟢 YOUR CUSTOM SVG ICON
SvgPicture.asset(
"assets/images/calendar.svg", // <-- replace with your icon filename
width: 24,
height: 24,
color: Color(
0xFF007C46,
), // Optional: match your green
),
],
),
const SizedBox(height: 15),
// From date row
_dateRow(
label: "من",
date: fromDate!,
time: fromTime!,
onDateTap:
isTimedLeave ? null : () => pickDate(true),
onTimeTap: () => pickTime(true),
),
const SizedBox(height: 15),
// To date row
_dateRow(
label: "الى",
date: toDate!,
time: toTime!,
onDateTap:
isTimedLeave ? null : () => pickDate(false),
onTimeTap: () => pickTime(false),
),
],
),
),
const SizedBox(height: 10),
// =============================
// REASON TEXTFIELD (Two Containers)
// =============================
Align(
alignment: Alignment.centerRight,
child: Directionality(
textDirection: TextDirection.rtl,
child: const Text(
"السبب",
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
// OUTER BORDER CONTAINER
Container(
padding: const EdgeInsets.all(
12,
), // border thickness space
decoration: BoxDecoration(
border: Border.all(
color: Color(0xFF00FFAA), // green border
width: 0.5,
),
borderRadius: BorderRadius.circular(14),
),
// INNER TEXTFIELD CONTAINER
child: Container(
height: 70,
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(12),
),
// Added Directionality to fix text direction
child: Directionality(
textDirection: TextDirection.rtl,
child: TextField(
controller: reasonController,
maxLines: 5,
textAlign:
TextAlign
.right, // Added this to align text to the right
decoration: const InputDecoration(
border: InputBorder.none,
hintText:
"اكتب السبب", // Added placeholder text
hintStyle: TextStyle(color: Colors.grey),
),
),
),
),
),
const SizedBox(height: 20),
// CONFIRM BUTTON
Center(
child: OnboardingButton(
text: "تأكيد الطلب",
backgroundColor: const Color(0xFFD1FEF0),
textColor: Colors.black, // Changed to black
onPressed: _saveLeaveRequest, // Call the save method
),
),
],
),
),
),
),
],
),
),
),
);
}
// ===============================================================
// CUSTOM DATE ROW WIDGET
// ===============================================================
Widget _dateRow({
required String label,
required DateTime date,
required TimeOfDay time,
required VoidCallback? onDateTap,
required VoidCallback onTimeTap,
}) {
bool dateDisabled = onDateTap == null;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
height: 55,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
// -----------------------
// DATE PART (can be disabled)
// -----------------------
Opacity(
opacity: dateDisabled ? 0.45 : 1,
child: IgnorePointer(
ignoring: dateDisabled,
child: GestureDetector(
onTap: onDateTap,
child: Row(
children: [
const Icon(Icons.arrow_drop_down),
const SizedBox(width: 4),
Text(
"${date.year}-${date.month}-${date.day}",
style: const TextStyle(fontSize: 16),
),
const SizedBox(width: 10),
Text(
_weekday(date.weekday),
style: const TextStyle(fontSize: 16),
),
],
),
),
),
),
const Spacer(),
// -----------------------
// TIME PART (always active)
// -----------------------
GestureDetector(
onTap: onTimeTap,
child: Text(
"${time.hour}:${time.minute.toString().padLeft(2, '0')}",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
const SizedBox(width: 10),
Text(
label,
style: const TextStyle(color: Colors.black, fontSize: 15),
),
],
),
);
}
String _weekday(int day) {
switch (day) {
case 1:
return "الإثنين";
case 2:
return "الثلاثاء";
case 3:
return "الأربعاء";
case 4:
return "الخميس";
case 5:
return "الجمعة";
case 6:
return "السبت";
case 7:
return "الأحد";
default:
return "";
}
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'onboarding_screen.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
FlutterNativeSplash.remove();
Future.delayed(const Duration(seconds: 2), () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => OnboardingScreen()),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/splash.png"),
fit: BoxFit.cover,
),
),
child: Center(child: Image.asset("assets/images/logo.png", width: 200)),
),
);
}
}

View File

@@ -0,0 +1,317 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/app_background.dart';
import '../widgets/settings_bar.dart';
import 'about_screen.dart';
import 'auth_screen.dart';
import '../widgets/change_password_modal.dart';
class UserSettingsScreen extends StatefulWidget {
const UserSettingsScreen({super.key});
@override
State<UserSettingsScreen> createState() => _UserSettingsScreenState();
}
class _UserSettingsScreenState extends State<UserSettingsScreen> {
bool _notificationsOn = false;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: AppBackground(
child: SafeArea(
child: Column(
children: [
/// -------------------- SETTINGS BAR --------------------
SettingsBar(
selectedIndex: 0,
onTap: (_) {},
showBackButton: true,
onBackTap: () => Navigator.pop(context),
iconPaths: const [],
),
const SizedBox(height: 10),
/// -------------------- PAGE TITLE --------------------
Padding(
padding: const EdgeInsets.only(right: 40),
child: Align(
alignment: Alignment.topRight,
child: const Text(
"الإعدادات",
style: TextStyle(
fontSize: 26,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
/// -------------------- FORM CONTAINER --------------------
Expanded(
child: Center(
child: Stack(
clipBehavior: Clip.none,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 40),
child: Container(
width: MediaQuery.of(context).size.width * 0.85,
height: MediaQuery.of(context).size.height * 0.62,
decoration: BoxDecoration(
color: const Color(0xF3EEFFFA),
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 30,
offset: Offset(0, 20),
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 22,
vertical: 55,
),
child: Column(
children: [
const SizedBox(height: 10),
/// -------------------- USER NAME --------------------
const Text(
"اسم الموظف",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 6),
/// ✅ CLICKABLE + UNDERLINED
GestureDetector(
onTap: () {
showDialog(
context: context,
barrierDismissible: true,
builder:
(_) => const ChangePasswordModal(),
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"تغيير كلمة المرور",
style: TextStyle(
fontSize: 15,
color: Colors.black54,
decoration: TextDecoration.underline,
decorationThickness: 1.5,
),
),
const SizedBox(width: 6),
SvgPicture.asset(
"assets/images/edit.svg",
width: 22,
height: 22,
),
],
),
),
const SizedBox(height: 20),
/// -------------------- NOTIFICATION SWITCH --------------------
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
_notificationsOn = !_notificationsOn;
});
},
child: AnimatedContainer(
duration: const Duration(
milliseconds: 250,
),
width: 75,
height: 30,
padding: const EdgeInsets.symmetric(
horizontal: 4,
),
decoration: BoxDecoration(
color:
_notificationsOn
? const Color(0xFF0A6B4A)
: const Color(0xFFB5B5B5),
borderRadius: BorderRadius.circular(
40,
),
),
child: Align(
alignment:
_notificationsOn
? Alignment.centerRight
: Alignment.centerLeft,
child: AnimatedContainer(
duration: const Duration(
milliseconds: 250,
),
width: 30,
height: 30,
decoration: BoxDecoration(
color:
_notificationsOn
? const Color(0xFF12BE85)
: const Color(0xFF0F0F0F),
shape: BoxShape.circle,
boxShadow: const [
BoxShadow(
color: Color(0x2D000000),
blurRadius: 6,
offset: Offset(0, 2),
),
],
),
),
),
),
),
Row(
children: [
const Text(
"الإشعارات",
style: TextStyle(
fontSize: 16,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
SvgPicture.asset(
"assets/images/ball2.svg",
width: 22,
),
],
),
],
),
_divider(),
_settingsRow(
label: "عن الشركة",
icon: "assets/images/info.svg",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AboutScreen(),
),
);
},
),
_divider(),
_settingsRow(
label: "تسجيل خروج",
icon: "assets/images/logout2.svg",
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const AuthScreen(),
),
);
},
),
],
),
),
),
/// -------------------- AVATAR --------------------
Positioned(
top: -55,
left: 0,
right: 0,
child: Center(
child: Container(
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFFE1E1E1),
border: Border.all(
color: const Color(0xFF2D5E50),
width: 12,
),
),
),
),
),
],
),
),
),
],
),
),
),
),
);
}
Widget _divider() {
return Container(
width: double.infinity,
height: 1,
color: const Color(0xFF008864),
margin: const EdgeInsets.symmetric(vertical: 12),
);
}
Widget _settingsRow({
required String label,
required String icon,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(width: 24),
Row(
children: [
Text(
label,
style: const TextStyle(
fontSize: 18,
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 10),
SvgPicture.asset(icon, width: 23),
],
),
],
),
),
);
}
}

View File

@@ -0,0 +1,128 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class NavBarItem {
final String iconPath;
final String label;
NavBarItem({required this.iconPath, required this.label});
}
class Floatingnavbar extends StatelessWidget {
final List<NavBarItem> items;
final int selectedIndex;
final ValueChanged<int> onTap;
const Floatingnavbar({
super.key,
required this.items,
required this.selectedIndex,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
return ClipRRect(
borderRadius: BorderRadius.circular(45),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
child: Container(
height: 70,
margin: EdgeInsets.only(
bottom: 10 + bottomPadding,
left: 28,
right: 28,
),
decoration: BoxDecoration(
color: Colors.white, // ⭐ frosted-glass effect
borderRadius: BorderRadius.circular(45),
border: Border.all(
color: const Color(0x4DFFFFFF), // subtle glass border
width: 1.2,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
offset: const Offset(0, -5),
spreadRadius: 0,
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: items.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isSelected = selectedIndex == index;
return _NavBarItemTile(
item: item,
isSelected: isSelected,
onTap: () => onTap(index),
);
}).toList(),
),
),
),
);
}
}
class _NavBarItemTile extends StatelessWidget {
final NavBarItem item;
final bool isSelected;
final VoidCallback onTap;
const _NavBarItemTile({
Key? key,
required this.item,
required this.isSelected,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(45),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
item.iconPath,
width: 30,
height: 30,
colorFilter: ColorFilter.mode(
isSelected ? const Color(0xFF177046) : Colors.black,
BlendMode.srcIn,
),
),
const SizedBox(height: 6),
Text(
item.label,
style: TextStyle(
color: isSelected ? const Color(0xFF177046) : Colors.black,
fontSize: 15,
fontFamily: 'AbdEriady',
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
class AppBackground extends StatelessWidget {
final Widget child;
const AppBackground({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.fromARGB(255, 41, 41, 41), // top dark gray
Color.fromARGB(255, 0, 20, 15), // bottom deep green
],
),
),
),
Positioned(
top: -250,
left: 100,
right: -200,
child: Container(
height: 300,
decoration: const BoxDecoration(
shape: BoxShape.circle,
// very soft inner fill
color: Color.fromARGB(0, 62, 254, 203),
boxShadow: [
BoxShadow(
// wide soft bloom
color: Color.fromARGB(69, 62, 254, 190),
blurRadius: 140,
spreadRadius: 160,
),
],
),
),
),
Positioned(
bottom: 100,
left: -140,
right: -120,
child: Container(
height: 320,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color.fromARGB(0, 62, 254, 203),
boxShadow: [
BoxShadow(
color: Color.fromARGB(83, 62, 254, 190),
blurRadius: 180,
spreadRadius: 60,
),
],
),
),
),
child,
],
);
}
}

View File

@@ -0,0 +1,310 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../screens/main_screen.dart';
import '../../domain/models/login_request.dart';
import '../blocs/login/login_bloc.dart';
import '../blocs/login/login_event.dart';
import '../blocs/login/login_state.dart';
import 'onboarding_button.dart';
class AuthForm extends StatefulWidget {
final VoidCallback? onSubmit;
const AuthForm({super.key, this.onSubmit});
@override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
bool _obscure = true;
// Text controllers
final TextEditingController _phoneNumberController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
// Focus nodes for text fields
late FocusNode _phoneNumberFocusNode;
late FocusNode _passwordFocusNode;
void _handleLogin() {
// Validate inputs
if (_phoneNumberController.text.trim().isEmpty) {
_showError('الرجاء إدخال رقم الهاتف');
return;
}
if (_passwordController.text.trim().isEmpty) {
_showError('الرجاء إدخال كلمة المرور');
return;
}
// Unfocus any focused text field
_phoneNumberFocusNode.unfocus();
_passwordFocusNode.unfocus();
// Dispatch login event
final request = LoginRequest(
phoneNumber: _phoneNumberController.text.trim(),
password: _passwordController.text.trim(),
);
context.read<LoginBloc>().add(LoginSubmitted(request));
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
),
);
}
@override
void initState() {
super.initState();
// Initialize focus nodes
_phoneNumberFocusNode = FocusNode();
_passwordFocusNode = FocusNode();
}
@override
void dispose() {
// Clean up controllers and focus nodes
_phoneNumberController.dispose();
_passwordController.dispose();
_phoneNumberFocusNode.dispose();
_passwordFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Get screen dimensions
final screenSize = MediaQuery.of(context).size;
final screenWidth = screenSize.width;
final screenHeight = screenSize.height;
// Calculate responsive dimensions
final formWidth = screenWidth > 600 ? screenWidth * 0.5 : screenWidth * 0.9;
final formHeight =
screenHeight > 800 ? screenHeight * 0.6 : screenHeight * 0.8;
final borderWidth = formWidth + 20;
final titleFontSize = screenWidth > 600 ? 28.0 : 24.0;
final labelFontSize = screenWidth > 600 ? 18.0 : 16.0;
final fieldFontSize = screenWidth > 600 ? 18.0 : 16.0;
final verticalSpacing = screenHeight > 800 ? 34.0 : 24.0;
final fieldSpacing = screenHeight > 800 ? 30.0 : 20.0;
final buttonSpacing = screenHeight > 800 ? 100.0 : 60.0;
final bottomSpacing = screenHeight > 800 ? 40.0 : 20.0;
final horizontalPadding = screenWidth > 600 ? 30.0 : 25.0;
final verticalPadding = screenHeight > 800 ? 38.0 : 28.0;
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
// Call the onSubmit callback if provided
if (widget.onSubmit != null) {
widget.onSubmit!();
}
// Navigate to the MainPage
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const MainPage()),
);
} else if (state is LoginError) {
_showError(state.message);
}
},
child: Directionality(
textDirection: TextDirection.rtl,
child: FocusScope(
child: Stack(
alignment: Alignment.center,
children: [
// Border container - decorative element behind the form
Container(
width: borderWidth,
constraints: BoxConstraints(
minHeight: formHeight + 40,
maxHeight:
formHeight + 80, // Allows shrinking when keyboard opens
),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(32),
border: Border.all(color: const Color(0xDD00C28E), width: 1),
),
),
// Main form container
Container(
width: formWidth,
padding: EdgeInsets.symmetric(
horizontal: horizontalPadding,
vertical: verticalPadding,
),
decoration: BoxDecoration(
color: const Color(0xFFEEFFFA),
borderRadius: BorderRadius.circular(28),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
/// Title
Center(
child: Text(
"تسجيل دخول",
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
SizedBox(height: verticalSpacing),
/// Phone Number Label
Align(
alignment: Alignment.centerRight,
child: Text(
"رقم الهاتف",
style: TextStyle(
fontSize: labelFontSize,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
_buildField(
controller: _phoneNumberController,
hint: "رقم الهاتف",
obscure: false,
keyboardType: TextInputType.phone,
focusNode: _phoneNumberFocusNode,
textInputAction: TextInputAction.next,
onSubmitted: (_) {
// Move focus to password field when next is pressed
FocusScope.of(context).requestFocus(_passwordFocusNode);
},
fontSize: fieldFontSize,
),
SizedBox(height: fieldSpacing),
/// Password Label
Align(
alignment: Alignment.centerRight,
child: Text(
"كلمة المرور",
style: TextStyle(
fontSize: labelFontSize,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
_buildField(
controller: _passwordController,
hint: "كلمة المرور",
obscure: _obscure,
hasEye: true,
focusNode: _passwordFocusNode,
textInputAction: TextInputAction.done,
onSubmitted: (_) => _handleLogin(),
fontSize: fieldFontSize,
),
SizedBox(height: buttonSpacing), // Responsive spacing
BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
final isLoading = state is LoginLoading;
return Center(
child: OnboardingButton(
text: isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول",
backgroundColor: const Color.fromARGB(239, 35, 87, 74),
onPressed: isLoading ? null : _handleLogin,
),
);
},
),
SizedBox(height: bottomSpacing),
],
),
),
],
),
),
),
);
}
Widget _buildField({
TextEditingController? controller,
required String hint,
required bool obscure,
bool hasEye = false,
TextInputType? keyboardType,
FocusNode? focusNode,
TextInputAction? textInputAction,
Function(String)? onSubmitted,
required double fontSize,
}) {
return Container(
decoration: BoxDecoration(
color: const Color(0xDEDEDEDE),
borderRadius: BorderRadius.circular(7),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 2)),
],
),
child: TextField(
controller: controller,
focusNode: focusNode,
obscureText: obscure,
keyboardType: keyboardType,
textAlign: TextAlign.right,
textInputAction: textInputAction,
onSubmitted: onSubmitted,
style: TextStyle(fontSize: fontSize),
decoration: InputDecoration(
hintText: hint,
hintStyle: const TextStyle(color: Colors.black54),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
suffixIcon:
hasEye
? IconButton(
icon: Icon(
obscure ? Icons.visibility_off : Icons.visibility,
color: Colors.black54,
),
onPressed: () {
setState(() => _obscure = !obscure);
},
)
: null,
),
),
);
}
}

View File

@@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import 'onboarding_button.dart';
class ChangePasswordModal extends StatefulWidget {
const ChangePasswordModal({super.key});
@override
State<ChangePasswordModal> createState() => _ChangePasswordModalState();
}
class _ChangePasswordModalState extends State<ChangePasswordModal> {
bool _oldObscure = true;
// bool _newObscure = true;
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
final screenWidth = screenSize.width;
final screenHeight = screenSize.height;
final formWidth =
screenWidth > 600 ? screenWidth * 0.5 : screenWidth * 0.88;
final labelFontSize = screenWidth > 600 ? 18.0 : 16.0;
final fieldFontSize = screenWidth > 600 ? 18.0 : 16.0;
final verticalPadding = screenHeight > 800 ? 38.0 : 28.0;
final fieldSpacing = screenHeight > 800 ? 30.0 : 20.0;
final buttonSpacing = screenHeight > 800 ? 80.0 : 60.0;
return Material(
color: Colors.transparent,
child: Stack(
children: [
/// ✅ DARK TRANSPARENT OVERLAY (NO BLUR)
Positioned.fill(child: Container(color: const Color(0x80000000))),
/// ---------- MODAL ----------
Center(
child: Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: formWidth,
padding: EdgeInsets.symmetric(
horizontal: 25,
vertical: verticalPadding,
),
decoration: BoxDecoration(
color: const Color(0xFFEEFFFA),
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
/// ---------- OLD PASSWORD ----------
Align(
alignment: Alignment.centerRight,
child: Text(
"كلمة المرور السابقة",
style: TextStyle(
fontSize: labelFontSize,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
_buildField(
hint: "أدخل كلمة المرور",
obscure: _oldObscure,
fontSize: fieldFontSize,
hasEye: true, // ✅ eye here
onEyeTap:
() => setState(() => _oldObscure = !_oldObscure),
),
SizedBox(height: fieldSpacing),
/// ---------- NEW PASSWORD ----------
Align(
alignment: Alignment.centerRight,
child: Text(
"كلمة المرور الجديدة",
style: TextStyle(
fontSize: labelFontSize,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
const SizedBox(height: 8),
_buildField(
hint: "كلمة المرور",
obscure: false,
fontSize: fieldFontSize,
hasEye: false,
onEyeTap: () {}, // unused
),
SizedBox(height: buttonSpacing),
Center(
child: OnboardingButton(
text: "حفظ التغيير",
backgroundColor: const Color(0xEE23574A),
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
),
),
),
],
),
);
}
Widget _buildField({
required String hint,
required bool obscure,
required double fontSize,
required bool hasEye,
required VoidCallback onEyeTap,
}) {
return Container(
decoration: BoxDecoration(
color: const Color(0xDEDEDEDE),
borderRadius: BorderRadius.circular(7),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 6, offset: Offset(0, 2)),
],
),
child: TextField(
obscureText: obscure,
textAlign: TextAlign.right,
style: TextStyle(fontSize: fontSize),
decoration: InputDecoration(
hintText: hint,
hintStyle: const TextStyle(color: Colors.black54),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
suffixIcon:
hasEye
? IconButton(
icon: Icon(
obscure ? Icons.visibility_off : Icons.visibility,
color: Colors.black54,
),
onPressed: onEyeTap,
)
: null,
),
),
);
}
}

View File

@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class FinanceSummaryCard extends StatelessWidget {
final String totalAmount;
final String dropdownValue;
final VoidCallback onCalendarTap;
final ValueChanged<String?> onDropdownChanged;
const FinanceSummaryCard({
super.key,
required this.totalAmount,
required this.dropdownValue,
required this.onCalendarTap,
required this.onDropdownChanged,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 6),
),
],
),
child: Column(
children: [
/// ==========================
/// TOP AMOUNT + ICON
/// ==========================
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"د.ع $totalAmount",
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Color(0xFFBA4404),
),
),
Row(
children: [
const Text(
"المبلغ الكلي",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
),
const SizedBox(width: 10),
SvgPicture.asset("assets/images/money2.svg", width: 50),
],
),
],
),
const SizedBox(height: 20),
/// ==========================
/// MONTH FILTER BOX
/// ==========================
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFF0A8F6B), width: 1),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
// CALENDAR BUTTON
GestureDetector(
onTap: onCalendarTap,
child: Container(
width: 90,
height: 60,
decoration: BoxDecoration(
color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(14),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/images/calendar.svg",
width: 28,
),
const SizedBox(height: 4),
const Text("الشهر", style: TextStyle(fontSize: 15)),
],
),
),
),
const SizedBox(width: 14),
// DROPDOWN MENU
Expanded(
child: Directionality(
textDirection: TextDirection.rtl,
child: Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(14),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: dropdownValue,
icon: const Icon(
Icons.arrow_drop_down,
size: 35,
color: Color(0xFF0A8F6B),
),
style: const TextStyle(
fontSize: 18,
color: Color.from(
alpha: 1,
red: 0,
green: 0,
blue: 0,
),
fontWeight: FontWeight.bold,
),
onChanged: onDropdownChanged,
items: const [
DropdownMenuItem(
value: "الكل",
child: Directionality(
textDirection: TextDirection.rtl,
child: Text("الكل"),
),
),
DropdownMenuItem(
value: "ساعات أضافية",
child: Directionality(
textDirection: TextDirection.rtl,
child: Text("ساعات أضافية"),
),
),
DropdownMenuItem(
value: "مكافئة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Text("مكافئة"),
),
),
DropdownMenuItem(
value: " عقوبة",
child: Directionality(
textDirection: TextDirection.rtl,
child: Text(" عقوبة"),
),
),
],
),
),
),
),
),
],
),
),
],
),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class GradientLine extends StatelessWidget {
final Color start;
final Color end;
const GradientLine({super.key, required this.start, required this.end});
@override
Widget build(BuildContext context) {
return Container(
height: 2,
decoration: BoxDecoration(gradient: LinearGradient(colors: [start, end])),
);
}
}

View File

@@ -0,0 +1,486 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'app_background.dart';
class LoginAnimationScreen extends StatefulWidget {
final bool isLogin;
final bool isSuccess;
const LoginAnimationScreen({
super.key,
required this.isLogin,
required this.isSuccess,
});
@override
State<LoginAnimationScreen> createState() => _LoginAnimationScreenState();
}
class _LoginAnimationScreenState extends State<LoginAnimationScreen>
with TickerProviderStateMixin {
late AnimationController ringController;
late AnimationController progressController;
late Animation<double> ringAnimation;
late Animation<double> progressAnimation;
// ERROR pulse animation
late AnimationController errorPulseController;
late Animation<double> errorPulseAnimation;
// SUCCESS one-time pulse
late AnimationController successPulseController;
late Animation<double> successPulseAnimation;
bool showResult = false;
@override
void initState() {
super.initState();
// MAIN loading ripple animations
ringController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
progressController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
ringAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: ringController, curve: Curves.easeOut));
progressAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: progressController, curve: Curves.linear),
);
// ERROR pulse animation
errorPulseController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
errorPulseAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: errorPulseController, curve: Curves.easeOut),
);
// SUCCESS one-time pulse
successPulseController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
successPulseAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: successPulseController, curve: Curves.easeOut),
);
startAnimations();
// MAIN DELAY (same as before)
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
setState(() => showResult = true);
ringController.stop();
progressController.stop();
if (!widget.isSuccess) {
startErrorPulse();
} else {
successPulseController.forward(); // ONE-TIME BEAT
Future.delayed(const Duration(seconds: 2), () {
if (mounted) Navigator.of(context).pop();
});
}
}
});
}
void startAnimations() {
progressController.repeat();
startPulseAnimation();
}
void startPulseAnimation() {
ringController.forward().then((_) {
ringController.reset();
if (!showResult) startPulseAnimation();
});
}
void startErrorPulse() {
errorPulseController.forward().then((_) {
errorPulseController.reset();
if (showResult && !widget.isSuccess) startErrorPulse();
});
}
@override
void dispose() {
ringController.dispose();
progressController.dispose();
errorPulseController.dispose();
successPulseController.dispose();
super.dispose();
}
Widget _buildIcon() {
if (!showResult) {
return SvgPicture.asset(
"assets/images/finger_print.svg",
key: const ValueKey("fingerprint"),
width: 70,
height: 70,
);
} else if (widget.isSuccess) {
return SizedBox(
width: 120,
height: 120,
child: Image.asset(
"assets/images/tick.png",
key: const ValueKey("success"),
fit: BoxFit.contain,
),
);
} else {
return SizedBox(
width: 120,
height: 120,
child: Image.asset(
"assets/images/error.png",
key: const ValueKey('error'),
fit: BoxFit.contain,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AppBackground(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.isLogin ? "تسجيل الدخول" : "تسجيل خروج",
style: const TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
),
),
const SizedBox(height: 100),
Container(
width: 280,
height: 400,
decoration: BoxDecoration(
color: const Color(0xFFEEFFFA),
borderRadius: BorderRadius.circular(38),
boxShadow: [
BoxShadow(
color: const Color(0x34000000),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 160,
height: 160,
child: Stack(
alignment: Alignment.center,
children: [
// GREY CIRCLE (base)
Container(
width: 120,
height: 120,
decoration: const BoxDecoration(
color: Color(0xFFE5E5E5),
shape: BoxShape.circle,
),
),
// MAIN ICON SWITCHER
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: _buildIcon(),
),
// LOADING MODE
if (!showResult)
Positioned.fill(
child: AnimatedBuilder(
animation: progressAnimation,
builder:
(_, __) => CustomPaint(
painter: _ProgressRingPainter(
progress: progressAnimation.value,
),
),
),
),
if (!showResult)
Positioned.fill(
child: AnimatedBuilder(
animation: ringAnimation,
builder:
(_, __) => CustomPaint(
painter: _PulseRingsPainter(
progress: ringAnimation.value,
),
),
),
),
// ERROR ANIMATION
if (showResult && !widget.isSuccess)
Positioned.fill(
child: AnimatedBuilder(
animation: errorPulseAnimation,
builder:
(_, __) => CustomPaint(
painter: _ErrorPulsePainter(
progress: errorPulseAnimation.value,
),
),
),
),
// SUCCESS ONE-TIME PULSE
if (showResult && widget.isSuccess)
Positioned.fill(
child: AnimatedBuilder(
animation: successPulseAnimation,
builder:
(_, __) => CustomPaint(
painter: _SuccessPulsePainter(
progress: successPulseAnimation.value,
),
),
),
),
],
),
),
const SizedBox(height: 40),
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child:
showResult
? Text(
widget.isSuccess
? (widget.isLogin
? "تم تسجيل دخولك بنجاح"
: "تم تسجيل خروجك بنجاح")
: "تم رفض تسجيل الدخول",
key: ValueKey("text_${widget.isSuccess}"),
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
decoration: TextDecoration.none,
),
)
: Text(
widget.isLogin
? "يتم تسجيل الدخول..."
: "يتم تسجيل الخروج...",
key: const ValueKey("loading_text"),
style: const TextStyle(
fontSize: 16,
decoration: TextDecoration.none,
),
),
),
// RETRY BUTTON
if (showResult && !widget.isSuccess) ...[
const SizedBox(height: 20),
GestureDetector(
onTap: () {
setState(() => showResult = false);
errorPulseController.stop();
successPulseController.reset();
startAnimations();
Future.delayed(const Duration(seconds: 2), () {
if (!mounted) return;
setState(() => showResult = true);
ringController.stop();
progressController.stop();
if (!widget.isSuccess) {
startErrorPulse();
} else {
successPulseController.forward();
Future.delayed(const Duration(seconds: 2), () {
if (mounted) Navigator.of(context).pop();
});
}
});
},
child: const Text(
"أعد المحاولة",
style: TextStyle(
color: Color(0xFFB00020),
fontSize: 16,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
),
),
),
],
],
),
),
],
),
),
),
);
}
}
// ------------------------- PAINTERS -------------------------- //
class _ProgressRingPainter extends CustomPainter {
final double progress;
_ProgressRingPainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
final radius = 50.0;
final bgPaint =
Paint()
..color = const Color(0x0032C599)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(center, radius, bgPaint);
final fgPaint =
Paint()
..color = const Color(0xC40A4433)
..style = PaintingStyle.stroke
..strokeWidth = 3
..strokeCap = StrokeCap.round;
final sweep = 2 * 3.1415926 * progress;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-1.5708,
sweep,
false,
fgPaint,
);
}
@override
bool shouldRepaint(_) => true;
}
class _PulseRingsPainter extends CustomPainter {
final double progress;
_PulseRingsPainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
const baseRadius = 60.0;
const maxRadius = 130.0;
for (final phase in [0.0, 0.25, 0.5]) {
final rp = (progress - phase).clamp(0.0, 1.0);
if (rp > 0) {
final radius = baseRadius + (maxRadius - baseRadius) * rp;
final opacity = (1 - rp) * 0.45;
final paint =
Paint()
..color = const Color(0xFF32C59A).withOpacity(opacity)
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, radius, paint);
}
}
}
@override
bool shouldRepaint(_) => true;
}
// ERROR PAINTER
class _ErrorPulsePainter extends CustomPainter {
final double progress;
_ErrorPulsePainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
// static ring
final staticPaint =
Paint()
..color = const Color(0xFFB00020).withOpacity(0.20)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(center, 70, staticPaint);
// pulse ring
final radius = 70 + (20 * progress);
final opacity = (1 - progress) * 0.45;
final pulsePaint =
Paint()
..color = const Color(0xFFB00020).withOpacity(opacity)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(center, radius, pulsePaint);
}
@override
bool shouldRepaint(_) => true;
}
// SUCCESS one-time pulse
class _SuccessPulsePainter extends CustomPainter {
final double progress;
_SuccessPulsePainter({required this.progress});
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
final radius = 70 + (20 * progress);
final opacity = (1 - progress) * 0.40;
final paint =
Paint()
..color = const Color(0xFF32C59A).withOpacity(opacity)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(_) => true;
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
class OnboardingButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final Color backgroundColor;
final Color textColor;
const OnboardingButton({
super.key,
required this.text,
this.onPressed,
this.backgroundColor = const Color(0xFF2D2D2D),
this.textColor = Colors.white,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: const Color.fromARGB(59, 59, 59, 59),
spreadRadius: 1,
blurRadius: 14,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: textColor,
disabledForegroundColor: textColor,
disabledBackgroundColor: backgroundColor,
padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 8,
shadowColor: const Color(0x47000000), // More defined shadow color
),
child: Text(
text,
style: TextStyle(
color: textColor, // Use the textColor parameter here
fontSize: 22,
fontWeight: FontWeight.w600,
),
),
),
);
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class OnboardingPage extends StatelessWidget {
final String imagePath;
final String text;
const OnboardingPage({
super.key,
required this.imagePath,
required this.text,
});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(imagePath, height: 280),
const SizedBox(height: 25),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Text(
text,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'AbdElRady',
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 20,
height: 1.5,
),
),
),
],
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class SettingsBar extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> onTap;
final bool showBackButton;
final VoidCallback? onBackTap;
final List<String> iconPaths;
const SettingsBar({
super.key,
required this.selectedIndex,
required this.onTap,
this.showBackButton = false, // to switch between back button and settings icons
this.onBackTap,
required this.iconPaths,
});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Image.asset('assets/images/logo2.png', width: 150, height: 40),
],
),
Row(
children: [
if (showBackButton)
GestureDetector(
onTap: onBackTap,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0x10000000),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Center(
// Always use Flutter's built-in back icon pointing to the right
child: const Icon(
Icons.arrow_forward, // Changed to arrow_forward for RTL
size: 26,
color: Colors.black, // Adjust color as needed
),
),
),
),
// When back button is OFF → show user + settings icons
if (!showBackButton)
...iconPaths.asMap().entries.map((entry) {
final index = entry.key;
final iconPath = entry.value;
// final isSelected = selectedIndex == index;
return Padding(
padding: const EdgeInsets.only(left: 10),
child: GestureDetector(
onTap: () => onTap(index),
child: Container(
width: 43,
height: 43,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color(0x10000000),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Stack(
children: [
SvgPicture.asset(iconPath, width: 30, height: 30),
],
),
),
),
),
);
}),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class StatusCircle extends StatelessWidget {
final Color color;
final Widget icon;
final double size;
const StatusCircle({
super.key,
required this.color,
required this.icon,
this.size = 42, // ✅ smaller = card height reduced
});
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
color: Colors.white,
),
child: Center(child: icon),
);
}
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'gradient_line.dart';
import 'status_circle.dart';
class WorkDayCard extends StatelessWidget {
const WorkDayCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 6),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 12,
offset: Offset(0, 6),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
"يوم عمل",
textAlign: TextAlign.right,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
/// 🔥 FIXED: CENTERED LINES BETWEEN CIRCLES
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_StatusItem(
color: const Color(0xFFD16400),
icon: SvgPicture.asset('assets/images/money3.svg', width: 20),
label: "سعر كلي\n18,250 د.ع",
),
/// LINE CENTERED VERTICALLY
Expanded(
child: Center(
child: GradientLine(
start: const Color(0xFFD16400),
end: const Color(0xFF1266A8),
),
),
),
_StatusItem(
color: const Color(0xFF1266A8),
icon: SvgPicture.asset('assets/images/watch.svg', width: 20),
label: "عدد ساعات\n5.50",
),
Expanded(
child: Center(
child: GradientLine(
start: const Color(0xFF1266A8),
end: const Color(0xFFB00000),
),
),
),
_StatusItem(
color: const Color(0xFFB00000),
icon: SvgPicture.asset('assets/images/out.svg', width: 20),
label: "خروج\n1:14pm",
),
Expanded(
child: Center(
child: GradientLine(
start: const Color(0xFFB00000),
end: const Color(0xFF0A8F6B),
),
),
),
_StatusItem(
color: const Color(0xFF0A8F6B),
icon: SvgPicture.asset('assets/images/in.svg', width: 20),
label: "دخول\n1:14pm",
),
],
),
const SizedBox(height: 12),
const Divider(color: Colors.black38),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text("2025.12.1", style: TextStyle(fontSize: 12)),
Text("ملاحظات ان وجدت", style: TextStyle(fontSize: 12)),
],
),
],
),
);
}
}
class _StatusItem extends StatelessWidget {
final Color color;
final Widget icon;
final String label;
const _StatusItem({
required this.color,
required this.icon,
required this.label,
});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
StatusCircle(color: color, icon: icon),
// const SizedBox(height: 3),
Text(
label,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12),
),
],
);
}
}