Web fix stable
This commit is contained in:
parent
5b83ec4bbf
commit
bfb9bd4d3b
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
: {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
// );
|
||||
// })
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
|
||||
if (service.isSuccess) {
|
||||
final message = service.result['message'];
|
||||
messages.insert(0, MessageData.fromJson(message));
|
||||
|
||||
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();
|
||||
// update();
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,7 +112,8 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
|
|
@ -108,8 +124,8 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
|||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
|
|
@ -134,6 +150,7 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _onMarkChange,
|
||||
child: Padding(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
110
pubspec.lock
110
pubspec.lock
|
|
@ -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:
|
||||
|
|
|
|||
14
pubspec.yaml
14
pubspec.yaml
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue