diff --git a/lib/assets/js/main.js b/lib/assets/js/main.js new file mode 100644 index 0000000..1097a24 --- /dev/null +++ b/lib/assets/js/main.js @@ -0,0 +1,21 @@ + +const init = () => { + console.log('js is connected!'); + + const showAlert = (message) => { + alert(message); + } + + const reqFullScreen = () => { + document.documentElement.requestFullscreen(); + } + + + window._showAlert = showAlert; + window._reqFullScreen = reqFullScreen; +} + + +window.onload = () => { + init(); +} diff --git a/lib/services/ai/ai_api_service.dart b/lib/services/ai/ai_api_service.dart index 0cc91c4..8a1a9b3 100644 --- a/lib/services/ai/ai_api_service.dart +++ b/lib/services/ai/ai_api_service.dart @@ -1,4 +1,4 @@ -// ignore_for_file: depend_on_referenced_packages +// ignore_for_file: depend_on_referenced_packages, avoid_web_libraries_in_flutter import 'dart:async'; import 'dart:convert'; @@ -8,9 +8,6 @@ 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'; -import 'package:path/path.dart' as p; -import 'package:http_parser/http_parser.dart' as parser; class AiApiService { static const String baseUrl = 'https://api.didvan.app/ai'; @@ -45,9 +42,20 @@ class AiApiService { if (file.bytes != null) { bytes = file.bytes!; } else { - final Uri audioUri = Uri.parse(file.path); + final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':')); final http.Response audioResponse = await http.get(audioUri); bytes = audioResponse.bodyBytes; + + // Fetch the blob using JavaScript interop + // final blob = await html.window + // .fetch(file.path.replaceAll('%3A', ':')) + // .then((response) => response.blob()); + + // // Read the blob as an array buffer + // final reader = html.FileReader(); + // reader.readAsArrayBuffer(blob); + // await reader.onLoadEnd.first; + // bytes = reader.result as Uint8List; } } else { // For other platforms @@ -75,8 +83,7 @@ class AiApiService { return request; } - static Future>> getResponse( - http.MultipartRequest req) async { + Future>> getResponse(http.MultipartRequest req) async { try { final response = await http.Client().send(req); if (response.statusCode == 400) { diff --git a/lib/services/js/js_interop_service.dart b/lib/services/js/js_interop_service.dart new file mode 100644 index 0000000..76bc392 --- /dev/null +++ b/lib/services/js/js_interop_service.dart @@ -0,0 +1,21 @@ +@JS() +library js_introp; + +import 'package:js/js.dart'; + +@JS() +external _showAlert(String message); + +@JS() +external _reqFullScreen(); + +class JsInteropService { + showAlert() { + _showAlert( + 'Helooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo MF'); + } + + fullScreen() { + _reqFullScreen(); + } +} diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index fb0f5b5..9b021b2 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -22,28 +22,6 @@ class MediaService { static Duration? get duration => audioPlayer.duration; - static Future getDuration({required String src}) async { - final ap = AudioPlayer(); - Duration? duration; - - try { - if (src.startsWith('/uploads')) { - duration = await ap.setUrl( - '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'); - } else if (src.startsWith('blob:')) { - duration = await ap.setAudioSource(AudioSource.uri(Uri.parse(src))); - } else { - duration = await ap.setFilePath(src); - } - } catch (e) { - print('Error setting audio source: $e'); - } finally { - await ap.dispose(); - } - - return duration; - } - static Future handleAudioPlayback({ required dynamic audioSource, required int id, @@ -56,13 +34,13 @@ class MediaService { String tag; tag = '${currentPodcast?.description == 'radar' ? 'radar' : isVoiceMessage ? 'message' : 'podcast'}-$id'; + print("tag: $tag"); if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) { audioSource = '${StorageService.appDocsDir}/podcasts/podcast-$id.mp3'; isNetworkAudio = false; } if (audioPlayerTag == tag) { - if (audioPlayer.playerState == - PlayerState(true, ProcessingState.ready)) { + if (audioPlayer.playing) { await audioPlayer.pause(); } else { await audioPlayer.play(); @@ -70,7 +48,7 @@ class MediaService { return; } - // await audioPlayer.stop(); + await audioPlayer.stop(); audioPlayerTag = tag; String source; if (isNetworkAudio) { diff --git a/lib/services/media/voice.dart b/lib/services/media/voice.dart index 09a567a..7ebbca9 100644 --- a/lib/services/media/voice.dart +++ b/lib/services/media/voice.dart @@ -1,64 +1,100 @@ +import 'package:didvan/services/media/media.dart'; 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 VoiceService { static final audioPlayer = AudioPlayer(); - static int? index; - static ConcatenatingAudioSource? _playlist; - - VoiceService({required final List audios}) { - _playlist = ConcatenatingAudioSource( - // Start loading next item just before reaching it - useLazyPreparation: true, - // Customise the shuffle algorithm - shuffleOrder: DefaultShuffleOrder(), - // Specify the playlist items - children: audios, - ); - } + static String? src; static Future getDuration({ required String src, }) async { - if (src.startsWith('/uploads')) { - return await audioPlayer.setUrl( - '${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}'); - } else if (src.startsWith('blob:')) { - return await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(src))); - } else { - return await audioPlayer.setFilePath(src); + 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))); + } else { + duration = await ap.setFilePath(src); + } + } catch (e) { + if (kDebugMode) { + print('Error setting audio source: $e'); + } + } finally { + await ap.dispose(); } + + await ap.load(); + + return duration; } - static Future voiceHelper({required int index}) async { + static Future voiceHelper({required String src}) async { try { - if (VoiceService.index == index) { - if (audioPlayer.playerState == - PlayerState(true, ProcessingState.ready)) { + if (VoiceService.src == src) { + if (audioPlayer.playing) { await audioPlayer.pause(); } else { await audioPlayer.play(); } - - return true; + return; + } + audioPlayer.stop(); + VoiceService.src = src; + 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))); + } else { + await audioPlayer.setFilePath(src); } - VoiceService.index = index; - await audioPlayer.setAudioSource(_playlist!, - initialIndex: index, initialPosition: Duration.zero); - await audioPlayer - .setLoopMode(LoopMode.off); // Set playlist to loop (off|all|one) await audioPlayer.play(); - return true; } catch (e) { resetVoicePlayer(); - // rethrow; - return false; } } static Future resetVoicePlayer() async { - index = null; + src = null; await audioPlayer.stop(); } diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index 6f60897..6a1c596 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -47,11 +47,11 @@ class RequestService { if (body != null) _requestBody = body; if (requestHeaders != null) _headers.addAll(requestHeaders); if (useAutherization) _headers.addAll({'Authorization': 'Bearer $token'}); - if (kDebugMode) { - try { - print('Authorization : Bearer $token'); - } catch (e) {} - } + // if (kDebugMode) { + // try { + // print('Authorization : Bearer $token'); + // } catch (e) {} + // } if (body != null) _requestBody = body; } diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index 623afdf..dce574a 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -1,7 +1,5 @@ // ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; @@ -23,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/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -49,7 +46,7 @@ class _AiChatPageState extends State { state.chatId = widget.args.chat!.id!; state.chat = widget.args.chat; } - + // JsInteropService().showAlert(); WidgetsBinding.instance.addPostFrameCallback((_) async { if (state.chatId != null) { state.getAllMessages(state.chatId!).then((value) => Future.delayed( diff --git a/lib/views/ai/ai_chat_state.dart b/lib/views/ai/ai_chat_state.dart index 7026b6d..a87041d 100644 --- a/lib/views/ai/ai_chat_state.dart +++ b/lib/views/ai/ai_chat_state.dart @@ -147,13 +147,19 @@ class AiChatState extends CoreProvier { .toIso8601String())); update(); await _scrolledEnd(); + // if (file != null) { + // html.AnchorElement anchorElement = html.AnchorElement(href: file!.path); + // anchorElement.download = '${file!.path}.m4a'; + // anchorElement.click(); + // } + final req = await AiApiService.initial( url: '/${bot.id}/${bot.name}'.toLowerCase(), message: message, chatId: chatId, file: file, edite: isEdite); - final res = await AiApiService.getResponse(req).catchError((e) { + final res = await AiApiService().getResponse(req).catchError((e) { _onError(e); // return e; }); @@ -189,7 +195,7 @@ class AiChatState extends CoreProvier { } messageOnstream.value = Stream.value(responseMessgae); - // update(); + update(); }); r.onDone(() async { diff --git a/lib/views/ai/widgets/audio_wave.dart b/lib/views/ai/widgets/audio_wave.dart index e6ef9df..3b13a09 100644 --- a/lib/views/ai/widgets/audio_wave.dart +++ b/lib/views/ai/widgets/audio_wave.dart @@ -7,17 +7,11 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/services/media/media.dart'; -import 'package:didvan/services/network/request.dart'; -import 'package:didvan/services/network/request_helper.dart'; -import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/services/media/voice.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; -import 'package:didvan/views/direct/widgets/message.dart'; -import 'package:didvan/views/widgets/audio/audio_slider.dart'; -import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; class AudioWave extends StatefulWidget { @@ -41,10 +35,12 @@ class _AudioWaveState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - await MediaService.getDuration(src: widget.file).then((duration) { + await VoiceService.getDuration(src: widget.file).then((duration) { setState(() { totalDuration = duration; - print(totalDuration!.inSeconds); + if (kDebugMode) { + print(totalDuration!.inSeconds); + } }); }); }); @@ -53,7 +49,7 @@ class _AudioWaveState extends State { @override void dispose() { - MediaService.resetAudioPlayer(); + VoiceService.resetVoicePlayer(); super.dispose(); } @@ -68,45 +64,50 @@ class _AudioWaveState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ StreamBuilder( - stream: MediaService.audioPlayer.playerStateStream, + stream: VoiceService.audioPlayer.playerStateStream, builder: (context, snapshot) { if (!snapshot.hasData) { return const SizedBox(); } - // if (snapshot.data!.processingState == - // ProcessingState.completed) { - // MediaService.audioPlayer.pause(); - // MediaService.audioPlayer.seek(Duration.zero); - // } + if (snapshot.data!.processingState == + ProcessingState.completed) { + VoiceService.audioPlayer.pause(); + VoiceService.audioPlayer.seek(Duration.zero); + } print(snapshot.data); + if ((snapshot.data!.processingState == + ProcessingState.loading || + snapshot.data!.processingState == + ProcessingState.buffering) && + VoiceService.src == widget.file) { + return MessageBarBtn( + enable: true, + icon: DidvanIcons.close_regular, + click: () async { + await VoiceService.resetVoicePlayer(); + }, + loading: true, + ); + } return MessageBarBtn( enable: true, icon: snapshot.data!.playing && - snapshot.data!.processingState != - ProcessingState.completed && - MediaService.audioPlayerTag == 'message-$id' + VoiceService.src == widget.file ? DidvanIcons.pause_solid : DidvanIcons.play_solid, click: () async { - await MediaService.handleAudioPlayback( - audioSource: widget.file, - id: id, - isNetworkAudio: widget.file.startsWith('/uploads'), - isVoiceMessage: true, - isBlob: widget.file.startsWith('blob:')); - MediaService.audioPlayerTag = 'message-$id'; + await VoiceService.voiceHelper(src: widget.file); }, ); }), - StreamBuilder( - stream: MediaService.audioPlayer.positionStream, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const SizedBox(); - } - print("Position: ${snapshot.data}"); - return Expanded( - child: totalDuration == null + Expanded( + child: StreamBuilder( + stream: VoiceService.audioPlayer.positionStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox(); + } + return totalDuration == null ? Padding( padding: EdgeInsets.symmetric( vertical: widget.loadingPaddingSize), @@ -123,62 +124,44 @@ class _AudioWaveState extends State { itemCount: 10, ))), ) - : Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12.0), - child: Expanded( - child: ProgressBar( - thumbColor: - Theme.of(context).colorScheme.title, - progressBarColor: DesignConfig.isDark - ? Theme.of(context).colorScheme.title - : Theme.of(context) - .colorScheme - .primary, - baseBarColor: - Theme.of(context).colorScheme.border, - bufferedBarColor: - Theme.of(context).colorScheme.splash, - total: totalDuration!, - progress: MediaService.audioPlayerTag == - 'message-$id' - ? snapshot.data ?? Duration.zero - : Duration.zero, - thumbRadius: 6, - barHeight: 3, - // timeLabelTextStyle: TextStyle( - // fontSize: showTimer ? null : 0, - // height: showTimer ? 3 : 0, - // color: - // Theme.of(context).colorScheme.text, - // fontFamily: - // DesignConfig.fontFamily.replaceAll( - // '-FA', - // '', - // ), - // ), - onSeek: (value) { - if (MediaService.audioPlayerTag == - 'message-$id') { - MediaService.audioPlayer.seek( - Duration( - milliseconds: - value.inMilliseconds)); - } - }, - ), - ), - ), - ), - DidvanText(DateTimeUtils.normalizeTimeDuration( - snapshot.data ?? Duration.zero)), - ], - ), - ); - }, + : Padding( + padding: const EdgeInsets.fromLTRB(24, 12, 12, 0), + child: ProgressBar( + thumbColor: Theme.of(context).colorScheme.title, + progressBarColor: DesignConfig.isDark + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.primary, + baseBarColor: + Theme.of(context).colorScheme.border, + bufferedBarColor: + Theme.of(context).colorScheme.splash, + total: totalDuration!, + progress: VoiceService.src == widget.file + ? snapshot.data ?? Duration.zero + : Duration.zero, + thumbRadius: 6, + barHeight: 3, + // timeLabelTextStyle: TextStyle( + // fontSize: showTimer ? null : 0, + // height: showTimer ? 3 : 0, + // color: + // Theme.of(context).colorScheme.text, + // fontFamily: + // DesignConfig.fontFamily.replaceAll( + // '-FA', + // '', + // ), + // ), + onSeek: (value) { + if (VoiceService.src == widget.file) { + VoiceService.audioPlayer.seek(Duration( + milliseconds: value.inMilliseconds)); + } + }, + ), + ); + }, + ), ) ], ), diff --git a/lib/views/ai/widgets/message_bar_btn.dart b/lib/views/ai/widgets/message_bar_btn.dart index d167f7c..38b6d96 100644 --- a/lib/views/ai/widgets/message_bar_btn.dart +++ b/lib/views/ai/widgets/message_bar_btn.dart @@ -1,17 +1,21 @@ import 'package:didvan/config/theme_data.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; class MessageBarBtn extends StatelessWidget { final bool enable; final IconData icon; final Function()? click; final Color? color; + final bool loading; const MessageBarBtn( {Key? key, required this.enable, required this.icon, this.click, - this.color}) + this.color, + this.loading = false}) : super(key: key); @override @@ -26,10 +30,25 @@ class MessageBarBtn extends StatelessWidget { : Theme.of(context).colorScheme.border), child: InkWell( onTap: click, - child: Icon( - icon, - size: 18, - color: enable ? Theme.of(context).colorScheme.white : null, + child: Stack( + children: [ + Positioned.fill( + child: Icon( + icon, + size: 18, + color: enable ? Theme.of(context).colorScheme.white : null, + ), + ), + if (loading) + const Positioned.fill( + child: Padding( + padding: EdgeInsets.all(8.0), + child: SpinKitCircle( + size: 24, + color: Colors.white, + ), + )) + ], ), ), ); diff --git a/lib/views/direct/direct_state.dart b/lib/views/direct/direct_state.dart index 7eeb965..bb6048c 100644 --- a/lib/views/direct/direct_state.dart +++ b/lib/views/direct/direct_state.dart @@ -10,6 +10,7 @@ import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:flutter/foundation.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'; @@ -61,10 +62,14 @@ class DirectState extends CoreProvier { Vibrate.feedback(FeedbackType.medium); } isRecording = true; - Directory? tempDir = await getDownloadsDirectory(); + Directory? tempDir; + if (!kIsWeb) { + tempDir = await getDownloadsDirectory(); + } _recorder.start(const RecordConfig(), - path: - '${tempDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'); + path: kIsWeb + ? '' + : '${tempDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'); notifyListeners(); } @@ -76,8 +81,12 @@ class DirectState extends CoreProvier { return; } if (kIsWeb) { - final uri = Uri.file(path); - recordedFile = File.fromUri(uri); + // final uri = Uri.file(path); + // recordedFile = File.fromUri(uri); + final Uri audioUri = Uri.parse(path); + final http.Response audioResponse = await http.get(audioUri); + final bytes = audioResponse.bodyBytes; + recordedFile = File.fromRawPath(bytes); } else { recordedFile = File(path); } diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index 46ff977..7769ed8 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -5,7 +5,6 @@ import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/direct/direct_state.dart'; import 'package:didvan/views/direct/widgets/audio_widget.dart'; -import 'package:didvan/views/direct/widgets/downloadable_audio_widget.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; diff --git a/lib/views/direct/widgets/message_box.dart b/lib/views/direct/widgets/message_box.dart index 1e60cbe..3624160 100644 --- a/lib/views/direct/widgets/message_box.dart +++ b/lib/views/direct/widgets/message_box.dart @@ -4,10 +4,8 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/direct/direct_state.dart'; import 'package:didvan/views/direct/widgets/audio_widget.dart'; -import 'package:didvan/views/direct/widgets/downloadable_audio_widget.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -138,14 +136,12 @@ class _TypingState extends State<_Typing> { size: 32, color: Theme.of(context).colorScheme.focusedBorder, ) - : kIsWeb - ? null - : DidvanIconButton( - icon: DidvanIcons.mic_solid, - onPressed: state.startRecording, - size: 32, - color: Theme.of(context).colorScheme.focusedBorder, - ), + : DidvanIconButton( + icon: DidvanIcons.mic_solid, + onPressed: state.startRecording, + size: 32, + color: Theme.of(context).colorScheme.focusedBorder, + ), ), ), Expanded( @@ -157,8 +153,7 @@ class _TypingState extends State<_Typing> { style: Theme.of(context).textTheme.bodyMedium, decoration: InputDecoration( border: InputBorder.none, - hintText: - kIsWeb ? 'بنویسید...' : 'بنویسید یا پیام صوتی بگذارید...', + hintText: 'بنویسید یا پیام صوتی بگذارید...', hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( color: Theme.of(context).colorScheme.disabledText), ), diff --git a/makefile b/makefile new file mode 100644 index 0000000..87723a8 --- /dev/null +++ b/makefile @@ -0,0 +1,2 @@ +update_js: + cp ./lib/assets/js/main.js ./build/flutter_assets/lib/assets/js/main.js \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index a282de3..3884820 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.2" + azblob: + dependency: "direct main" + description: + name: azblob + sha256: e9e8689280bd8fa19c1c21a476547de80d2a770c81d2da9da68173c7f5479c7b + url: "https://pub.dev" + source: hosted + version: "4.0.0" boolean_selector: dependency: transitive description: @@ -670,7 +678,7 @@ packages: source: hosted version: "0.18.1" js: - dependency: transitive + dependency: "direct main" description: name: js sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 diff --git a/pubspec.yaml b/pubspec.yaml index dde99d1..6d2e86f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,8 @@ dependencies: flutter_cache_manager: any flutter_local_notifications: ^17.2.2 flutter_background_service: ^5.0.10 + js: ^0.6.7 + azblob: ^4.0.0 # url_launcher: ^6.3.0 dev_dependencies: @@ -127,6 +129,7 @@ flutter: - lib/assets/images/features/ - lib/assets/icons/ - lib/assets/animations/ + - lib/assets/js/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/web/index.html b/web/index.html index bf5f328..4642943 100644 --- a/web/index.html +++ b/web/index.html @@ -40,6 +40,7 @@ Didvan +