Houshan-Basa/lib/ui/widgets/components/dialog/bottom_sheets.dart

898 lines
39 KiB
Dart

// 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<void> showStringList(
{required final List<String> 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<void> 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<ThemeModeCubit>().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<UserInfoCubit, UserInfoState>(
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<SortByModel> 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<XFile?> file = ValueNotifier(null);
ValueNotifier<String> text = ValueNotifier('');
ValueNotifier<bool> 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<ThemeModeCubit>().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),
),
),
],
),
);
},
);
}
}