Web fix stable
This commit is contained in:
parent
5b83ec4bbf
commit
bfb9bd4d3b
|
|
@ -27,12 +27,18 @@ class FilesModel {
|
||||||
this.duration,
|
this.duration,
|
||||||
}) {
|
}) {
|
||||||
basename = name ?? p.basename(path);
|
basename = name ?? p.basename(path);
|
||||||
extname = p.extension(path);
|
extname = path.isNotEmpty
|
||||||
|
? p.extension(path)
|
||||||
|
: name != null
|
||||||
|
? p.extension(name)
|
||||||
|
: '';
|
||||||
main = File(path);
|
main = File(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAudio() {
|
bool isAudio() {
|
||||||
return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false;
|
return audio ??
|
||||||
|
(lookupMimeType(path)?.startsWith('audio/') ?? false) ||
|
||||||
|
(lookupMimeType(path)?.startsWith('video/') ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isImage() {
|
bool isImage() {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:didvan/models/ai/files_model.dart';
|
import 'package:didvan/models/ai/files_model.dart';
|
||||||
import 'package:didvan/services/app_initalizer.dart';
|
|
||||||
import 'package:didvan/services/storage/storage.dart';
|
import 'package:didvan/services/storage/storage.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
|
||||||
class AiApiService {
|
class AiApiService {
|
||||||
static const String baseUrl = 'https://api.didvan.app/ai';
|
static const String baseUrl = 'https://api.didvan.app/ai';
|
||||||
|
|
@ -48,8 +48,11 @@ class AiApiService {
|
||||||
bytes = file.bytes!;
|
bytes = file.bytes!;
|
||||||
} else {
|
} else {
|
||||||
final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':'));
|
final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':'));
|
||||||
|
|
||||||
final http.Response audioResponse = await http.get(audioUri);
|
final http.Response audioResponse = await http.get(audioUri);
|
||||||
|
|
||||||
bytes = audioResponse.bodyBytes;
|
bytes = audioResponse.bodyBytes;
|
||||||
|
|
||||||
// final f = File.fromUri(Uri.parse(file.path));
|
// final f = File.fromUri(Uri.parse(file.path));
|
||||||
// bytes = await f.readAsBytes();
|
// bytes = await f.readAsBytes();
|
||||||
|
|
||||||
|
|
@ -68,15 +71,15 @@ class AiApiService {
|
||||||
// For other platforms
|
// For other platforms
|
||||||
bytes = await file.main.readAsBytes();
|
bytes = await file.main.readAsBytes();
|
||||||
}
|
}
|
||||||
filename = file.basename;
|
if (file.isRecorded) {
|
||||||
|
filename = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.mp4';
|
||||||
|
|
||||||
mimeType = file.isAudio()
|
mimeType = lookupMimeType(filename, headerBytes: bytes) ?? 'audio/mp4';
|
||||||
? file.isRecorded
|
} else {
|
||||||
? 'audio/${kIsWeb && AppInitializer.getOperatingSystem() == 'iOS' ? 'webm' : 'm4a'}'
|
filename = file.basename;
|
||||||
: 'audio/${file.extname.replaceAll('.', '')}'
|
mimeType = lookupMimeType(filename, headerBytes: bytes) ??
|
||||||
: file.isImage()
|
'application/octet-stream';
|
||||||
? 'image/png'
|
}
|
||||||
: 'application/pdf';
|
|
||||||
|
|
||||||
request.files.add(http.MultipartFile.fromBytes(
|
request.files.add(http.MultipartFile.fromBytes(
|
||||||
'file',
|
'file',
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'dart:html' as html;
|
|
||||||
|
|
||||||
class AppInitializer {
|
class AppInitializer {
|
||||||
static String? fcmToken;
|
static String? fcmToken;
|
||||||
|
|
@ -42,24 +41,6 @@ class AppInitializer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getOperatingSystem() {
|
|
||||||
final userAgent = html.window.navigator.userAgent.toLowerCase();
|
|
||||||
|
|
||||||
if (userAgent.contains('windows')) {
|
|
||||||
return 'Windows';
|
|
||||||
} else if (userAgent.contains('mac os')) {
|
|
||||||
return 'MacOS';
|
|
||||||
} else if (userAgent.contains('iphone') || userAgent.contains('ipad')) {
|
|
||||||
return 'iOS';
|
|
||||||
} else if (userAgent.contains('android')) {
|
|
||||||
return 'Android';
|
|
||||||
} else if (userAgent.contains('linux')) {
|
|
||||||
return 'Linux';
|
|
||||||
} else {
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<SettingsData> initilizeSettings() async {
|
static Future<SettingsData> initilizeSettings() async {
|
||||||
try {
|
try {
|
||||||
final brightness = await StorageService.getValue(key: 'brightness');
|
final brightness = await StorageService.getValue(key: 'brightness');
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:didvan/services/storage/storage.dart';
|
import 'package:didvan/services/storage/storage.dart';
|
||||||
import 'package:didvan/utils/action_sheet.dart';
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
@ -58,7 +57,7 @@ class MediaService {
|
||||||
source = audioSource;
|
source = audioSource;
|
||||||
}
|
}
|
||||||
audioPlayer.setUrl(
|
audioPlayer.setUrl(
|
||||||
kIsWeb ? source.replaceAll('%3A', ':') : source,
|
source,
|
||||||
tag: isVoiceMessage
|
tag: isVoiceMessage
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,53 @@
|
||||||
|
|
||||||
import 'package:didvan/services/network/request.dart';
|
import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:universal_html/html.dart' as html;
|
class MyJABytesSource extends StreamAudioSource {
|
||||||
|
final Uint8List _buffer;
|
||||||
|
|
||||||
|
MyJABytesSource(this._buffer) : super(tag: 'MyAudioSource');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamAudioResponse> request([int? start, int? end]) async {
|
||||||
|
// Returning the stream audio response with the parameters
|
||||||
|
return StreamAudioResponse(
|
||||||
|
sourceLength: _buffer.length,
|
||||||
|
contentLength: (end ?? _buffer.length) - (start ?? 0),
|
||||||
|
offset: start ?? 0,
|
||||||
|
stream: Stream.fromIterable([_buffer.sublist(start ?? 0, end)]),
|
||||||
|
contentType: 'audio/wav',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class VoiceService {
|
class VoiceService {
|
||||||
static final audioPlayer = AudioPlayer();
|
static final audioPlayer = AudioPlayer();
|
||||||
static String? src;
|
static String? src;
|
||||||
|
|
||||||
static Future<Duration?> getDuration({
|
static Future<Duration?> getDuration(
|
||||||
required String src,
|
{required String src, Uint8List? bytes}) async {
|
||||||
}) async {
|
|
||||||
final ap = AudioPlayer();
|
final ap = AudioPlayer();
|
||||||
Duration? duration;
|
Duration? duration;
|
||||||
try {
|
try {
|
||||||
if (src.startsWith('/uploads')) {
|
if (src.startsWith('/uploads')) {
|
||||||
final source =
|
final source =
|
||||||
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
|
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
|
||||||
if (kIsWeb) {
|
// if (kIsWeb) {
|
||||||
final response =
|
// final response =
|
||||||
await http.get(Uri.parse(source.replaceAll('%3A', ':')));
|
// await http.get(Uri.parse(source.replaceAll('%3A', ':')));
|
||||||
final bytes = response.bodyBytes;
|
// final bytes = response.bodyBytes;
|
||||||
final blob = html.Blob([bytes]);
|
// final blob = html.Blob([bytes]);
|
||||||
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
// final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||||
duration = await ap.setAudioSource(
|
// duration = await ap.setAudioSource(
|
||||||
AudioSource.uri(Uri.parse(blobUrl)),
|
// AudioSource.uri(Uri.parse(blobUrl)),
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
final lockCachingAudioSource =
|
duration = await ap.setUrl(source);
|
||||||
LockCachingAudioSource(Uri.parse(source));
|
// }
|
||||||
|
} else if (src.startsWith('blob:') || src == '') {
|
||||||
duration = await ap.setAudioSource(lockCachingAudioSource);
|
duration = await ap.setUrl(src);
|
||||||
}
|
|
||||||
} else if (src.startsWith('blob:')) {
|
|
||||||
duration = await ap.setAudioSource(AudioSource.uri(Uri.parse(src)));
|
|
||||||
} else {
|
} else {
|
||||||
duration = await ap.setFilePath(src);
|
duration = await ap.setFilePath(src);
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +66,7 @@ class VoiceService {
|
||||||
|
|
||||||
static Future<void> voiceHelper(
|
static Future<void> voiceHelper(
|
||||||
{required String src,
|
{required String src,
|
||||||
|
final Uint8List? bytes,
|
||||||
final Duration? duration,
|
final Duration? duration,
|
||||||
final Function()? startTimer,
|
final Function()? startTimer,
|
||||||
final Function()? stopTimer}) async {
|
final Function()? stopTimer}) async {
|
||||||
|
|
@ -72,23 +86,20 @@ class VoiceService {
|
||||||
if (src.startsWith('/uploads')) {
|
if (src.startsWith('/uploads')) {
|
||||||
final source =
|
final source =
|
||||||
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
|
'${RequestHelper.baseUrl + src}?accessToken=${RequestService.token}';
|
||||||
if (kIsWeb) {
|
// if (kIsWeb) {
|
||||||
final response =
|
// final response =
|
||||||
await http.get(Uri.parse(source.replaceAll('%3A', ':')));
|
// await http.get(Uri.parse(source.replaceAll('%3A', ':')));
|
||||||
final bytes = response.bodyBytes;
|
// final bytes = response.bodyBytes;
|
||||||
final blob = html.Blob([bytes]);
|
// final blob = html.Blob([bytes]);
|
||||||
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
// final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||||
await audioPlayer.setAudioSource(
|
// await audioPlayer.setAudioSource(
|
||||||
AudioSource.uri(Uri.parse(blobUrl)),
|
// AudioSource.uri(Uri.parse(blobUrl)),
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
final lockCachingAudioSource =
|
await audioPlayer.setUrl(source);
|
||||||
LockCachingAudioSource(Uri.parse(source));
|
// }
|
||||||
|
} else if (src.startsWith('blob:') || src == '') {
|
||||||
await audioPlayer.setAudioSource(lockCachingAudioSource);
|
await audioPlayer.setUrl(src);
|
||||||
}
|
|
||||||
} else if (src.startsWith('blob:')) {
|
|
||||||
await audioPlayer.setAudioSource(AudioSource.uri(Uri.parse(src)));
|
|
||||||
} else {
|
} else {
|
||||||
await audioPlayer.setFilePath(src);
|
await audioPlayer.setFilePath(src);
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +113,6 @@ class VoiceService {
|
||||||
|
|
||||||
static Future<void> resetVoicePlayer() async {
|
static Future<void> resetVoicePlayer() async {
|
||||||
src = null;
|
src = null;
|
||||||
|
|
||||||
await audioPlayer.stop();
|
await audioPlayer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,37 @@ class ActionSheetUtils {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PopupMenuItem<dynamic> popUpBtns({
|
||||||
|
required final String value,
|
||||||
|
required final IconData icon,
|
||||||
|
final Color? color,
|
||||||
|
final double? height,
|
||||||
|
final double? size,
|
||||||
|
}) {
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: value,
|
||||||
|
height: height ?? 46,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: color,
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
|
value,
|
||||||
|
color: color,
|
||||||
|
fontSize: size,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void pop() {
|
void pop() {
|
||||||
DesignConfig.updateSystemUiOverlayStyle();
|
DesignConfig.updateSystemUiOverlayStyle();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ class _AiState extends State<Ai> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const MessageBarBtn(
|
const MessageBarBtn(
|
||||||
enable: false,
|
enable: true,
|
||||||
icon:
|
icon:
|
||||||
DidvanIcons.mic_regular),
|
DidvanIcons.mic_regular),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
@ -225,19 +225,19 @@ class _AiState extends State<Ai> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
const MessageBarBtn(
|
||||||
|
enable: false,
|
||||||
|
icon: Icons
|
||||||
|
.attach_file_rounded),
|
||||||
],
|
],
|
||||||
))))
|
))))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
width: 18,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.attach_file_rounded,
|
|
||||||
color: Theme.of(context).colorScheme.focusedBorder,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/marquee_text.dart';
|
import 'package:didvan/views/widgets/marquee_text.dart';
|
||||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
|
@ -269,8 +268,8 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: state.file != null &&
|
bottom: state.file != null &&
|
||||||
!state.file!.isRecorded
|
!(state.file!.isRecorded)
|
||||||
? 150
|
? 180
|
||||||
: 100),
|
: 100),
|
||||||
itemBuilder: (context, mIndex) {
|
itemBuilder: (context, mIndex) {
|
||||||
final prompts = state.messages[mIndex].prompts;
|
final prompts = state.messages[mIndex].prompts;
|
||||||
|
|
@ -299,7 +298,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
children: [
|
children: [
|
||||||
AiMessageBar(
|
AiMessageBar(
|
||||||
bot: widget.args.bot,
|
bot: widget.args.bot,
|
||||||
focusNode: focusNode,
|
// focusNode: focusNode,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
|
@ -509,6 +508,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
.remove(message);
|
.remove(message);
|
||||||
state.messages.last.prompts.add(
|
state.messages.last.prompts.add(
|
||||||
message.copyWith(error: false));
|
message.copyWith(error: false));
|
||||||
|
state.file = file;
|
||||||
state.update();
|
state.update();
|
||||||
await state
|
await state
|
||||||
.postMessage(widget.args.bot);
|
.postMessage(widget.args.bot);
|
||||||
|
|
@ -559,6 +559,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
} else {
|
} else {
|
||||||
state.messages[mIndex].prompts
|
state.messages[mIndex].prompts
|
||||||
.removeAt(index);
|
.removeAt(index);
|
||||||
|
state.update();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
|
@ -624,18 +625,18 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
stop: const Duration(seconds: 3),
|
stop: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (state.file != null && !kIsWeb)
|
// if (state.file != null && !kIsWeb)
|
||||||
FutureBuilder(
|
// FutureBuilder(
|
||||||
future: state.file!.main.length(),
|
// future: state.file!.main.length(),
|
||||||
builder: (context, snapshot) {
|
// builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
// if (!snapshot.hasData) {
|
||||||
return const SizedBox();
|
// return const SizedBox();
|
||||||
}
|
// }
|
||||||
return DidvanText(
|
// return DidvanText(
|
||||||
'File Size ${(snapshot.data! / 1000).round()} KB',
|
// 'File Size ${(snapshot.data! / 1000).round()} KB',
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
);
|
// );
|
||||||
})
|
// })
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,8 @@ class AiChatState extends CoreProvier {
|
||||||
|
|
||||||
Future<void> postMessage(BotsModel bot) async {
|
Future<void> postMessage(BotsModel bot) async {
|
||||||
onResponsing = true;
|
onResponsing = true;
|
||||||
|
final uploadedFile = file;
|
||||||
|
file = null;
|
||||||
update();
|
update();
|
||||||
String message = messages.last.prompts.last.text!;
|
String message = messages.last.prompts.last.text!;
|
||||||
|
|
||||||
|
|
@ -157,7 +159,7 @@ class AiChatState extends CoreProvier {
|
||||||
url: '/${bot.id}/${bot.name}'.toLowerCase(),
|
url: '/${bot.id}/${bot.name}'.toLowerCase(),
|
||||||
message: message,
|
message: message,
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
file: file,
|
file: uploadedFile,
|
||||||
edite: isEdite);
|
edite: isEdite);
|
||||||
final res = await AiApiService().getResponse(req).catchError((e) {
|
final res = await AiApiService().getResponse(req).catchError((e) {
|
||||||
_onError(e);
|
_onError(e);
|
||||||
|
|
@ -166,7 +168,6 @@ class AiChatState extends CoreProvier {
|
||||||
|
|
||||||
String responseMessgae = '';
|
String responseMessgae = '';
|
||||||
String dataMessgae = '';
|
String dataMessgae = '';
|
||||||
file = null;
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
final r = res.listen((value) async {
|
final r = res.listen((value) async {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,13 +15,15 @@ import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
class AudioWave extends StatefulWidget {
|
class AudioWave extends StatefulWidget {
|
||||||
final String file;
|
final String file;
|
||||||
|
final Uint8List? bytes;
|
||||||
final double loadingPaddingSize;
|
final double loadingPaddingSize;
|
||||||
final Duration? totalDuration;
|
final Duration? totalDuration;
|
||||||
const AudioWave(
|
const AudioWave(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.file,
|
required this.file,
|
||||||
this.loadingPaddingSize = 0,
|
this.loadingPaddingSize = 0,
|
||||||
this.totalDuration})
|
this.totalDuration,
|
||||||
|
this.bytes})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -60,6 +62,7 @@ class _AudioWaveState extends State<AudioWave> {
|
||||||
if (event.processingState == ProcessingState.completed) {
|
if (event.processingState == ProcessingState.completed) {
|
||||||
await VoiceService.audioPlayer.pause();
|
await VoiceService.audioPlayer.pause();
|
||||||
await VoiceService.audioPlayer.seek(Duration.zero);
|
await VoiceService.audioPlayer.seek(Duration.zero);
|
||||||
|
VoiceService.resetVoicePlayer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +110,8 @@ class _AudioWaveState extends State<AudioWave> {
|
||||||
? DidvanIcons.pause_solid
|
? DidvanIcons.pause_solid
|
||||||
: DidvanIcons.play_solid,
|
: DidvanIcons.play_solid,
|
||||||
click: () async {
|
click: () async {
|
||||||
await VoiceService.voiceHelper(src: widget.file);
|
await VoiceService.voiceHelper(
|
||||||
|
src: widget.file, bytes: widget.bytes);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@ class _DirectState extends State<Direct> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final state = context.read<DirectState>();
|
final state = context.read<DirectState>();
|
||||||
|
state.mPlayer!.openPlayer().then((value) {
|
||||||
|
state.mPlayerIsInited = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.openTheRecorder().then((value) {
|
||||||
|
state.mRecorderIsInited = true;
|
||||||
|
});
|
||||||
|
|
||||||
state.replyNews = widget.pageData['newsAttachment'];
|
state.replyNews = widget.pageData['newsAttachment'];
|
||||||
state.replyRadar = widget.pageData['radarAttachment'];
|
state.replyRadar = widget.pageData['radarAttachment'];
|
||||||
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);
|
final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']);
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,26 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:didvan/models/enums.dart';
|
import 'package:didvan/models/enums.dart';
|
||||||
import 'package:didvan/models/message_data/message_data.dart';
|
import 'package:didvan/models/message_data/message_data.dart';
|
||||||
import 'package:didvan/models/message_data/news_attachment.dart';
|
import 'package:didvan/models/message_data/news_attachment.dart';
|
||||||
import 'package:didvan/models/message_data/radar_attachment.dart';
|
import 'package:didvan/models/message_data/radar_attachment.dart';
|
||||||
import 'package:didvan/providers/core.dart';
|
import 'package:didvan/providers/core.dart';
|
||||||
import 'package:didvan/services/media/media.dart';
|
|
||||||
import 'package:didvan/services/media/voice.dart';
|
import 'package:didvan/services/media/voice.dart';
|
||||||
import 'package:didvan/services/network/request.dart';
|
import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_sound/public/flutter_sound_player.dart';
|
||||||
|
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
|
||||||
|
import 'package:flutter_sound_platform_interface/flutter_sound_platform_interface.dart';
|
||||||
|
import 'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart';
|
||||||
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:record/record.dart';
|
|
||||||
|
|
||||||
class DirectState extends CoreProvier {
|
class DirectState extends CoreProvier {
|
||||||
final _recorder = AudioRecorder();
|
// final _recorder = AudioRecorder();
|
||||||
final List<MessageData> messages = [];
|
final List<MessageData> messages = [];
|
||||||
late final int typeId;
|
late final int typeId;
|
||||||
final Map<String, List<int>> dailyMessages = {};
|
final Map<String, List<int>> dailyMessages = {};
|
||||||
|
|
@ -25,12 +29,60 @@ class DirectState extends CoreProvier {
|
||||||
String? text;
|
String? text;
|
||||||
NewsAttachment? replyNews;
|
NewsAttachment? replyNews;
|
||||||
RadarAttachment? replyRadar;
|
RadarAttachment? replyRadar;
|
||||||
File? recordedFile;
|
|
||||||
Uint8List? recordedFileBytes;
|
|
||||||
int? audioDuration;
|
int? audioDuration;
|
||||||
String? path;
|
String? path;
|
||||||
|
|
||||||
bool isRecording = false;
|
FlutterSoundPlayer? mPlayer = FlutterSoundPlayer();
|
||||||
|
FlutterSoundRecorder? mRecorder = FlutterSoundRecorder();
|
||||||
|
|
||||||
|
Codec _codec = Codec.aacMP4;
|
||||||
|
String _mPath = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.mp4';
|
||||||
|
|
||||||
|
bool mPlayerIsInited = false;
|
||||||
|
bool mRecorderIsInited = false;
|
||||||
|
bool mplaybackReady = false;
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
final theSource = AudioSource.microphone;
|
||||||
|
final ValueNotifier<Duration> countTimer = ValueNotifier(Duration.zero);
|
||||||
|
|
||||||
|
Future<void> openTheRecorder() async {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
var status = await Permission.microphone.request();
|
||||||
|
if (status != PermissionStatus.granted) {
|
||||||
|
throw RecordingPermissionException('Microphone permission not granted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await mRecorder!.openRecorder();
|
||||||
|
if (!await mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
|
||||||
|
_codec = Codec.opusWebM;
|
||||||
|
_mPath = '${DateTime.now().millisecondsSinceEpoch ~/ 1000}.webm';
|
||||||
|
if (!await mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
|
||||||
|
mRecorderIsInited = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final session = await AudioSession.instance;
|
||||||
|
await session.configure(AudioSessionConfiguration(
|
||||||
|
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
|
||||||
|
avAudioSessionCategoryOptions:
|
||||||
|
AVAudioSessionCategoryOptions.allowBluetooth |
|
||||||
|
AVAudioSessionCategoryOptions.defaultToSpeaker,
|
||||||
|
avAudioSessionMode: AVAudioSessionMode.spokenAudio,
|
||||||
|
avAudioSessionRouteSharingPolicy:
|
||||||
|
AVAudioSessionRouteSharingPolicy.defaultPolicy,
|
||||||
|
avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none,
|
||||||
|
androidAudioAttributes: const AndroidAudioAttributes(
|
||||||
|
contentType: AndroidAudioContentType.speech,
|
||||||
|
flags: AndroidAudioFlags.none,
|
||||||
|
usage: AndroidAudioUsage.voiceCommunication,
|
||||||
|
),
|
||||||
|
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
|
||||||
|
androidWillPauseWhenDucked: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
mRecorderIsInited = true;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getMessages() async {
|
Future<void> getMessages() async {
|
||||||
appState = AppState.busy;
|
appState = AppState.busy;
|
||||||
|
|
@ -48,48 +100,108 @@ class DirectState extends CoreProvier {
|
||||||
appState = AppState.idle;
|
appState = AppState.idle;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
appState = AppState.failed;
|
appState = AppState.failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
mPlayer!.closePlayer();
|
||||||
|
mPlayer = null;
|
||||||
|
|
||||||
|
mRecorder!.closeRecorder();
|
||||||
|
mRecorder = null;
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void startTimer() {
|
||||||
|
const oneSec = Duration(seconds: 1);
|
||||||
|
_timer = Timer.periodic(
|
||||||
|
oneSec,
|
||||||
|
(Timer timer) {
|
||||||
|
countTimer.value = Duration(seconds: countTimer.value.inSeconds + 1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void record() async {
|
||||||
|
countTimer.value = Duration.zero;
|
||||||
|
|
||||||
|
await mRecorder!
|
||||||
|
.startRecorder(
|
||||||
|
toFile: _mPath,
|
||||||
|
codec: _codec,
|
||||||
|
audioSource: theSource,
|
||||||
|
)
|
||||||
|
.then((value) {
|
||||||
|
startTimer();
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void play() async {
|
||||||
|
assert(mPlayerIsInited &&
|
||||||
|
mplaybackReady &&
|
||||||
|
mRecorder!.isStopped &&
|
||||||
|
mPlayer!.isStopped);
|
||||||
|
mPlayer!
|
||||||
|
.startPlayer(
|
||||||
|
fromURI: _mPath,
|
||||||
|
// fromDataBuffer: Uint8List.fromList(
|
||||||
|
// recordedData.expand((element) => element).toList()),
|
||||||
|
//codec: kIsWeb ? Codec.opusWebM : Codec.aacADTS,
|
||||||
|
whenFinished: () {})
|
||||||
|
.then((value) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPlayer() {
|
||||||
|
mPlayer!.stopPlayer().then((value) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void pausePlayer() {
|
||||||
|
mPlayer!.pausePlayer().then((value) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void resumePlayer() {
|
||||||
|
mPlayer!.resumePlayer().then((value) {});
|
||||||
|
}
|
||||||
|
|
||||||
void deleteRecordedFile() {
|
void deleteRecordedFile() {
|
||||||
recordedFile!.delete();
|
path = null;
|
||||||
recordedFile = null;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startRecording() async {
|
Future<void> startRecording() async {
|
||||||
text = null;
|
text = null;
|
||||||
await _recorder.hasPermission();
|
// await _recorder.hasPermission();
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
Vibrate.feedback(FeedbackType.medium);
|
Vibrate.feedback(FeedbackType.medium);
|
||||||
}
|
}
|
||||||
isRecording = true;
|
record();
|
||||||
Directory? tempDir;
|
|
||||||
if (!kIsWeb) {
|
|
||||||
tempDir = await getApplicationDocumentsDirectory();
|
|
||||||
}
|
|
||||||
_recorder.start(const RecordConfig(),
|
|
||||||
path: kIsWeb
|
|
||||||
? ''
|
|
||||||
: '${tempDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopRecording({required bool sendImidiately}) async {
|
Future<void> stopRecording({required bool sendImidiately}) async {
|
||||||
path = await _recorder.stop();
|
path = await mRecorder!.stopRecorder();
|
||||||
isRecording = false;
|
mplaybackReady = true;
|
||||||
|
_timer?.cancel();
|
||||||
|
mPlayer!.setSubscriptionDuration(countTimer.value);
|
||||||
|
update();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (kIsWeb) {
|
|
||||||
final Uri audioUri = Uri.parse(path!);
|
// if (kIsWeb) {
|
||||||
final http.Response audioResponse = await http.get(audioUri);
|
// final Uri audioUri = Uri.parse(path!);
|
||||||
recordedFileBytes = audioResponse.bodyBytes;
|
// final http.Response audioResponse = await http.get(audioUri);
|
||||||
} else {
|
// recordedFileBytes = audioResponse.bodyBytes;
|
||||||
recordedFile = File(path!);
|
// } else {
|
||||||
}
|
// recordedFile = File(path!);
|
||||||
|
// }
|
||||||
if (sendImidiately) {
|
if (sendImidiately) {
|
||||||
await sendMessage();
|
await sendMessage();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -121,9 +233,8 @@ class DirectState extends CoreProvier {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage() async {
|
||||||
if ((text == null || text!.isEmpty) &&
|
if ((text == null || text!.isEmpty) && path == null) return;
|
||||||
(recordedFile == null && recordedFileBytes == null)) return;
|
VoiceService.audioPlayer.stop();
|
||||||
MediaService.audioPlayer.stop();
|
|
||||||
|
|
||||||
final body = {};
|
final body = {};
|
||||||
|
|
||||||
|
|
@ -144,26 +255,18 @@ class DirectState extends CoreProvier {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
final duration = await VoiceService.getDuration(src: path!);
|
body.addAll({'duration': countTimer.value.inSeconds.toString()});
|
||||||
if (duration != null) {
|
|
||||||
body.addAll({'duration': duration.inSeconds.toString()});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final uploadFile = recordedFile;
|
|
||||||
final uploadFileBytes = recordedFileBytes;
|
|
||||||
text = null;
|
text = null;
|
||||||
recordedFile = null;
|
|
||||||
recordedFileBytes = null;
|
|
||||||
replyRadar = null;
|
replyRadar = null;
|
||||||
replyNews = null;
|
replyNews = null;
|
||||||
path = null;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
final service =
|
final service =
|
||||||
RequestService(RequestHelper.sendDirectMessage(typeId), body: body);
|
RequestService(RequestHelper.sendDirectMessage(typeId), body: body);
|
||||||
|
|
||||||
if (uploadFile == null && uploadFileBytes == null) {
|
if (path == null) {
|
||||||
await service.post();
|
await service.post();
|
||||||
|
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
|
|
@ -177,57 +280,33 @@ class DirectState extends CoreProvier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messages.insert(
|
final Uint8List uploadFile = kIsWeb
|
||||||
0,
|
? (await http.get(Uri.parse(path!))).bodyBytes
|
||||||
MessageData(
|
: await File(path!).readAsBytes();
|
||||||
id: 0,
|
|
||||||
writedByAdmin: false,
|
path = null;
|
||||||
readed: false,
|
|
||||||
createdAt:
|
await service.multipartBytes(
|
||||||
DateTime.now().subtract(const Duration(minutes: 210)).toString(),
|
file: uploadFile,
|
||||||
text: text,
|
method: 'POST',
|
||||||
// audio: path,
|
fieldName: 'audio',
|
||||||
// audioFile: uploadFile,
|
fileName: 'voice-message',
|
||||||
radar: replyRadar,
|
mediaExtension: 'm4a',
|
||||||
news: replyNews,
|
mediaFormat: 'audio',
|
||||||
audioDuration: audioDuration,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
dailyMessages.clear();
|
|
||||||
|
|
||||||
for (var i = 0; i < messages.length; i++) {
|
|
||||||
_addToDailyGrouped(messages[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
await service.multipartBytes(
|
|
||||||
file: uploadFileBytes!,
|
|
||||||
method: 'POST',
|
|
||||||
fieldName: 'audio',
|
|
||||||
fileName: 'voice-message',
|
|
||||||
mediaExtension: 'm4a',
|
|
||||||
mediaFormat: 'audio',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await service.multipart(
|
|
||||||
file: Platform.isIOS
|
|
||||||
? File(uploadFile!.path.replaceAll('file://', ''))
|
|
||||||
: uploadFile,
|
|
||||||
method: 'POST',
|
|
||||||
fieldName: 'audio',
|
|
||||||
fileName: 'voice-message',
|
|
||||||
mediaExtension: 'm4a',
|
|
||||||
mediaFormat: 'audio',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
final message = service.result['message'];
|
final message = service.result['message'];
|
||||||
final m = MessageData.fromJson(message);
|
messages.insert(0, MessageData.fromJson(message));
|
||||||
messages[0] = m;
|
|
||||||
update();
|
dailyMessages.clear();
|
||||||
|
|
||||||
|
for (var i = 0; i < messages.length; i++) {
|
||||||
|
_addToDailyGrouped(messages[i]);
|
||||||
|
}
|
||||||
|
// update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,10 @@ class MessageBox extends StatelessWidget {
|
||||||
isMessage: true,
|
isMessage: true,
|
||||||
child: Consumer<DirectState>(
|
child: Consumer<DirectState>(
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
if (state.isRecording) {
|
if (state.mRecorder!.isRecording) {
|
||||||
return const _Recording();
|
return const _Recording();
|
||||||
} else if (!state.isRecording &&
|
} else if (!(state.mRecorder!.isRecording) &&
|
||||||
(state.recordedFile != null ||
|
state.path != null) {
|
||||||
state.recordedFileBytes != null)) {
|
|
||||||
return const _RecordChecking();
|
return const _RecordChecking();
|
||||||
}
|
}
|
||||||
return const _Typing();
|
return const _Typing();
|
||||||
|
|
@ -227,6 +226,7 @@ class _RecordChecking extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: AudioWave(
|
child: AudioWave(
|
||||||
file: state.path!,
|
file: state.path!,
|
||||||
|
totalDuration: state.countTimer.value,
|
||||||
// deleteClidk: () => state.deleteRecordedFile,
|
// deleteClidk: () => state.deleteRecordedFile,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import 'package:didvan/services/notification/notification_service.dart';
|
||||||
import 'package:didvan/utils/action_sheet.dart';
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:didvan/views/ai/ai.dart';
|
import 'package:didvan/views/ai/ai.dart';
|
||||||
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
||||||
import 'package:didvan/views/ai/widgets/ai_message_bar.dart';
|
|
||||||
import 'package:didvan/views/home/categories/categories_page.dart';
|
import 'package:didvan/views/home/categories/categories_page.dart';
|
||||||
import 'package:didvan/views/home/main/main_page.dart';
|
import 'package:didvan/views/home/main/main_page.dart';
|
||||||
import 'package:didvan/views/home/home_state.dart';
|
import 'package:didvan/views/home/home_state.dart';
|
||||||
|
|
@ -618,13 +617,13 @@ class _HomeState extends State<Home>
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) {
|
itemBuilder: (BuildContext context) {
|
||||||
return <PopupMenuEntry>[
|
return <PopupMenuEntry>[
|
||||||
AiMessageBar.popUpBtns(
|
ActionSheetUtils.popUpBtns(
|
||||||
value: 'حذف پیام',
|
value: 'حذف پیام',
|
||||||
icon: DidvanIcons.trash_regular,
|
icon: DidvanIcons.trash_regular,
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
height: 24,
|
height: 24,
|
||||||
size: 12),
|
size: 12),
|
||||||
AiMessageBar.popUpBtns(
|
ActionSheetUtils.popUpBtns(
|
||||||
value: 'آرشیو',
|
value: 'آرشیو',
|
||||||
icon: Icons.folder_copy,
|
icon: Icons.folder_copy,
|
||||||
height: 24,
|
height: 24,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||||
|
|
||||||
import 'package:didvan/config/theme_data.dart';
|
import 'package:didvan/config/theme_data.dart';
|
||||||
import 'package:didvan/constants/app_icons.dart';
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/models/home_page_content/content.dart';
|
import 'package:didvan/models/home_page_content/content.dart';
|
||||||
|
|
@ -10,9 +12,11 @@ import 'package:didvan/views/widgets/didvan/card.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:didvan/providers/user.dart';
|
import 'package:didvan/providers/user.dart';
|
||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
class MainPagePodcastItem extends StatefulWidget {
|
class MainPagePodcastItem extends StatefulWidget {
|
||||||
final MainPageContentType content;
|
final MainPageContentType content;
|
||||||
|
|
@ -24,6 +28,7 @@ class MainPagePodcastItem extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
||||||
|
bool loading = false;
|
||||||
void _onMarkChange() {
|
void _onMarkChange() {
|
||||||
UserProvider.changeItemMark(
|
UserProvider.changeItemMark(
|
||||||
widget.type,
|
widget.type,
|
||||||
|
|
@ -43,8 +48,18 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
||||||
args: const StudioRequestArgs(page: 0, type: 'podcast'),
|
args: const StudioRequestArgs(page: 0, type: 'podcast'),
|
||||||
);
|
);
|
||||||
MediaService.currentPodcast = state.studio;
|
MediaService.currentPodcast = state.studio;
|
||||||
|
loading = true;
|
||||||
|
state.update();
|
||||||
|
final response =
|
||||||
|
await get(Uri.parse(widget.content.link.replaceAll('%3A', ':')));
|
||||||
|
final bytes = response.bodyBytes;
|
||||||
|
final blob = html.Blob([bytes]);
|
||||||
|
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
loading = false;
|
||||||
|
state.update();
|
||||||
MediaService.handleAudioPlayback(
|
MediaService.handleAudioPlayback(
|
||||||
audioSource: widget.content.link,
|
audioSource: blobUrl,
|
||||||
id: widget.content.id,
|
id: widget.content.id,
|
||||||
isNetworkAudio: true,
|
isNetworkAudio: true,
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
|
@ -97,42 +112,44 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Expanded(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
DidvanText(
|
children: [
|
||||||
widget.content.title,
|
DidvanText(
|
||||||
style:
|
widget.content.title,
|
||||||
Theme.of(context).textTheme.bodyLarge,
|
style:
|
||||||
maxLines: 1,
|
Theme.of(context).textTheme.bodyLarge,
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 1,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
DidvanIcons.calendar_day_light,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
DidvanText(
|
|
||||||
DateTime.parse(
|
|
||||||
widget.content.subtitles[0])
|
|
||||||
.toPersianDateStr(),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
],
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 4),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
DidvanIcons.calendar_day_light,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
DidvanText(
|
||||||
|
DateTime.parse(
|
||||||
|
widget.content.subtitles[0])
|
||||||
|
.toPersianDateStr(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: _onMarkChange,
|
onTap: _onMarkChange,
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,7 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
DidvanText(
|
DidvanText(
|
||||||
'نسخه نرمافزار: 3.3.3',
|
'نسخه نرمافزار: 3.3.4',
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
110
pubspec.lock
110
pubspec.lock
|
|
@ -50,7 +50,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
audio_session:
|
audio_session:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: audio_session
|
name: audio_session
|
||||||
sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261"
|
sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261"
|
||||||
|
|
@ -345,6 +345,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.12"
|
version: "3.8.12"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
fl_chart:
|
fl_chart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -507,6 +515,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
flutter_sound:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_sound
|
||||||
|
sha256: "1f40b26b92907a433afe877c927cd48f5a2e4d0f7188e5d39eb5756008aa51ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.6.0"
|
||||||
|
flutter_sound_platform_interface:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_sound_platform_interface
|
||||||
|
sha256: "2e218521d8187b9a4c65063ad9c79bfe88119531ff68047a2eaa6b027cb276bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.6.0"
|
||||||
|
flutter_sound_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_sound_web
|
||||||
|
sha256: "5013a15e4e69a4bdc8badd729130eef43c3305e30ba8e6933f863436ce969932"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.6.0"
|
||||||
flutter_spinkit:
|
flutter_spinkit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -741,6 +773,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
logger:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logger
|
||||||
|
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -989,62 +1029,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.2"
|
||||||
record:
|
recase:
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: record
|
|
||||||
sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.1.2"
|
|
||||||
record_android:
|
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_android
|
name: recase
|
||||||
sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c
|
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.6"
|
version: "4.1.0"
|
||||||
record_darwin:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_darwin
|
|
||||||
sha256: fe90d302acb1f3cee1ade5df9c150ca5cee33b48d8cdf1cf433bf577d7f00134
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.2"
|
|
||||||
record_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_linux
|
|
||||||
sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.2"
|
|
||||||
record_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_platform_interface
|
|
||||||
sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
record_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_web
|
|
||||||
sha256: "0ef370d1e6553ad33c39dd03103b374e7861f3518b0533e64c94d73f988a5ffa"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
record_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_windows
|
|
||||||
sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.3"
|
|
||||||
rive:
|
rive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1090,6 +1082,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1270,10 +1270,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "4.5.1"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
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.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 3.3.3+3330
|
version: 3.3.4+3340
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.0 <3.0.0"
|
sdk: ">=2.19.0 <3.0.0"
|
||||||
|
|
@ -51,7 +51,8 @@ dependencies:
|
||||||
carousel_slider: ^4.0.0
|
carousel_slider: ^4.0.0
|
||||||
flutter_vibrate: ^1.3.0
|
flutter_vibrate: ^1.3.0
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
record: ^5.1.2
|
# record: ^5.1.2
|
||||||
|
|
||||||
persian_datetime_picker: ^2.6.0
|
persian_datetime_picker: ^2.6.0
|
||||||
persian_number_utility: ^1.1.1
|
persian_number_utility: ^1.1.1
|
||||||
bot_toast: ^4.0.1
|
bot_toast: ^4.0.1
|
||||||
|
|
@ -82,7 +83,7 @@ dependencies:
|
||||||
android_intent_plus: ^5.0.0
|
android_intent_plus: ^5.0.0
|
||||||
get: ^4.6.6
|
get: ^4.6.6
|
||||||
# firebase_auth: ^4.19.6
|
# firebase_auth: ^4.19.6
|
||||||
just_audio: ^0.9.39
|
just_audio: ^0.9.11
|
||||||
video_player: ^2.8.7
|
video_player: ^2.8.7
|
||||||
chewie: ^1.8.3
|
chewie: ^1.8.3
|
||||||
typewritertext: ^3.0.8
|
typewritertext: ^3.0.8
|
||||||
|
|
@ -94,9 +95,14 @@ dependencies:
|
||||||
flutter_cache_manager: any
|
flutter_cache_manager: any
|
||||||
flutter_local_notifications: ^17.2.2
|
flutter_local_notifications: ^17.2.2
|
||||||
flutter_background_service: ^5.0.10
|
flutter_background_service: ^5.0.10
|
||||||
js: ^0.6.7
|
# js: ^0.6.7
|
||||||
|
flutter_sound: ^9.6.0
|
||||||
|
audio_session: ^0.1.21
|
||||||
|
|
||||||
# url_launcher: ^6.3.0
|
# url_launcher: ^6.3.0
|
||||||
|
|
||||||
|
js: any
|
||||||
|
flutter_sound_platform_interface: any
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@
|
||||||
|
|
||||||
<title>Didvan</title>
|
<title>Didvan</title>
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<script src="/lib/assets/js/main.js" defer=""></script>
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue