715 lines
33 KiB
Dart
715 lines
33 KiB
Dart
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<ChallengeDetailsScreen> {
|
|
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<TaskDetail> 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)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|