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 {
|
class AiChatArgs {
|
||||||
final BotsModel bot;
|
final BotsModel bot;
|
||||||
final ChatsModel? chat;
|
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
|
: name != null
|
||||||
? p.extension(name)
|
? p.extension(name)
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
main = File(path);
|
main = File(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +43,9 @@ class FilesModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isImage() {
|
bool isImage() {
|
||||||
return image ?? lookupMimeType(path)?.startsWith('image/') ?? false;
|
return image ??
|
||||||
|
lookupMimeType(path)?.startsWith('image/') ??
|
||||||
|
false || path.contains(".png");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isNetwork() {
|
bool isNetwork() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'package:didvan/services/network/request.dart';
|
import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -66,7 +65,6 @@ class VoiceService {
|
||||||
|
|
||||||
static Future<void> voiceHelper(
|
static Future<void> voiceHelper(
|
||||||
{required String src,
|
{required String src,
|
||||||
final Uint8List? bytes,
|
|
||||||
final Duration? duration,
|
final Duration? duration,
|
||||||
final Function()? startTimer,
|
final Function()? startTimer,
|
||||||
final Function()? stopTimer}) async {
|
final Function()? stopTimer}) async {
|
||||||
|
|
@ -96,7 +94,7 @@ class VoiceService {
|
||||||
// AudioSource.uri(Uri.parse(blobUrl)),
|
// AudioSource.uri(Uri.parse(blobUrl)),
|
||||||
// );
|
// );
|
||||||
// } else {
|
// } else {
|
||||||
await audioPlayer.setUrl(source);
|
await audioPlayer.setUrl(source.replaceAll('%3A', ''));
|
||||||
// }
|
// }
|
||||||
} else if (src.startsWith('blob:') || src == '') {
|
} else if (src.startsWith('blob:') || src == '') {
|
||||||
await audioPlayer.setUrl(src);
|
await audioPlayer.setUrl(src);
|
||||||
|
|
|
||||||
|
|
@ -364,12 +364,14 @@ class ActionSheetUtils {
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8),
|
vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: index == state.bots.length - 1
|
||||||
bottom: BorderSide(
|
? null
|
||||||
color: Theme.of(context)
|
: Border(
|
||||||
.colorScheme
|
bottom: BorderSide(
|
||||||
.border,
|
color: Theme.of(context)
|
||||||
width: 1))),
|
.colorScheme
|
||||||
|
.border,
|
||||||
|
width: 1))),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ClipOval(
|
ClipOval(
|
||||||
|
|
|
||||||
|
|
@ -164,80 +164,96 @@ class _AiState extends State<Ai> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 32),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
Navigator.of(context).pushNamed(Routes.aiChat,
|
Navigator.of(context).pushNamed(Routes.aiChat,
|
||||||
arguments: AiChatArgs(
|
arguments: AiChatArgs(
|
||||||
bot: bot,
|
bot: bot,
|
||||||
)),
|
)),
|
||||||
child: Row(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Row(
|
||||||
child: Container(
|
children: [
|
||||||
decoration: BoxDecoration(
|
Expanded(
|
||||||
boxShadow: DesignConfig.defaultShadow,
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
boxShadow: DesignConfig.defaultShadow,
|
||||||
color:
|
color:
|
||||||
Theme.of(context).colorScheme.border),
|
Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: DesignConfig.highBorderRadius),
|
border: Border.all(
|
||||||
child: Row(
|
color: Theme.of(context)
|
||||||
children: [
|
.colorScheme
|
||||||
const SizedBox(
|
.border),
|
||||||
width: 8,
|
borderRadius:
|
||||||
),
|
DesignConfig.highBorderRadius),
|
||||||
Expanded(
|
child: Row(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(
|
Expanded(
|
||||||
horizontal: 8.0,
|
child: Padding(
|
||||||
),
|
padding:
|
||||||
child: Form(
|
const EdgeInsets.symmetric(
|
||||||
child: Row(
|
horizontal: 8.0,
|
||||||
children: [
|
|
||||||
const MessageBarBtn(
|
|
||||||
enable: true,
|
|
||||||
icon:
|
|
||||||
DidvanIcons.mic_regular),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
),
|
),
|
||||||
Expanded(
|
child: Form(
|
||||||
child: TextFormField(
|
child: Row(
|
||||||
textInputAction:
|
children: [
|
||||||
TextInputAction.newline,
|
const MessageBarBtn(
|
||||||
style: Theme.of(context)
|
enable: true,
|
||||||
.textTheme
|
icon: DidvanIcons
|
||||||
.bodyMedium,
|
.mic_regular),
|
||||||
minLines: 1,
|
const SizedBox(
|
||||||
enabled: false,
|
width: 8,
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: 'بنویسید...',
|
|
||||||
hintStyle: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: Theme.of(
|
|
||||||
context)
|
|
||||||
.colorScheme
|
|
||||||
.disabledText),
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
),
|
child: TextFormField(
|
||||||
const SizedBox(
|
textInputAction:
|
||||||
width: 8,
|
TextInputAction
|
||||||
),
|
.newline,
|
||||||
const MessageBarBtn(
|
style: Theme.of(context)
|
||||||
enable: false,
|
.textTheme
|
||||||
icon: Icons
|
.bodyMedium,
|
||||||
.attach_file_rounded),
|
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
|
// 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:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/constants/assets.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/ai_chat_args.dart';
|
||||||
import 'package:didvan/models/ai/chats_model.dart';
|
import 'package:didvan/models/ai/chats_model.dart';
|
||||||
import 'package:didvan/models/ai/files_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/enums.dart';
|
||||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||||
import 'package:didvan/models/view/alert_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/action_sheet.dart';
|
||||||
import 'package:didvan/utils/date_time.dart';
|
import 'package:didvan/utils/date_time.dart';
|
||||||
import 'package:didvan/views/ai/ai_chat_state.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/history_ai_chat_state.dart';
|
||||||
import 'package:didvan/views/ai/widgets/ai_message_bar.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/ai/widgets/audio_wave.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
|
|
@ -62,6 +68,17 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
() => focusNode.requestFocus(),
|
() => 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();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
@ -296,10 +313,13 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
bottomSheet: Column(
|
bottomSheet: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
AiMessageBar(
|
Platform.isIOS
|
||||||
bot: widget.args.bot,
|
? AiMessageBarIOS(
|
||||||
// focusNode: focusNode,
|
bot: widget.args.bot,
|
||||||
),
|
)
|
||||||
|
: AiMessageBar(
|
||||||
|
bot: widget.args.bot,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
|
@ -429,8 +449,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
if (message.role.toString().contains('user') &&
|
if (file != null)
|
||||||
file != null)
|
|
||||||
(file.isAudio()
|
(file.isAudio()
|
||||||
// && (!kIsWeb && !Platform.isIOS)
|
// && (!kIsWeb && !Platform.isIOS)
|
||||||
)
|
)
|
||||||
|
|
@ -472,6 +491,78 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
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
|
if (message.role
|
||||||
.toString()
|
.toString()
|
||||||
.contains('user') &&
|
.contains('user') &&
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,13 @@ class AiChatState extends CoreProvier {
|
||||||
|
|
||||||
final r = res.listen((value) async {
|
final r = res.listen((value) async {
|
||||||
var str = utf8.decode(value);
|
var str = utf8.decode(value);
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
|
if (bot.id == 12) {
|
||||||
|
responseMessgae += str.split('{{{').first;
|
||||||
|
}
|
||||||
if (str.contains('{{{')) {
|
if (str.contains('{{{')) {
|
||||||
dataMessgae += str;
|
dataMessgae += "{{{${str.split('{{{').last}";
|
||||||
update();
|
update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -198,6 +202,10 @@ class AiChatState extends CoreProvier {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.printError();
|
e.printError();
|
||||||
}
|
}
|
||||||
|
if (bot.id == 12) {
|
||||||
|
responseMessgae = "${responseMessgae.split('.png').first}.png";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
messageOnstream.value = Stream.value(responseMessgae);
|
messageOnstream.value = Stream.value(responseMessgae);
|
||||||
|
|
||||||
|
|
@ -233,8 +241,11 @@ class AiChatState extends CoreProvier {
|
||||||
e.printError();
|
e.printError();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
messages.last.prompts.last = messages.last.prompts.last
|
messages.last.prompts.last = messages.last.prompts.last.copyWith(
|
||||||
.copyWith(finished: true, text: responseMessgae, id: aiMessageId);
|
finished: true,
|
||||||
|
text: bot.id == 12 ? null : responseMessgae,
|
||||||
|
file: bot.id == 12 ? responseMessgae : null,
|
||||||
|
id: aiMessageId);
|
||||||
if (messages.last.prompts.length > 2) {
|
if (messages.last.prompts.length > 2) {
|
||||||
messages.last.prompts[messages.last.prompts.length - 2] = messages
|
messages.last.prompts[messages.last.prompts.length - 2] = messages
|
||||||
.last.prompts[messages.last.prompts.length - 2]
|
.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
|
// ignore_for_file: library_private_types_in_public_api, avoid_web_libraries_in_flutter
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
// import 'dart:html' as html;
|
import 'package:universal_html/html.dart' as html;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:audio_session/audio_session.dart';
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
|
@ -60,6 +60,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
final theSource = AudioSource.microphone;
|
final theSource = AudioSource.microphone;
|
||||||
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
|
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
|
||||||
|
late HistoryAiChatState historyState = context.read<HistoryAiChatState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -174,9 +175,6 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
_mPlayer!
|
_mPlayer!
|
||||||
.startPlayer(
|
.startPlayer(
|
||||||
fromURI: _mPath,
|
fromURI: _mPath,
|
||||||
// fromDataBuffer: Uint8List.fromList(
|
|
||||||
// recordedData.expand((element) => element).toList()),
|
|
||||||
//codec: kIsWeb ? Codec.opusWebM : Codec.aacADTS,
|
|
||||||
whenFinished: () {
|
whenFinished: () {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
})
|
})
|
||||||
|
|
@ -226,26 +224,18 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<AiChatState>(builder: (context, state, child) {
|
return Consumer<AiChatState>(builder: (context, state, child) {
|
||||||
final historyState = context.read<HistoryAiChatState>();
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 12)
|
padding: const EdgeInsets.fromLTRB(12, 24, 12, 0).copyWith(
|
||||||
.copyWith(
|
top: (state.file != null && !state.file!.isRecorded) ? 0 : 24),
|
||||||
top: openAttach ||
|
|
||||||
(state.file != null && !state.file!.isRecorded)
|
|
||||||
? 0
|
|
||||||
: 24),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
border: Border(
|
),
|
||||||
top: openAttach
|
child: Column(
|
||||||
? BorderSide(color: Theme.of(context).colorScheme.border)
|
|
||||||
: BorderSide.none)),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
Column(
|
if (state.file != null && !(state.file!.isRecorded))
|
||||||
|
fileContainer(),
|
||||||
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
attachmentLayout(state, historyState, context),
|
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
boxShadow: DesignConfig.defaultShadow,
|
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)
|
MediaQuery.of(context).viewInsets.bottom == 0
|
||||||
Positioned.fill(
|
? const Padding(
|
||||||
child: Container(
|
padding: EdgeInsets.fromLTRB(8, 8, 8, 4),
|
||||||
decoration: BoxDecoration(
|
child: DidvanText(
|
||||||
color: Theme.of(context)
|
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
|
||||||
.colorScheme
|
fontSize: 12,
|
||||||
.focused
|
|
||||||
.withOpacity(0.5)),
|
|
||||||
child: Center(
|
|
||||||
child: SpinKitThreeBounce(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
size: 32,
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
: const SizedBox(
|
||||||
)
|
height: 12,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -411,26 +412,28 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
Padding(
|
attachmentLayout(state, context),
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
if (widget.bot.attachmentType!.isNotEmpty)
|
||||||
child: MessageBarBtn(
|
Padding(
|
||||||
enable: false,
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||||
icon: openAttach || state.file != null
|
child: MessageBarBtn(
|
||||||
? DidvanIcons.close_regular
|
enable: false,
|
||||||
: Icons.attach_file_outlined,
|
icon: openAttach || state.file != null
|
||||||
click: () {
|
? DidvanIcons.close_regular
|
||||||
if (_mPlayer!.isPlaying) {
|
: Icons.attach_file_outlined,
|
||||||
stopPlayer();
|
click: () {
|
||||||
}
|
if (_mPlayer!.isPlaying) {
|
||||||
if (state.file != null) {
|
stopPlayer();
|
||||||
state.file = null;
|
}
|
||||||
} else {
|
if (state.file != null) {
|
||||||
openAttach = !openAttach;
|
state.file = null;
|
||||||
}
|
} else {
|
||||||
state.update();
|
openAttach = !openAttach;
|
||||||
},
|
}
|
||||||
),
|
state.update();
|
||||||
)
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -510,186 +513,328 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility attachmentLayout(AiChatState state,
|
// AnimatedVisibility attachmentLayout(AiChatState state,
|
||||||
HistoryAiChatState historyState, BuildContext context) {
|
// 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(
|
return AnimatedVisibility(
|
||||||
isVisible: openAttach,
|
isVisible: openAttach,
|
||||||
|
fadeMode: FadeMode.horizontal,
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
duration: DesignConfig.lowAnimationDuration,
|
||||||
child: Container(
|
child: Row(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: state.file != null && !(state.file!.isRecorded)
|
children: [
|
||||||
? fileContainer()
|
if (historyState.bot!.attachmentType!.contains('pdf'))
|
||||||
: Row(
|
MessageBarBtn(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
enable: true,
|
||||||
children: [
|
icon: CupertinoIcons.doc_fill,
|
||||||
if (historyState.bot!.attachmentType!.contains('pdf'))
|
click: () async {
|
||||||
attachBtn(
|
MediaService.onLoadingPickFile(context);
|
||||||
title: "PDF",
|
FilePickerResult? result = await MediaService.pickPdfFile();
|
||||||
icon: CupertinoIcons.doc_fill,
|
if (result != null) {
|
||||||
color: Colors.redAccent,
|
if (kIsWeb) {
|
||||||
click: () async {
|
Uint8List? bytes =
|
||||||
MediaService.onLoadingPickFile(context);
|
result.files.first.bytes; // Access the bytes property
|
||||||
FilePickerResult? result =
|
String? name = result.files.first.name;
|
||||||
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
|
// Store bytes and file name directly in your state or model
|
||||||
state.file = FilesModel(
|
state.file = FilesModel(
|
||||||
'', // No need for a file path on web
|
'', // No need for a file path on web
|
||||||
name: name,
|
name: name,
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
audio: false,
|
audio: false,
|
||||||
image: false,
|
image: false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
state.file = FilesModel(result.files.single.path!,
|
state.file = FilesModel(result.files.single.path!,
|
||||||
audio: false, image: false);
|
audio: false, image: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
Duration.zero,
|
Duration.zero,
|
||||||
() => ActionSheetUtils(context).pop(),
|
() => ActionSheetUtils(context).pop(),
|
||||||
);
|
);
|
||||||
},
|
openAttach = !openAttach;
|
||||||
),
|
|
||||||
if (historyState.bot!.attachmentType!.contains('image'))
|
|
||||||
attachBtn(
|
|
||||||
title: "تصویر",
|
|
||||||
icon: CupertinoIcons.photo,
|
|
||||||
color: Colors.deepOrangeAccent,
|
|
||||||
click: () async {
|
|
||||||
MediaService.onLoadingPickFile(context);
|
|
||||||
|
|
||||||
final pickedFile = await MediaService.pickImage(
|
state.update();
|
||||||
source: ImageSource.gallery);
|
},
|
||||||
File? file;
|
),
|
||||||
if (pickedFile != null && !kIsWeb) {
|
if (historyState.bot!.attachmentType!.contains('image'))
|
||||||
file = await ImageCropper().cropImage(
|
Padding(
|
||||||
sourcePath: pickedFile.path,
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
androidUiSettings: const AndroidUiSettings(
|
child: MessageBarBtn(
|
||||||
toolbarTitle: 'برش تصویر'),
|
enable: true,
|
||||||
iosUiSettings: const IOSUiSettings(
|
icon: CupertinoIcons.photo,
|
||||||
title: 'برش تصویر',
|
click: () async {
|
||||||
doneButtonTitle: 'تایید',
|
MediaService.onLoadingPickFile(context);
|
||||||
cancelButtonTitle: 'بازگشت',
|
|
||||||
),
|
|
||||||
compressQuality: 30,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (file == null) {
|
final pickedFile = await MediaService.pickImage(
|
||||||
await Future.delayed(
|
source: ImageSource.gallery);
|
||||||
Duration.zero,
|
File? file;
|
||||||
() => ActionSheetUtils(context).pop(),
|
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 (file == null) {
|
||||||
}
|
await Future.delayed(
|
||||||
}
|
Duration.zero,
|
||||||
if (pickedFile == null) {
|
() => ActionSheetUtils(context).pop(),
|
||||||
await Future.delayed(
|
);
|
||||||
Duration.zero,
|
|
||||||
() => ActionSheetUtils(context).pop(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.file = kIsWeb
|
}
|
||||||
? FilesModel(pickedFile.path,
|
if (pickedFile == null) {
|
||||||
name: pickedFile.name,
|
await Future.delayed(
|
||||||
image: true,
|
Duration.zero,
|
||||||
audio: false)
|
() => ActionSheetUtils(context).pop(),
|
||||||
: 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 =
|
return;
|
||||||
await MediaService.pickAudioFile();
|
}
|
||||||
if (result != null) {
|
state.file = kIsWeb
|
||||||
if (kIsWeb) {
|
? FilesModel(pickedFile.path,
|
||||||
Uint8List? bytes = result.files.first
|
name: pickedFile.name, image: true, audio: false)
|
||||||
.bytes; // Access the bytes property
|
: FilesModel(file!.path, image: true, audio: false);
|
||||||
String? name = result.files.first.name;
|
await Future.delayed(
|
||||||
|
Duration.zero,
|
||||||
|
() => ActionSheetUtils(context).pop(),
|
||||||
|
);
|
||||||
|
openAttach = !openAttach;
|
||||||
|
|
||||||
// final blob = html.Blob([bytes]);
|
state.update();
|
||||||
// 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(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
// 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(
|
// InkWell attachBtn(
|
||||||
{required final String title,
|
// {required final String title,
|
||||||
required final IconData icon,
|
// required final IconData icon,
|
||||||
final Color? color,
|
// final Color? color,
|
||||||
final Function()? click}) {
|
// final Function()? click}) {
|
||||||
final state = context.read<AiChatState>();
|
// final state = context.read<AiChatState>();
|
||||||
return InkWell(
|
// return InkWell(
|
||||||
onTap: () async {
|
// onTap: () async {
|
||||||
await click?.call();
|
// await click?.call();
|
||||||
|
|
||||||
state.update();
|
// state.update();
|
||||||
},
|
// },
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
Container(
|
// Container(
|
||||||
width: 40,
|
// width: 40,
|
||||||
height: 40,
|
// height: 40,
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
// decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||||
padding: const EdgeInsets.all(0.8),
|
// padding: const EdgeInsets.all(0.8),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
// margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Icon(
|
// child: Icon(
|
||||||
icon,
|
// icon,
|
||||||
size: 20,
|
// size: 20,
|
||||||
color: Theme.of(context).colorScheme.white,
|
// color: Theme.of(context).colorScheme.white,
|
||||||
)),
|
// )),
|
||||||
const SizedBox(
|
// const SizedBox(
|
||||||
height: 4,
|
// height: 4,
|
||||||
),
|
// ),
|
||||||
DidvanText(
|
// DidvanText(
|
||||||
title,
|
// title,
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
)
|
// )
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget audioContainer() {
|
Widget audioContainer() {
|
||||||
final state = context.watch<AiChatState>();
|
final state = context.watch<AiChatState>();
|
||||||
|
|
@ -711,6 +856,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
borderRadius: DesignConfig.mediumBorderRadius,
|
borderRadius: DesignConfig.mediumBorderRadius,
|
||||||
color: Theme.of(context).colorScheme.border,
|
color: Theme.of(context).colorScheme.border,
|
||||||
),
|
),
|
||||||
|
margin: const EdgeInsets.fromLTRB(4, 4, 4, 8),
|
||||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
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 {
|
class AudioWave extends StatefulWidget {
|
||||||
final String file;
|
final String file;
|
||||||
final Uint8List? bytes;
|
|
||||||
final double loadingPaddingSize;
|
final double loadingPaddingSize;
|
||||||
final Duration? totalDuration;
|
final Duration? totalDuration;
|
||||||
const AudioWave(
|
const AudioWave({
|
||||||
{Key? key,
|
Key? key,
|
||||||
required this.file,
|
required this.file,
|
||||||
this.loadingPaddingSize = 0,
|
this.loadingPaddingSize = 0,
|
||||||
this.totalDuration,
|
this.totalDuration,
|
||||||
this.bytes})
|
}) : super(key: key);
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AudioWaveState createState() => _AudioWaveState();
|
_AudioWaveState createState() => _AudioWaveState();
|
||||||
|
|
@ -110,8 +108,7 @@ class _AudioWaveState extends State<AudioWave> {
|
||||||
? DidvanIcons.pause_solid
|
? DidvanIcons.pause_solid
|
||||||
: DidvanIcons.play_solid,
|
: DidvanIcons.play_solid,
|
||||||
click: () async {
|
click: () async {
|
||||||
await VoiceService.voiceHelper(
|
await VoiceService.voiceHelper(src: widget.file);
|
||||||
src: widget.file, bytes: widget.bytes);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -281,17 +281,15 @@ class DirectState extends CoreProvier {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final Uint8List uploadFile = kIsWeb
|
final Uint8List uploadFile = kIsWeb
|
||||||
? (await http.get(Uri.parse(path!))).bodyBytes
|
? (await http.get(Uri.parse(path!.replaceAll('%3A', ':')))).bodyBytes
|
||||||
: await File(path!).readAsBytes();
|
: await File(path!).readAsBytes();
|
||||||
|
|
||||||
path = null;
|
|
||||||
|
|
||||||
await service.multipartBytes(
|
await service.multipartBytes(
|
||||||
file: uploadFile,
|
file: uploadFile,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
fieldName: 'audio',
|
fieldName: 'audio',
|
||||||
fileName: 'voice-message',
|
fileName: 'voice-message',
|
||||||
mediaExtension: 'm4a',
|
mediaExtension: 'mp3',
|
||||||
mediaFormat: 'audio',
|
mediaFormat: 'audio',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -304,8 +302,8 @@ class DirectState extends CoreProvier {
|
||||||
for (var i = 0; i < messages.length; i++) {
|
for (var i = 0; i < messages.length; i++) {
|
||||||
_addToDailyGrouped(messages[i]);
|
_addToDailyGrouped(messages[i]);
|
||||||
}
|
}
|
||||||
// update();
|
|
||||||
}
|
}
|
||||||
|
path = null;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ class Message extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (message.text != null) DidvanText(message.text!),
|
if (message.text != null) DidvanText(message.text!),
|
||||||
if (message.audio != null || message.audioFile != null)
|
if (message.audio != null)
|
||||||
AudioWave(
|
AudioWave(
|
||||||
file: message.audio ?? message.audioFile!.path,
|
file: message.audio!,
|
||||||
totalDuration: message.duration != null
|
totalDuration: message.duration != null
|
||||||
? Duration(seconds: message.duration!)
|
? Duration(seconds: message.duration!)
|
||||||
: null,
|
: null,
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ class SkeletonImage extends StatelessWidget {
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: borderRadius ?? BorderRadius.zero,
|
borderRadius: borderRadius ?? BorderRadius.zero,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
errorWidget: (context, url, error) =>
|
errorWidget: (context, url, error) {
|
||||||
const Text("مشکلی پیش آمده است"),
|
return const Text("مشکلی پیش آمده است");
|
||||||
|
},
|
||||||
errorListener: (value) {},
|
errorListener: (value) {},
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||||
|
|
@ -41,8 +42,8 @@ class SkeletonImage extends StatelessWidget {
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
imageUrl: imageUrl.startsWith('http')
|
imageUrl: imageUrl.startsWith('http')
|
||||||
? imageUrl
|
? imageUrl.replaceAll('\n', '')
|
||||||
: RequestHelper.baseUrl + imageUrl,
|
: RequestHelper.baseUrl + imageUrl.replaceAll('\n', ''),
|
||||||
placeholder: (context, _) => ShimmerPlaceholder(
|
placeholder: (context, _) => ShimmerPlaceholder(
|
||||||
width: pWidth,
|
width: pWidth,
|
||||||
height: pHeight,
|
height: pHeight,
|
||||||
|
|
|
||||||
56
pubspec.lock
56
pubspec.lock
|
|
@ -1037,6 +1037,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
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:
|
rive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ dependencies:
|
||||||
carousel_slider: ^4.0.0
|
carousel_slider: ^4.0.0
|
||||||
flutter_vibrate: ^1.3.0
|
flutter_vibrate: ^1.3.0
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
# record: ^5.1.2
|
record: ^5.1.2
|
||||||
|
|
||||||
persian_datetime_picker: ^2.6.0
|
persian_datetime_picker: ^2.6.0
|
||||||
persian_number_utility: ^1.1.1
|
persian_number_utility: ^1.1.1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue