From 08132b52a9db36213959748d7095b4fc1f929711 Mon Sep 17 00:00:00 2001
From: Daniah Ayad Al-sultani <148902945+Cactuskiller@users.noreply.github.com>
Date: Thu, 4 Dec 2025 17:23:06 +0300
Subject: [PATCH] login agnimation has been fixed + request holiday screen was
created
---
assets/images/back.svg | 3 +
assets/images/calendar.svg | 3 +
assets/images/money.svg | 2 +-
assets/images/money2.svg | 3 +
lib/screens/attendence_screen.dart | 6 +-
lib/screens/holiday_screen.dart | 121 +++++-
lib/screens/request_leave_screen.dart | 547 ++++++++++++++++++++++++
lib/widgets/login_animation.dart | 486 +++++++++++++++++++++
lib/widgets/login_animation_screen.dart | 353 ---------------
lib/widgets/settings_bar.dart | 103 ++---
pubspec.lock | 16 +-
11 files changed, 1200 insertions(+), 443 deletions(-)
create mode 100644 assets/images/back.svg
create mode 100644 assets/images/calendar.svg
create mode 100644 assets/images/money2.svg
create mode 100644 lib/screens/request_leave_screen.dart
create mode 100644 lib/widgets/login_animation.dart
delete mode 100644 lib/widgets/login_animation_screen.dart
diff --git a/assets/images/back.svg b/assets/images/back.svg
new file mode 100644
index 0000000..1816639
--- /dev/null
+++ b/assets/images/back.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/calendar.svg b/assets/images/calendar.svg
new file mode 100644
index 0000000..5b73799
--- /dev/null
+++ b/assets/images/calendar.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/money.svg b/assets/images/money.svg
index 5a498c5..9db1b4b 100644
--- a/assets/images/money.svg
+++ b/assets/images/money.svg
@@ -1,3 +1,3 @@
diff --git a/assets/images/money2.svg b/assets/images/money2.svg
new file mode 100644
index 0000000..345bedb
--- /dev/null
+++ b/assets/images/money2.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/screens/attendence_screen.dart b/lib/screens/attendence_screen.dart
index ddea7b8..b9478fa 100644
--- a/lib/screens/attendence_screen.dart
+++ b/lib/screens/attendence_screen.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
-import '../widgets/login_animation_screen.dart'; // Import the LoginAnimationScreen
+import '../widgets/login_animation.dart';
class AttendanceScreen extends StatelessWidget {
const AttendanceScreen({super.key});
@@ -153,8 +153,8 @@ class AttendanceScreen extends StatelessWidget {
MaterialPageRoute(
builder:
(context) => LoginAnimationScreen(
- isLogin: true,
- isSuccess: false,
+ isLogin: false,
+ isSuccess: true,
),
),
);
diff --git a/lib/screens/holiday_screen.dart b/lib/screens/holiday_screen.dart
index de3643d..9d1f92c 100644
--- a/lib/screens/holiday_screen.dart
+++ b/lib/screens/holiday_screen.dart
@@ -1,9 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
+import '../screens/request_leave_screen.dart';
-class HolidayScreen extends StatelessWidget {
+class HolidayScreen extends StatefulWidget {
const HolidayScreen({super.key});
+ @override
+ State createState() => _HolidayScreenState();
+}
+
+class _HolidayScreenState extends State {
+ int activeTab = 0; // 0 = السلف | 1 = الأجازات
+
@override
Widget build(BuildContext context) {
return Directionality(
@@ -11,20 +19,99 @@ class HolidayScreen extends StatelessWidget {
child: Stack(
children: [
Positioned(
- bottom: 40,
+ top: 40,
+ left: 0,
+ right: 0,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // الأجازات TAB
+ 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),
+
+ // السلف TAB
+ 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),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ bottom: 30,
right: 20,
child: Column(
children: [
+ // Money icon (custom size)
_HolidayActionButton(
label: "طلب سلفة",
- svgPath: "assets/images/money.svg", // placeholder
+ svgPath: "assets/images/money2.svg",
+ iconWidth: 38,
+ iconHeight: 30,
onTap: () {},
),
const SizedBox(height: 18),
+
+ // Plus icon (custom size)
_HolidayActionButton(
label: "طلب إجازة",
- svgPath: "assets/images/plus.svg", // placeholder
- onTap: () {},
+ svgPath: "assets/images/plus.svg",
+ iconWidth: 30,
+ iconHeight: 30,
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const RequestLeaveScreen(),
+ ),
+ );
+ },
),
],
),
@@ -39,11 +126,15 @@ 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
@@ -51,28 +142,26 @@ class _HolidayActionButton extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: Container(
- width: 75,
- height: 75,
+ width: 80,
+ height: 80,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
- boxShadow: [
+ boxShadow: const [
BoxShadow(
- color: const Color(0x4B00C68B),
+ color: Color(0x4B00C68B),
blurRadius: 20,
- offset: const Offset(0, 6),
+ offset: Offset(0, 6),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- SvgPicture.asset(
- svgPath,
- width: 32,
- height: 32,
- fit: BoxFit.contain, // 🔥 Ensures same icon size
- alignment: Alignment.center,
+ SizedBox(
+ width: iconWidth,
+ height: iconHeight,
+ child: SvgPicture.asset(svgPath, fit: BoxFit.contain),
),
const SizedBox(height: 6),
diff --git a/lib/screens/request_leave_screen.dart b/lib/screens/request_leave_screen.dart
new file mode 100644
index 0000000..074a5c0
--- /dev/null
+++ b/lib/screens/request_leave_screen.dart
@@ -0,0 +1,547 @@
+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';
+
+class RequestLeaveScreen extends StatefulWidget {
+ const RequestLeaveScreen({super.key});
+
+ @override
+ State createState() => _RequestLeaveScreenState();
+}
+
+class _RequestLeaveScreenState extends State {
+ // Dropdown value
+ 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();
+
+ /// PICK DATE
+ Future 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 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;
+ }
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: AppBackground(
+ child: SafeArea(
+ child: Column(
+ children: [
+ // =============================
+ // TOP NAV BAR
+ // =============================
+ 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),
+
+ Expanded(
+ child: SingleChildScrollView(
+ padding: const EdgeInsets.symmetric(horizontal: 25),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Align(
+ alignment: Alignment.topRight,
+ child: const Text(
+ "طلب أجازة ",
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 28,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 25),
+ //=============================
+ // DROPDOWN: نوع الإجازة
+ //=============================
+ 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, // <<< CHANGE DIRECTION HERE
+ 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: DropdownButtonHideUnderline(
+ child: DropdownButton(
+ 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!);
+ },
+ 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("إجازة غير مدفوعة"),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 25),
+
+ //=============================
+ // PERFECT CUSTOM TOGGLE (NEW)
+ //=============================
+ GestureDetector(
+ onTap:
+ () => setState(() => isTimedLeave = !isTimedLeave),
+ child: Row(
+ children: [
+ // ---------- TOGGLE ----------
+ AnimatedContainer(
+ duration: const Duration(milliseconds: 250),
+ width: 95,
+ height: 42,
+ 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: 40,
+ height: 40,
+ 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: 25),
+
+ // =============================
+ // 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(
+ 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),
+ ),
+ child: TextField(
+ controller: reasonController,
+ maxLines: 5,
+ decoration: const InputDecoration(
+ border: InputBorder.none,
+ hintText: "",
+ ),
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 40),
+
+ // CONFIRM BUTTON
+ Center(
+ child: OnboardingButton(
+ text: "تأكيد الطلب",
+ backgroundColor: const Color(0xFFD1FEF0),
+ onPressed: () {},
+ ),
+ ),
+
+ const SizedBox(height: 40),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ // ===============================================================
+ // 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 "";
+ }
+ }
+}
diff --git a/lib/widgets/login_animation.dart b/lib/widgets/login_animation.dart
new file mode 100644
index 0000000..126749c
--- /dev/null
+++ b/lib/widgets/login_animation.dart
@@ -0,0 +1,486 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import '../widgets/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 createState() => _LoginAnimationScreenState();
+}
+
+class _LoginAnimationScreenState extends State
+ with TickerProviderStateMixin {
+ late AnimationController ringController;
+ late AnimationController progressController;
+ late Animation ringAnimation;
+ late Animation progressAnimation;
+
+ // ERROR pulse animation
+ late AnimationController errorPulseController;
+ late Animation errorPulseAnimation;
+
+ // SUCCESS one-time pulse
+ late AnimationController successPulseController;
+ late Animation 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(
+ begin: 0,
+ end: 1,
+ ).animate(CurvedAnimation(parent: ringController, curve: Curves.easeOut));
+
+ progressAnimation = Tween(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(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(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;
+}
diff --git a/lib/widgets/login_animation_screen.dart b/lib/widgets/login_animation_screen.dart
deleted file mode 100644
index 201a697..0000000
--- a/lib/widgets/login_animation_screen.dart
+++ /dev/null
@@ -1,353 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-import '../widgets/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 createState() => _LoginAnimationScreenState();
-}
-
-class _LoginAnimationScreenState extends State
- with TickerProviderStateMixin {
- late AnimationController ringController;
- late AnimationController progressController;
- late Animation ringAnimation;
- late Animation progressAnimation;
-
- bool showResult = false;
-
- @override
- void initState() {
- super.initState();
-
- ringController = AnimationController(
- vsync: this,
- duration: const Duration(seconds: 1),
- );
-
- progressController = AnimationController(
- vsync: this,
- duration: const Duration(seconds: 2),
- );
-
- ringAnimation = Tween(
- begin: 0.0,
- end: 1.0,
- ).animate(CurvedAnimation(parent: ringController, curve: Curves.easeOut));
-
- progressAnimation = Tween(begin: 0.0, end: 1.0).animate(
- CurvedAnimation(parent: progressController, curve: Curves.linear),
- );
-
- startAnimations();
-
- Future.delayed(const Duration(seconds: 2), () {
- if (mounted) {
- setState(() {
- showResult = true;
- });
-
- ringController.stop();
- progressController.stop();
-
- if (widget.isSuccess) {
- 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();
- });
- }
-
- @override
- void dispose() {
- ringController.dispose();
- progressController.dispose();
- super.dispose();
- }
-
- // FIXED: Simplified _buildIcon method that returns the appropriate SVG
- Widget _buildIcon() {
- if (!showResult) {
- // Loading state - fingerprint
- return SvgPicture.asset(
- "assets/images/finger_print.svg",
- key: const ValueKey('fingerprint'),
- width: 70,
- height: 70,
- placeholderBuilder: (context) => Icon(
- Icons.fingerprint,
- size: 70,
- color: const Color(0xFF102D25),
- ),
- );
- } else if (widget.isSuccess) {
- // Success state - tick mark
- return Image.asset(
- "assets/images/tick.png",
- key: const ValueKey('success'),
- width: 120,
- height: 120,
- fit: BoxFit.contain,
- // placeholderBuilder: (context) => Icon(
- // Icons.check_circle,
- // size: 70,
- // color: const Color(0xFF32C59A),
- // ),
- );
- } else {
- // Error state - error icon
- return Image.asset(
- "assets/images/error.png",
- key: const ValueKey('error'),
- width: 120,
- height: 120,
- 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: [
- Container(
- width: 120,
- height: 120,
- decoration: const BoxDecoration(
- color: Color(0xFFE5E5E5),
- shape: BoxShape.circle,
- ),
- ),
-
- // FIXED: AnimatedSwitcher with proper key handling
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 500),
- child: _buildIcon(),
- layoutBuilder: (currentChild, previousChildren) {
- return Stack(
- alignment: Alignment.center,
- children: [
- ...previousChildren,
- if (currentChild != null) currentChild,
- ],
- );
- },
- transitionBuilder: (child, animation) {
- return FadeTransition(
- opacity: animation,
- child: ScaleTransition(
- scale: animation,
- child: child,
- ),
- );
- },
- ),
-
- if (!showResult)
- Positioned.fill(
- child: AnimatedBuilder(
- animation: progressAnimation,
- builder: (_, __) {
- return CustomPaint(
- painter: _ProgressRingPainter(
- progress: progressAnimation.value,
- ),
- );
- },
- ),
- ),
-
- if (!showResult)
- Positioned.fill(
- child: AnimatedBuilder(
- animation: ringAnimation,
- builder: (_, __) {
- return CustomPaint(
- painter: _PulseRingsPainter(
- progress: ringAnimation.value,
- ),
- );
- },
- ),
- ),
- ],
- ),
- ),
-
- const SizedBox(height: 40),
-
- // Text AnimatedSwitcher with proper key handling
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 500),
- child: showResult
- ? Text(
- widget.isSuccess
- ? "تم تسجيل دخولك بنجاح"
- : "تم رفض تسجيل الدخول ",
- key: ValueKey("text_${widget.isSuccess}"),
- style: const TextStyle(
- fontSize: 17,
- fontWeight: FontWeight.w600,
- decoration: TextDecoration.none,
- ),
- )
- : const Text(
- "يتم تسجيل الدخول ...",
- key: ValueKey("loading_text"),
- style: TextStyle(
- fontSize: 16,
- decoration: TextDecoration.none,
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _ProgressRingPainter extends CustomPainter {
- final double progress;
-
- _ProgressRingPainter({required this.progress});
-
- @override
- void paint(Canvas canvas, Size size) {
- final center = size.center(Offset.zero);
-
- // radius slightly bigger than gray circle (60)
- final radius = 50.0;
-
- // background circle (very subtle)
- final bgPaint = Paint()
- ..color = const Color(0x0032C599)
- ..style = PaintingStyle.stroke
- ..strokeWidth = 3.0;
-
- canvas.drawCircle(center, radius, bgPaint);
-
- // foreground arc that animates
- final progressPaint = Paint()
- ..color = const Color(0xC40A4433)
- ..style = PaintingStyle.stroke
- ..strokeWidth = 3.0
- ..strokeCap = StrokeCap.round;
-
- const startAngle = -1.5708; // -90° in radians
- final sweepAngle = 2 * 3.1415926 * progress;
-
- canvas.drawArc(
- Rect.fromCircle(center: center, radius: radius),
- startAngle,
- sweepAngle,
- false,
- progressPaint,
- );
- }
-
- @override
- bool shouldRepaint(_ProgressRingPainter oldDelegate) =>
- oldDelegate.progress != progress;
-}
-
-class _PulseRingsPainter extends CustomPainter {
- final double progress;
-
- _PulseRingsPainter({required this.progress});
-
- @override
- void paint(Canvas canvas, Size size) {
- final center = size.center(Offset.zero);
-
- final baseRadius = 60.0; // start at grey circle
- final maxRadius = 130.0; // outermost ripple
-
- final ringPhases = [0.0, 0.25, 0.5];
-
- for (final phase in ringPhases) {
- final ringProgress = (progress - phase).clamp(0.0, 1.0);
-
- if (ringProgress > 0) {
- final radius = baseRadius + (maxRadius - baseRadius) * ringProgress;
- final opacity = (1.0 - ringProgress) * 0.45;
-
- final paint = Paint()
- ..color = const Color(0xFF32C59A).withOpacity(opacity)
- ..style = PaintingStyle.stroke
- ..strokeWidth = 2;
-
- canvas.drawCircle(center, radius, paint);
- }
- }
- }
-
- @override
- bool shouldRepaint(_PulseRingsPainter oldDelegate) =>
- oldDelegate.progress != progress;
-}
\ No newline at end of file
diff --git a/lib/widgets/settings_bar.dart b/lib/widgets/settings_bar.dart
index ea38677..26dc700 100644
--- a/lib/widgets/settings_bar.dart
+++ b/lib/widgets/settings_bar.dart
@@ -12,7 +12,7 @@ class SettingsBar extends StatelessWidget {
super.key,
required this.selectedIndex,
required this.onTap,
- this.showBackButton = false,
+ this.showBackButton = false, //to swicth between back button and settings icons
this.onBackTap,
required this.iconPaths,
});
@@ -21,24 +21,18 @@ class SettingsBar extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
- padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
+ 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,
- ),
+ Image.asset('assets/images/logo2.png', width: 150, height: 40),
],
),
-
- // Navigation icons on the right
Row(
children: [
- // Back button (only shown when showBackButton is true)
+
if (showBackButton)
GestureDetector(
onTap: onBackTap,
@@ -56,71 +50,56 @@ class SettingsBar extends StatelessWidget {
),
],
),
- child: const Center(
- child: Icon(
- Icons.arrow_back,
- color: Color(0xFF006838),
+ child: Center(
+ child: SvgPicture.asset(
+ "assets/images/back.svg",
+ width: 26,
+ height: 26,
),
),
),
),
-
- // Add spacing if back button is shown
- if (showBackButton) const SizedBox(width: 20),
-
- // Settings and notification icons
- ...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,
-
+ // 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),
),
- if (index == 1)
- Positioned(
- top: 0,
- right: 0,
- child: Container(
- width: 10,
- height: 10,
- ),
- ),
],
),
+ child: Center(
+ child: Stack(
+ children: [
+ SvgPicture.asset(iconPath, width: 30, height: 30),
+ ],
+ ),
+ ),
),
),
- ),
- );
- }).toList(),
+ );
+ }),
],
),
],
),
);
}
-}
\ No newline at end of file
+}
diff --git a/pubspec.lock b/pubspec.lock
index e42a4d5..ed985c9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -29,10 +29,10 @@ packages:
dependency: transitive
description:
name: async
- sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev"
source: hosted
- version: "2.13.0"
+ version: "2.12.0"
boolean_selector:
dependency: transitive
description:
@@ -85,10 +85,10 @@ packages:
dependency: transitive
description:
name: fake_async
- sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev"
source: hosted
- version: "1.3.3"
+ version: "1.3.2"
ffi:
dependency: transitive
description:
@@ -172,10 +172,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
- sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev"
source: hosted
- version: "10.0.9"
+ version: "10.0.8"
leak_tracker_flutter_testing:
dependency: transitive
description:
@@ -361,10 +361,10 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
+ sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev"
source: hosted
- version: "15.0.0"
+ version: "14.3.1"
web:
dependency: transitive
description: