// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously import 'package:cross_file/cross_file.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:hoshan/core/gen/assets.gen.dart'; import 'package:hoshan/core/services/api/dio_service.dart'; import 'package:hoshan/core/services/file_manager/pick_file_services.dart'; import 'package:hoshan/core/utils/crop_image.dart'; import 'package:hoshan/core/utils/date_time.dart'; import 'package:hoshan/core/utils/strings.dart'; import 'package:hoshan/data/model/ai/bots_model.dart'; import 'package:hoshan/data/model/edittext_state_model.dart'; import 'package:hoshan/data/model/forum_model.dart'; import 'package:hoshan/data/model/sort_by_model.dart'; import 'package:hoshan/ui/screens/splash/cubit/user_info_cubit.dart'; import 'package:hoshan/ui/theme/colors.dart'; import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart'; import 'package:hoshan/ui/theme/responsive.dart'; import 'package:hoshan/ui/theme/text.dart'; import 'package:hoshan/ui/widgets/components/button/loading_button.dart'; import 'package:hoshan/ui/widgets/components/dialog/dialog_handler.dart'; import 'package:hoshan/ui/widgets/components/image/custome_image.dart'; import 'package:hoshan/ui/widgets/components/image/network_image.dart'; import 'package:hoshan/ui/widgets/components/snackbar/snackbar_manager.dart'; import 'package:hoshan/ui/widgets/components/text/credit_cost.dart'; import 'package:hoshan/ui/widgets/components/text/labeled_text_field.dart'; import 'package:image_cropper/image_cropper.dart'; class BottomSheetHandler { final BuildContext context; BottomSheetHandler(this.context); close(BuildContext c) { c.pop(); } Future showStringList( {required final List values, required final String title, final Function(String)? onSelect}) async { final ScrollController scrollController = ScrollController(); await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, builder: (context) => Container( width: MediaQuery.sizeOf(context).width, constraints: BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height / 2.4), padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ const SizedBox( height: 32, ), Text( title, style: AppTextStyles.body3.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ), const SizedBox( height: 12, ), Expanded( child: Directionality( textDirection: TextDirection.rtl, child: Scrollbar( thumbVisibility: true, trackVisibility: true, interactive: true, controller: scrollController, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), controller: scrollController, child: Column( children: List.generate( values.length, (index) => GestureDetector( onTap: () { onSelect?.call(values[index]); close(context); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: Row( children: [ Text( values[index], style: AppTextStyles.body4.copyWith( color: Theme.of(context) .colorScheme .onSurface), ), ], ), ), ), ), ), ), ), ), ), ], ), ), ); } Future showPickImage( {final bool withAvatar = false, final Function(XFile)? onSelect, final bool profile = false}) async { await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, builder: (context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 64.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ if (!kIsWeb) _sqrBtn( icon: Assets.icon.outline.camera, title: 'دوربین', onTap: () async { try { XFile? file = await PickFileService.getCameraImage(); if (file != null) { file = await CropImage().getCroppedFile( context: context, sourcePath: file.path, aspectRatioPresets: profile ? CropAspectRatioPreset.square : null); if (file != null) { onSelect?.call(file); } close(context); } } catch (e) { if (kDebugMode) { print('Error Choosing Image: $e'); } } }), if (!kIsWeb) const SizedBox( width: 24, ), _sqrBtn( icon: Assets.icon.outline.galleryAdd, title: 'گالری', onTap: () async { try { final file = await PickFileService(context) .getFile(fileType: FileType.image); if (file != null) { if (kIsWeb) { onSelect?.call(file.single); } else { final croppedFile = await CropImage().getCroppedFile( context: context, sourcePath: file.single.path, aspectRatioPresets: profile ? CropAspectRatioPreset.square : null); if (croppedFile != null) { onSelect?.call(XFile(croppedFile.path, name: file.single.name, mimeType: file.single.mimeType, length: await file.single.length(), lastModified: await file.single.lastModified(), bytes: await croppedFile.readAsBytes())); } else { onSelect?.call(file.single); } } close(context); } } catch (e) { if (kDebugMode) { print('Error Choosing Image: $e'); } } }), // if (withAvatar) // Row( // mainAxisSize: MainAxisSize.min, // children: [ // const SizedBox( // width: 24, // ), // _sqrBtn( // icon: Assets.icon.outline.emojiHappy, // title: 'آواتار', // onTap: () async { // final file = await PickFileService(context) // .getFile(fileType: FileType.image); // if (file != null) { // onSelect?.call(file.single); // context.pop(); // } // }), // ], // ) ], ), ); }, ); } Future showIncomeFormula() async { await showModalBottomSheet( showDragHandle: true, backgroundColor: Theme.of(context).colorScheme.surface, context: context, builder: (context) { return SizedBox( width: MediaQuery.sizeOf(context).width, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( 'فرمول محاسبه درآمد از طریق ساخت دستیار', style: AppTextStyles.body3.copyWith( color: context.read().isDark() ? Theme.of(context).colorScheme.primary : AppColors.primaryColor[900], fontWeight: FontWeight.bold), ), const SizedBox( height: 12, ), const Divider(), const SizedBox( height: 12, ), Text( '10 درصد از کل سکه های مصرف شده توسط دستیار شما', style: AppTextStyles.body4 .copyWith(color: Theme.of(context).colorScheme.onSurface), textDirection: TextDirection.rtl, ), ], ), ), ); }, ); } Future showInventory({required final Bots bot}) async { await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, showDragHandle: true, builder: (context) { return Container( width: MediaQuery.sizeOf(context).width, padding: const EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(0, 24, 0, 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Assets.icon.outline.coin.svg(), const CreditCost(), ], ), Text( 'سکه‌های باقی‌مانده', style: AppTextStyles.body3.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ), ], ), ), const Divider(), Container( alignment: Alignment.centerRight, margin: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( bot.name ?? '', style: AppTextStyles.body4.copyWith( color: Theme.of(context).colorScheme.onSurface), ), const SizedBox( width: 8, ), ImageNetwork( url: bot.image, width: 36, height: 36, radius: 360, color: bot.image != null && bot.image!.contains('/llm') ? Theme.of(context).colorScheme.onSurface : null, ), ], ), ), Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Assets.icon.outline.coin.svg(), Padding( padding: const EdgeInsets.only(top: 4.0), child: Text( '${bot.cost ?? 0}', style: AppTextStyles.body4.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ), ) ], ), Text( 'سکه برای هر پیام', style: AppTextStyles.body4.copyWith( color: Theme.of(context).colorScheme.onSurface), ), ], ), const SizedBox( height: 16, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ BlocBuilder( builder: (context, state) { if (bot.cost != null && bot.cost! > 0) { final credit = (UserInfoCubit.userInfoModel.freeCredit ?? 0) + (UserInfoCubit.userInfoModel.credit ?? 0) + (UserInfoCubit.userInfoModel.gift_credit ?? 0); final count = (credit / bot.cost!).floor(); return Text( '$count پیام', textDirection: TextDirection.rtl, style: AppTextStyles.body4.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ); } else { return Text( 'نامحدود', textDirection: TextDirection.rtl, style: AppTextStyles.body4.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ); } }, ), Text( 'پیام های باقی‌مانده', style: AppTextStyles.body3.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface), ), ], ), ], ) ], ), ); }, ); } Future showSortBy( {required final SortByModel initailValue, required final List items, Function(SortByModel)? onSelected}) async { await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, enableDrag: true, showDragHandle: true, useSafeArea: true, builder: (context) { return Container( width: MediaQuery.sizeOf(context).width, padding: const EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 16), child: Directionality( textDirection: TextDirection.rtl, child: ListView.builder( itemCount: items.length, shrinkWrap: true, itemBuilder: (context, index) { final item = items[index]; return GestureDetector( onTap: () { onSelected?.call(item); close(context); }, child: Row( children: [ Checkbox( value: initailValue.value == item.value, onChanged: (val) { onSelected?.call(item); close(context); }), const SizedBox( width: 4, ), Text( item.text, style: AppTextStyles.body4.copyWith( color: Theme.of(context).colorScheme.onSurface), ) ], ), ); }, ), ), ); }, ); } Future showAddComment( {final Function(String, XFile?)? onSend, final Comment? comment}) async { ValueNotifier file = ValueNotifier(null); ValueNotifier text = ValueNotifier(''); ValueNotifier loading = ValueNotifier(false); final EdittextStateModel stateModel = EdittextStateModel( label: 'متن ${comment == null ? 'سوال' : 'پاسخ'}', hintText: '${comment == null ? 'سوال' : 'پاسخ'} خود را بنویسید...', ); int? daysAgo; if (comment != null) { daysAgo = DateTimeUtils.getDaysBetweenNowAnd(comment.createdAt!); } await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, isScrollControlled: true, enableDrag: true, showDragHandle: true, useSafeArea: true, constraints: BoxConstraints( minHeight: MediaQuery.sizeOf(context).height * 0.8, maxHeight: double.infinity), builder: (context) { return Responsive(context).maxWidthInDesktop( maxWidth: 800, child: (contxet, mw) => Container( width: mw, padding: const EdgeInsets.symmetric(horizontal: 16).copyWith(bottom: 16), child: SingleChildScrollView( child: ValueListenableBuilder( valueListenable: loading, builder: (context, wait, _) { return Column( children: [ if (comment != null) Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.only(bottom: 24), decoration: BoxDecoration( border: Border.all( color: AppColors.gray.defaultShade), borderRadius: BorderRadius.circular(10)), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ comment.createdAt != null ? Row( children: [ Assets.icon.outline.clock.svg( width: 20, height: 20, color: AppColors .gray[context .read< ThemeModeCubit>() .isDark() ? 600 : 900]), const SizedBox( width: 8, ), Text( daysAgo == 0 ? 'امروز' : ('$daysAgo روز پیش'), style: AppTextStyles.body5 .copyWith( color: Theme.of( context) .colorScheme .onSurface), textDirection: TextDirection.rtl, ) ], ) : const SizedBox.shrink(), Row( children: [ Text( comment.user?.username ?? '', style: AppTextStyles.body4 .copyWith( fontWeight: FontWeight.bold, color: Theme.of(context) .colorScheme .onSurface), ), const SizedBox( width: 8, ), ], ) ], ), const SizedBox( height: 8, ), SizedBox( width: MediaQuery.sizeOf(context).width, child: Text( comment.text ?? '', style: AppTextStyles.body3.copyWith( color: Theme.of(context) .colorScheme .onSurface), textDirection: comment.text != null && comment.text! .startsWithEnglish() ? TextDirection.ltr : TextDirection.rtl, ), ), const SizedBox( height: 8, ), if (comment.image != null) Column( children: [ Container( margin: const EdgeInsets.symmetric( vertical: 12), constraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context) .height * 0.2), alignment: Alignment.centerRight, child: AspectRatio( aspectRatio: 16 / 9, child: ImageNetwork( url: DioService.baseURL + comment.image!, radius: 16, showHero: true, ), ), ), const SizedBox( height: 8, ), ], ), ], ), ), const SizedBox( width: 12, ), ImageNetwork( url: comment.user != null && comment.user!.image != null ? DioService.baseURL + comment.user!.image! : 'err', width: 40, height: 40, radius: 360, ) ], ), ), LabeledTextField( stateController: stateModel, maxLines: 6, minLines: 6, enabled: !wait, onChange: (value) { text.value = value; }, ), const SizedBox( height: 12, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ValueListenableBuilder( valueListenable: text, builder: (context, message, _) { return LoadingButton( loading: wait, onPressed: message.isNotEmpty ? () async { loading.value = true; await onSend?.call( message, file.value); loading.value = false; } : null, color: AppColors.green.defaultShade, child: Row( mainAxisSize: MainAxisSize.min, children: [ Transform.flip( flipX: true, child: Assets.icon.bold.send .svg( color: Colors.white), ), const SizedBox( width: 8, ), Text( 'انتشار', style: AppTextStyles.body3 .copyWith( color: Colors.white, fontWeight: FontWeight .bold), ), ], )); }), ], ), const SizedBox( height: 12, ), ValueListenableBuilder( valueListenable: file, builder: (context, value, child) { return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ LoadingButton( loading: wait, onPressed: () async { if (value != null) { file.value = null; return; } file.value = (await PickFileService( context) .getFile( fileType: FileType .image)) ?.single; }, color: value != null ? AppColors.red.defaultShade : AppColors .primaryColor.defaultShade, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( value == null ? 'اضافه کردن عکس' : 'حذف عکس', style: AppTextStyles.body3 .copyWith( color: Colors.white, fontWeight: FontWeight.bold), ), const SizedBox( width: 8, ), value != null ? Assets.icon.outline.trash .svg( color: Colors.white) : Assets .icon.outline.galleryAdd .svg( color: Colors.white) ], )), ], ); }, ), ], ), const SizedBox( height: 12, ), ValueListenableBuilder( valueListenable: file, builder: (context, value, child) { if (value != null) { return Padding( padding: const EdgeInsets.only(right: 8.0), child: GestureDetector( onTap: () => DialogHandler(context: context) .showImageHero(image: value.path), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: AspectRatio( aspectRatio: 16 / 9, child: CustomeImage( src: value.path, fit: BoxFit.cover, ), ), ), ), ); } return const SizedBox.shrink(); }, ), ], ) ], ); }), ), ), ); }, ); } Widget _sqrBtn( {required final SvgGenImage icon, final String? title, final Function()? onTap}) { final isDark = context.read().state == ThemeMode.dark; return GestureDetector( onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 64, height: 64, padding: const EdgeInsets.all(12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: isDark ? Theme.of(context).colorScheme.onSurface : AppColors.gray[400]), child: icon.svg(color: AppColors.black.defaultShade)), if (title != null) Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox( height: 4, ), Text( title, style: AppTextStyles.body3.copyWith( color: isDark ? Colors.white : AppColors.primaryColor[800]), ) ], ) ], ), ); } Future showReportOptions() async { await showModalBottomSheet( context: context, backgroundColor: Theme.of(context).colorScheme.surface, showDragHandle: true, useSafeArea: true, builder: (context) { final textStyle = AppTextStyles.body4 .copyWith(color: Theme.of(context).colorScheme.onSurface); final options = [ 'محتوای توهین‌آمیز یا نفرت‌پراکن', 'اسپم یا تبلیغات نامربوط', 'اطلاعات نادرست یا گمراه‌کننده', 'نقض حریم خصوصی' ]; return Directionality( textDirection: TextDirection.rtl, child: Column( children: [ ...List.generate( options.length, (index) { return Column( children: [ InkWell( onTap: () { context.pop(); SnackBarManager(context, id: 'report-success').show( status: SnackBarStatus.success, message: 'گزارش با موفقیت ارسال شد'); }, child: ListTile( title: Text( options[index], style: textStyle, ), ), ), const Divider(), ], ); }, ), InkWell( onTap: () { DialogHandler(context: context).showCustomeReport(); }, child: ListTile( title: Text('دیگر (لطفاً توضیح دهید)', style: textStyle), ), ), ], ), ); }, ); } }