484 lines
18 KiB
Dart
484 lines
18 KiB
Dart
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';
|
|
|
|
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: 24,
|
|
color: data.titleColor,
|
|
),
|
|
),
|
|
if (data.titleIcon != 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<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: [
|
|
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: 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();
|
|
}
|
|
}
|