didvan-app/lib/utils/action_sheet.dart

477 lines
17 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:bot_toast/bot_toast.dart';
import 'package:cached_network_image/cached_network_image.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';
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<void> showLogoLoadingIndicator() async {
await showDialog(
barrierDismissible: false,
context: context,
builder: (context) => Center(
child: Image.asset(
Assets.loadingAnimation,
width: 160,
height: 160,
),
),
);
}
Future<void> 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<void> 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.titleIcon != null)
Icon(
data.titleIcon,
color: data.titleColor ??
Theme.of(context).colorScheme.title,
),
if (data.titleIcon != 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)
Expanded(
child: DidvanButton(
onPressed: () {
Navigator.of(context).pop();
data.onDismissed?.call();
},
title: data.dismissTitle ?? 'بازگشت',
style: ButtonStyleMode.secondary,
),
),
if (data.hasDismissButton) const SizedBox(width: 20),
if (data.hasConfirmButton)
Expanded(
flex: data.smallDismissButton ? 2 : 1,
child: DidvanButton(
style: ButtonStyleMode.primary,
onPressed: () {
data.onConfirmed?.call();
pop();
},
title: data.confrimTitle ?? 'تایید',
),
),
],
),
],
),
),
),
);
}
Future<void> 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.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.title != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (data.titleIcon != null)
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Icon(
data.titleIcon,
size: 20,
color: data.titleColor,
),
),
if (data.titleIcon != null)
const SizedBox(
width: 8,
),
Expanded(
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<void> botsDialogSelect(
{required final BuildContext context,
required final HistoryAiChatState state}) async {
ActionSheetUtils(context).openDialog(
data: ActionSheetData(
hasConfirmButton: false,
hasDismissButton: false,
content: Column(
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 8.0),
// child: InkWell(
// onTap: () {
// ActionSheetUtils.pop();
// },
// child: const Icon(DidvanIcons.close_solid)),
// )
// ],
// ),
// SearchField(
// title: 'هوش مصنوعی',
// value: state.search,
// onChanged: (value) {
// state.search = value;
// if (value.isEmpty) {
// state.getBots();
// return;
// }
// state.timer?.cancel();
// state.timer = Timer(const Duration(seconds: 1), () {
// state.getSearchBots(value);
// });
// },
// focusNode: FocusNode()),
// const SizedBox(
// height: 12,
// ),
SizedBox(
width: double.infinity,
height: MediaQuery.sizeOf(context).height / 3,
child: ValueListenableBuilder<bool>(
valueListenable: state.loadingBots,
builder: (context, value, child) => value
? 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: [
ClipOval(
child: CachedNetworkImage(
imageUrl: bot.image.toString(),
width: 42,
height: 42,
),
),
const SizedBox(width: 12),
Text(bot.name.toString())
],
),
),
);
}),
),
)
],
)));
}
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: 24,
top: 24,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface),
child: const BackButton()),
),
],
),
),
);
}
static PopupMenuItem<dynamic> 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();
}
}