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,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),
),
],
);
}
}