@ -1,5 +1,6 @@
import ' package:flutter/material.dart ' ;
import ' package:flutter_countdown_timer/index.dart ' ;
import ' package:jiffy/jiffy.dart ' ;
import ' package:lottie/lottie.dart ' ;
import ' package:mohem_flutter_app/api/dashboard_api_client.dart ' ;
import ' package:mohem_flutter_app/app_state/app_state.dart ' ;
@ -13,12 +14,14 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import ' package:mohem_flutter_app/main.dart ' ;
import ' package:mohem_flutter_app/models/dashboard/menu_entries.dart ' ;
import ' package:mohem_flutter_app/models/itg/advertisement.dart ' as ads ;
import ' package:mohem_flutter_app/models/itg/itg_response_model.dart ' as itgres ;
import ' package:mohem_flutter_app/provider/dashboard_provider_model.dart ' ;
import ' package:mohem_flutter_app/ui/my_attendance/services_menu_list_screen.dart ' ;
import ' package:mohem_flutter_app/widgets/app_bar_widget.dart ' ;
import ' package:mohem_flutter_app/widgets/button/default_button.dart ' ;
import ' package:mohem_flutter_app/widgets/my_video_progress_indicator.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:sizer/sizer.dart ' ;
import ' package:url_launcher/url_launcher.dart ' ;
import ' package:video_player/video_player.dart ' ;
@ -42,6 +45,7 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
/ / late File imageFile ;
late String imageUrl ;
itgres . ItgResponseData ? advertisementPayload ;
ads . Advertisement ? advertisementData ;
dynamic data ;
String ? masterID ;
@ -52,6 +56,7 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
final ValueNotifier < bool > hasTimerEnded = ValueNotifier ( false ) ;
late CountdownTimerController timerController ;
CountdownTimerController ? deadlineTimerController ;
void checkFileType ( ) {
if ( advertisementData ! . viewAttachFileColl ! . first . contentType ! . toLowerCase ( ) = = " text " ) {
@ -128,34 +133,227 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
} ) ;
}
String getDeadlineText ( ) {
if ( advertisementPayload ? . deadline = = null ) {
return " " ;
}
try {
/ / Parse the deadline string using Jiffy ( format: MM / dd / yyyy hh: mm: ss aa )
Jiffy deadlineJiffy = Jiffy . parse ( advertisementPayload ! . deadline ! , pattern: " MM/dd/yyyy hh:mm:ss aa " ) ;
Jiffy nowJiffy = Jiffy . now ( ) ;
/ / Calculate days left
int daysLeft = deadlineJiffy . diff ( nowJiffy , unit: Unit . day ) . toInt ( ) ;
if ( daysLeft < 0 ) {
/ / Deadline has passed
int daysAgo = daysLeft . abs ( ) ;
if ( AppState ( ) . isArabic ( context ) ) {
return " انتهى الموعد النهائي منذ $ daysAgo ${ daysAgo = = 1 ? ' يوم ' : ' أيام ' } " ;
} else {
return " Deadline passed $ daysAgo ${ daysAgo = = 1 ? ' day ' : ' days ' } ago " ;
}
} else if ( daysLeft = = 0 ) {
/ / Deadline is today
if ( AppState ( ) . isArabic ( context ) ) {
return " الموعد النهائي اليوم! " ;
} else {
return " Deadline is today! " ;
}
} else if ( daysLeft = = 1 ) {
/ / Deadline is tomorrow
if ( AppState ( ) . isArabic ( context ) ) {
return " الموعد النهائي غداً " ;
} else {
return " Deadline is tomorrow " ;
}
} else {
/ / Deadline is in the future
if ( AppState ( ) . isArabic ( context ) ) {
return " متبقي $ daysLeft ${ daysLeft < = 10 ? ' أيام ' : ' يوم ' } " ;
} else {
return " $ daysLeft ${ daysLeft = = 1 ? ' day ' : ' days ' } left " ;
}
}
} catch ( e ) {
logger . e ( " Error parsing deadline: $ e " ) ;
return " Deadline: ${ Utils . formatDate ( advertisementPayload ! . deadline ) } " ;
}
}
void initDeadlineTimer ( ) {
if ( advertisementPayload ? . deadline = = null ) return ;
try {
/ / Parse the deadline string using Jiffy ( format: MM / dd / yyyy hh: mm: ss aa )
Jiffy deadlineJiffy = Jiffy . parse ( advertisementPayload ! . deadline ! , pattern: " MM/dd/yyyy hh:mm:ss aa " ) ;
DateTime deadlineDate = deadlineJiffy . dateTime ;
int endTime = deadlineDate . millisecondsSinceEpoch ;
int currentTime = DateTime . now ( ) . millisecondsSinceEpoch ;
/ / Only create timer if deadline is in the future
if ( endTime > currentTime ) {
deadlineTimerController = CountdownTimerController (
endTime: endTime ,
onEnd: ( ) {
/ / Timer ended , could trigger a refresh or notification
logger . d ( " Deadline timer ended " ) ;
} ,
) ;
}
} catch ( e ) {
logger . e ( " Error initializing deadline timer: $ e " ) ;
}
}
@ override
void dispose ( ) {
_controller ? . dispose ( ) ;
deadlineTimerController ? . dispose ( ) ;
super . dispose ( ) ;
}
/ / Helper method to get localized text
String _getLocalizedText ( String arabic , String english ) {
return AppState ( ) . isArabic ( context ) ? arabic : english ;
}
/ / Build advertisement content text
Widget _buildAdvertisementContent ( ) {
String content = AppState ( ) . isArabic ( context ) ? advertisementData ! . viewAttachFileColl ! [ 0 ] . base64String ! : advertisementData ! . viewAttachFileColl ! [ 1 ] . base64String ! ;
return content . toText16 ( color: MyColors . darkTextColor ) ;
}
/ / Build deadline section with timer
Widget _buildDeadlineSection ( ) {
if ( deadlineTimerController ! = null ) {
return Center (
child: Column ( crossAxisAlignment: CrossAxisAlignment . center , mainAxisAlignment: MainAxisAlignment . center , children: [ _buildEndDateText ( ) , 4. height , _buildTimeRemainingRow ( ) , 8. height ] ) ,
) ;
} else {
return Center ( child: _buildEndDateText ( isLargeText: true ) ) ;
}
}
/ / Build end date text
Widget _buildEndDateText ( { bool isLargeText = false } ) {
String label = _getLocalizedText ( " تاريخ الانتهاء: " , " End Date: " ) ;
String fullText = label + advertisementPayload ! . deadline ! ;
return isLargeText ? fullText . toText16 ( color: MyColors . darkTextColor ) : fullText . toText14 ( color: MyColors . darkTextColor ) ;
}
/ / Build time remaining row with timer
Widget _buildTimeRemainingRow ( ) {
return Row (
crossAxisAlignment: CrossAxisAlignment . center ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [ _getLocalizedText ( " الوقت المتبقي " , " Time Remaining " ) . toText16 ( color: MyColors . darkTextColor , isBold: true ) , 8. width , _buildCountdownTimer ( ) ] ,
) ;
}
/ / Build countdown timer widget
Widget _buildCountdownTimer ( ) {
return CountdownTimer (
controller: deadlineTimerController ,
endWidget: _getLocalizedText ( " انتهى الموعد النهائي! " , " Deadline Expired! " ) . toText16 ( color: MyColors . redColor , isBold: true ) ,
textStyle: const TextStyle ( color: MyColors . darkTextColor , fontSize: 16 , letterSpacing: - 0.48 , fontWeight: FontWeight . bold ) ,
) ;
}
/ / Build action buttons list
Widget _buildActionButtons ( ) {
return ListView . separated (
itemCount: advertisementData ! . actionButtonsColl ? . length ? ? 0 ,
separatorBuilder: ( cxt , index ) = > 16. height ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
padding: EdgeInsets . zero ,
itemBuilder: ( cxt , index ) {
var actionButton = advertisementData ! . actionButtonsColl ! [ index ] ;
String btnText = _getLocalizedText ( actionButton . btnTextAr , actionButton . btnTextEn ) ;
return DefaultButton ( btnText , ( ) = > _handleButtonAction ( actionButton ) ) ;
} ,
) ;
}
/ / Handle button action based on type
Future < void > _handleButtonAction ( actionButton ) async {
String actionValue = actionButton . actionValue . toLowerCase ( ) ;
if ( actionValue = = " skip " ) {
Navigator . pop ( context ) ;
return ;
}
String actionType = actionButton . actionType ? . toLowerCase ( ) ? ? ' ' ;
if ( actionType = = ' redirect ' & & actionValue = = ' external ' ) {
await _handleExternalRedirect ( actionButton . url ) ;
} else if ( actionType = = ' redirect ' & & actionValue = = ' internal ' ) {
await _handleInternalRedirect ( actionButton . url ) ;
} else if ( actionType = = ' eit ' & & actionValue = = ' internal ' ) {
await _handleEitInternal ( actionButton . url ) ;
} else {
await _handleExternalRedirect ( actionButton . iconOrImage ) ;
}
}
/ / Handle external URL redirect
Future < void > _handleExternalRedirect ( String url ) async {
Uri uri = Uri . parse ( url ) ;
await launchUrl ( uri , mode: LaunchMode . externalApplication ) . catchError ( ( err ) {
Utils . showToast ( ' Could not launch ' ) ;
} ) ;
Navigator . pop ( context ) ;
}
/ / Handle internal route navigation
Future < void > _handleInternalRedirect ( String url ) async {
await Navigator . pushNamed ( context , url ) ;
}
/ / Handle EIT internal navigation
Future < void > _handleEitInternal ( String url ) async {
var pro = Provider . of < DashboardProviderModel > ( context , listen: false ) ;
List < GetMenuEntriesList > menuList =
pro . getMenuEntriesList ? . where ( ( element ) = > element . parentMenuName = = url & & ( element . menuEntryType = = " FUNCTION " | | element . menuEntryType = = " MENU " ) ) . toList ( ) ? ? [ ] ;
Navigator . pushNamed ( context , AppRoutes . servicesMenuListScreen , arguments: ServicesMenuListScreenParams ( advertisementData ! . advertisementTitle ! , menuList ) ) ;
}
@ override
Widget build ( BuildContext context ) {
data = ModalRoute . of ( context ) ! . settings . arguments ;
advertisementData ? ? = data [ " advertisement " ] as ads . Advertisement ;
advertisementPayload ? ? = data [ " advertisement " ] as itgres . ItgResponseData ;
advertisementData = advertisementPayload ! . advertisement ;
masterID ? ? = data [ " masterId " ] ;
if ( advertisementData ! = null ) {
checkFileType ( ) ;
videoDuration = advertisementData ? . durationInSeconds ? ? 0 ;
timerController = CountdownTimerController ( endTime: DateTime . now ( ) . millisecondsSinceEpoch + 1000 * videoDuration , onEnd: onTimerEnd ) ;
if ( isTextURL ) {
if ( deadlineTimerController = = null ) {
initDeadlineTimer ( ) ;
}
} else {
videoDuration = advertisementData ? . durationInSeconds ? ? 0 ;
timerController = CountdownTimerController ( endTime: DateTime . now ( ) . millisecondsSinceEpoch + 1000 * videoDuration , onEnd: onTimerEnd ) ;
}
}
return Scaffold (
backgroundColor: isTextURL ? Colors . white : Colors . black ,
appBar: isTextURL
? AppBar (
centerTitle: true ,
automaticallyImplyLeading: false ,
backgroundColor: Colors . white ,
title: ( AppState ( ) . isArabic ( context ) ? advertisementData ! . advertisementTitleAr ! : advertisementData ! . advertisementTitle ! ) . toText24 ( color: MyColors . darkTextColor , isBold: true ) ,
/ / advertisementData ! . viewAttachFileColl ! . first . base64String ! . toText24 ( color: MyColors . darkTextColor , isBold: true ) ,
)
: null ,
appBar:
isTextURL
? AppBar (
centerTitle: true ,
automaticallyImplyLeading: false ,
backgroundColor: Colors . white ,
title: ( AppState ( ) . isArabic ( context ) ? advertisementData ! . advertisementTitleAr ! : advertisementData ! . advertisementTitle ! ) . toText24 ( color: MyColors . darkTextColor , isBold: true ) ,
)
: null ,
body: Stack (
children: [
if ( isVideo )
@ -168,38 +366,33 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Center (
child: isAudio
? Lottie . asset ( MyLottieConsts . audioPlaybackLottie )
: AspectRatio (
aspectRatio: _controller ! . value . aspectRatio ,
child: Stack (
alignment: Alignment . bottomCenter ,
children: [
VideoPlayer ( _controller ! ) ,
if ( advertisementData ? . isOptional ? ? false )
Positioned (
top: 4 ,
right: 8 ,
child: Container (
padding: const EdgeInsets . only ( left: 8 , right: 8 , top: 4 , bottom: 4 ) ,
decoration: BoxDecoration ( color: Colors . white . withOpacity ( 0.5 ) , borderRadius: BorderRadius . circular ( 30 ) ) ,
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( AppState ( ) . isArabic ( context ) ? " يتخطى " : " Skip " ) ,
Icon ( Icons . skip_next ) ,
] ,
) ,
) . onPress ( ( ) {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , " Skip " ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} ) ,
) ,
] ,
child:
isAudio
? Lottie . asset ( MyLottieConsts . audioPlaybackLottie )
: AspectRatio (
aspectRatio: _controller ! . value . aspectRatio ,
child: Stack (
alignment: Alignment . bottomCenter ,
children: [
VideoPlayer ( _controller ! ) ,
if ( advertisementData ? . isOptional ? ? false )
Positioned (
top: 4 ,
right: 8 ,
child: Container (
padding: const EdgeInsets . only ( left: 8 , right: 8 , top: 4 , bottom: 4 ) ,
decoration: BoxDecoration ( color: Colors . white . withOpacity ( 0.5 ) , borderRadius: BorderRadius . circular ( 30 ) ) ,
child: Row ( mainAxisSize: MainAxisSize . min , children: [ Text ( AppState ( ) . isArabic ( context ) ? " يتخطى " : " Skip " ) , Icon ( Icons . skip_next ) ] ) ,
) . onPress ( ( ) {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , " Skip " ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} ) ,
) ,
] ,
) ,
) ,
) ,
) ,
30. height ,
CountdownTimer (
@ -243,13 +436,13 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
btnText ! ,
hasTimerEndedBool
? ( ) async {
Navigator . pop ( context ) ;
DashboardApiClient ( )
. setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , advertisementData ? . actionButtonsColl ! [ index ] . actionValue )
. then ( ( value ) {
logger . d ( value ) ;
} ) ;
}
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , advertisementData ? . actionButtonsColl ! [ index ] . actionValue ) . then ( (
value ,
) {
logger . d ( value ) ;
} ) ;
}
: null ,
disabledColor: Colors . transparent ,
textColor: ! hasTimerEndedBool ? Colors . white . withOpacity ( . 5 ) : Colors . white ,
@ -257,15 +450,12 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
] ,
) ;
}
return DefaultButton (
btnText ! ,
( ) async {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , advertisementData ? . actionButtonsColl ! [ index ] . actionValue ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} ,
) . paddingOnly ( left: 60.0 , right: 60.0 , top: 8 , bottom: 8 ) ;
return DefaultButton ( btnText ! , ( ) async {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , advertisementData ? . actionButtonsColl ! [ index ] . actionValue ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} ) . paddingOnly ( left: 60.0 , right: 60.0 , top: 8 , bottom: 8 ) ;
} ,
/ / gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount (
/ / crossAxisCount: 1 ,
@ -338,9 +528,7 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
] ,
) ;
} else {
return const Center (
child: CircularProgressIndicator ( ) ,
) ;
return const Center ( child: CircularProgressIndicator ( ) ) ;
}
} ,
) ,
@ -353,19 +541,17 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
children: [
Image . network ( imageUrl ) ,
50. height ,
Container ( padding: const EdgeInsets . all ( 16 ) , decoration: Utils . containerRadius ( MyColors . white , 10 ) , child: const Icon ( Icons . thumb_up , color: MyColors . gradiantEndColor ) ) . onPress (
( ) {
try {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , " Like " ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} catch ( ex ) {
logger . wtf ( ex ) ;
Utils . handleException ( ex , context , null ) ;
}
} ,
) ,
Container ( padding: const EdgeInsets . all ( 16 ) , decoration: Utils . containerRadius ( MyColors . white , 10 ) , child: const Icon ( Icons . thumb_up , color: MyColors . gradiantEndColor ) ) . onPress ( ( ) {
try {
Navigator . pop ( context ) ;
DashboardApiClient ( ) . setAdvertisementViewed ( masterID ! , advertisementData ! . advertisementId ! , " Like " ) . then ( ( value ) {
logger . d ( value ) ;
} ) ;
} catch ( ex ) {
logger . wtf ( ex ) ;
Utils . handleException ( ex , context , null ) ;
}
} ) ,
] ,
) ,
) ,
@ -375,60 +561,7 @@ class _ITGAdsScreenState extends State<ITGAdsScreen> {
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisSize: MainAxisSize . min ,
children: [
( AppState ( ) . isArabic ( context ) ? advertisementData ! . viewAttachFileColl ! [ 0 ] . base64String ! : advertisementData ! . viewAttachFileColl ! [ 1 ] . base64String ! )
. toText16 ( color: MyColors . darkTextColor ) ,
24. height ,
ListView . separated (
itemCount: advertisementData ! . actionButtonsColl ? . length ? ? 0 ,
separatorBuilder: ( cxt , index ) = > 16. height ,
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
padding: EdgeInsets . zero ,
itemBuilder: ( cxt , index ) = > DefaultButton (
AppState ( ) . isArabic ( context ) ? advertisementData ! . actionButtonsColl ! [ index ] . btnTextAr : advertisementData ! . actionButtonsColl ! [ index ] . btnTextEn ,
( ) async {
if ( advertisementData ! . actionButtonsColl ! [ index ] . actionValue . toLowerCase ( ) = = " skip " ) {
Navigator . pop ( context ) ;
} else {
if ( advertisementData ! . actionButtonsColl ! [ index ] . actionType ! . toLowerCase ( ) = = ' redirect ' & & advertisementData ! . actionButtonsColl ! [ index ] . actionValue ! . toLowerCase ( ) = =
' external ' ) {
Uri uri = Uri . parse ( advertisementData ! . actionButtonsColl ! [ index ] . url ! ) ;
await launchUrl ( uri , mode: LaunchMode . externalApplication ) . catchError ( ( err ) {
/ / print ( err ) ;
Utils . showToast ( ' Could not launch ' ) ;
} ) ;
Navigator . pop ( context ) ;
} else if ( advertisementData ! . actionButtonsColl ! [ index ] . actionType ! . toLowerCase ( ) = = ' redirect ' & & advertisementData ! . actionButtonsColl ! [ index ] . actionValue ! . toLowerCase ( ) = =
' internal ' ) {
await Navigator . pushNamed ( context , advertisementData ! . actionButtonsColl ! [ index ] . url ! ) ;
} else if ( advertisementData ! . actionButtonsColl ! [ index ] . actionType ! . toLowerCase ( ) = = ' eit ' & & advertisementData ! . actionButtonsColl ! [ index ] . actionValue ! . toLowerCase ( ) = =
' internal ' ) {
var pro = Provider . of < DashboardProviderModel > ( context , listen: false ) ;
List < GetMenuEntriesList > _menuList =
pro . getMenuEntriesList ? . where ( ( element ) = > element . parentMenuName = = advertisementData ! . actionButtonsColl ! [ index ] . url & & ( element . menuEntryType = = " FUNCTION " | | element . menuEntryType = = " MENU " ) ) . toList ( ) ? ? [ ] ;
Navigator . pushNamed ( context , AppRoutes . servicesMenuListScreen , arguments: ServicesMenuListScreenParams ( advertisementData ! . advertisementTitle ! , _menuList ) ) ;
}
else {
Uri uri = Uri . parse ( advertisementData ! . actionButtonsColl ! [ index ] . iconOrImage ) ;
/ / if ( await canLaunchUrl ( uri ) ) {
await launchUrl ( uri , mode: LaunchMode . externalApplication ) . catchError ( ( err ) {
/ / print ( err ) ;
Utils . showToast ( ' Could not launch ' ) ;
} ) ;
Navigator . pop ( context ) ;
/ / } else {
/ / Utils . showToast ( ' Could not launch ' ) ;
/ / }
}
}
} ,
) ,
) ,
] ,
children: [ _buildAdvertisementContent ( ) , 24. height , _buildDeadlineSection ( ) , 24. height , _buildActionButtons ( ) ] ,
) ,
) ,
] ,