import 'dart:async'; import 'dart:io'; import 'dart:ui'; import 'package:bot_toast/bot_toast.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class ActionSheetUtils { final BuildContext context; ActionSheetUtils(this.context); MediaQueryData get mediaQueryData { final ds = MediaQuery.of(context).size; double width = ds.width; final shortestSide = ds.shortestSide; final bool useMobileLayout = shortestSide < 600; if (kIsWeb && !useMobileLayout) { width = ds.height * 9 / 16; } return MediaQuery.of(context).copyWith(size: Size(width, ds.height)); } Future showLogoLoadingIndicator() async { await showDialog( barrierDismissible: false, context: context, builder: (context) => Center( child: Image.asset( Assets.loadingAnimation, width: 160, height: 160, ), ), ); } Future showAlert(AlertData alertData) async { bool isInit = true; Color backgroundColor; Color foregroundColor; switch (alertData.aLertType) { case ALertType.info: backgroundColor = Theme.of(context).colorScheme.focused; foregroundColor = Theme.of(context).colorScheme.focusedBorder; break; case ALertType.success: backgroundColor = Theme.of(context).colorScheme.successBack; foregroundColor = Theme.of(context).colorScheme.success; break; case ALertType.error: backgroundColor = Theme.of(context).colorScheme.errorBack; foregroundColor = Theme.of(context).colorScheme.error; break; } BotToast.showNotification( backgroundColor: backgroundColor, title: (cancelFunc) => StatefulBuilder( builder: (context, setState) { if (isInit) { Future.delayed(Duration.zero, () { setState(() {}); isInit = false; }); } return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanText(alertData.message, color: foregroundColor), AnimatedContainer( duration: const Duration(seconds: 2), width: isInit ? MediaQuery.of(context).size.width - 32 : 0, height: 2, color: foregroundColor, ), ], ); }, ), ); } Future showBottomSheet({required ActionSheetData data}) async { await showModalBottomSheet( constraints: BoxConstraints( maxWidth: mediaQueryData.size.width, ), backgroundColor: data.backgroundColor ?? Colors.transparent, isScrollControlled: true, context: context, builder: (context) => Container( padding: data.hasPadding ? const EdgeInsets.all(20).copyWith(top: 0) : EdgeInsets.zero, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical( top: Radius.circular(10), ), ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), Center( child: Container( height: 3, width: 50, color: Theme.of(context).colorScheme.hint, ), ), const SizedBox(height: 8), if (data.title != null) Row( children: [ if (data.titleIconWidget != null) data.titleIconWidget! else if (data.titleIcon != null) Icon( data.titleIcon, color: data.titleColor ?? Theme.of(context).colorScheme.title, ), if (data.titleIcon != null || data.titleIconWidget != null) const SizedBox(width: 8), DidvanText( data.title!, style: Theme.of(context).textTheme.titleMedium, color: data.titleColor ?? Theme.of(context).colorScheme.title, ) ], ), const SizedBox(height: 28), data.content, const SizedBox(height: 28), if (!data.withoutButtonMode) Row( children: [ if (data.hasDismissButton) if (data.hasConfirmButton) Expanded( flex: 2, child: DidvanButton( style: ButtonStyleMode.primary, onPressed: () { data.onConfirmed?.call(); pop(); }, title: data.confrimTitle ?? 'تایید', ), ), if (data.hasDismissButton) const SizedBox(width: 20), Expanded( child: DidvanButton( onPressed: () { Navigator.of(context).pop(); data.onDismissed?.call(); }, title: data.dismissTitle ?? 'بازگشت', style: ButtonStyleMode.secondary, ), ), ], ), ], ), ), ), ); } Future openDialog( {required ActionSheetData data, final bool barrierDismissible = true}) async { await showDialog( context: context, barrierDismissible: barrierDismissible, builder: (context) => BackdropFilter( filter: ImageFilter.blur( sigmaX: data.isBackgroundDropBlur ? 10 : 0, sigmaY: data.isBackgroundDropBlur ? 10 : 0), child: Dialog( backgroundColor: data.backgroundColor ?? Theme.of(context).colorScheme.surface, shape: const RoundedRectangleBorder( borderRadius: DesignConfig.mediumBorderRadius, ), child: Container( decoration: BoxDecoration( borderRadius: DesignConfig.mediumBorderRadius, color: data.backgroundColor ?? Theme.of(context).colorScheme.surface, ), width: mediaQueryData.size.width * 0.8, padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (data.title != null) Row( mainAxisSize: MainAxisSize.min, children: [ if (data.titleIconWidget != null) GestureDetector( onTap: () => Navigator.of(context).pop(), child: data.titleIconWidget!, ) else if (data.titleIcon != null) GestureDetector( onTap: () => Navigator.of(context).pop(), child: Icon( data.titleIcon, size: 24, color: data.titleColor, ), ), if (data.titleIcon != null || data.titleIconWidget != null) const SizedBox( width: 4, ), Expanded( child: Padding( padding: const EdgeInsets.only(top: 4.0), child: DidvanText( data.title!, style: Theme.of(context).textTheme.displaySmall, color: data.titleColor, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox( height: 12, ), data.content, const SizedBox( height: 12, ), Row( children: [ if (data.hasDismissButton) Expanded( child: DidvanButton( onPressed: () { data.onDismissed?.call(); pop(); }, title: data.dismissTitle ?? 'بازگشت', style: ButtonStyleMode.flat, ), ), if (data.hasDismissButton && data.hasDismissButton) const SizedBox( width: 20, ), if (data.hasDismissButton) Expanded( child: DidvanButton( onPressed: () { data.onConfirmed?.call(); if (data.hasConfirmButtonClose) { pop(); } }, title: data.confrimTitle ?? 'تایید', ), ), ], ), ], ), ), ), ), ); } Future botsDialogSelect({ required final BuildContext context, }) async { ActionSheetUtils(context).openDialog( data: ActionSheetData( hasConfirmButton: false, hasDismissButton: false, content: SizedBox( width: double.infinity, height: MediaQuery.sizeOf(context).height / 3, child: Consumer( builder: (context, state, child) { return state.loadingBots ? Center( child: Image.asset( Assets.loadingAnimation, width: 60, height: 60, ), ) : state.bots.isEmpty ? Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: EmptyState( asset: Assets.emptyResult, title: 'نتیجه‌ای پیدا نشد', height: 120, ), ) : ListView.builder( itemCount: state.bots.length, physics: const BouncingScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { final bot = state.bots[index]; return InkWell( onTap: () { ActionSheetUtils(context).pop(); state.bot = bot; state.update(); }, child: Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( border: index == state.bots.length - 1 ? null : Border( bottom: BorderSide( color: Theme.of(context) .colorScheme .border, width: 1))), child: Row( children: [ SkeletonImage( imageUrl: bot.image.toString(), width: 42, height: 42, borderRadius: BorderRadius.circular(360), ), const SizedBox(width: 12), Expanded( child: DidvanText( bot.name.toString(), maxLines: 1, overflow: TextOverflow.ellipsis, )) ], ), ), ); }); }), ))); } void openInteractiveViewer(BuildContext context, String image, bool isFile) { showDialog( context: context, barrierDismissible: true, builder: (context) => Dialog( backgroundColor: Colors.transparent, insetPadding: EdgeInsets.zero, child: Stack( children: [ Positioned.fill( child: InteractiveViewer( child: Padding( padding: const EdgeInsets.all(20.0), child: Center( child: isFile ? ClipRRect( borderRadius: DesignConfig.lowBorderRadius, child: Image.file(File(image))) : image.startsWith('blob:') ? ClipRRect( borderRadius: DesignConfig.lowBorderRadius, child: Image.network(image)) : SkeletonImage( imageUrl: image, ), ), ), ), ), Positioned( right: 16, top: 0, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).colorScheme.surface), child: const BackButton()), ), ], ), ), ); } static PopupMenuItem popUpBtns({ required final String value, required final IconData icon, final Color? color, final double? height, final double? size, }) { return PopupMenuItem( value: value, height: height ?? 46, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: color, size: size, ), const SizedBox( width: 12, ), DidvanText( value, color: color, fontSize: size, ), ], ), ); } void pop() { DesignConfig.updateSystemUiOverlayStyle(); Navigator.of(context).pop(); } }