import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:onufitness/constants/asset_constants.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/constant.dart'; import 'package:onufitness/constants/text_constant.dart'; import 'package:onufitness/screens/rise/controllers/rise_controller.dart'; import 'package:onufitness/screens/rise/models/get_challenges_response_model.dart'; import 'package:onufitness/screens/rise/widgets/input_challenge_performance.dart'; import 'package:onufitness/utils/helper_function.dart'; import 'package:onufitness/widgets/Buttons/custom_social_login_button.dart'; import 'package:onufitness/widgets/appbars/custom_appbar.dart'; import 'package:onufitness/widgets/Buttons/custom_submit_button.dart'; // Create a model class for better data structure class TaskDetail { final String label; final String value; TaskDetail({required this.label, required this.value}); } class ChallengeDetailsScreen extends StatefulWidget { final int index; final ChallengeItem challengeItem; RiseController riseController; ChallengeDetailsScreen({ super.key, required this.index, required this.challengeItem, required this.riseController, }); @override ActivityDetailsScreenState createState() => ActivityDetailsScreenState(); } class ActivityDetailsScreenState extends State { bool isExpanded = false; RxBool isJoinedSuccessful = false.obs; String formatDate(String? dateString) { if (dateString == null || dateString.isEmpty) { return 'N/A'; } try { DateTime dateTime = DateTime.parse(dateString); return DateFormat('MM-dd-yyyy').format(dateTime); } catch (e) { try { DateFormat inputFormat = DateFormat('yyyy-MM-dd'); DateTime dateTime = inputFormat.parse(dateString); return DateFormat('MM-dd-yyyy').format(dateTime); } catch (e) { return dateString; } } } List get taskDetails { final challenge = widget.challengeItem; final firstTask = challenge.tasks?.isNotEmpty == true ? challenge.tasks![0] : null; return [ TaskDetail(label: 'Task Name', value: firstTask?.taskTitle ?? 'N/A'), TaskDetail( label: 'Challenge Category', value: challenge.fitnessGoalTitle ?? 'N/A', ), TaskDetail( label: 'Target', value: "${firstTask?.targetValue?.toString()} ${firstTask?.taskUnit?.toString()}", ), TaskDetail( label: 'Start Date', value: formatDate(challenge.startDate?.toString()), ), TaskDetail( label: 'End Date', value: formatDate(challenge.endDate?.toString()), ), ]; } late ScrollController _participantsScrollController; @override void initState() { super.initState(); isJoinedSuccessful.value == false; _participantsScrollController = ScrollController(); _participantsScrollController.addListener(_onParticipantsScroll); // Load participants when screen initializes if (widget.challengeItem.challengeId != null) { widget.riseController.fetchChallengeParticipants( challengeId: widget.challengeItem.challengeId!, isRefresh: true, ); } } @override void dispose() { _participantsScrollController.removeListener(_onParticipantsScroll); _participantsScrollController.dispose(); super.dispose(); } void _onParticipantsScroll() { if (_participantsScrollController.position.pixels >= _participantsScrollController.position.maxScrollExtent - 200) { widget.riseController.loadMoreChallengeParticipants(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar( backgroundColor: Colors.white, title: widget.challengeItem.challengeTitle ?? ".", textColor: appbarTextColor, titleFontSize: appBarHeardingText, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Get.back(); }, ), ), body: SingleChildScrollView( padding: EdgeInsets.all(15.w), child: Column( children: [ ClipRRect( borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), child: widget.challengeItem.challengeImageName != null && widget.challengeItem.challengeImageName!.isNotEmpty ? Image.network( widget.challengeItem.challengeImageName!, width: double.infinity, height: 150.h, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return defaultBannerImage( context: context, title: widget.challengeItem.challengeTitle ?? ".", ); }, ) : defaultBannerImage( context: context, title: widget.challengeItem.challengeTitle ?? ".", ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: containerBorderColor), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20.r), bottomRight: Radius.circular(20.r), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( flex: 7, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.challengeItem.challengeTitle ?? "", style: TextStyle( fontSize: smallSizeText, fontWeight: FontWeight.w600, ), ), SizedBox(height: 4.h), Text( "${widget.challengeItem.fitnessGoalTitle ?? ""} • ${widget.challengeItem.totalParticipants ?? ""} Participants", style: TextStyle( fontSize: verySmallSizeText, color: greyTextColor1, ), ), SizedBox(height: 12.h), ], ), ), Divider(color: containerBorderColor), (widget.challengeItem.isChallengeJoined == false && isJoinedSuccessful.value == false) ? Expanded( flex: 3, child: Obx( () => CustomSubmitButton( height: 35.h, backgroundColor: Color(primaryColor), text: "Join", textColor: Colors.black, fontWeight: FontWeight.w600, onPressed: () async { bool joinResult = await widget .riseController .joinChallenge( challengeID: widget .challengeItem .challengeId!, ); if (joinResult) { // Update both states immediately setState(() { widget .challengeItem .isChallengeJoined = true; }); isJoinedSuccessful.value = true; // Refresh the challenges list widget.riseController .fetchOngoingChallenges( isRefresh: true, ); } }, isLoading: widget .riseController .isJoiningChallengeLoading .contains( widget.challengeItem.challengeId, ), ), ), ) : CustomSocialLoginButton( width: isTablet ? 120.w : 100.w, height: 35.h, text: "Joined", fontSize: smallSizeText, onPressed: null, ), ], ), Divider(color: containerBorderColor), Text( 'About Challenge', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8.h), Text( widget.challengeItem.challengeDescription ?? "", style: TextStyle(fontSize: 14.sp, color: Colors.grey), ), SizedBox(height: 16.h), Divider(color: containerBorderColor), SizedBox(height: 16.h), Column( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: taskDetails.map((detail) { return Padding( padding: EdgeInsets.symmetric( vertical: 5.w, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( detail.label, style: customTextStyle.copyWith( fontSize: smallSizeText, fontWeight: FontWeight.w600, ), ), ), Expanded( child: Text( detail.value, style: customTextStyle.copyWith( fontSize: verySmallSizeText, fontWeight: FontWeight.w400, color: greyTextColor1, ), ), ), ], ), ); }).toList(), ), SizedBox(height: 30.h), if ((widget.challengeItem.isChallengeJoined == true || isJoinedSuccessful.value == true) && widget.challengeItem.isChallengeStarted == true && widget.challengeItem.isChallengeEnded == false) CustomSubmitButton( backgroundColor: Colors.black, text: "Give Your Input Here", onPressed: () { showModalBottomSheet( context: context, // backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) => SafeArea( child: Padding( padding: EdgeInsets.only( bottom: MediaQuery.of( context, ).viewInsets.bottom, ), child: ChallengePerformanceInputBottomSheet( challengeTask: widget.challengeItem.tasks!, ), ), ), ); }, ), ], ), SizedBox(height: 16.h), ], ), ), SizedBox(height: 20.h), InkWell( onTap: () { setState(() { isExpanded = !isExpanded; }); }, child: Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.white, border: Border.all(color: containerBorderColor), borderRadius: BorderRadius.circular(20.r), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Challenge Rankings', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), Icon( isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, ), ], ), if (isExpanded) ...[ SizedBox(height: 20.h), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "Participants", style: TextStyle( color: greyTextColor1, fontSize: smallSizeText, fontWeight: FontWeight.w600, ), ), ], ), SizedBox(height: 20.h), // Real participants leaderboard list with scroll pagination Obx(() { if (widget .riseController .isParticipantsLoading .value && widget .riseController .challengeParticipants .isEmpty) { return Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 20.h), child: CircularProgressIndicator(), ), ); } if (widget .riseController .challengeParticipants .isEmpty) { return Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 20.h), child: Text( 'No participants found', style: TextStyle( color: greyTextColor1, fontSize: mediumSizeText, ), ), ), ); } // Sort participants by leaderboard score in descending order var sortedParticipants = widget.riseController.challengeParticipants .toList() ..sort( (a, b) => (b.leaderboardScore ?? 0) .compareTo(a.leaderboardScore ?? 0), ); return SizedBox( height: 300.h, child: ListView.builder( controller: _participantsScrollController, itemCount: sortedParticipants.length + (widget .riseController .participantsHasMoreData .value ? 1 : 0), itemBuilder: (context, index) { // Show loading indicator at the end if more data available if (index == sortedParticipants.length) { return widget .riseController .isParticipantsLoading .value ? Padding( padding: EdgeInsets.symmetric( vertical: 10.h, ), child: Center( child: CircularProgressIndicator(), ), ) : SizedBox.shrink(); } final participant = sortedParticipants[index]; return Padding( padding: EdgeInsets.symmetric( vertical: 12.h, ), child: Row( children: [ Stack( alignment: Alignment.center, children: [ CircleAvatar( radius: 24.r, backgroundColor: lightGreyColor, ), ClipOval( child: Image.network( participant.profilePicture != null && participant .profilePicture! .isNotEmpty ? participant .profilePicture! : '', // empty string will trigger errorBuilder width: 48.r, height: 48.r, fit: BoxFit.cover, loadingBuilder: ( context, child, loadingProgress, ) { if (loadingProgress == null) { return child; } return Container( width: 25.r, height: 25.r, alignment: Alignment.center, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation< Color >(Colors.grey), ), ); }, errorBuilder: ( context, error, stackTrace, ) { return Image.asset( AssetConstants .dummyUserImage, width: 48.r, height: 48.r, fit: BoxFit.cover, ); }, ), ), ], ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${participant.fullName ?? {participant.firstName}} (${participant.leaderboardScore ?? 0} Point)', style: TextStyle( fontWeight: FontWeight.w600, fontSize: smallSizeText, color: Colors.black, ), ), SizedBox(height: 8.h), Container( height: 4.h, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular( 10.r, ), color: Colors.grey.shade300, ), child: Stack( children: [ FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: (participant .challengeProgressPercentage ?? 0) / 100, child: Container( height: 5.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular( 10.r, ), gradient: LinearGradient( colors: [ Color( primaryColor, ), Color( darkGreyColor, ), ], ), ), ), ), ], ), ), ], ), ), SizedBox(width: 16.w), Column( children: [ if (index < 3) ...[ if (index == 0) Image.asset( AssetConstants.challengeRank1, height: isTablet ? 30.h : 25.h, width: isTablet ? 30.w : 25.w, ), if (index == 1) Image.asset( AssetConstants.challengeRank2, height: isTablet ? 30.h : 25.h, width: isTablet ? 30.w : 25.w, ), if (index == 2) Image.asset( AssetConstants.challengeRank3, height: isTablet ? 30.h : 25.h, width: isTablet ? 30.w : 25.w, ), SizedBox(height: 5.h), ] else SizedBox( height: 30.h, ), // Space to align with medal positions Text( "${participant.challengeProgressPercentage ?? 0}", style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.bold, color: Colors.black, ), ), ], ), ], ), ); }, ), ); }), ], ], ), ), ), ], ), Container( padding: EdgeInsetsDirectional.only( bottom: MediaQuery.of(context).viewPadding.bottom, ), color: Colors.white, ), ], ), ), ); } } Widget defaultBannerImage({ required BuildContext context, required String title, }) { return Container( width: double.infinity, height: 150.h, decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.white, Color(primaryColor).withValues(alpha: 0.5)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), ), child: Center( child: Text( title.isNotEmpty ? title[0].toUpperCase() : '', style: TextStyle( fontSize: 60.sp, fontWeight: FontWeight.bold, color: Colors.white, shadows: [ Shadow(blurRadius: 4, color: Colors.black26, offset: Offset(2, 2)), ], ), ), ), ); }