924 lines
35 KiB
Dart
924 lines
35 KiB
Dart
// ignore_for_file: library_private_types_in_public_api, avoid_web_libraries_in_flutter
|
|
|
|
import 'dart:async';
|
|
import 'package:record/record.dart';
|
|
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';
|
|
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/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/services.dart';
|
|
import 'package:flutter_sound/flutter_sound.dart';
|
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|
import 'package:image_cropper/image_cropper.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';
|
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
typedef _Fn = void Function();
|
|
|
|
class AiMessageBar extends StatefulWidget {
|
|
final BotsModel bot;
|
|
final bool? attch;
|
|
const AiMessageBar({Key? key, required this.bot, this.attch})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_AiMessageBarState createState() => _AiMessageBarState();
|
|
}
|
|
|
|
class _AiMessageBarState extends State<AiMessageBar> {
|
|
FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer();
|
|
FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
|
|
|
|
Codec _codec = Codec.aacMP4;
|
|
String _mPath = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.mp4';
|
|
|
|
bool _mPlayerIsInited = false;
|
|
bool _mRecorderIsInited = false;
|
|
bool _mplaybackReady = false;
|
|
late bool openAttach = widget.attch ?? false;
|
|
|
|
Timer? _timer;
|
|
final theSource = AudioSource.microphone;
|
|
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
|
|
late HistoryAiChatState historyState = context.read<HistoryAiChatState>();
|
|
|
|
@override
|
|
void initState() {
|
|
_mPlayer!.openPlayer().then((value) {
|
|
setState(() {
|
|
_mPlayerIsInited = true;
|
|
});
|
|
});
|
|
|
|
openTheRecorder().then((value) {
|
|
setState(() {
|
|
_mRecorderIsInited = true;
|
|
});
|
|
});
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_mPlayer!.closePlayer();
|
|
_mPlayer = null;
|
|
|
|
_mRecorder!.closeRecorder();
|
|
_mRecorder = null;
|
|
_timer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> openTheRecorder() async {
|
|
if (!kIsWeb) {
|
|
var status = await Permission.microphone.status;
|
|
await AudioRecorder().hasPermission();
|
|
if (status != PermissionStatus.granted) {
|
|
if (!Platform.isIOS) {
|
|
throw RecordingPermissionException(
|
|
'Microphone permission not granted');
|
|
}
|
|
}
|
|
}
|
|
await _mRecorder!.openRecorder();
|
|
if (!await _mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
|
|
_codec = Codec.opusWebM;
|
|
_mPath = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.webm';
|
|
if (!await _mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
|
|
_mRecorderIsInited = true;
|
|
return;
|
|
}
|
|
}
|
|
final session = await AudioSession.instance;
|
|
await session.configure(AudioSessionConfiguration(
|
|
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
|
|
avAudioSessionCategoryOptions:
|
|
AVAudioSessionCategoryOptions.allowBluetooth |
|
|
AVAudioSessionCategoryOptions.defaultToSpeaker,
|
|
avAudioSessionMode: AVAudioSessionMode.spokenAudio,
|
|
avAudioSessionRouteSharingPolicy:
|
|
AVAudioSessionRouteSharingPolicy.defaultPolicy,
|
|
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
|
|
androidAudioAttributes: const AndroidAudioAttributes(
|
|
contentType: AndroidAudioContentType.speech,
|
|
flags: AndroidAudioFlags.none,
|
|
usage: AndroidAudioUsage.voiceCommunication,
|
|
),
|
|
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
|
|
androidWillPauseWhenDucked: true,
|
|
));
|
|
|
|
_mRecorderIsInited = true;
|
|
}
|
|
|
|
void startTimer() {
|
|
const oneSec = Duration(seconds: 1);
|
|
_timer = Timer.periodic(
|
|
oneSec,
|
|
(Timer timer) {
|
|
_countTimer.value = Duration(seconds: _countTimer.value.inSeconds + 1);
|
|
},
|
|
);
|
|
}
|
|
|
|
void record() async {
|
|
_countTimer.value = Duration.zero;
|
|
|
|
await _mRecorder!
|
|
.startRecorder(
|
|
toFile: _mPath,
|
|
codec: _codec,
|
|
audioSource: theSource,
|
|
)
|
|
.then((value) {
|
|
setState(() {
|
|
startTimer();
|
|
});
|
|
});
|
|
}
|
|
|
|
void stopRecorder() async {
|
|
await _mRecorder!.stopRecorder().then((value) {
|
|
setState(() {
|
|
var url = value;
|
|
final state = context.read<AiChatState>();
|
|
state.file = FilesModel(url!,
|
|
audio: true, isRecorded: true, duration: _countTimer.value);
|
|
_mplaybackReady = true;
|
|
_timer?.cancel();
|
|
});
|
|
});
|
|
_mPlayer!.setSubscriptionDuration(_countTimer.value);
|
|
}
|
|
|
|
void play() async {
|
|
assert(_mPlayerIsInited &&
|
|
_mplaybackReady &&
|
|
_mRecorder!.isStopped &&
|
|
_mPlayer!.isStopped);
|
|
_mPlayer!
|
|
.startPlayer(
|
|
fromURI: _mPath,
|
|
whenFinished: () {
|
|
setState(() {});
|
|
})
|
|
.then((value) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
void stopPlayer() {
|
|
_mPlayer!.stopPlayer().then((value) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
void pausePlayer() {
|
|
_mPlayer!.pausePlayer().then((value) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
void resumePlayer() {
|
|
_mPlayer!.resumePlayer().then((value) {
|
|
setState(() {});
|
|
});
|
|
}
|
|
|
|
_Fn? getRecorderFn() {
|
|
if (!_mRecorderIsInited || !_mPlayer!.isStopped) {
|
|
return null;
|
|
}
|
|
return _mRecorder!.isStopped ? record : stopRecorder;
|
|
}
|
|
|
|
_Fn? getPlaybackFn() {
|
|
if (!_mPlayerIsInited || !_mplaybackReady || !_mRecorder!.isStopped) {
|
|
return null;
|
|
}
|
|
return _mPlayer!.isPlaying
|
|
? pausePlayer
|
|
: _mPlayer!.isPaused
|
|
? resumePlayer
|
|
: _mPlayer!.isStopped
|
|
? play
|
|
: stopPlayer;
|
|
}
|
|
|
|
@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(
|
|
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(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
recorderAndSendButton(state, historyState),
|
|
if (!(_mRecorder!.isStopped))
|
|
ValueListenableBuilder(
|
|
valueListenable: _countTimer,
|
|
builder: (context, value, child) => Padding(
|
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
|
|
child: SizedBox(
|
|
width: 50,
|
|
child: Center(
|
|
child: DidvanText(
|
|
DateTimeUtils.normalizeTimeDuration(value)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding:
|
|
_mRecorder!.isPaused || _mRecorder!.isRecording
|
|
? const EdgeInsets.fromLTRB(12, 8, 0, 8)
|
|
: EdgeInsets.zero,
|
|
child: recorderAndTextMessageHandler(context, state),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (state.onResponsing)
|
|
Positioned.fill(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.focused
|
|
.withOpacity(0.5)),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
MediaQuery.of(context).viewInsets.bottom == 0
|
|
? const Padding(
|
|
padding: EdgeInsets.fromLTRB(8, 8, 8, 4),
|
|
child: DidvanText(
|
|
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.',
|
|
fontSize: 12,
|
|
),
|
|
)
|
|
: const SizedBox(
|
|
height: 12,
|
|
)
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Row recorderAndTextMessageHandler(BuildContext context, AiChatState state) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: _mRecorder!.isPaused
|
|
? [
|
|
Expanded(
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(
|
|
32,
|
|
(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)),
|
|
),
|
|
)),
|
|
),
|
|
),
|
|
MessageBarBtn(
|
|
enable: true,
|
|
icon: DidvanIcons.play_regular,
|
|
click: () async {
|
|
await _mRecorder?.resumeRecorder();
|
|
setState(() {
|
|
startTimer();
|
|
});
|
|
},
|
|
)
|
|
]
|
|
: _mRecorder!.isRecording
|
|
? [
|
|
Expanded(
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(
|
|
5,
|
|
(index) => SpinKitWave(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary
|
|
.withOpacity(0.4),
|
|
size: 32,
|
|
itemCount: 10,
|
|
)))),
|
|
MessageBarBtn(
|
|
enable: true,
|
|
icon: DidvanIcons.pause_regular,
|
|
click: () async {
|
|
await _mRecorder?.pauseRecorder();
|
|
|
|
setState(() {
|
|
_timer!.cancel();
|
|
});
|
|
},
|
|
)
|
|
]
|
|
: [
|
|
Expanded(
|
|
child: 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
|
|
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) {
|
|
state.message.text = value;
|
|
state.update();
|
|
},
|
|
)),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 8.0),
|
|
child: Row(children: [
|
|
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();
|
|
},
|
|
),
|
|
)
|
|
]),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
Padding recorderAndSendButton(
|
|
AiChatState state, HistoryAiChatState historyState) {
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
|
child: state.message.text.isEmpty &&
|
|
historyState.bot!.attachmentType!.contains('audio') &&
|
|
state.file == null &&
|
|
widget.bot.attachment != 0
|
|
? MessageBarBtn(
|
|
enable: true,
|
|
icon: _mRecorder!.isRecording || _mRecorder!.isPaused
|
|
? Icons.stop_rounded
|
|
: DidvanIcons.mic_regular,
|
|
click: getRecorderFn(),
|
|
)
|
|
: MessageBarBtn(
|
|
enable: (state.file != null && state.file!.isRecorded) ||
|
|
(widget.bot.attachment == 1) ||
|
|
state.message.text.isNotEmpty,
|
|
icon: DidvanIcons.send_light,
|
|
click: () async {
|
|
if ((state.file == null || !state.file!.isRecorded) &&
|
|
(widget.bot.attachment != 1) &&
|
|
state.message.text.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();
|
|
openAttach = false;
|
|
state.update();
|
|
await state.postMessage(widget.bot);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
// 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: 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) {
|
|
String? name = result.files.first.name;
|
|
|
|
if (kIsWeb) {
|
|
Uint8List? bytes =
|
|
result.files.first.bytes; // Access the bytes property
|
|
|
|
// 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, name: name);
|
|
}
|
|
}
|
|
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,
|
|
name: pickedFile.name, 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) {
|
|
String? name = result.files.first.name;
|
|
|
|
if (kIsWeb) {
|
|
Uint8List? bytes =
|
|
result.files.first.bytes; // Access the bytes property
|
|
|
|
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!,
|
|
name: name, 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();
|
|
|
|
// 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>();
|
|
|
|
return SizedBox(
|
|
width: MediaQuery.sizeOf(context).width,
|
|
child: AudioWave(
|
|
file: state.file!.path,
|
|
loadingPaddingSize: 8.0,
|
|
totalDuration: _countTimer.value,
|
|
),
|
|
);
|
|
}
|
|
|
|
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,
|
|
);
|
|
})
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|