import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:onufitness/constants/api_enum_constant.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/text_constant.dart'; import 'package:onufitness/screens/rise/controllers/create_challenge_controller.dart'; import 'package:onufitness/screens/rise/controllers/rise_controller.dart'; import 'package:onufitness/screens/echoboard/widget/exclusive_connection_tile.dart'; import 'package:onufitness/services/local_storage_services/shared_services.dart'; import 'package:onufitness/utils/custom_sneakbar.dart'; import 'package:onufitness/widgets/Dropdowns/custom_lebel_dropdown.dart'; import 'package:onufitness/widgets/appbars/custom_appbar.dart'; import 'package:onufitness/widgets/TextFields/custom_textfield_with_inside_container_label_text.dart'; import 'package:onufitness/widgets/bottomsheet/common_upload_option_bottomsheet.dart'; import 'package:onufitness/widgets/pickers/lebel_text_date_picker.dart'; import 'package:onufitness/widgets/pickers/lebel_text_file_picker.dart'; class CreateChallengeScreen extends StatefulWidget { const CreateChallengeScreen({super.key}); @override State createState() => _CreateChallengeScreenState(); } class _CreateChallengeScreenState extends State { final ScrollController _membersScrollController = ScrollController(); CreateChallengeController createChallengeController = Get.put( CreateChallengeController(), ); final riseController = Get.find(); @override void initState() { super.initState(); _membersScrollController.addListener(_onMembersScroll); WidgetsBinding.instance.addPostFrameCallback((_) { createChallengeController.fetchExclusiveConnections(refresh: true); createChallengeController.fetchMetricsList(); createChallengeController.fetchFitnessGoals(); }); } void _onMembersScroll() { if (_membersScrollController.position.pixels >= _membersScrollController.position.maxScrollExtent - 200) { if (createChallengeController.hasMoreDataConnection.value && !createChallengeController.isConnectionPaginationLoading.value) { createChallengeController.fetchExclusiveConnections(); } } } Future _onRefresh() async { await createChallengeController.fetchExclusiveConnections(refresh: true); createChallengeController.selectedConnections.clear(); createChallengeController.selectedConnections.refresh(); createChallengeController.selectedIds.clear(); return Future.value(); } @override void dispose() { createChallengeController.clearAll(); createChallengeController.selectedConnections.clear(); createChallengeController.selectedConnections.refresh(); createChallengeController.selectedIds.clear(); super.dispose(); } bool hasValidWordLength(String text) { final wordCount = text.trim().split(RegExp(r'\s+')).length; return wordCount <= 100; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: CustomAppBar( title: 'Create a Challenge', textColor: appbarTextColor, titleFontSize: appBarHeardingText, backgroundColor: Colors.white, leading: IconButton( icon: Icon(Icons.arrow_back_ios, color: Colors.black), onPressed: () => Get.back(), ), ), body: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 15.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10.h), StatefulBuilder( builder: (context, setState) { return CustomTextFieldContainerInsideLabel( controller: createChallengeController.titleController, borderColor: lightGreyColor, label: 'Challenge Title *', fillColor: textFieldFillColor, lebelTextPadding: 12.h, topContentPadding: 20.h, height: createChallengeController.titleController.text.isEmpty ? 40.h : null, lebelTextFontWeight: FontWeight.w500, ); }, ), SizedBox(height: 15), Obx(() { return createChallengeController.isFitnessGoalsLoading.value ? const Center(child: CircularProgressIndicator()) : EnhancedLebelTextDropdown( title: "Fitness Goals *", items: createChallengeController.localFitnessGoalsList, selectedItem: createChallengeController.selectedFitnessGoal.isEmpty ? null : createChallengeController .selectedFitnessGoal .value, onChanged: (String? newValue) { if (newValue != null) { createChallengeController.selectedFitnessGoal.value = newValue; var selectedObj = createChallengeController .apiFitnessGoalsList .firstWhere( (element) => element.fitnessGoalTitle == newValue, ); createChallengeController.selectedFitnessGoalId.value = selectedObj.fitnessGoalId!; } }, borderColor: lightGreyColor, ); }), SizedBox(height: 15.h), Obx( () => EnhancedDatePicker( label: "Start date *", selectedDate: createChallengeController.startDate.value?.toString() ?? "", onDatePicked: createChallengeController.pickStartDate, borderColor: lightGreyColor, ), ), SizedBox(height: 15.h), Obx( () => EnhancedDatePicker( label: "End date *", selectedDate: createChallengeController.endDate.value?.toString() ?? "", onDatePicked: createChallengeController.pickEndDate, borderColor: lightGreyColor, ), ), SizedBox(height: 15.h), CustomTextFieldContainerInsideLabel( borderColor: lightGreyColor, label: 'Description *', fillColor: textFieldFillColor, controller: createChallengeController.descriptionController, maxLine: 4, lebelTextPadding: 12.h, topContentPadding: 30.h, lebelTextFontWeight: FontWeight.w500, ), SizedBox(height: 15.h), // Upload Demo Video Obx( () => EnhancedFilePickerTile( hintText: "Upload Demo Video", fileName: createChallengeController.demoVideoName.value.isEmpty ? null : createChallengeController.demoVideoName.value, icon: Icons.file_upload_outlined, onTap: () { CommonUploadBottomSheet.show( context: context, title: "Upload Video From", allowedFileTypes: ['mp4', 'm4v'], maxFileSizeMB: 50.0, showCamera: false, showGallery: true, showDocument: true, onFileSelected: ( String filePath, String fileName, String fileExtension, ) { // Convert String path to PlatformFile createChallengeController .demoVideoFile .value = PlatformFile( path: filePath, name: fileName, size: File(filePath).lengthSync(), bytes: null, ); createChallengeController.demoVideoName.value = fileName; }, onError: (String errorMessage) { // Show error message customSnackbar(title: "Error", message: errorMessage); }, ); }, onClear: () => createChallengeController.clearDemoVideo(), ), ), SizedBox(height: 16.h), // Upload Banner Image Obx( () => EnhancedFilePickerTile( hintText: "Upload Banner Image", fileName: createChallengeController.bannerImageName.value.isEmpty ? null : createChallengeController.bannerImageName.value, icon: Icons.image_outlined, onTap: () { CommonUploadBottomSheet.show( context: context, title: "Upload Image from", allowedFileTypes: ['jpg', 'jpeg', 'png'], maxFileSizeMB: 5.0, showCamera: true, showGallery: true, showDocument: true, onFileSelected: ( String filePath, String fileName, String fileExtension, ) { // Convert String path to PlatformFile createChallengeController .bannerImageFile .value = PlatformFile( path: filePath, name: fileName, size: File(filePath).lengthSync(), bytes: null, ); createChallengeController.bannerImageName.value = fileName; }, onError: (String errorMessage) { // Show error message customSnackbar(title: "Error", message: errorMessage); }, ); }, onClear: () => createChallengeController.clearBannerImage(), ), ), SizedBox(height: 16.h), Container( width: double.infinity, padding: EdgeInsets.all(16.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), color: Colors.grey[100], border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Challenge task", style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), ), SizedBox(height: 12.h), SizedBox(height: 12.h), CustomTextFieldContainerInsideLabel( controller: createChallengeController.taskTitleController, borderColor: lightGreyColor, label: "Title *", fillColor: Colors.white, lebelTextPadding: 12.h, topContentPadding: 20.h, ), SizedBox(height: 12.h), // Updated Metric Dropdown Obx(() { return createChallengeController.isMetricsLoading.value ? const Center(child: CircularProgressIndicator()) : EnhancedLebelTextDropdown( title: "Metric *", items: createChallengeController.localMetricsList, selectedItem: createChallengeController.selectedMetric.isEmpty ? null : createChallengeController .selectedMetric .value, onChanged: (String? newValue) { if (newValue != null) { createChallengeController.selectedMetric.value = newValue; // Find the selected metric object to get its ID and units var selectedMetricObj = createChallengeController .apiMetricsList .firstWhere( (element) => element.metricName == newValue, ); createChallengeController.selectedMetricId.value = selectedMetricObj.metricId!; // Update units list based on selected metric createChallengeController .updateUnitsForSelectedMetric( selectedMetricObj.metricUnits ?? [], ); // Clear selected unit when metric changes createChallengeController.selectedUnit.value = ''; } }, borderColor: lightGreyColor, filledColor: Colors.white, ); }), SizedBox(height: 12.h), CustomTextFieldContainerInsideLabel( controller: createChallengeController.targetValueController, borderColor: lightGreyColor, label: 'Target Value *', fillColor: Colors.white, lebelTextPadding: 12.h, keyboardType: TextInputType.numberWithOptions(), topContentPadding: 20.h, ), SizedBox(height: 12.h), // Updated Unit Dropdown Obx(() { return EnhancedLebelTextDropdown( title: "Unit *", items: createChallengeController.localUnitsList.toList(), selectedItem: createChallengeController.selectedUnit.isEmpty ? null : createChallengeController.selectedUnit.value, onChanged: (String? newValue) { if (newValue != null) { createChallengeController.selectedUnit.value = newValue; } }, borderColor: lightGreyColor, filledColor: Colors.white, ); }), ], ), ), SizedBox(height: 24.h), // Challenge Visibility Section challengeVisibilitySection(), SizedBox(height: 24.h), Obx(() { if (createChallengeController.challengeVisibilityId.value == 3) { return Column( children: [membersSelectionSection(), SizedBox(height: 32.h)], ); } return SizedBox(height: 32.h); }), Row( children: [ Expanded( child: ElevatedButton( onPressed: () => Get.back(), style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), padding: EdgeInsets.symmetric(vertical: 14.h), ), child: Text( 'Cancel', style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.w600, ), ), ), ), SizedBox(width: 16.w), Expanded( child: Obx( () => ElevatedButton( onPressed: () async { if (!createChallengeController .isCreatingChallengeLoading .value) { await createChallengeController .createChallenge() .then((value) async { if (value) { await riseController.fetchOngoingChallenges( isRefresh: true, ); await riseController.fetchUpcomingChallenges( isRefresh: true, ); await riseController.fetchCreatedChallenges( isRefresh: true, ); await riseController.fetchJoinedChallenges( isRefresh: true, ); } }); } }, style: ElevatedButton.styleFrom( backgroundColor: createChallengeController .isCreatingChallengeLoading .value ? lightGreyColor : Color(primaryColor), foregroundColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), padding: EdgeInsets.symmetric(vertical: 14.h), ), child: createChallengeController .isCreatingChallengeLoading .value ? SizedBox( height: 20.h, width: 20.w, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2.0, ), ) : Text( 'Create', style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), SizedBox(height: 60.h), ], ), ), ); } Widget challengeVisibilitySection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (SharedServices.getLoginDetails()?.data?.userRole == ApiEnum.coachUserRole) Obx( () => Row( children: [ Checkbox( value: createChallengeController.isAutoJoin.value, onChanged: (bool? value) { createChallengeController.isAutoJoin.value = value ?? true; }, activeColor: Color(primaryColor), checkColor: Colors.white, ), Expanded( child: Text( 'Auto-join challenge (You will automatically join this challenge)', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, color: Colors.black87, ), ), ), ], ), ), if (SharedServices.getLoginDetails()?.data?.userRole == ApiEnum.coachUserRole) SizedBox(height: 15.h), Text( 'Challenge Visibility', style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.w600, color: Colors.black, ), ), SizedBox(height: 10.h), Obx(() { return Column( children: [ RadioListTile( title: Text( 'Public', style: TextStyle( fontSize: 15.sp, fontWeight: FontWeight.w500, ), ), subtitle: Text( 'Anyone can join this challenge', style: TextStyle(fontSize: 13.sp, color: greyTextColor1), ), value: ApiEnum.publicPostVisibilityID, groupValue: createChallengeController.challengeVisibilityId.value, activeColor: Color(primaryColor), onChanged: (int? value) { if (value != null) { createChallengeController.challengeVisibilityId.value = value; // Clear selected connections when switching to public if (value == 1) { createChallengeController.selectedConnections.clear(); createChallengeController.selectedIds.clear(); } } }, contentPadding: EdgeInsets.zero, ), RadioListTile( title: Text( 'Exclusive', style: TextStyle( fontSize: 15.sp, fontWeight: FontWeight.w500, ), ), subtitle: Text( 'Only selected members can join', style: TextStyle(fontSize: 13.sp, color: greyTextColor1), ), value: ApiEnum.exclusivePostVisibilityID, groupValue: createChallengeController.challengeVisibilityId.value, activeColor: Color(primaryColor), onChanged: (int? value) { if (value != null) { createChallengeController.challengeVisibilityId.value = value; } }, contentPadding: EdgeInsets.zero, ), ], ); }), ], ); } Widget membersSelectionSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Select Members and Groups', style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.w600, color: Colors.black, ), ), SizedBox(height: 5.h), Text( 'Select members and groups to add to your challenge.', style: TextStyle(fontSize: verySmallSizeText, color: greyTextColor1), ), SizedBox(height: 20.h), // Search Field TextField( controller: createChallengeController.connectionSearchController, decoration: InputDecoration( hintText: 'Search for members', hintStyle: TextStyle( fontWeight: FontWeight.w600, color: greyBorderColor, ), prefixIcon: Padding( padding: EdgeInsets.only(left: 20.w), child: Icon(Icons.search), ), suffixIcon: IconButton( icon: Padding( padding: EdgeInsets.only(right: 10.w), child: Icon(Icons.close), ), onPressed: () => createChallengeController.clearSearch(), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10.r), borderSide: BorderSide(color: lightGreyColor), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10.r), borderSide: BorderSide(color: lightGreyColor), ), filled: true, fillColor: textFieldFillColor, ), onChanged: (newValue) { createChallengeController.connectionSearchValue.value = newValue; }, onSubmitted: (_) { createChallengeController.fetchExclusiveConnections(refresh: true); }, ), SizedBox(height: 20.h), Container( height: 400.h, decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8.r), ), child: Obx( () => createChallengeController.isConnectionsFetchLoading.value && createChallengeController .exclusiveConnections .data ?.items == null ? Center( child: CircularProgressIndicator(color: Colors.black), ) : createChallengeController .exclusiveConnections .data ?.items == null || createChallengeController .exclusiveConnections .data! .items! .isEmpty ? Center( child: RefreshIndicator( onRefresh: _onRefresh, child: ListView( physics: AlwaysScrollableScrollPhysics(), children: [ SizedBox(height: 150.h), Center(child: Text('No members found')), ], ), ), ) : RefreshIndicator( backgroundColor: Colors.white, color: Colors.black, onRefresh: _onRefresh, child: ListView.builder( controller: _membersScrollController, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.all(8.w), itemCount: createChallengeController .exclusiveConnections .data! .items! .length + (createChallengeController .isConnectionPaginationLoading .value ? 1 : 0), itemBuilder: (_, i) { if (i == createChallengeController .exclusiveConnections .data! .items! .length) { return Padding( padding: EdgeInsets.symmetric(vertical: 16.h), child: Center( child: CircularProgressIndicator( backgroundColor: Colors.white, color: Colors.black, ), ), ); } final connection = createChallengeController .exclusiveConnections .data! .items![i]; final String itemId = connection.tribeId != null ? 'tribe_${connection.tribeId}' : 'user_${connection.connectedUserId}'; return Obx(() { final bool isSelected = createChallengeController .selectedIds .contains(itemId); return ExclusiveConnectionTile( name: connection.tribeId != null ? connection.tribeName ?? 'Unknown Tribe' : connection.fullName ?? 'Unknown User', type: connection.tribeId != null ? 'group' : 'user', members: connection.totalMemberCount, imageUrl: connection.tribeId != null ? connection.adminProfilePicture ?? '' : connection.profilePicture ?? '', isSelected: isSelected, onTap: () { createChallengeController .exclusiveConnectionToggleSelection( tribeId: connection.tribeId, connectedUserId: connection.connectedUserId, ); }, ); }); }, ), ), ), ), ], ); } } Widget filePickerTile({ required String label, String? fileName, required IconData icon, required VoidCallback onTap, VoidCallback? onClear, bool isRequired = false, }) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(10.r), child: Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), decoration: BoxDecoration( color: textFieldFillColor, borderRadius: BorderRadius.circular(10.r), border: Border.all(color: lightGreyColor), ), child: Row( children: [ Icon(icon, color: Colors.grey), SizedBox(width: 12.w), Expanded( child: Text( fileName ?? "$label${isRequired ? ' *' : ''}", style: TextStyle( fontSize: 15.sp, color: fileName == null ? Colors.black54 : Colors.black, fontWeight: fileName == null ? FontWeight.normal : FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), if (fileName != null && fileName.isNotEmpty && onClear != null) GestureDetector( onTap: () { onClear(); }, child: Padding( padding: EdgeInsets.only(left: 8.w), child: Icon(Icons.close, color: Colors.grey), ), ), ], ), ), ); }