last changes

This commit is contained in:
Daniah Ayad Al-sultani
2025-12-13 16:56:34 +03:00
parent de48e56a41
commit 5cdfa102f3
5 changed files with 389 additions and 484 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/settings_bar.dart';
import '../screens/request_leave_screen.dart';
import '../screens/request_advance_scrren.dart';
import '../models/leave_request.dart';
@@ -9,43 +10,49 @@ import '../models/advance_request.dart';
import '../services/request_service.dart';
class HolidayScreen extends StatefulWidget {
const HolidayScreen({super.key});
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; // 0 = السلف | 1 = الأجازات
int activeTab = 0;
final RequestService _requestService = RequestService();
List<LeaveRequest> _leaveRequests = [];
List<AdvanceRequest> _advanceRequests = [];
/// ⭐ Telegram-style FAB animation
final ScrollController _scrollController = ScrollController();
bool _showActions = true;
bool _showSettings = true; // NEW — local control for SettingsBar
@override
void initState() {
super.initState();
_initializeData();
// ⭐ Listen to scroll and animate the floating buttons (Telegram style)
_scrollController.addListener(() {
final direction = _scrollController.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
// scrolling DOWN → hide buttons
if (_showActions) setState(() => _showActions = false);
setState(() => _showSettings = false);
widget.onScrollEvent?.call(true);
} else if (direction == ScrollDirection.forward) {
// scrolling UP → show buttons
if (!_showActions) setState(() => _showActions = true);
setState(() => _showSettings = true);
widget.onScrollEvent?.call(false);
}
// If user reaches top → force visible
if (_scrollController.position.pixels <= 10) {
if (!_showActions) setState(() => _showActions = true);
if (_scrollController.position.pixels <= 5) {
setState(() {
_showActions = true;
_showSettings = true;
});
widget.onScrollEvent?.call(false);
}
});
}
@@ -55,74 +62,55 @@ class _HolidayScreenState extends State<HolidayScreen> {
_advanceRequests = await _requestService.getAdvanceRequests();
_requestService.leaveRequestsStream.listen((requests) {
setState(() {
_leaveRequests = requests;
});
if (mounted) setState(() => _leaveRequests = requests);
});
_requestService.advanceRequestsStream.listen((requests) {
setState(() {
_advanceRequests = requests;
});
if (mounted) setState(() => _advanceRequests = requests);
});
}
Widget buildStatusCircle({
required bool active,
required String label,
required String? svgPath,
required Color activeColor,
required Color glowColor,
}) {
return Column(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: active ? activeColor : const Color(0xFFD6D6D6),
boxShadow:
active
? [
BoxShadow(
color: glowColor.withOpacity(0.6),
blurRadius: 15,
spreadRadius: 2,
),
]
: [],
),
child:
active && svgPath != null
? Center(
child: SvgPicture.asset(
svgPath,
width: 18,
height: 18,
color: const Color(0xFFFFFFFF),
),
)
: null,
),
const SizedBox(height: 4),
Text(label, style: const TextStyle(fontSize: 10)),
],
);
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Stack(
children: [
// -------------------- TABS --------------------
Positioned(
top: 0,
// ---------------------------------------------------------
// ⭐ SETTINGS BAR (now inside Holiday screen)
// ---------------------------------------------------------
AnimatedPositioned(
duration: const Duration(milliseconds: 250),
top: _showSettings ? 0 : -80,
left: 0,
right: 0,
bottom: 5,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: SettingsBar(
selectedIndex: 0,
showBackButton: false,
iconPaths: [
'assets/images/user.svg',
'assets/images/ball.svg',
],
onTap: (index) {
// do your navigation logic
},
),
),
),
),
// ---------------------------------------------------------
// ⭐ TABS SECTION
// Directly under SettingsBar, NO GAP
// ---------------------------------------------------------
Positioned(
top: 70,
left: 0,
right: 0,
height: 55,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -185,33 +173,30 @@ class _HolidayScreenState extends State<HolidayScreen> {
),
),
// ------------- CONTENT AREA (LISTS) -------------
Positioned(
top: 30,
left: 0,
right: 0,
bottom: 0,
// ---------------------------------------------------------
// ⭐ CONTENT AREA (LISTS)
// Starts right under the TABS
// ---------------------------------------------------------
Positioned.fill(
top: 130,
child:
activeTab == 1
? _buildLeaveRequestsTab()
: _buildAdvanceRequestsTab(),
),
// -------------------------------------------------------------
// ⭐ TELEGRAM STYLE FLOATING ACTION BUTTONS (SLIDE + FADE)
// -------------------------------------------------------------
// ---------------------------------------------------------
// ⭐ FLOATING ACTION BUTTONS
// ---------------------------------------------------------
Positioned(
bottom: 30,
bottom: 60,
right: 20,
child: AnimatedSlide(
offset: _showActions ? const Offset(0, 0) : const Offset(0, 1.4),
duration: const Duration(milliseconds: 350),
curve: Curves.easeOut,
offset: _showActions ? Offset.zero : const Offset(0, 1.3),
duration: const Duration(milliseconds: 300),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: _showActions ? 1 : 0,
duration: const Duration(milliseconds: 250),
child: Column(
children: [
_HolidayActionButton(
@@ -219,28 +204,26 @@ class _HolidayScreenState extends State<HolidayScreen> {
svgPath: "assets/images/money2.svg",
iconWidth: 28,
iconHeight: 20,
onTap: () async {
await Navigator.push(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RequestAdvanceScreen(),
builder: (_) => const RequestAdvanceScreen(),
),
);
},
),
const SizedBox(height: 18),
_HolidayActionButton(
label: "طلب إجازة",
svgPath: "assets/images/plus.svg",
iconWidth: 28,
iconHeight: 20,
onTap: () async {
await Navigator.push(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RequestLeaveScreen(),
builder: (_) => const RequestLeaveScreen(),
),
);
},
@@ -255,15 +238,15 @@ class _HolidayScreenState extends State<HolidayScreen> {
);
}
// -------------------- LIST BUILDERS --------------------
// ----------------------------------------------------------------
// LISTS
// ----------------------------------------------------------------
Widget _buildLeaveRequestsTab() {
if (_leaveRequests.isEmpty) {
return const Center(
child: Text(
'لا توجد طلبات أجازة',
style: TextStyle(color: Colors.white, fontSize: 18),
),
child:
Text("لا توجد طلبات أجازة", style: TextStyle(color: Colors.white)),
);
}
@@ -280,10 +263,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
Widget _buildAdvanceRequestsTab() {
if (_advanceRequests.isEmpty) {
return const Center(
child: Text(
'لا توجد طلبات سلف',
style: TextStyle(color: Colors.white, fontSize: 18),
),
child:
Text("لا توجد طلبات سلف", style: TextStyle(color: Colors.white)),
);
}
@@ -297,7 +278,9 @@ class _HolidayScreenState extends State<HolidayScreen> {
);
}
// -------------------- LEAVE REQUEST CARD --------------------
// ----------------------------------------------------------------
// CARD BUILDERS (unchanged)
// ----------------------------------------------------------------
Widget _buildLeaveRequestCard(LeaveRequest request) {
const bgColor = Color(0xFFEAF8F3);
@@ -313,7 +296,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: MediaQuery.of(context).size.width * 0.8,
width: double.infinity,
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
decoration: BoxDecoration(
@@ -323,261 +306,126 @@ class _HolidayScreenState extends State<HolidayScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Status circles
// STATUS ROW
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildStatusCircle(
active: isWaiting,
label: "قيد الانتظار",
svgPath: isWaiting ? "assets/images/waiting.svg" : null,
activeColor: const Color(0xFFF9C8A01B),
glowColor: const Color(0xB4FFDC69),
),
_statusCircle(isWaiting, "قيد الانتظار",
isWaiting ? "assets/images/waiting.svg" : null,
const Color(0xFFA58A1B), const Color(0xFFFFDC69)),
const SizedBox(width: 12),
buildStatusCircle(
active: isApproved,
label: "موافقة",
svgPath: isApproved ? "assets/images/yes.svg" : null,
activeColor: const Color(0xFF0A8A60),
glowColor: const Color(0xFF00D7A3),
),
_statusCircle(isApproved, "موافقة",
isApproved ? "assets/images/yes.svg" : null,
const Color(0xFF0A8A60), const Color(0xFF00D7A3)),
const SizedBox(width: 12),
buildStatusCircle(
active: isDenied,
label: "تم الرفض",
svgPath: isDenied ? "assets/images/no.svg" : null,
activeColor: const Color(0xFFE63946),
glowColor: const Color(0xFFE63946),
),
_statusCircle(isDenied, "تم الرفض",
isDenied ? "assets/images/no.svg" : null,
const Color(0xFFE63946), const Color(0xFFE63946)),
],
),
const SizedBox(height: 8),
// Title + date row
// TITLE + DATE
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// icon + leave type
Row(
children: [
SizedBox(
SvgPicture.asset(
"assets/images/holiday.svg",
width: 32,
height: 32,
child: Center(
child: SvgPicture.asset(
"assets/images/holiday.svg",
width: 32,
height: 32,
color: const Color(0xFF11663C),
),
),
color: const Color(0xFF11663C),
),
const SizedBox(width: 8),
Text(
request.leaveType,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
// date
Text(
dateText,
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
Text(dateText),
],
),
const SizedBox(height: 10),
// divider
Container(width: double.infinity, height: 1.4, color: lineColor),
Container(height: 1.4, width: double.infinity, color: lineColor),
const SizedBox(height: 10),
// Reason row
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Text(
request.reason,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 13,
color: Colors.black87,
),
),
),
const SizedBox(width: 6),
const Text(
"السبب :",
// REASON ROW
Row(
children: [
Expanded(
child: Text(
request.reason,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
const SizedBox(width: 6),
const Text(
"السبب:",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
)
],
),
),
);
}
// -------------------- ADVANCE REQUEST CARD --------------------
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) {
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 =
"${DateTime.now().year}.${DateTime.now().month}.${DateTime.now().day}";
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
width: MediaQuery.of(context).size.width * 0.8,
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildStatusCircle(
active: isWaiting,
label: "قيد الانتظار",
svgPath: isWaiting ? "assets/images/waiting.svg" : null,
activeColor: const Color(0xFFF9C8A01B),
glowColor: const Color(0xB4FFDC69),
),
const SizedBox(width: 12),
buildStatusCircle(
active: isApproved,
label: "موافقة",
svgPath: isApproved ? "assets/images/yes.svg" : null,
activeColor: const Color(0xFF0A8A60),
glowColor: const Color(0xFF00D7A3),
),
const SizedBox(width: 12),
buildStatusCircle(
active: isDenied,
label: "تم الرفض",
svgPath: isDenied ? "assets/images/no.svg" : null,
activeColor: const Color(0xFFE63946),
glowColor: const Color(0xFFE63946),
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SizedBox(
width: 32,
height: 32,
child: Center(
child: SvgPicture.asset(
"assets/images/money.svg",
width: 32,
height: 32,
color: const Color(0xFF11663C),
),
),
),
const SizedBox(width: 8),
Text(
"سلفة: ${request.amount.toStringAsFixed(0)} دع",
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
Text(
dateText,
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 10),
Container(width: double.infinity, height: 1, color: lineColor),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
textDirection: TextDirection.ltr,
children: [
Expanded(
child: Text(
request.reason,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 13,
color: Colors.black87,
),
),
),
const SizedBox(width: 6),
const Text(
"السبب :",
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
// 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 ACTION BUTTON --------------------
// ----------------------------------------------------------------
// FLOATING BUTTON WIDGET (unchanged)
// ----------------------------------------------------------------
class _HolidayActionButton extends StatelessWidget {
final String label;