Web fix stable

This commit is contained in:
OkaykOrhmn 2024-09-30 16:50:18 +03:30
parent 5b83ec4bbf
commit bfb9bd4d3b
20 changed files with 1067 additions and 1026 deletions

View File

@ -27,12 +27,18 @@ class FilesModel {
this.duration, this.duration,
}) { }) {
basename = name ?? p.basename(path); basename = name ?? p.basename(path);
extname = p.extension(path); extname = path.isNotEmpty
? p.extension(path)
: name != null
? p.extension(name)
: '';
main = File(path); main = File(path);
} }
bool isAudio() { bool isAudio() {
return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false; return audio ??
(lookupMimeType(path)?.startsWith('audio/') ?? false) ||
(lookupMimeType(path)?.startsWith('video/') ?? false);
} }
bool isImage() { bool isImage() {

View File

@ -4,11 +4,11 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:didvan/models/ai/files_model.dart'; import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
class AiApiService { class AiApiService {
static const String baseUrl = 'https://api.didvan.app/ai'; static const String baseUrl = 'https://api.didvan.app/ai';
@ -48,8 +48,11 @@ class AiApiService {
bytes = file.bytes!; bytes = file.bytes!;
} else { } else {
final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':')); final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':'));
final http.Response audioResponse = await http.get(audioUri); final http.Response audioResponse = await http.get(audioUri);
bytes = audioResponse.bodyBytes; bytes = audioResponse.bodyBytes;
// final f = File.fromUri(Uri.parse(file.path)); // final f = File.fromUri(Uri.parse(file.path));
// bytes = await f.readAsBytes(); // bytes = await f.readAsBytes();
@ -68,15 +71,15 @@ class AiApiService {
// For other platforms // For other platforms
bytes = await file.main.readAsBytes(); bytes = await file.main.readAsBytes();
} }
filename = file.basename; if (file.isRecorded) {
filename = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.mp4';
mimeType = file.isAudio() mimeType = lookupMimeType(filename, headerBytes: bytes) ?? 'audio/mp4';
? file.isRecorded } else {
? 'audio/${kIsWeb && AppInitializer.getOperatingSystem() == 'iOS' ? 'webm' : 'm4a'}' filename = file.basename;
: 'audio/${file.extname.replaceAll('.', '')}' mimeType = lookupMimeType(filename, headerBytes: bytes) ??
: file.isImage() 'application/octet-stream';
? 'image/png' }
: 'application/pdf';
request.files.add(http.MultipartFile.fromBytes( request.files.add(http.MultipartFile.fromBytes(
'file', 'file',

View File

@ -16,7 +16,6 @@ import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'dart:html' as html;
class AppInitializer { class AppInitializer {
static String? fcmToken; static String? fcmToken;
@ -42,24 +41,6 @@ class AppInitializer {
}); });
} }
static String getOperatingSystem() {
final userAgent = html.window.navigator.userAgent.toLowerCase();
if (userAgent.contains('windows')) {
return 'Windows';
} else if (userAgent.contains('mac os')) {
return 'MacOS';
} else if (userAgent.contains('iphone') || userAgent.contains('ipad')) {
return 'iOS';
} else if (userAgent.contains('android')) {
return 'Android';
} else if (userAgent.contains('linux')) {
return 'Linux';
} else {
return 'Unknown';
}
}
static Future<SettingsData> initilizeSettings() async { static Future<SettingsData> initilizeSettings() async {
try { try {
final brightness = await StorageService.getValue(key: 'brightness'); final brightness = await StorageService.getValue(key: 'brightness');

View File

@ -7,7 +7,6 @@ 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:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
@ -58,7 +57,7 @@ class MediaService {
source = audioSource; source = audioSource;
} }
audioPlayer.setUrl( audioPlayer.setUrl(
kIsWeb ? source.replaceAll('%3A', ':') : source, source,
tag: isVoiceMessage tag: isVoiceMessage
? null ? null
: { : {

View File

@ -1,40 +1,53 @@
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';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html; class MyJABytesSource extends StreamAudioSource {
final Uint8List _buffer;
MyJABytesSource(this._buffer) : super(tag: 'MyAudioSource');
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
// Returning the stream audio response with the parameters
return StreamAudioResponse(
sourceLength: _buffer.length,
contentLength: (end ?? _buffer.length) - (start ?? 0),
offset: start ?? 0,
stream: Stream.fromIterable([_buffer.sublist(start ?? 0, end)]),
contentType: 'audio/wav',
);
}
}
class VoiceService { class VoiceService {
static final audioPlayer = AudioPlayer(); static final audioPlayer = AudioPlayer();
static String? src; static String? src;
static Future<Duration?> getDuration({ static Future<Duration?> getDuration(
required String src, {required String src, Uint8List? bytes}) async {
}) async {
final ap = AudioPlayer(); final ap = AudioPlayer();
Duration? duration; Duration? duration;
try { try {
if (src.startsWith('/uploads')) { if (src.startsWith('/uploads')) {
final source = final source =
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'; '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
if (kIsWeb) { // if (kIsWeb) {
final response = // final response =
await http.get(Uri.parse(source.replaceAll('%3A', ':'))); // await http.get(Uri.parse(source.replaceAll('%3A', ':')));
final bytes = response.bodyBytes; // final bytes = response.bodyBytes;
final blob = html.Blob([bytes]); // final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob); // final blobUrl = html.Url.createObjectUrlFromBlob(blob);
duration = await ap.setAudioSource( // duration = await ap.setAudioSource(
AudioSource.uri(Uri.parse(blobUrl)), // AudioSource.uri(Uri.parse(blobUrl)),
); // );
} else { // } else {
final lockCachingAudioSource = duration = await ap.setUrl(source);
LockCachingAudioSource(Uri.parse(source)); // }
} else if (src.startsWith('blob:') || src == '') {
duration = await ap.setAudioSource(lockCachingAudioSource); duration = await ap.setUrl(src);
}
} else if (src.startsWith('blob:')) {
duration = await ap.setAudioSource(AudioSource.uri(Uri.parse(src)));
} else { } else {
duration = await ap.setFilePath(src); duration = await ap.setFilePath(src);
} }
@ -53,6 +66,7 @@ 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 {
@ -72,23 +86,20 @@ class VoiceService {
if (src.startsWith('/uploads')) { if (src.startsWith('/uploads')) {
final source = final source =
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'; '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
if (kIsWeb) { // if (kIsWeb) {
final response = // final response =
await http.get(Uri.parse(source.replaceAll('%3A', ':'))); // await http.get(Uri.parse(source.replaceAll('%3A', ':')));
final bytes = response.bodyBytes; // final bytes = response.bodyBytes;
final blob = html.Blob([bytes]); // final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob); // final blobUrl = html.Url.createObjectUrlFromBlob(blob);
await audioPlayer.setAudioSource( // await audioPlayer.setAudioSource(
AudioSource.uri(Uri.parse(blobUrl)), // AudioSource.uri(Uri.parse(blobUrl)),
); // );
} else { // } else {
final lockCachingAudioSource = await audioPlayer.setUrl(source);
LockCachingAudioSource(Uri.parse(source)); // }
} else if (src.startsWith('blob:') || src == '') {
await audioPlayer.setAudioSource(lockCachingAudioSource); await audioPlayer.setUrl(src);
}
} else if (src.startsWith('blob:')) {
await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(src)));
} else { } else {
await audioPlayer.setFilePath(src); await audioPlayer.setFilePath(src);
} }
@ -102,7 +113,6 @@ class VoiceService {
static Future<void> resetVoicePlayer() async { static Future<void> resetVoicePlayer() async {
src = null; src = null;
await audioPlayer.stop(); await audioPlayer.stop();
} }
} }

View File

@ -436,6 +436,37 @@ class ActionSheetUtils {
); );
} }
static PopupMenuItem<dynamic> popUpBtns({
required final String value,
required final IconData icon,
final Color? color,
final double? height,
final double? size,
}) {
return PopupMenuItem(
value: value,
height: height ?? 46,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: color,
size: size,
),
const SizedBox(
width: 12,
),
DidvanText(
value,
color: color,
fontSize: size,
),
],
),
);
}
void pop() { void pop() {
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -196,7 +196,7 @@ class _AiState extends State<Ai> {
child: Row( child: Row(
children: [ children: [
const MessageBarBtn( const MessageBarBtn(
enable: false, enable: true,
icon: icon:
DidvanIcons.mic_regular), DidvanIcons.mic_regular),
const SizedBox( const SizedBox(
@ -225,19 +225,19 @@ class _AiState extends State<Ai> {
), ),
), ),
), ),
const SizedBox(
width: 8,
),
const MessageBarBtn(
enable: false,
icon: Icons
.attach_file_rounded),
], ],
)))) ))))
], ],
), ),
), ),
), ),
const SizedBox(
width: 18,
),
Icon(
Icons.attach_file_rounded,
color: Theme.of(context).colorScheme.focusedBorder,
),
], ],
)), )),
), ),

View File

@ -21,7 +21,6 @@ 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';
import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
@ -269,8 +268,8 @@ class _AiChatPageState extends State<AiChatPage> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: state.file != null && bottom: state.file != null &&
!state.file!.isRecorded !(state.file!.isRecorded)
? 150 ? 180
: 100), : 100),
itemBuilder: (context, mIndex) { itemBuilder: (context, mIndex) {
final prompts = state.messages[mIndex].prompts; final prompts = state.messages[mIndex].prompts;
@ -299,7 +298,7 @@ class _AiChatPageState extends State<AiChatPage> {
children: [ children: [
AiMessageBar( AiMessageBar(
bot: widget.args.bot, bot: widget.args.bot,
focusNode: focusNode, // focusNode: focusNode,
), ),
], ],
)), )),
@ -509,6 +508,7 @@ class _AiChatPageState extends State<AiChatPage> {
.remove(message); .remove(message);
state.messages.last.prompts.add( state.messages.last.prompts.add(
message.copyWith(error: false)); message.copyWith(error: false));
state.file = file;
state.update(); state.update();
await state await state
.postMessage(widget.args.bot); .postMessage(widget.args.bot);
@ -559,6 +559,7 @@ class _AiChatPageState extends State<AiChatPage> {
} else { } else {
state.messages[mIndex].prompts state.messages[mIndex].prompts
.removeAt(index); .removeAt(index);
state.update();
} }
}, },
child: Icon( child: Icon(
@ -624,18 +625,18 @@ class _AiChatPageState extends State<AiChatPage> {
stop: const Duration(seconds: 3), stop: const Duration(seconds: 3),
), ),
), ),
if (state.file != null && !kIsWeb) // if (state.file != null && !kIsWeb)
FutureBuilder( // FutureBuilder(
future: state.file!.main.length(), // future: state.file!.main.length(),
builder: (context, snapshot) { // builder: (context, snapshot) {
if (!snapshot.hasData) { // if (!snapshot.hasData) {
return const SizedBox(); // return const SizedBox();
} // }
return DidvanText( // return DidvanText(
'File Size ${(snapshot.data! / 1000).round()} KB', // 'File Size ${(snapshot.data! / 1000).round()} KB',
fontSize: 12, // fontSize: 12,
); // );
}) // })
], ],
), ),
) )

View File

@ -134,6 +134,8 @@ class AiChatState extends CoreProvier {
Future<void> postMessage(BotsModel bot) async { Future<void> postMessage(BotsModel bot) async {
onResponsing = true; onResponsing = true;
final uploadedFile = file;
file = null;
update(); update();
String message = messages.last.prompts.last.text!; String message = messages.last.prompts.last.text!;
@ -157,7 +159,7 @@ class AiChatState extends CoreProvier {
url: '/${bot.id}/${bot.name}'.toLowerCase(), url: '/${bot.id}/${bot.name}'.toLowerCase(),
message: message, message: message,
chatId: chatId, chatId: chatId,
file: file, file: uploadedFile,
edite: isEdite); edite: isEdite);
final res = await AiApiService().getResponse(req).catchError((e) { final res = await AiApiService().getResponse(req).catchError((e) {
_onError(e); _onError(e);
@ -166,7 +168,6 @@ class AiChatState extends CoreProvier {
String responseMessgae = ''; String responseMessgae = '';
String dataMessgae = ''; String dataMessgae = '';
file = null;
update(); update();
final r = res.listen((value) async { final r = res.listen((value) async {

File diff suppressed because it is too large Load Diff

View File

@ -15,13 +15,15 @@ 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
@ -60,6 +62,7 @@ class _AudioWaveState extends State<AudioWave> {
if (event.processingState == ProcessingState.completed) { if (event.processingState == ProcessingState.completed) {
await VoiceService.audioPlayer.pause(); await VoiceService.audioPlayer.pause();
await VoiceService.audioPlayer.seek(Duration.zero); await VoiceService.audioPlayer.seek(Duration.zero);
VoiceService.resetVoicePlayer();
} }
}); });
} }
@ -107,7 +110,8 @@ class _AudioWaveState extends State<AudioWave> {
? DidvanIcons.pause_solid ? DidvanIcons.pause_solid
: DidvanIcons.play_solid, : DidvanIcons.play_solid,
click: () async { click: () async {
await VoiceService.voiceHelper(src: widget.file); await VoiceService.voiceHelper(
src: widget.file, bytes: widget.bytes);
}, },
); );
}), }),

View File

@ -30,6 +30,14 @@ class _DirectState extends State<Direct> {
@override @override
void initState() { void initState() {
final state = context.read<DirectState>(); final state = context.read<DirectState>();
state.mPlayer!.openPlayer().then((value) {
state.mPlayerIsInited = true;
});
state.openTheRecorder().then((value) {
state.mRecorderIsInited = true;
});
state.replyNews = widget.pageData['newsAttachment']; state.replyNews = widget.pageData['newsAttachment'];
state.replyRadar = widget.pageData['radarAttachment']; state.replyRadar = widget.pageData['radarAttachment'];
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']); final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);

View File

@ -1,22 +1,26 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:audio_session/audio_session.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/models/message_data/news_attachment.dart'; import 'package:didvan/models/message_data/news_attachment.dart';
import 'package:didvan/models/message_data/radar_attachment.dart'; import 'package:didvan/models/message_data/radar_attachment.dart';
import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/core.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/media/voice.dart'; import 'package:didvan/services/media/voice.dart';
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';
import 'package:flutter_sound/public/flutter_sound_player.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_platform_interface.dart';
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';
import 'package:flutter_vibrate/flutter_vibrate.dart'; import 'package:flutter_vibrate/flutter_vibrate.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:record/record.dart';
class DirectState extends CoreProvier { class DirectState extends CoreProvier {
final _recorder = AudioRecorder(); // final _recorder = AudioRecorder();
final List<MessageData> messages = []; final List<MessageData> messages = [];
late final int typeId; late final int typeId;
final Map<String, List<int>> dailyMessages = {}; final Map<String, List<int>> dailyMessages = {};
@ -25,12 +29,60 @@ class DirectState extends CoreProvier {
String? text; String? text;
NewsAttachment? replyNews; NewsAttachment? replyNews;
RadarAttachment? replyRadar; RadarAttachment? replyRadar;
File? recordedFile;
Uint8List? recordedFileBytes;
int? audioDuration; int? audioDuration;
String? path; String? path;
bool isRecording = false; 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;
Timer? _timer;
final theSource = AudioSource.microphone;
final ValueNotifier<Duration> countTimer = ValueNotifier(Duration.zero);
Future<void> openTheRecorder() async {
if (!kIsWeb) {
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
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;
}
Future<void> getMessages() async { Future<void> getMessages() async {
appState = AppState.busy; appState = AppState.busy;
@ -48,48 +100,108 @@ class DirectState extends CoreProvier {
appState = AppState.idle; appState = AppState.idle;
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
} }
@override
void dispose() {
super.dispose();
mPlayer!.closePlayer();
mPlayer = null;
mRecorder!.closeRecorder();
mRecorder = null;
_timer?.cancel();
}
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) {
startTimer();
update();
});
}
void play() async {
assert(mPlayerIsInited &&
mplaybackReady &&
mRecorder!.isStopped &&
mPlayer!.isStopped);
mPlayer!
.startPlayer(
fromURI: _mPath,
// fromDataBuffer: Uint8List.fromList(
// recordedData.expand((element) => element).toList()),
//codec: kIsWeb ? Codec.opusWebM : Codec.aacADTS,
whenFinished: () {})
.then((value) {});
}
void stopPlayer() {
mPlayer!.stopPlayer().then((value) {});
}
void pausePlayer() {
mPlayer!.pausePlayer().then((value) {});
}
void resumePlayer() {
mPlayer!.resumePlayer().then((value) {});
}
void deleteRecordedFile() { void deleteRecordedFile() {
recordedFile!.delete(); path = null;
recordedFile = null;
notifyListeners(); notifyListeners();
update(); update();
} }
Future<void> startRecording() async { Future<void> startRecording() async {
text = null; text = null;
await _recorder.hasPermission(); // await _recorder.hasPermission();
if (!kIsWeb) { if (!kIsWeb) {
Vibrate.feedback(FeedbackType.medium); Vibrate.feedback(FeedbackType.medium);
} }
isRecording = true; record();
Directory? tempDir;
if (!kIsWeb) {
tempDir = await getApplicationDocumentsDirectory();
}
_recorder.start(const RecordConfig(),
path: kIsWeb
? ''
: '${tempDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
notifyListeners(); notifyListeners();
} }
Future<void> stopRecording({required bool sendImidiately}) async { Future<void> stopRecording({required bool sendImidiately}) async {
path = await _recorder.stop(); path = await mRecorder!.stopRecorder();
isRecording = false; mplaybackReady = true;
_timer?.cancel();
mPlayer!.setSubscriptionDuration(countTimer.value);
update();
if (path == null) { if (path == null) {
notifyListeners(); notifyListeners();
return; return;
} }
if (kIsWeb) {
final Uri audioUri = Uri.parse(path!); // if (kIsWeb) {
final http.Response audioResponse = await http.get(audioUri); // final Uri audioUri = Uri.parse(path!);
recordedFileBytes = audioResponse.bodyBytes; // final http.Response audioResponse = await http.get(audioUri);
} else { // recordedFileBytes = audioResponse.bodyBytes;
recordedFile = File(path!); // } else {
} // recordedFile = File(path!);
// }
if (sendImidiately) { if (sendImidiately) {
await sendMessage(); await sendMessage();
} else { } else {
@ -121,9 +233,8 @@ class DirectState extends CoreProvier {
} }
Future<void> sendMessage() async { Future<void> sendMessage() async {
if ((text == null || text!.isEmpty) && if ((text == null || text!.isEmpty) && path == null) return;
(recordedFile == null && recordedFileBytes == null)) return; VoiceService.audioPlayer.stop();
MediaService.audioPlayer.stop();
final body = {}; final body = {};
@ -144,26 +255,18 @@ class DirectState extends CoreProvier {
} }
if (path != null) { if (path != null) {
final duration = await VoiceService.getDuration(src: path!); body.addAll({'duration': countTimer.value.inSeconds.toString()});
if (duration != null) {
body.addAll({'duration': duration.inSeconds.toString()});
}
} }
final uploadFile = recordedFile;
final uploadFileBytes = recordedFileBytes;
text = null; text = null;
recordedFile = null;
recordedFileBytes = null;
replyRadar = null; replyRadar = null;
replyNews = null; replyNews = null;
path = null;
notifyListeners(); notifyListeners();
final service = final service =
RequestService(RequestHelper.sendDirectMessage(typeId), body: body); RequestService(RequestHelper.sendDirectMessage(typeId), body: body);
if (uploadFile == null && uploadFileBytes == null) { if (path == null) {
await service.post(); await service.post();
if (service.isSuccess) { if (service.isSuccess) {
@ -177,57 +280,33 @@ class DirectState extends CoreProvier {
} }
} }
} else { } else {
messages.insert( final Uint8List uploadFile = kIsWeb
0, ? (await http.get(Uri.parse(path!))).bodyBytes
MessageData( : await File(path!).readAsBytes();
id: 0,
writedByAdmin: false, path = null;
readed: false,
createdAt: await service.multipartBytes(
DateTime.now().subtract(const Duration(minutes: 210)).toString(), file: uploadFile,
text: text, method: 'POST',
// audio: path, fieldName: 'audio',
// audioFile: uploadFile, fileName: 'voice-message',
radar: replyRadar, mediaExtension: 'm4a',
news: replyNews, mediaFormat: 'audio',
audioDuration: audioDuration,
),
); );
dailyMessages.clear();
for (var i = 0; i < messages.length; i++) {
_addToDailyGrouped(messages[i]);
}
if (kIsWeb) {
await service.multipartBytes(
file: uploadFileBytes!,
method: 'POST',
fieldName: 'audio',
fileName: 'voice-message',
mediaExtension: 'm4a',
mediaFormat: 'audio',
);
} else {
await service.multipart(
file: Platform.isIOS
? File(uploadFile!.path.replaceAll('file://', ''))
: uploadFile,
method: 'POST',
fieldName: 'audio',
fileName: 'voice-message',
mediaExtension: 'm4a',
mediaFormat: 'audio',
);
}
if (service.isSuccess) { if (service.isSuccess) {
final message = service.result['message']; final message = service.result['message'];
final m = MessageData.fromJson(message); messages.insert(0, MessageData.fromJson(message));
messages[0] = m;
update(); dailyMessages.clear();
for (var i = 0; i < messages.length; i++) {
_addToDailyGrouped(messages[i]);
}
// update();
} }
} }
notifyListeners();
} }
} }

View File

@ -62,11 +62,10 @@ class MessageBox extends StatelessWidget {
isMessage: true, isMessage: true,
child: Consumer<DirectState>( child: Consumer<DirectState>(
builder: (context, state, child) { builder: (context, state, child) {
if (state.isRecording) { if (state.mRecorder!.isRecording) {
return const _Recording(); return const _Recording();
} else if (!state.isRecording && } else if (!(state.mRecorder!.isRecording) &&
(state.recordedFile != null || state.path != null) {
state.recordedFileBytes != null)) {
return const _RecordChecking(); return const _RecordChecking();
} }
return const _Typing(); return const _Typing();
@ -227,6 +226,7 @@ class _RecordChecking extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: AudioWave( child: AudioWave(
file: state.path!, file: state.path!,
totalDuration: state.countTimer.value,
// deleteClidk: () => state.deleteRecordedFile, // deleteClidk: () => state.deleteRecordedFile,
), ),
), ),

View File

@ -19,7 +19,6 @@ import 'package:didvan/services/notification/notification_service.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/ai/ai.dart'; import 'package:didvan/views/ai/ai.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/home/categories/categories_page.dart'; import 'package:didvan/views/home/categories/categories_page.dart';
import 'package:didvan/views/home/main/main_page.dart'; import 'package:didvan/views/home/main/main_page.dart';
import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/home/home_state.dart';
@ -618,13 +617,13 @@ class _HomeState extends State<Home>
}, },
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return <PopupMenuEntry>[ return <PopupMenuEntry>[
AiMessageBar.popUpBtns( ActionSheetUtils.popUpBtns(
value: 'حذف پیام', value: 'حذف پیام',
icon: DidvanIcons.trash_regular, icon: DidvanIcons.trash_regular,
color: Theme.of(context).colorScheme.error, color: Theme.of(context).colorScheme.error,
height: 24, height: 24,
size: 12), size: 12),
AiMessageBar.popUpBtns( ActionSheetUtils.popUpBtns(
value: 'آرشیو', value: 'آرشیو',
icon: Icons.folder_copy, icon: Icons.folder_copy,
height: 24, height: 24,

View File

@ -1,3 +1,5 @@
// ignore_for_file: avoid_web_libraries_in_flutter
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/models/home_page_content/content.dart'; import 'package:didvan/models/home_page_content/content.dart';
@ -10,9 +12,11 @@ import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:didvan/providers/user.dart'; import 'package:didvan/providers/user.dart';
import 'dart:html' as html;
class MainPagePodcastItem extends StatefulWidget { class MainPagePodcastItem extends StatefulWidget {
final MainPageContentType content; final MainPageContentType content;
@ -24,6 +28,7 @@ class MainPagePodcastItem extends StatefulWidget {
} }
class _MainPagePodcastItemState extends State<MainPagePodcastItem> { class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
bool loading = false;
void _onMarkChange() { void _onMarkChange() {
UserProvider.changeItemMark( UserProvider.changeItemMark(
widget.type, widget.type,
@ -43,8 +48,18 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
args: const StudioRequestArgs(page: 0, type: 'podcast'), args: const StudioRequestArgs(page: 0, type: 'podcast'),
); );
MediaService.currentPodcast = state.studio; MediaService.currentPodcast = state.studio;
loading = true;
state.update();
final response =
await get(Uri.parse(widget.content.link.replaceAll('%3A', ':')));
final bytes = response.bodyBytes;
final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
await Future.delayed(const Duration(seconds: 3));
loading = false;
state.update();
MediaService.handleAudioPlayback( MediaService.handleAudioPlayback(
audioSource: widget.content.link, audioSource: blobUrl,
id: widget.content.id, id: widget.content.id,
isNetworkAudio: true, isNetworkAudio: true,
isVoiceMessage: false, isVoiceMessage: false,
@ -97,42 +112,44 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
DidvanText( children: [
widget.content.title, DidvanText(
style: widget.content.title,
Theme.of(context).textTheme.bodyLarge, style:
maxLines: 1, Theme.of(context).textTheme.bodyLarge,
overflow: TextOverflow.ellipsis, maxLines: 1,
), overflow: TextOverflow.ellipsis,
Padding(
padding:
const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Icon(
DidvanIcons.calendar_day_light,
size: 16,
),
const SizedBox(width: 4),
DidvanText(
DateTime.parse(
widget.content.subtitles[0])
.toPersianDateStr(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodySmall,
),
],
), ),
), Padding(
], padding: const EdgeInsets.symmetric(
vertical: 4),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Icon(
DidvanIcons.calendar_day_light,
size: 16,
),
const SizedBox(width: 4),
DidvanText(
DateTime.parse(
widget.content.subtitles[0])
.toPersianDateStr(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodySmall,
),
],
),
),
],
),
), ),
GestureDetector( GestureDetector(
onTap: _onMarkChange, onTap: _onMarkChange,

View File

@ -348,7 +348,7 @@ class _ProfilePageState extends State<ProfilePage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DidvanText( DidvanText(
'نسخه نرم‌افزار: 3.3.3', 'نسخه نرم‌افزار: 3.3.4',
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
], ],

View File

@ -50,7 +50,7 @@ packages:
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
audio_session: audio_session:
dependency: transitive dependency: "direct main"
description: description:
name: audio_session name: audio_session
sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261"
@ -345,6 +345,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.12" version: "3.8.12"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fl_chart: fl_chart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -507,6 +515,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
flutter_sound:
dependency: "direct main"
description:
name: flutter_sound
sha256: "1f40b26b92907a433afe877c927cd48f5a2e4d0f7188e5d39eb5756008aa51ab"
url: "https://pub.dev"
source: hosted
version: "9.6.0"
flutter_sound_platform_interface:
dependency: "direct main"
description:
name: flutter_sound_platform_interface
sha256: "2e218521d8187b9a4c65063ad9c79bfe88119531ff68047a2eaa6b027cb276bb"
url: "https://pub.dev"
source: hosted
version: "9.6.0"
flutter_sound_web:
dependency: transitive
description:
name: flutter_sound_web
sha256: "5013a15e4e69a4bdc8badd729130eef43c3305e30ba8e6933f863436ce969932"
url: "https://pub.dev"
source: hosted
version: "9.6.0"
flutter_spinkit: flutter_spinkit:
dependency: "direct main" dependency: "direct main"
description: description:
@ -741,6 +773,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
logger:
dependency: transitive
description:
name: logger
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
markdown: markdown:
dependency: transitive dependency: transitive
description: description:
@ -989,62 +1029,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.2"
record: recase:
dependency: "direct main"
description:
name: record
sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867"
url: "https://pub.dev"
source: hosted
version: "5.1.2"
record_android:
dependency: transitive dependency: transitive
description: description:
name: record_android name: recase
sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "4.1.0"
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:
@ -1090,6 +1082,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite: sqflite:
dependency: transitive dependency: transitive
description: description:
@ -1270,10 +1270,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: uuid name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "4.5.1"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.3.3+3330 version: 3.3.4+3340
environment: environment:
sdk: ">=2.19.0 <3.0.0" sdk: ">=2.19.0 <3.0.0"
@ -51,7 +51,8 @@ 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
bot_toast: ^4.0.1 bot_toast: ^4.0.1
@ -82,7 +83,7 @@ dependencies:
android_intent_plus: ^5.0.0 android_intent_plus: ^5.0.0
get: ^4.6.6 get: ^4.6.6
# firebase_auth: ^4.19.6 # firebase_auth: ^4.19.6
just_audio: ^0.9.39 just_audio: ^0.9.11
video_player: ^2.8.7 video_player: ^2.8.7
chewie: ^1.8.3 chewie: ^1.8.3
typewritertext: ^3.0.8 typewritertext: ^3.0.8
@ -94,9 +95,14 @@ dependencies:
flutter_cache_manager: any flutter_cache_manager: any
flutter_local_notifications: ^17.2.2 flutter_local_notifications: ^17.2.2
flutter_background_service: ^5.0.10 flutter_background_service: ^5.0.10
js: ^0.6.7 # js: ^0.6.7
flutter_sound: ^9.6.0
audio_session: ^0.1.21
# url_launcher: ^6.3.0 # url_launcher: ^6.3.0
js: any
flutter_sound_platform_interface: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

View File

@ -40,7 +40,6 @@
<title>Didvan</title> <title>Didvan</title>
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="manifest.json" />
<script src="/lib/assets/js/main.js" defer=""></script>
<style> <style>
.container { .container {
width: 100vw; width: 100vw;