11/06/1403 -- Rhmn -- fix web and change ai message bar and dall e bot. appVersion: 3.3.4
This commit is contained in:
parent
dfac461eee
commit
0512e22727
|
|
@ -4,6 +4,7 @@ import 'package:didvan/models/ai/chats_model.dart';
|
|||
class AiChatArgs {
|
||||
final BotsModel bot;
|
||||
final ChatsModel? chat;
|
||||
final Prompts? prompts;
|
||||
|
||||
AiChatArgs({required this.bot, this.chat});
|
||||
AiChatArgs({required this.bot, this.chat, this.prompts});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class FilesModel {
|
|||
: name != null
|
||||
? p.extension(name)
|
||||
: '';
|
||||
|
||||
main = File(path);
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +43,9 @@ class FilesModel {
|
|||
}
|
||||
|
||||
bool isImage() {
|
||||
return image ?? lookupMimeType(path)?.startsWith('image/') ?? false;
|
||||
return image ??
|
||||
lookupMimeType(path)?.startsWith('image/') ??
|
||||
false || path.contains(".png");
|
||||
}
|
||||
|
||||
bool isNetwork() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -66,7 +65,6 @@ class VoiceService {
|
|||
|
||||
static Future<void> voiceHelper(
|
||||
{required String src,
|
||||
final Uint8List? bytes,
|
||||
final Duration? duration,
|
||||
final Function()? startTimer,
|
||||
final Function()? stopTimer}) async {
|
||||
|
|
@ -96,7 +94,7 @@ class VoiceService {
|
|||
// AudioSource.uri(Uri.parse(blobUrl)),
|
||||
// );
|
||||
// } else {
|
||||
await audioPlayer.setUrl(source);
|
||||
await audioPlayer.setUrl(source.replaceAll('%3A', ''));
|
||||
// }
|
||||
} else if (src.startsWith('blob:') || src == '') {
|
||||
await audioPlayer.setUrl(src);
|
||||
|
|
|
|||
|
|
@ -364,12 +364,14 @@ class ActionSheetUtils {
|
|||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.border,
|
||||
width: 1))),
|
||||
border: index == state.bots.length - 1
|
||||
? null
|
||||
: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.border,
|
||||
width: 1))),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
|
|
|
|||
|
|
@ -164,80 +164,96 @@ class _AiState extends State<Ai> {
|
|||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 32),
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushNamed(Routes.aiChat,
|
||||
arguments: AiChatArgs(
|
||||
bot: bot,
|
||||
)),
|
||||
child: Row(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: DesignConfig.defaultShadow,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border.all(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: DesignConfig.defaultShadow,
|
||||
color:
|
||||
Theme.of(context).colorScheme.border),
|
||||
borderRadius: DesignConfig.highBorderRadius),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: Form(
|
||||
child: Row(
|
||||
children: [
|
||||
const MessageBarBtn(
|
||||
enable: true,
|
||||
icon:
|
||||
DidvanIcons.mic_regular),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
Theme.of(context).colorScheme.surface,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.border),
|
||||
borderRadius:
|
||||
DesignConfig.highBorderRadius),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
textInputAction:
|
||||
TextInputAction.newline,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
minLines: 1,
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'بنویسید...',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: Theme.of(
|
||||
context)
|
||||
.colorScheme
|
||||
.disabledText),
|
||||
child: Form(
|
||||
child: Row(
|
||||
children: [
|
||||
const MessageBarBtn(
|
||||
enable: true,
|
||||
icon: DidvanIcons
|
||||
.mic_regular),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const MessageBarBtn(
|
||||
enable: false,
|
||||
icon: Icons
|
||||
.attach_file_rounded),
|
||||
],
|
||||
))))
|
||||
],
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.newline,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
minLines: 1,
|
||||
enabled: false,
|
||||
decoration:
|
||||
InputDecoration(
|
||||
border:
|
||||
InputBorder.none,
|
||||
hintText: 'بنویسید...',
|
||||
hintStyle: Theme.of(
|
||||
context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: Theme.of(
|
||||
context)
|
||||
.colorScheme
|
||||
.disabledText),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const MessageBarBtn(
|
||||
enable: false,
|
||||
icon: Icons
|
||||
.attach_file_rounded),
|
||||
],
|
||||
))))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 8, 8, 4),
|
||||
child: DidvanText(
|
||||
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
|
||||
fontSize: 12,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
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/app_icons.dart';
|
||||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:didvan/main.dart';
|
||||
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||
import 'package:didvan/models/ai/chats_model.dart';
|
||||
import 'package:didvan/models/ai/files_model.dart';
|
||||
import 'package:didvan/models/ai/messages_model.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/routes/routes.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/utils/date_time.dart';
|
||||
import 'package:didvan/views/ai/ai_chat_state.dart';
|
||||
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
||||
import 'package:didvan/views/ai/widgets/ai_message_bar.dart';
|
||||
import 'package:didvan/views/ai/widgets/ai_message_bar_ios.dart';
|
||||
import 'package:didvan/views/ai/widgets/audio_wave.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
|
|
@ -62,6 +68,17 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
() => focusNode.requestFocus(),
|
||||
);
|
||||
}
|
||||
if (widget.args.prompts != null) {
|
||||
state.messages.add(MessageModel(
|
||||
dateTime: DateTime.now()
|
||||
.subtract(const Duration(minutes: 210))
|
||||
.toIso8601String(),
|
||||
prompts: [widget.args.prompts!]));
|
||||
|
||||
state.message.clear();
|
||||
state.update();
|
||||
await state.postMessage(widget.args.bot);
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
|
@ -296,10 +313,13 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
bottomSheet: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AiMessageBar(
|
||||
bot: widget.args.bot,
|
||||
// focusNode: focusNode,
|
||||
),
|
||||
Platform.isIOS
|
||||
? AiMessageBarIOS(
|
||||
bot: widget.args.bot,
|
||||
)
|
||||
: AiMessageBar(
|
||||
bot: widget.args.bot,
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
|
|
@ -429,8 +449,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
)
|
||||
: Column(
|
||||
children: [
|
||||
if (message.role.toString().contains('user') &&
|
||||
file != null)
|
||||
if (file != null)
|
||||
(file.isAudio()
|
||||
// && (!kIsWeb && !Platform.isIOS)
|
||||
)
|
||||
|
|
@ -472,6 +491,78 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (message.role.toString().contains('user'))
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
onSelected: (value) async {
|
||||
navigatorKey.currentState!.pushNamed(
|
||||
Routes.aiChat,
|
||||
arguments: AiChatArgs(
|
||||
bot: value,
|
||||
prompts: message));
|
||||
},
|
||||
itemBuilder: (BuildContext context) {
|
||||
final historyAiChatState = context
|
||||
.read<HistoryAiChatState>();
|
||||
return <PopupMenuEntry>[
|
||||
...List.generate(
|
||||
historyAiChatState.bots.length,
|
||||
(index) => PopupMenuItem(
|
||||
value: historyAiChatState
|
||||
.bots[index],
|
||||
height: 72,
|
||||
child: Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
historyAiChatState
|
||||
.bots[index]
|
||||
.image
|
||||
.toString(),
|
||||
width: 42,
|
||||
height: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
DidvanText(
|
||||
'${historyAiChatState.bots[index].name}X',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 120),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
DesignConfig.lowBorderRadius,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.title)),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DidvanText(
|
||||
'${widget.args.bot.name}',
|
||||
maxLines: 1,
|
||||
overflow:
|
||||
TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
DidvanIcons.angle_down_light),
|
||||
],
|
||||
),
|
||||
)),
|
||||
if (message.role
|
||||
.toString()
|
||||
.contains('user') &&
|
||||
|
|
|
|||
|
|
@ -177,9 +177,13 @@ class AiChatState extends CoreProvier {
|
|||
|
||||
final r = res.listen((value) async {
|
||||
var str = utf8.decode(value);
|
||||
|
||||
if (!kIsWeb) {
|
||||
if (bot.id == 12) {
|
||||
responseMessgae += str.split('{{{').first;
|
||||
}
|
||||
if (str.contains('{{{')) {
|
||||
dataMessgae += str;
|
||||
dataMessgae += "{{{${str.split('{{{').last}";
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
|
@ -198,6 +202,10 @@ class AiChatState extends CoreProvier {
|
|||
} catch (e) {
|
||||
e.printError();
|
||||
}
|
||||
if (bot.id == 12) {
|
||||
responseMessgae = "${responseMessgae.split('.png').first}.png";
|
||||
return;
|
||||
}
|
||||
}
|
||||
messageOnstream.value = Stream.value(responseMessgae);
|
||||
|
||||
|
|
@ -233,8 +241,11 @@ class AiChatState extends CoreProvier {
|
|||
e.printError();
|
||||
return;
|
||||
}
|
||||
messages.last.prompts.last = messages.last.prompts.last
|
||||
.copyWith(finished: true, text: responseMessgae, id: aiMessageId);
|
||||
messages.last.prompts.last = messages.last.prompts.last.copyWith(
|
||||
finished: true,
|
||||
text: bot.id == 12 ? null : responseMessgae,
|
||||
file: bot.id == 12 ? responseMessgae : null,
|
||||
id: aiMessageId);
|
||||
if (messages.last.prompts.length > 2) {
|
||||
messages.last.prompts[messages.last.prompts.length - 2] = messages
|
||||
.last.prompts[messages.last.prompts.length - 2]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// ignore_for_file: library_private_types_in_public_api, avoid_web_libraries_in_flutter
|
||||
|
||||
import 'dart:async';
|
||||
// import 'dart:html' as html;
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
import 'dart:io';
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
|
|
@ -60,6 +60,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
Timer? _timer;
|
||||
final theSource = AudioSource.microphone;
|
||||
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
|
||||
late HistoryAiChatState historyState = context.read<HistoryAiChatState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -174,9 +175,6 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
_mPlayer!
|
||||
.startPlayer(
|
||||
fromURI: _mPath,
|
||||
// fromDataBuffer: Uint8List.fromList(
|
||||
// recordedData.expand((element) => element).toList()),
|
||||
//codec: kIsWeb ? Codec.opusWebM : Codec.aacADTS,
|
||||
whenFinished: () {
|
||||
setState(() {});
|
||||
})
|
||||
|
|
@ -226,26 +224,18 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<AiChatState>(builder: (context, state, child) {
|
||||
final historyState = context.read<HistoryAiChatState>();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 12)
|
||||
.copyWith(
|
||||
top: openAttach ||
|
||||
(state.file != null && !state.file!.isRecorded)
|
||||
? 0
|
||||
: 24),
|
||||
padding: const EdgeInsets.fromLTRB(12, 24, 12, 0).copyWith(
|
||||
top: (state.file != null && !state.file!.isRecorded) ? 0 : 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
border: Border(
|
||||
top: openAttach
|
||||
? BorderSide(color: Theme.of(context).colorScheme.border)
|
||||
: BorderSide.none)),
|
||||
child: Stack(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
if (state.file != null && !(state.file!.isRecorded))
|
||||
fileContainer(),
|
||||
Stack(
|
||||
children: [
|
||||
attachmentLayout(state, historyState, context),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: DesignConfig.defaultShadow,
|
||||
|
|
@ -283,24 +273,35 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (state.onResponsing)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.focused
|
||||
.withOpacity(0.5)),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (state.onResponsing)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.focused
|
||||
.withOpacity(0.5)),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 32,
|
||||
MediaQuery.of(context).viewInsets.bottom == 0
|
||||
? const Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 8, 8, 4),
|
||||
child: DidvanText(
|
||||
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 12,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
@ -411,26 +412,28 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
},
|
||||
)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
child: MessageBarBtn(
|
||||
enable: false,
|
||||
icon: openAttach || state.file != null
|
||||
? DidvanIcons.close_regular
|
||||
: Icons.attach_file_outlined,
|
||||
click: () {
|
||||
if (_mPlayer!.isPlaying) {
|
||||
stopPlayer();
|
||||
}
|
||||
if (state.file != null) {
|
||||
state.file = null;
|
||||
} else {
|
||||
openAttach = !openAttach;
|
||||
}
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
)
|
||||
attachmentLayout(state, context),
|
||||
if (widget.bot.attachmentType!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
child: MessageBarBtn(
|
||||
enable: false,
|
||||
icon: openAttach || state.file != null
|
||||
? DidvanIcons.close_regular
|
||||
: Icons.attach_file_outlined,
|
||||
click: () {
|
||||
if (_mPlayer!.isPlaying) {
|
||||
stopPlayer();
|
||||
}
|
||||
if (state.file != null) {
|
||||
state.file = null;
|
||||
} else {
|
||||
openAttach = !openAttach;
|
||||
}
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -510,186 +513,328 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
);
|
||||
}
|
||||
|
||||
AnimatedVisibility attachmentLayout(AiChatState state,
|
||||
HistoryAiChatState historyState, BuildContext context) {
|
||||
// AnimatedVisibility attachmentLayout(AiChatState state,
|
||||
// HistoryAiChatState historyState, BuildContext context) {
|
||||
// return AnimatedVisibility(
|
||||
// isVisible: openAttach,
|
||||
// duration: DesignConfig.lowAnimationDuration,
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12),
|
||||
// child: state.file != null && !(state.file!.isRecorded)
|
||||
// ? fileContainer()
|
||||
// : Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// if (historyState.bot!.attachmentType!.contains('pdf'))
|
||||
// attachBtn(
|
||||
// title: "PDF",
|
||||
// icon: CupertinoIcons.doc_fill,
|
||||
// color: Colors.redAccent,
|
||||
// click: () async {
|
||||
// MediaService.onLoadingPickFile(context);
|
||||
// FilePickerResult? result =
|
||||
// await MediaService.pickPdfFile();
|
||||
// if (result != null) {
|
||||
// if (kIsWeb) {
|
||||
// Uint8List? bytes = result.files.first
|
||||
// .bytes; // Access the bytes property
|
||||
// String? name = result.files.first.name;
|
||||
|
||||
// // Store bytes and file name directly in your state or model
|
||||
// state.file = FilesModel(
|
||||
// '', // No need for a file path on web
|
||||
// name: name,
|
||||
// bytes: bytes,
|
||||
// audio: false,
|
||||
// image: false,
|
||||
// );
|
||||
// } else {
|
||||
// state.file = FilesModel(result.files.single.path!,
|
||||
// audio: false, image: false);
|
||||
// }
|
||||
// }
|
||||
// Future.delayed(
|
||||
// Duration.zero,
|
||||
// () => ActionSheetUtils(context).pop(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// if (historyState.bot!.attachmentType!.contains('image'))
|
||||
// attachBtn(
|
||||
// title: "تصویر",
|
||||
// icon: CupertinoIcons.photo,
|
||||
// color: Colors.deepOrangeAccent,
|
||||
// click: () async {
|
||||
// MediaService.onLoadingPickFile(context);
|
||||
|
||||
// final pickedFile = await MediaService.pickImage(
|
||||
// source: ImageSource.gallery);
|
||||
// File? file;
|
||||
// if (pickedFile != null && !kIsWeb) {
|
||||
// file = await ImageCropper().cropImage(
|
||||
// sourcePath: pickedFile.path,
|
||||
// androidUiSettings: const AndroidUiSettings(
|
||||
// toolbarTitle: 'برش تصویر'),
|
||||
// iosUiSettings: const IOSUiSettings(
|
||||
// title: 'برش تصویر',
|
||||
// doneButtonTitle: 'تایید',
|
||||
// cancelButtonTitle: 'بازگشت',
|
||||
// ),
|
||||
// compressQuality: 30,
|
||||
// );
|
||||
|
||||
// if (file == null) {
|
||||
// await Future.delayed(
|
||||
// Duration.zero,
|
||||
// () => ActionSheetUtils(context).pop(),
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// if (pickedFile == null) {
|
||||
// await Future.delayed(
|
||||
// Duration.zero,
|
||||
// () => ActionSheetUtils(context).pop(),
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
// state.file = kIsWeb
|
||||
// ? FilesModel(pickedFile.path,
|
||||
// name: pickedFile.name,
|
||||
// image: true,
|
||||
// audio: false)
|
||||
// : FilesModel(file!.path,
|
||||
// image: true, audio: false);
|
||||
// await Future.delayed(
|
||||
// Duration.zero,
|
||||
// () => ActionSheetUtils(context).pop(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// // if (!kIsWeb && !Platform.isIOS)
|
||||
// if (historyState.bot!.attachmentType!.contains('audio'))
|
||||
// attachBtn(
|
||||
// title: "صوت",
|
||||
// icon: CupertinoIcons.music_note_2,
|
||||
// color: Colors.indigoAccent,
|
||||
// click: () async {
|
||||
// MediaService.onLoadingPickFile(context);
|
||||
|
||||
// FilePickerResult? result =
|
||||
// await MediaService.pickAudioFile();
|
||||
// if (result != null) {
|
||||
// if (kIsWeb) {
|
||||
// Uint8List? bytes = result.files.first
|
||||
// .bytes; // Access the bytes property
|
||||
// String? name = result.files.first.name;
|
||||
|
||||
// // final blob = html.Blob([bytes]);
|
||||
// // final blobUrl =
|
||||
// // html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
// state.file = FilesModel(
|
||||
// "", // No need for a file path on web
|
||||
// name: name,
|
||||
// bytes: bytes,
|
||||
// audio: true,
|
||||
// image: false,
|
||||
// );
|
||||
// } else {
|
||||
// state.file = FilesModel(result.files.single.path!,
|
||||
// audio: true, image: false);
|
||||
// }
|
||||
// }
|
||||
// await Future.delayed(
|
||||
// Duration.zero,
|
||||
// () => ActionSheetUtils(context).pop(),
|
||||
// );
|
||||
// },
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ));
|
||||
// }
|
||||
|
||||
AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) {
|
||||
return AnimatedVisibility(
|
||||
isVisible: openAttach,
|
||||
fadeMode: FadeMode.horizontal,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12),
|
||||
child: state.file != null && !(state.file!.isRecorded)
|
||||
? fileContainer()
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (historyState.bot!.attachmentType!.contains('pdf'))
|
||||
attachBtn(
|
||||
title: "PDF",
|
||||
icon: CupertinoIcons.doc_fill,
|
||||
color: Colors.redAccent,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
FilePickerResult? result =
|
||||
await MediaService.pickPdfFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes = result.files.first
|
||||
.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (historyState.bot!.attachmentType!.contains('pdf'))
|
||||
MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.doc_fill,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
FilePickerResult? result = await MediaService.pickPdfFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes =
|
||||
result.files.first.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
|
||||
// Store bytes and file name directly in your state or model
|
||||
state.file = FilesModel(
|
||||
'', // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: false,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: false, image: false);
|
||||
}
|
||||
}
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (historyState.bot!.attachmentType!.contains('image'))
|
||||
attachBtn(
|
||||
title: "تصویر",
|
||||
icon: CupertinoIcons.photo,
|
||||
color: Colors.deepOrangeAccent,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
// Store bytes and file name directly in your state or model
|
||||
state.file = FilesModel(
|
||||
'', // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: false,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: false, image: false);
|
||||
}
|
||||
}
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
final pickedFile = await MediaService.pickImage(
|
||||
source: ImageSource.gallery);
|
||||
File? file;
|
||||
if (pickedFile != null && !kIsWeb) {
|
||||
file = await ImageCropper().cropImage(
|
||||
sourcePath: pickedFile.path,
|
||||
androidUiSettings: const AndroidUiSettings(
|
||||
toolbarTitle: 'برش تصویر'),
|
||||
iosUiSettings: const IOSUiSettings(
|
||||
title: 'برش تصویر',
|
||||
doneButtonTitle: 'تایید',
|
||||
cancelButtonTitle: 'بازگشت',
|
||||
),
|
||||
compressQuality: 30,
|
||||
);
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
if (historyState.bot!.attachmentType!.contains('image'))
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.photo,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
|
||||
if (file == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
final pickedFile = await MediaService.pickImage(
|
||||
source: ImageSource.gallery);
|
||||
File? file;
|
||||
if (pickedFile != null && !kIsWeb) {
|
||||
file = await ImageCropper().cropImage(
|
||||
sourcePath: pickedFile.path,
|
||||
androidUiSettings:
|
||||
const AndroidUiSettings(toolbarTitle: 'برش تصویر'),
|
||||
iosUiSettings: const IOSUiSettings(
|
||||
title: 'برش تصویر',
|
||||
doneButtonTitle: 'تایید',
|
||||
cancelButtonTitle: 'بازگشت',
|
||||
),
|
||||
compressQuality: 30,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pickedFile == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
if (file == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
state.file = kIsWeb
|
||||
? FilesModel(pickedFile.path,
|
||||
name: pickedFile.name,
|
||||
image: true,
|
||||
audio: false)
|
||||
: FilesModel(file!.path,
|
||||
image: true, audio: false);
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
// if (!kIsWeb && !Platform.isIOS)
|
||||
if (historyState.bot!.attachmentType!.contains('audio'))
|
||||
attachBtn(
|
||||
title: "صوت",
|
||||
icon: CupertinoIcons.music_note_2,
|
||||
color: Colors.indigoAccent,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pickedFile == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
|
||||
FilePickerResult? result =
|
||||
await MediaService.pickAudioFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes = result.files.first
|
||||
.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
return;
|
||||
}
|
||||
state.file = kIsWeb
|
||||
? FilesModel(pickedFile.path,
|
||||
name: pickedFile.name, image: true, audio: false)
|
||||
: FilesModel(file!.path, image: true, audio: false);
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
// final blob = html.Blob([bytes]);
|
||||
// final blobUrl =
|
||||
// html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
state.file = FilesModel(
|
||||
"", // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: true,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: true, image: false);
|
||||
}
|
||||
}
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
),
|
||||
// if (!kIsWeb && !Platform.isIOS)
|
||||
if (historyState.bot!.attachmentType!.contains('audio'))
|
||||
MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.music_note_2,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
|
||||
FilePickerResult? result = await MediaService.pickAudioFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes =
|
||||
result.files.first.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
|
||||
final blob = html.Blob([bytes]);
|
||||
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
state.file = FilesModel(
|
||||
blobUrl, // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: true,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: true, image: false);
|
||||
}
|
||||
}
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
state.update();
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
InkWell attachBtn(
|
||||
{required final String title,
|
||||
required final IconData icon,
|
||||
final Color? color,
|
||||
final Function()? click}) {
|
||||
final state = context.read<AiChatState>();
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
await click?.call();
|
||||
// InkWell attachBtn(
|
||||
// {required final String title,
|
||||
// required final IconData icon,
|
||||
// final Color? color,
|
||||
// final Function()? click}) {
|
||||
// final state = context.read<AiChatState>();
|
||||
// return InkWell(
|
||||
// onTap: () async {
|
||||
// await click?.call();
|
||||
|
||||
state.update();
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||
padding: const EdgeInsets.all(0.8),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.white,
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
DidvanText(
|
||||
title,
|
||||
fontSize: 12,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
// state.update();
|
||||
// },
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Container(
|
||||
// width: 40,
|
||||
// height: 40,
|
||||
// decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||
// padding: const EdgeInsets.all(0.8),
|
||||
// margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
// child: Icon(
|
||||
// icon,
|
||||
// size: 20,
|
||||
// color: Theme.of(context).colorScheme.white,
|
||||
// )),
|
||||
// const SizedBox(
|
||||
// height: 4,
|
||||
// ),
|
||||
// DidvanText(
|
||||
// title,
|
||||
// fontSize: 12,
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget audioContainer() {
|
||||
final state = context.watch<AiChatState>();
|
||||
|
|
@ -711,6 +856,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
|||
borderRadius: DesignConfig.mediumBorderRadius,
|
||||
color: Theme.of(context).colorScheme.border,
|
||||
),
|
||||
margin: const EdgeInsets.fromLTRB(4, 4, 4, 8),
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,791 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/ai/bots_model.dart';
|
||||
import 'package:didvan/models/ai/chats_model.dart';
|
||||
import 'package:didvan/models/ai/files_model.dart';
|
||||
import 'package:didvan/models/ai/messages_model.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/services/media/voice.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/utils/date_time.dart';
|
||||
import 'package:didvan/views/ai/ai_chat_state.dart';
|
||||
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
||||
import 'package:didvan/views/ai/widgets/audio_wave.dart';
|
||||
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
|
||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/marquee_text.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_cropper/image_cropper.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:record/record.dart';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class AiMessageBarIOS extends StatefulWidget {
|
||||
final BotsModel bot;
|
||||
const AiMessageBarIOS({
|
||||
super.key,
|
||||
required this.bot,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AiMessageBarIOS> createState() => _AiMessageBarIOSState();
|
||||
}
|
||||
|
||||
class _AiMessageBarIOSState extends State<AiMessageBarIOS> {
|
||||
final ValueNotifier<String> messageText = ValueNotifier('');
|
||||
bool openAttach = false;
|
||||
String? path;
|
||||
late HistoryAiChatState historyState = context.read<HistoryAiChatState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final record = AudioRecorder();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
record.dispose();
|
||||
try {
|
||||
_timer.cancel();
|
||||
} catch (e) {
|
||||
e.printError();
|
||||
}
|
||||
}
|
||||
|
||||
late Timer _timer;
|
||||
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
|
||||
void startTimer() {
|
||||
const oneSec = Duration(seconds: 1);
|
||||
_timer = Timer.periodic(
|
||||
oneSec,
|
||||
(Timer timer) {
|
||||
_countTimer.value = Duration(seconds: _countTimer.value.inSeconds + 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<AiChatState>(
|
||||
builder: (context, state, child) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 24, 12, 0).copyWith(
|
||||
top: (state.file != null && !state.file!.isRecorded) ? 0 : 24),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.file != null && !(state.file!.isRecorded))
|
||||
fileContainer(),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
// height: 50,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: DesignConfig.defaultShadow,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.border),
|
||||
borderRadius: DesignConfig.highBorderRadius),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: StreamBuilder<RecordState>(
|
||||
stream: record.onStateChanged(),
|
||||
builder: (context, snapshot) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: messageText,
|
||||
builder: (context, value, child) {
|
||||
return Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0),
|
||||
child: (
|
||||
// !kIsWeb &&
|
||||
snapshot.hasData &&
|
||||
snapshot.data! !=
|
||||
RecordState.stop)
|
||||
? MessageBarBtn(
|
||||
enable: true,
|
||||
icon: DidvanIcons
|
||||
.stop_circle_solid,
|
||||
click: () async {
|
||||
path =
|
||||
await record.stop();
|
||||
|
||||
Duration? duration =
|
||||
await VoiceService
|
||||
.getDuration(
|
||||
src: path ??
|
||||
'');
|
||||
|
||||
state.file = FilesModel(
|
||||
path.toString(),
|
||||
name:
|
||||
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a',
|
||||
isRecorded: true,
|
||||
audio: true,
|
||||
image: false,
|
||||
duration: duration);
|
||||
_timer.cancel();
|
||||
|
||||
state.update();
|
||||
},
|
||||
)
|
||||
:
|
||||
// (!kIsWeb &&
|
||||
// !Platform
|
||||
// .isIOS) &&
|
||||
widget.bot.attachmentType!
|
||||
.contains(
|
||||
'audio') &&
|
||||
value.isEmpty &&
|
||||
state.file == null &&
|
||||
widget.bot.attachment !=
|
||||
0
|
||||
? MessageBarBtn(
|
||||
enable: true,
|
||||
icon: DidvanIcons
|
||||
.mic_regular,
|
||||
click: () async {
|
||||
if (await record
|
||||
.hasPermission()) {
|
||||
try {
|
||||
String path =
|
||||
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a';
|
||||
//
|
||||
if (!kIsWeb) {
|
||||
Directory?
|
||||
downloadDir =
|
||||
await getApplicationDocumentsDirectory();
|
||||
path = p.join(
|
||||
downloadDir
|
||||
.path,
|
||||
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
|
||||
}
|
||||
setState(
|
||||
() {
|
||||
openAttach =
|
||||
false;
|
||||
},
|
||||
);
|
||||
record.start(
|
||||
const RecordConfig(),
|
||||
path: path);
|
||||
startTimer();
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'Error starting recording: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
: MessageBarBtn(
|
||||
enable: (state
|
||||
.file !=
|
||||
null &&
|
||||
state.file!
|
||||
.isRecorded) ||
|
||||
(widget.bot
|
||||
.attachment ==
|
||||
1) ||
|
||||
value.isNotEmpty,
|
||||
icon: DidvanIcons
|
||||
.send_light,
|
||||
click: () async {
|
||||
if ((state.file ==
|
||||
null ||
|
||||
!state.file!
|
||||
.isRecorded) &&
|
||||
(widget.bot
|
||||
.attachment !=
|
||||
1) &&
|
||||
value.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.messages
|
||||
.isNotEmpty &&
|
||||
DateTime.parse(state
|
||||
.messages
|
||||
.last
|
||||
.dateTime)
|
||||
.toPersianDateStr()
|
||||
.contains(DateTime.parse(DateTime
|
||||
.now()
|
||||
.subtract(
|
||||
const Duration(minutes: 210))
|
||||
.toIso8601String())
|
||||
.toPersianDateStr())) {
|
||||
state.messages.last
|
||||
.prompts
|
||||
.add(Prompts(
|
||||
error: false,
|
||||
text: state
|
||||
.message.text,
|
||||
file: state
|
||||
.file?.path,
|
||||
fileName: state
|
||||
.file
|
||||
?.basename,
|
||||
fileLocal:
|
||||
state.file,
|
||||
finished: true,
|
||||
role: 'user',
|
||||
createdAt: DateTime
|
||||
.now()
|
||||
.subtract(
|
||||
const Duration(
|
||||
minutes:
|
||||
210))
|
||||
.toIso8601String(),
|
||||
));
|
||||
} else {
|
||||
state.messages.add(MessageModel(
|
||||
dateTime: DateTime
|
||||
.now()
|
||||
.subtract(const Duration(
|
||||
minutes:
|
||||
210))
|
||||
.toIso8601String(),
|
||||
prompts: [
|
||||
Prompts(
|
||||
error:
|
||||
false,
|
||||
text: state
|
||||
.message
|
||||
.text,
|
||||
finished:
|
||||
true,
|
||||
file: state
|
||||
.file
|
||||
?.path,
|
||||
fileName: state
|
||||
.file
|
||||
?.basename,
|
||||
fileLocal:
|
||||
state
|
||||
.file,
|
||||
role:
|
||||
'user',
|
||||
createdAt: DateTime
|
||||
.now()
|
||||
.subtract(const Duration(
|
||||
minutes:
|
||||
210))
|
||||
.toIso8601String(),
|
||||
)
|
||||
]));
|
||||
}
|
||||
state.message.clear();
|
||||
messageText.value =
|
||||
state
|
||||
.message.text;
|
||||
await state
|
||||
.postMessage(
|
||||
widget.bot);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: snapshot.hasData &&
|
||||
(snapshot.data! ==
|
||||
RecordState
|
||||
.record ||
|
||||
snapshot.data! ==
|
||||
RecordState.pause)
|
||||
? Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Directionality(
|
||||
textDirection:
|
||||
TextDirection
|
||||
.ltr,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.center,
|
||||
children: List.generate(
|
||||
4,
|
||||
(index) => snapshot.data! == RecordState.pause
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: List.generate(
|
||||
8,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 12),
|
||||
child: Container(
|
||||
width: 3,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary.withOpacity(0.4)),
|
||||
),
|
||||
)),
|
||||
)
|
||||
: SpinKitWave(
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
size:
|
||||
32,
|
||||
itemCount:
|
||||
10,
|
||||
))),
|
||||
),
|
||||
ValueListenableBuilder<
|
||||
Duration>(
|
||||
valueListenable:
|
||||
_countTimer,
|
||||
builder: (context,
|
||||
value,
|
||||
child) =>
|
||||
DidvanText(DateTimeUtils
|
||||
.normalizeTimeDuration(
|
||||
value)),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: state.file != null &&
|
||||
state.file!.isRecorded
|
||||
? audioContainer()
|
||||
: Form(
|
||||
child: TextFormField(
|
||||
textInputAction:
|
||||
TextInputAction
|
||||
.newline,
|
||||
style:
|
||||
Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
minLines: 1,
|
||||
maxLines:
|
||||
6, // Set this
|
||||
// expands: true, //
|
||||
// keyboardType: TextInputType.text,
|
||||
keyboardType:
|
||||
TextInputType
|
||||
.multiline,
|
||||
controller:
|
||||
state.message,
|
||||
|
||||
enabled: !(state
|
||||
.file !=
|
||||
null &&
|
||||
widget.bot
|
||||
.attachment ==
|
||||
1),
|
||||
|
||||
decoration:
|
||||
InputDecoration(
|
||||
border: InputBorder
|
||||
.none,
|
||||
hintText:
|
||||
'بنویسید...',
|
||||
hintStyle: Theme.of(
|
||||
context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: Theme.of(
|
||||
context)
|
||||
.colorScheme
|
||||
.disabledText),
|
||||
suffixIcon: state
|
||||
.isEdite
|
||||
? InkWell(
|
||||
onTap: () {
|
||||
state.isEdite =
|
||||
false;
|
||||
state
|
||||
.update();
|
||||
},
|
||||
child: const Icon(
|
||||
DidvanIcons
|
||||
.close_circle_solid),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
|
||||
onChanged: (value) {
|
||||
messageText.value =
|
||||
value;
|
||||
state.update();
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
if (snapshot.hasData)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0),
|
||||
child: snapshot.data! ==
|
||||
RecordState.record
|
||||
? MessageBarBtn(
|
||||
enable: false,
|
||||
icon: DidvanIcons
|
||||
.pause_solid,
|
||||
click: () async {
|
||||
await record.pause();
|
||||
_timer.cancel();
|
||||
},
|
||||
)
|
||||
: snapshot.data! ==
|
||||
RecordState.pause
|
||||
? MessageBarBtn(
|
||||
enable: false,
|
||||
icon: DidvanIcons
|
||||
.play_solid,
|
||||
click: () async {
|
||||
await record
|
||||
.resume();
|
||||
startTimer();
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0),
|
||||
child: attachmentLayout(
|
||||
state, context),
|
||||
),
|
||||
if (!snapshot.hasData ||
|
||||
(snapshot.hasData &&
|
||||
snapshot.data ==
|
||||
RecordState.stop))
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(
|
||||
12, 0, 12, 8),
|
||||
child: state.file != null ||
|
||||
openAttach
|
||||
? MessageBarBtn(
|
||||
click: () {
|
||||
if (openAttach) {
|
||||
setState(() {
|
||||
openAttach =
|
||||
!openAttach;
|
||||
});
|
||||
return;
|
||||
}
|
||||
state.file = null;
|
||||
_countTimer.value =
|
||||
Duration.zero;
|
||||
state.update();
|
||||
},
|
||||
enable: false,
|
||||
icon: DidvanIcons
|
||||
.close_solid)
|
||||
: widget.bot.attachmentType!
|
||||
.isNotEmpty
|
||||
? MessageBarBtn(
|
||||
click: () {
|
||||
setState(() {
|
||||
openAttach =
|
||||
!openAttach;
|
||||
});
|
||||
},
|
||||
enable: false,
|
||||
icon: Icons
|
||||
.attach_file_rounded,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.onResponsing)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.focused
|
||||
.withOpacity(0.5)),
|
||||
child: Center(
|
||||
child: SpinKitThreeBounce(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
MediaQuery.of(context).viewInsets.bottom == 0
|
||||
? const Padding(
|
||||
padding: EdgeInsets.fromLTRB(8, 8, 8, 4),
|
||||
child: DidvanText(
|
||||
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
|
||||
fontSize: 12,
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget audioContainer() {
|
||||
final state = context.watch<AiChatState>();
|
||||
|
||||
return SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: AudioWave(
|
||||
file: state.file!.path,
|
||||
loadingPaddingSize: 8.0,
|
||||
totalDuration: _countTimer.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) {
|
||||
return AnimatedVisibility(
|
||||
isVisible: openAttach,
|
||||
fadeMode: FadeMode.horizontal,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (historyState.bot!.attachmentType!.contains('pdf'))
|
||||
MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.doc_fill,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
FilePickerResult? result = await MediaService.pickPdfFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes =
|
||||
result.files.first.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
|
||||
// Store bytes and file name directly in your state or model
|
||||
state.file = FilesModel(
|
||||
'', // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: false,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: false, image: false);
|
||||
}
|
||||
}
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
if (historyState.bot!.attachmentType!.contains('image'))
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.photo,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
|
||||
final pickedFile = await MediaService.pickImage(
|
||||
source: ImageSource.gallery);
|
||||
File? file;
|
||||
if (pickedFile != null && !kIsWeb) {
|
||||
file = await ImageCropper().cropImage(
|
||||
sourcePath: pickedFile.path,
|
||||
androidUiSettings:
|
||||
const AndroidUiSettings(toolbarTitle: 'برش تصویر'),
|
||||
iosUiSettings: const IOSUiSettings(
|
||||
title: 'برش تصویر',
|
||||
doneButtonTitle: 'تایید',
|
||||
cancelButtonTitle: 'بازگشت',
|
||||
),
|
||||
compressQuality: 30,
|
||||
);
|
||||
|
||||
if (file == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (pickedFile == null) {
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
state.file = kIsWeb
|
||||
? FilesModel(pickedFile.path,
|
||||
name: pickedFile.name, image: true, audio: false)
|
||||
: FilesModel(file!.path, image: true, audio: false);
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
state.update();
|
||||
},
|
||||
),
|
||||
),
|
||||
// if (!kIsWeb && !Platform.isIOS)
|
||||
if (historyState.bot!.attachmentType!.contains('audio'))
|
||||
MessageBarBtn(
|
||||
enable: true,
|
||||
icon: CupertinoIcons.music_note_2,
|
||||
click: () async {
|
||||
MediaService.onLoadingPickFile(context);
|
||||
|
||||
FilePickerResult? result = await MediaService.pickAudioFile();
|
||||
if (result != null) {
|
||||
if (kIsWeb) {
|
||||
Uint8List? bytes =
|
||||
result.files.first.bytes; // Access the bytes property
|
||||
String? name = result.files.first.name;
|
||||
|
||||
state.file = FilesModel(
|
||||
'', // No need for a file path on web
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
audio: true,
|
||||
image: false,
|
||||
);
|
||||
} else {
|
||||
state.file = FilesModel(result.files.single.path!,
|
||||
audio: true, image: false);
|
||||
}
|
||||
}
|
||||
await Future.delayed(
|
||||
Duration.zero,
|
||||
() => ActionSheetUtils(context).pop(),
|
||||
);
|
||||
openAttach = !openAttach;
|
||||
|
||||
state.update();
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget fileContainer() {
|
||||
final state = context.watch<AiChatState>();
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: DesignConfig.mediumBorderRadius,
|
||||
color: Theme.of(context).colorScheme.border,
|
||||
),
|
||||
margin: const EdgeInsets.fromLTRB(4, 4, 4, 8),
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
state.file != null && state.file!.isImage()
|
||||
? SizedBox(
|
||||
width: 32,
|
||||
height: 42,
|
||||
child: ClipRRect(
|
||||
borderRadius: DesignConfig.lowBorderRadius,
|
||||
child: state.file!.isNetwork()
|
||||
? Image.network(
|
||||
state.file!.path,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.file(
|
||||
state.file!.main,
|
||||
fit: BoxFit.cover,
|
||||
)))
|
||||
: const Icon(Icons.file_copy),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: MarqueeText(
|
||||
text: state.file != null ? state.file!.basename : '',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
stop: const Duration(seconds: 3),
|
||||
),
|
||||
),
|
||||
if (state.file != null && !kIsWeb)
|
||||
FutureBuilder(
|
||||
future: state.file!.main.length(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return DidvanText(
|
||||
'File Size ${(snapshot.data! / 1000).round()} KB',
|
||||
fontSize: 12,
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,16 +15,14 @@ import 'package:just_audio/just_audio.dart';
|
|||
|
||||
class AudioWave extends StatefulWidget {
|
||||
final String file;
|
||||
final Uint8List? bytes;
|
||||
final double loadingPaddingSize;
|
||||
final Duration? totalDuration;
|
||||
const AudioWave(
|
||||
{Key? key,
|
||||
required this.file,
|
||||
this.loadingPaddingSize = 0,
|
||||
this.totalDuration,
|
||||
this.bytes})
|
||||
: super(key: key);
|
||||
const AudioWave({
|
||||
Key? key,
|
||||
required this.file,
|
||||
this.loadingPaddingSize = 0,
|
||||
this.totalDuration,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AudioWaveState createState() => _AudioWaveState();
|
||||
|
|
@ -110,8 +108,7 @@ class _AudioWaveState extends State<AudioWave> {
|
|||
? DidvanIcons.pause_solid
|
||||
: DidvanIcons.play_solid,
|
||||
click: () async {
|
||||
await VoiceService.voiceHelper(
|
||||
src: widget.file, bytes: widget.bytes);
|
||||
await VoiceService.voiceHelper(src: widget.file);
|
||||
},
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -281,17 +281,15 @@ class DirectState extends CoreProvier {
|
|||
}
|
||||
} else {
|
||||
final Uint8List uploadFile = kIsWeb
|
||||
? (await http.get(Uri.parse(path!))).bodyBytes
|
||||
? (await http.get(Uri.parse(path!.replaceAll('%3A', ':')))).bodyBytes
|
||||
: await File(path!).readAsBytes();
|
||||
|
||||
path = null;
|
||||
|
||||
await service.multipartBytes(
|
||||
file: uploadFile,
|
||||
method: 'POST',
|
||||
fieldName: 'audio',
|
||||
fileName: 'voice-message',
|
||||
mediaExtension: 'm4a',
|
||||
mediaExtension: 'mp3',
|
||||
mediaFormat: 'audio',
|
||||
);
|
||||
|
||||
|
|
@ -304,8 +302,8 @@ class DirectState extends CoreProvier {
|
|||
for (var i = 0; i < messages.length; i++) {
|
||||
_addToDailyGrouped(messages[i]);
|
||||
}
|
||||
// update();
|
||||
}
|
||||
path = null;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ class Message extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (message.text != null) DidvanText(message.text!),
|
||||
if (message.audio != null || message.audioFile != null)
|
||||
if (message.audio != null)
|
||||
AudioWave(
|
||||
file: message.audio ?? message.audioFile!.path,
|
||||
file: message.audio!,
|
||||
totalDuration: message.duration != null
|
||||
? Duration(seconds: message.duration!)
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ class SkeletonImage extends StatelessWidget {
|
|||
child: ClipRRect(
|
||||
borderRadius: borderRadius ?? BorderRadius.zero,
|
||||
child: CachedNetworkImage(
|
||||
errorWidget: (context, url, error) =>
|
||||
const Text("مشکلی پیش آمده است"),
|
||||
errorWidget: (context, url, error) {
|
||||
return const Text("مشکلی پیش آمده است");
|
||||
},
|
||||
errorListener: (value) {},
|
||||
fit: BoxFit.cover,
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
|
|
@ -41,8 +42,8 @@ class SkeletonImage extends StatelessWidget {
|
|||
width: width,
|
||||
height: height,
|
||||
imageUrl: imageUrl.startsWith('http')
|
||||
? imageUrl
|
||||
: RequestHelper.baseUrl + imageUrl,
|
||||
? imageUrl.replaceAll('\n', '')
|
||||
: RequestHelper.baseUrl + imageUrl.replaceAll('\n', ''),
|
||||
placeholder: (context, _) => ShimmerPlaceholder(
|
||||
width: pWidth,
|
||||
height: pHeight,
|
||||
|
|
|
|||
56
pubspec.lock
56
pubspec.lock
|
|
@ -1037,6 +1037,62 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
record:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: record
|
||||
sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.2"
|
||||
record_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_android
|
||||
sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.6"
|
||||
record_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_darwin
|
||||
sha256: fe90d302acb1f3cee1ade5df9c150ca5cee33b48d8cdf1cf433bf577d7f00134
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
record_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_linux
|
||||
sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
record_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_platform_interface
|
||||
sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
record_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_web
|
||||
sha256: "0ef370d1e6553ad33c39dd03103b374e7861f3518b0533e64c94d73f988a5ffa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
record_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_windows
|
||||
sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
rive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ dependencies:
|
|||
carousel_slider: ^4.0.0
|
||||
flutter_vibrate: ^1.3.0
|
||||
universal_html: ^2.0.8
|
||||
# record: ^5.1.2
|
||||
record: ^5.1.2
|
||||
|
||||
persian_datetime_picker: ^2.6.0
|
||||
persian_number_utility: ^1.1.1
|
||||
|
|
|
|||
Loading…
Reference in New Issue