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,
}) {
basename = name ?? p.basename(path);
extname = p.extension(path);
extname = path.isNotEmpty
? p.extension(path)
: name != null
? p.extension(name)
: '';
main = File(path);
}
bool isAudio() {
return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false;
return audio ??
(lookupMimeType(path)?.startsWith('audio/') ?? false) ||
(lookupMimeType(path)?.startsWith('video/') ?? false);
}
bool isImage() {

View File

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

View File

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

View File

@ -1,40 +1,53 @@
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/foundation.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 {
static final audioPlayer = AudioPlayer();
static String? src;
static Future<Duration?> getDuration({
required String src,
}) async {
static Future<Duration?> getDuration(
{required String src, Uint8List? bytes}) async {
final ap = AudioPlayer();
Duration? duration;
try {
if (src.startsWith('/uploads')) {
final source =
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
if (kIsWeb) {
final response =
await http.get(Uri.parse(source.replaceAll('%3A', ':')));
final bytes = response.bodyBytes;
final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
duration = await ap.setAudioSource(
AudioSource.uri(Uri.parse(blobUrl)),
);
} else {
final lockCachingAudioSource =
LockCachingAudioSource(Uri.parse(source));
duration = await ap.setAudioSource(lockCachingAudioSource);
}
} else if (src.startsWith('blob:')) {
duration = await ap.setAudioSource(AudioSource.uri(Uri.parse(src)));
// if (kIsWeb) {
// final response =
// await http.get(Uri.parse(source.replaceAll('%3A', ':')));
// final bytes = response.bodyBytes;
// final blob = html.Blob([bytes]);
// final blobUrl = html.Url.createObjectUrlFromBlob(blob);
// duration = await ap.setAudioSource(
// AudioSource.uri(Uri.parse(blobUrl)),
// );
// } else {
duration = await ap.setUrl(source);
// }
} else if (src.startsWith('blob:') || src == '') {
duration = await ap.setUrl(src);
} else {
duration = await ap.setFilePath(src);
}
@ -53,6 +66,7 @@ class VoiceService {
static Future<void> voiceHelper(
{required String src,
final Uint8List? bytes,
final Duration? duration,
final Function()? startTimer,
final Function()? stopTimer}) async {
@ -72,23 +86,20 @@ class VoiceService {
if (src.startsWith('/uploads')) {
final source =
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
if (kIsWeb) {
final response =
await http.get(Uri.parse(source.replaceAll('%3A', ':')));
final bytes = response.bodyBytes;
final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
await audioPlayer.setAudioSource(
AudioSource.uri(Uri.parse(blobUrl)),
);
} else {
final lockCachingAudioSource =
LockCachingAudioSource(Uri.parse(source));
await audioPlayer.setAudioSource(lockCachingAudioSource);
}
} else if (src.startsWith('blob:')) {
await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(src)));
// if (kIsWeb) {
// final response =
// await http.get(Uri.parse(source.replaceAll('%3A', ':')));
// final bytes = response.bodyBytes;
// final blob = html.Blob([bytes]);
// final blobUrl = html.Url.createObjectUrlFromBlob(blob);
// await audioPlayer.setAudioSource(
// AudioSource.uri(Uri.parse(blobUrl)),
// );
// } else {
await audioPlayer.setUrl(source);
// }
} else if (src.startsWith('blob:') || src == '') {
await audioPlayer.setUrl(src);
} else {
await audioPlayer.setFilePath(src);
}
@ -102,7 +113,6 @@ class VoiceService {
static Future<void> resetVoicePlayer() async {
src = null;
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() {
DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop();

View File

@ -196,7 +196,7 @@ class _AiState extends State<Ai> {
child: Row(
children: [
const MessageBarBtn(
enable: false,
enable: true,
icon:
DidvanIcons.mic_regular),
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/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
@ -269,8 +268,8 @@ class _AiChatPageState extends State<AiChatPage> {
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: state.file != null &&
!state.file!.isRecorded
? 150
!(state.file!.isRecorded)
? 180
: 100),
itemBuilder: (context, mIndex) {
final prompts = state.messages[mIndex].prompts;
@ -299,7 +298,7 @@ class _AiChatPageState extends State<AiChatPage> {
children: [
AiMessageBar(
bot: widget.args.bot,
focusNode: focusNode,
// focusNode: focusNode,
),
],
)),
@ -509,6 +508,7 @@ class _AiChatPageState extends State<AiChatPage> {
.remove(message);
state.messages.last.prompts.add(
message.copyWith(error: false));
state.file = file;
state.update();
await state
.postMessage(widget.args.bot);
@ -559,6 +559,7 @@ class _AiChatPageState extends State<AiChatPage> {
} else {
state.messages[mIndex].prompts
.removeAt(index);
state.update();
}
},
child: Icon(
@ -624,18 +625,18 @@ class _AiChatPageState extends State<AiChatPage> {
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,
);
})
// 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,
// );
// })
],
),
)

View File

@ -134,6 +134,8 @@ class AiChatState extends CoreProvier {
Future<void> postMessage(BotsModel bot) async {
onResponsing = true;
final uploadedFile = file;
file = null;
update();
String message = messages.last.prompts.last.text!;
@ -157,7 +159,7 @@ class AiChatState extends CoreProvier {
url: '/${bot.id}/${bot.name}'.toLowerCase(),
message: message,
chatId: chatId,
file: file,
file: uploadedFile,
edite: isEdite);
final res = await AiApiService().getResponse(req).catchError((e) {
_onError(e);
@ -166,7 +168,6 @@ class AiChatState extends CoreProvier {
String responseMessgae = '';
String dataMessgae = '';
file = null;
update();
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 {
final String file;
final Uint8List? bytes;
final double loadingPaddingSize;
final Duration? totalDuration;
const AudioWave(
{Key? key,
required this.file,
this.loadingPaddingSize = 0,
this.totalDuration})
this.totalDuration,
this.bytes})
: super(key: key);
@override
@ -60,6 +62,7 @@ class _AudioWaveState extends State<AudioWave> {
if (event.processingState == ProcessingState.completed) {
await VoiceService.audioPlayer.pause();
await VoiceService.audioPlayer.seek(Duration.zero);
VoiceService.resetVoicePlayer();
}
});
}
@ -107,7 +110,8 @@ class _AudioWaveState extends State<AudioWave> {
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
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
void initState() {
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.replyRadar = widget.pageData['radarAttachment'];
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);

View File

@ -1,22 +1,26 @@
import 'dart:async';
import 'dart:io';
import 'package:audio_session/audio_session.dart';
import 'package:didvan/models/enums.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/radar_attachment.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/network/request.dart';
import 'package:didvan/services/network/request_helper.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:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
import 'package:permission_handler/permission_handler.dart';
class DirectState extends CoreProvier {
final _recorder = AudioRecorder();
// final _recorder = AudioRecorder();
final List<MessageData> messages = [];
late final int typeId;
final Map<String, List<int>> dailyMessages = {};
@ -25,12 +29,60 @@ class DirectState extends CoreProvier {
String? text;
NewsAttachment? replyNews;
RadarAttachment? replyRadar;
File? recordedFile;
Uint8List? recordedFileBytes;
int? audioDuration;
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 {
appState = AppState.busy;
@ -48,48 +100,108 @@ class DirectState extends CoreProvier {
appState = AppState.idle;
return;
}
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() {
recordedFile!.delete();
recordedFile = null;
path = null;
notifyListeners();
update();
}
Future<void> startRecording() async {
text = null;
await _recorder.hasPermission();
// await _recorder.hasPermission();
if (!kIsWeb) {
Vibrate.feedback(FeedbackType.medium);
}
isRecording = true;
Directory? tempDir;
if (!kIsWeb) {
tempDir = await getApplicationDocumentsDirectory();
}
_recorder.start(const RecordConfig(),
path: kIsWeb
? ''
: '${tempDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
record();
notifyListeners();
}
Future<void> stopRecording({required bool sendImidiately}) async {
path = await _recorder.stop();
isRecording = false;
path = await mRecorder!.stopRecorder();
mplaybackReady = true;
_timer?.cancel();
mPlayer!.setSubscriptionDuration(countTimer.value);
update();
if (path == null) {
notifyListeners();
return;
}
if (kIsWeb) {
final Uri audioUri = Uri.parse(path!);
final http.Response audioResponse = await http.get(audioUri);
recordedFileBytes = audioResponse.bodyBytes;
} else {
recordedFile = File(path!);
}
// if (kIsWeb) {
// final Uri audioUri = Uri.parse(path!);
// final http.Response audioResponse = await http.get(audioUri);
// recordedFileBytes = audioResponse.bodyBytes;
// } else {
// recordedFile = File(path!);
// }
if (sendImidiately) {
await sendMessage();
} else {
@ -121,9 +233,8 @@ class DirectState extends CoreProvier {
}
Future<void> sendMessage() async {
if ((text == null || text!.isEmpty) &&
(recordedFile == null && recordedFileBytes == null)) return;
MediaService.audioPlayer.stop();
if ((text == null || text!.isEmpty) && path == null) return;
VoiceService.audioPlayer.stop();
final body = {};
@ -144,26 +255,18 @@ class DirectState extends CoreProvier {
}
if (path != null) {
final duration = await VoiceService.getDuration(src: path!);
if (duration != null) {
body.addAll({'duration': duration.inSeconds.toString()});
}
body.addAll({'duration': countTimer.value.inSeconds.toString()});
}
final uploadFile = recordedFile;
final uploadFileBytes = recordedFileBytes;
text = null;
recordedFile = null;
recordedFileBytes = null;
replyRadar = null;
replyNews = null;
path = null;
notifyListeners();
final service =
RequestService(RequestHelper.sendDirectMessage(typeId), body: body);
if (uploadFile == null && uploadFileBytes == null) {
if (path == null) {
await service.post();
if (service.isSuccess) {
@ -177,57 +280,33 @@ class DirectState extends CoreProvier {
}
}
} else {
messages.insert(
0,
MessageData(
id: 0,
writedByAdmin: false,
readed: false,
createdAt:
DateTime.now().subtract(const Duration(minutes: 210)).toString(),
text: text,
// audio: path,
// audioFile: uploadFile,
radar: replyRadar,
news: replyNews,
audioDuration: audioDuration,
),
final Uint8List uploadFile = kIsWeb
? (await http.get(Uri.parse(path!))).bodyBytes
: await File(path!).readAsBytes();
path = null;
await service.multipartBytes(
file: uploadFile,
method: 'POST',
fieldName: 'audio',
fileName: 'voice-message',
mediaExtension: 'm4a',
mediaFormat: 'audio',
);
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) {
final message = service.result['message'];
final m = MessageData.fromJson(message);
messages[0] = m;
update();
messages.insert(0, MessageData.fromJson(message));
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,
child: Consumer<DirectState>(
builder: (context, state, child) {
if (state.isRecording) {
if (state.mRecorder!.isRecording) {
return const _Recording();
} else if (!state.isRecording &&
(state.recordedFile != null ||
state.recordedFileBytes != null)) {
} else if (!(state.mRecorder!.isRecording) &&
state.path != null) {
return const _RecordChecking();
}
return const _Typing();
@ -227,6 +226,7 @@ class _RecordChecking extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 8),
child: AudioWave(
file: state.path!,
totalDuration: state.countTimer.value,
// 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/views/ai/ai.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/main/main_page.dart';
import 'package:didvan/views/home/home_state.dart';
@ -618,13 +617,13 @@ class _HomeState extends State<Home>
},
itemBuilder: (BuildContext context) {
return <PopupMenuEntry>[
AiMessageBar.popUpBtns(
ActionSheetUtils.popUpBtns(
value: 'حذف پیام',
icon: DidvanIcons.trash_regular,
color: Theme.of(context).colorScheme.error,
height: 24,
size: 12),
AiMessageBar.popUpBtns(
ActionSheetUtils.popUpBtns(
value: 'آرشیو',
icon: Icons.folder_copy,
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/constants/app_icons.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/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:didvan/providers/user.dart';
import 'dart:html' as html;
class MainPagePodcastItem extends StatefulWidget {
final MainPageContentType content;
@ -24,6 +28,7 @@ class MainPagePodcastItem extends StatefulWidget {
}
class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
bool loading = false;
void _onMarkChange() {
UserProvider.changeItemMark(
widget.type,
@ -43,8 +48,18 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
args: const StudioRequestArgs(page: 0, type: 'podcast'),
);
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(
audioSource: widget.content.link,
audioSource: blobUrl,
id: widget.content.id,
isNetworkAudio: true,
isVoiceMessage: false,
@ -97,42 +112,44 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
widget.content.title,
style:
Theme.of(context).textTheme.bodyLarge,
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,
),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
widget.content.title,
style:
Theme.of(context).textTheme.bodyLarge,
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,
),
],
),
),
],
),
),
GestureDetector(
onTap: _onMarkChange,

View File

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

View File

@ -50,7 +50,7 @@ packages:
source: hosted
version: "2.11.0"
audio_session:
dependency: transitive
dependency: "direct main"
description:
name: audio_session
sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261"
@ -345,6 +345,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.8.12"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fl_chart:
dependency: "direct main"
description:
@ -507,6 +515,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -741,6 +773,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
logger:
dependency: transitive
description:
name: logger
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
markdown:
dependency: transitive
description:
@ -989,62 +1029,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.2"
record:
dependency: "direct main"
description:
name: record
sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867"
url: "https://pub.dev"
source: hosted
version: "5.1.2"
record_android:
recase:
dependency: transitive
description:
name: record_android
sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
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"
version: "4.1.0"
rive:
dependency: "direct main"
description:
@ -1090,6 +1082,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:
@ -1270,10 +1270,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "4.5.1"
vector_graphics:
dependency: transitive
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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.3.3+3330
version: 3.3.4+3340
environment:
sdk: ">=2.19.0 <3.0.0"
@ -51,7 +51,8 @@ dependencies:
carousel_slider: ^4.0.0
flutter_vibrate: ^1.3.0
universal_html: ^2.0.8
record: ^5.1.2
# record: ^5.1.2
persian_datetime_picker: ^2.6.0
persian_number_utility: ^1.1.1
bot_toast: ^4.0.1
@ -82,7 +83,7 @@ dependencies:
android_intent_plus: ^5.0.0
get: ^4.6.6
# firebase_auth: ^4.19.6
just_audio: ^0.9.39
just_audio: ^0.9.11
video_player: ^2.8.7
chewie: ^1.8.3
typewritertext: ^3.0.8
@ -94,9 +95,14 @@ dependencies:
flutter_cache_manager: any
flutter_local_notifications: ^17.2.2
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
js: any
flutter_sound_platform_interface: any
dev_dependencies:
flutter_test:
sdk: flutter

View File

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