Files
finger_print_app/lib/presentation/screens/holiday_screen.dart
Mohammed Al-Samarraie 33099c4497 1111
2026-01-18 19:52:10 +03:00

716 lines
22 KiB
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/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';
import '../../core/di/injection_container.dart';
import '../../domain/usecases/get_vacations_usecase.dart';
import '../../domain/usecases/get_advances_usecase.dart';
import '../../domain/models/vacations_list_response_model.dart';
import '../../domain/models/vacation_response_model.dart';
import '../../domain/models/advances_list_response_model.dart';
import '../../domain/models/advance_request_model.dart';
import '../../core/error/failures.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();
final GetVacationsUseCase _getVacationsUseCase = sl<GetVacationsUseCase>();
final GetAdvancesUseCase _getAdvancesUseCase = sl<GetAdvancesUseCase>();
List<LeaveRequest> _leaveRequests = [];
List<AdvanceRequest> _advanceRequests = [];
bool _isLoadingVacations = false;
bool _isLoadingAdvances = false;
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 {
// Load from API
_loadVacationsFromAPI();
_loadAdvancesFromAPI();
// Also listen to local changes (for newly created requests)
_requestService.leaveRequestsStream.listen((requests) {
if (mounted) {
setState(() {
// Merge with API data if needed
_leaveRequests = requests;
});
}
});
_requestService.advanceRequestsStream.listen((requests) {
if (mounted) setState(() => _advanceRequests = requests);
});
}
Future<void> _loadVacationsFromAPI() async {
setState(() {
_isLoadingVacations = true;
});
final result = await _getVacationsUseCase();
result.fold(
(failure) {
if (mounted) {
setState(() {
_isLoadingVacations = false;
});
// Load from local service as fallback
_requestService.getLeaveRequests().then((requests) {
if (mounted) {
setState(() {
_leaveRequests = requests;
});
}
});
}
},
(response) {
if (mounted && response.data != null) {
setState(() {
_leaveRequests = response.data!.items
.map((vacation) => _convertVacationToLeaveRequest(vacation))
.toList();
_isLoadingVacations = false;
});
}
},
);
}
LeaveRequest _convertVacationToLeaveRequest(VacationDataModel vacation) {
// Convert state (0=waiting, 1=approved, 2=denied) to status string
String status = "waiting";
if (vacation.state == 1) {
status = "approved";
} else if (vacation.state == 2) {
status = "denied";
}
// Convert type to Arabic name
String leaveTypeName = _getArabicVacationTypeName(vacation.type);
// Check if it's timed leave (same day but different times)
bool isTimedLeave = vacation.startDate.year == vacation.endDate.year &&
vacation.startDate.month == vacation.endDate.month &&
vacation.startDate.day == vacation.endDate.day &&
vacation.startDate.hour != vacation.endDate.hour;
return LeaveRequest(
id: vacation.id,
leaveType: leaveTypeName,
isTimedLeave: isTimedLeave,
fromDate: vacation.startDate,
toDate: vacation.endDate,
fromTime: TimeOfDay.fromDateTime(vacation.startDate),
toTime: TimeOfDay.fromDateTime(vacation.endDate),
reason: vacation.reason,
requestDate: vacation.createdAt ?? vacation.startDate,
status: status,
);
}
String _getArabicVacationTypeName(int type) {
switch (type) {
case 1:
return 'أجازة زمنية';
case 2:
return 'إجازة مرضية';
case 3:
return 'إجازة مدفوعة';
case 4:
return 'إجازة غير مدفوعة';
default:
return 'إجازة';
}
}
Future<void> _loadAdvancesFromAPI() async {
setState(() {
_isLoadingAdvances = true;
});
final result = await _getAdvancesUseCase();
result.fold(
(failure) {
if (mounted) {
setState(() {
_isLoadingAdvances = false;
});
// Load from local service as fallback
_requestService.getAdvanceRequests().then((requests) {
if (mounted) {
setState(() {
_advanceRequests = requests;
});
}
});
}
},
(response) {
if (mounted && response.data != null) {
setState(() {
_advanceRequests = response.data!.items
.map((advance) => _convertAdvanceToAdvanceRequest(advance))
.toList();
_isLoadingAdvances = false;
});
}
},
);
}
AdvanceRequest _convertAdvanceToAdvanceRequest(AdvanceDataModel advance) {
// Convert state (0=waiting, 1=approved, 2=denied) to status string
String status = "waiting";
if (advance.state == 1) {
status = "approved";
} else if (advance.state == 2) {
status = "denied";
}
return AdvanceRequest(
id: advance.id,
amount: advance.amount,
reason: advance.reason,
status: status,
);
}
@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 (_isLoadingVacations) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Center(
child: CircularProgressIndicator(
color: Color(0xFF8EFDC2),
),
),
),
);
}
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 (_isLoadingAdvances) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: Center(
child: CircularProgressIndicator(
color: Color(0xFF8EFDC2),
),
),
),
);
}
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,
),
),
],
),
),
);
}
}