This commit is contained in:
OkaykOrhmn 2024-09-15 15:20:09 +03:30
parent 6c3117a918
commit 9ea80f21fb
32 changed files with 480 additions and 199 deletions

View File

@ -1,5 +1,7 @@
// ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use
import 'dart:async';
import 'package:android_intent_plus/android_intent.dart'; import 'package:android_intent_plus/android_intent.dart';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
@ -12,9 +14,11 @@ import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/route_generator.dart'; import 'package:didvan/routes/route_generator.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_home_widget/home_widget_repository.dart'; import 'package:didvan/services/app_home_widget/home_widget_repository.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/notification/firebase_api.dart'; import 'package:didvan/services/notification/firebase_api.dart';
import 'package:didvan/services/notification/notification_service.dart'; import 'package:didvan/services/notification/notification_service.dart';
import 'package:didvan/utils/my_custom_scroll_behavior.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/podcasts/podcasts_state.dart'; import 'package:didvan/views/podcasts/podcasts_state.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
@ -23,6 +27,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -56,21 +61,28 @@ Future<void> _backgroundCallbackHomeWidget(Uri? uri) async {
} }
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); await runZonedGuarded(() async {
try { WidgetsFlutterBinding.ensureInitialized();
HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget);
HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget);
await NotificationService.initializeNotification(); try {
FirebaseMessaging.onBackgroundMessage(_initPushNotification); if (!kIsWeb) {
await Firebase.initializeApp( HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget);
options: DefaultFirebaseOptions.currentPlatform); HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget);
await FirebaseApi().initNotification(); await NotificationService.initializeNotification();
} catch (e) { }
debugPrint;
}
runApp(const Didvan()); FirebaseMessaging.onBackgroundMessage(_initPushNotification);
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification();
} catch (e) {
debugPrint;
}
runApp(const Didvan());
}, (error, stack) async {
error.printError();
});
} }
class Didvan extends StatefulWidget { class Didvan extends StatefulWidget {
@ -88,10 +100,12 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
@override @override
void initState() { void initState() {
NotificationService.startListeningNotificationEvents(); if (!kIsWeb) {
NotificationService.startListeningNotificationEvents();
}
WidgetsBinding.instance.addObserver(this);
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
} }
@override @override
@ -108,15 +122,17 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) async { void didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) { if (!kIsWeb) {
var r = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); if (state == AppLifecycleState.resumed) {
if (r!.toString() != Routes.splash) { var r = await HomeWidget.getWidgetData("cRoute", defaultValue: '');
await HomeWidgetRepository.decideWhereToGo(); if (r!.toString() != Routes.splash) {
await HomeWidgetRepository.decideWhereToGo();
NotificationMessage? data = HomeWidgetRepository.data; NotificationMessage? data = HomeWidgetRepository.data;
if (data != null) { if (data != null) {
await HomeWidgetRepository.decideWhereToGoNotif(); await HomeWidgetRepository.decideWhereToGoNotif();
}
} }
} }
} }
@ -150,6 +166,7 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: SafeArea( child: SafeArea(
child: MaterialApp( child: MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Didvan', title: 'Didvan',

View File

@ -1,4 +1,5 @@
import 'package:didvan/models/ai/bots_model.dart'; import 'package:didvan/models/ai/bots_model.dart';
import 'package:didvan/models/ai/files_model.dart';
class ChatsModel { class ChatsModel {
int? id; int? id;
@ -97,6 +98,7 @@ class Prompts {
bool? finished; bool? finished;
bool? error; bool? error;
bool? audio; bool? audio;
FilesModel? fileLocal;
Prompts({ Prompts({
this.id, this.id,
@ -108,8 +110,9 @@ class Prompts {
this.createdAt, this.createdAt,
this.finished, this.finished,
this.error, this.error,
this.fileLocal,
this.audio, this.audio,
}); }) {}
Prompts.fromJson(Map<String, dynamic> json) { Prompts.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
@ -141,6 +144,7 @@ class Prompts {
String? text, String? text,
String? file, String? file,
String? fileName, String? fileName,
FilesModel? fileLocal,
String? role, String? role,
String? createdAt, String? createdAt,
bool? finished, bool? finished,
@ -152,6 +156,7 @@ class Prompts {
text: text ?? this.text, text: text ?? this.text,
file: file ?? this.file, file: file ?? this.file,
fileName: fileName ?? this.fileName, fileName: fileName ?? this.fileName,
fileLocal: fileLocal ?? this.fileLocal,
role: role ?? this.role, role: role ?? this.role,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
finished: finished ?? this.finished, finished: finished ?? this.finished,

View File

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -9,14 +10,38 @@ class FilesModel {
late String extname; late String extname;
late File main; late File main;
final bool isRecorded; final bool isRecorded;
late bool isAudio; final bool? audio;
late bool isImage; final bool? image;
final bool? network;
final Uint8List? bytes;
FilesModel(this.path, {this.isRecorded = false}) { FilesModel(
basename = p.basename(path); this.path, {
final String? name,
this.isRecorded = false,
this.audio,
this.image,
this.network,
this.bytes,
}) {
basename = name ?? p.basename(path);
extname = p.extension(path); extname = p.extension(path);
main = File(path); main = File(path);
isAudio = lookupMimeType(path)?.startsWith('audio/') ?? false; print(isAudio());
isImage = lookupMimeType(path)?.startsWith('image/') ?? false; print(isImage());
print(isNetwork());
print(path);
}
bool isAudio() {
return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false;
}
bool isImage() {
return image ?? lookupMimeType(path)?.startsWith('image/') ?? false;
}
bool isNetwork() {
return network ?? path.startsWith('blob:') || path.startsWith('/uploads');
} }
} }

View File

@ -67,7 +67,9 @@ import '../views/notification_time/notification_time.dart';
class RouteGenerator { class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) { static Route<dynamic> generateRoute(RouteSettings settings) {
HomeWidget.saveWidgetData("cRoute", settings.name!); if (!kIsWeb) {
HomeWidget.saveWidgetData("cRoute", settings.name!);
}
switch (settings.name) { switch (settings.name) {
case Routes.splash: case Routes.splash:
return _createRoute( return _createRoute(

View File

@ -4,7 +4,10 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get_connect/http/src/multipart/multipart_file.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -17,7 +20,7 @@ class AiApiService {
{required final String url, {required final String url,
required final String message, required final String message,
final int? chatId, final int? chatId,
final File? file, final FilesModel? file,
final bool? edite}) async { final bool? edite}) async {
final headers = { final headers = {
"Authorization": "Bearer ${await StorageService.getValue(key: 'token')}", "Authorization": "Bearer ${await StorageService.getValue(key: 'token')}",
@ -35,26 +38,69 @@ class AiApiService {
request.fields['edit'] = edite.toString().toLowerCase(); request.fields['edit'] = edite.toString().toLowerCase();
} }
if (file != null) { if (file != null) {
int length = 0; // final file = file;
try { // final filePath = file.path;
length = await file.length(); // final mimeType = filePath != null ? lookupMimeType(filePath) : null;
// ... // final contentType = mimeType != null ? MediaType.parse(mimeType) : null;
} catch (e) {
// Handle the error or return an error response // final fileReadStream = file.readStream;
// if (fileReadStream == null) {
// throw Exception('Cannot read file from null stream');
// }
// final stream = http.ByteStream(fileReadStream);
// final multipartFile = http.MultipartFile(
// 'file',
// stream,
// file.size,
// filename: file.name,
// contentType: contentType,
// );
// request.files.add(multipartFile);
Uint8List bytes;
if (kIsWeb) {
if (file.bytes != null) {
bytes = file.bytes!;
} else {
final Uri audioUri = Uri.parse(file.path);
final http.Response audioResponse = await http.get(audioUri);
bytes = audioResponse.bodyBytes;
}
request.files.add(http.MultipartFile.fromBytes(
'file',
bytes,
filename: file.isAudio()
? 'wav'
: file.isImage()
? 'png'
: 'pdf', // You can set a filename here
contentType: parser.MediaType.parse(file.isAudio()
? 'audio/mpeg'
: file.isImage()
? 'image/png'
: 'application/pdf'), // Set the MIME type
) // Use MediaType.parse to parse the MIME type
);
} else {
int length = 0;
try {
length = await file.main.length();
// ...
} catch (e) {
// Handle the error or return an error response
}
String? mimeType = lookupMimeType(
file.path); // Use MIME type instead of file extension
mimeType ??= 'application/octet-stream';
if (mimeType.startsWith('audio')) {
mimeType = 'audio/${p.extension(file.path).replaceAll('.', '')}';
}
request.files.add(
http.MultipartFile('file', file.main.readAsBytes().asStream(), length,
filename: file.basename,
contentType: parser.MediaType.parse(
mimeType)), // Use MediaType.parse to parse the MIME type
);
} }
String basename = p.basename(file.path);
String? mimeType =
lookupMimeType(file.path); // Use MIME type instead of file extension
mimeType ??= 'application/octet-stream';
if (mimeType.startsWith('audio')) {
mimeType = 'audio/${p.extension(file.path).replaceAll('.', '')}';
}
request.files.add(
http.MultipartFile('file', file.readAsBytes().asStream(), length,
filename: basename,
contentType: parser.MediaType.parse(
mimeType)), // Use MediaType.parse to parse the MIME type
);
} }
// print("req: ${request.files}"); // print("req: ${request.files}");
@ -76,7 +122,7 @@ class AiApiService {
// Handle other non-200 responses // Handle other non-200 responses
throw Exception('Failed to load data'); throw Exception('Failed to load data');
} else { } else {
return response.stream; return response.stream.asBroadcastStream();
} }
} catch (e) { } catch (e) {
// Handle any other errors // Handle any other errors

View File

@ -17,7 +17,7 @@ import '../network/request_helper.dart';
class HomeWidgetRepository { class HomeWidgetRepository {
static Future<void> fetchWidget() async { static Future<void> fetchWidget() async {
RequestService.token = await StorageService.getValue(key: 'token'); // RequestService.token = await StorageService.getValue(key: 'token');
final service = RequestService( final service = RequestService(
RequestHelper.widgetNews(), RequestHelper.widgetNews(),
); );
@ -90,7 +90,7 @@ class HomeWidgetRepository {
); );
if (data.link!.startsWith('http')) { if (data.link!.startsWith('http')) {
launchUrlString( openInWebView(
'${data.link}', '${data.link}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );
@ -219,7 +219,7 @@ class HomeWidgetRepository {
} }
} else { } else {
if (data.link!.startsWith('http')) { if (data.link!.startsWith('http')) {
launchUrlString( openInWebView(
'${data.link}', '${data.link}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -13,10 +13,15 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; 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';
enum LaunchMode { inAppWebView } enum LaunchMode { inAppWebView }
void launchUrlString(String src, {dynamic mode}) { void openInWebView(String src, {dynamic mode}) {
if (kIsWeb) {
launchUrlString(src);
return;
}
navigatorKey.currentState!.pushNamed(Routes.web, arguments: src); navigatorKey.currentState!.pushNamed(Routes.web, arguments: src);
} }

View File

@ -1,8 +1,10 @@
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/main.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/media.dart';
import 'package:didvan/services/app_initalizer.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:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.dart';
@ -121,6 +123,7 @@ class MediaService {
return result; return result;
} catch (e) { } catch (e) {
e.printError(info: 'Pick PDF Fail'); e.printError(info: 'Pick PDF Fail');
// navigatorKey.currentState!.pop();
return null; return null;
} }
} }

View File

@ -410,11 +410,13 @@ class ActionSheetUtils {
? ClipRRect( ? ClipRRect(
borderRadius: DesignConfig.lowBorderRadius, borderRadius: DesignConfig.lowBorderRadius,
child: Image.file(File(image))) child: Image.file(File(image)))
: SkeletonImage( : image.startsWith('blob:')
width: min(MediaQuery.of(context).size.width, ? ClipRRect(
MediaQuery.of(context).size.height), borderRadius: DesignConfig.lowBorderRadius,
imageUrl: image, child: Image.network(image))
), : SkeletonImage(
imageUrl: image,
),
), ),
), ),
), ),

View File

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// Get screen media. /// Get screen media.
@ -7,9 +8,9 @@ final MediaQueryData media =
/// This extention help us to make widget responsive. /// This extention help us to make widget responsive.
extension NumberParsing on num { extension NumberParsing on num {
double w() => this * media.size.width / 100; double w() => this * media.size.width / (kIsWeb ? 250 : 100);
double h() => this * media.size.height / 100; double h() => this * media.size.height / (kIsWeb ? 550 : 100);
} }
/// ///

View File

@ -0,0 +1,11 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}

View File

@ -63,8 +63,12 @@ class _AiState extends State<Ai> {
Icon( Icon(
DidvanIcons.ai_solid, DidvanIcons.ai_solid,
size: MediaQuery.sizeOf(context).width / 5, size: MediaQuery.sizeOf(context).width / 5,
color: Theme.of(context).colorScheme.title,
),
DidvanText(
'هوشان',
color: Theme.of(context).colorScheme.title,
), ),
const DidvanText('هوشان'),
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
@ -74,8 +78,12 @@ class _AiState extends State<Ai> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(DidvanIcons.caret_down_solid), Icon(
Text(bot.name.toString()), DidvanIcons.caret_down_solid,
color: Theme.of(context).colorScheme.title,
),
DidvanText(bot.name.toString(),
color: Theme.of(context).colorScheme.title),
], ],
), ),
), ),
@ -177,7 +185,10 @@ class _AiState extends State<Ai> {
topLeft: Radius.circular(12), topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12)), bottomLeft: Radius.circular(12)),
boxShadow: DesignConfig.defaultShadow), boxShadow: DesignConfig.defaultShadow),
child: const Icon(DidvanIcons.angle_left_light), child: Icon(
DidvanIcons.angle_left_light,
color: Theme.of(context).colorScheme.title,
),
)), )),
) )
], ],

View File

@ -21,6 +21,7 @@ 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';
@ -324,8 +325,8 @@ class _AiChatPageState extends State<AiChatPage> {
Widget messageBubble(Prompts message, BuildContext context, AiChatState state, Widget messageBubble(Prompts message, BuildContext context, AiChatState state,
int index, int mIndex) { int index, int mIndex) {
FilesModel? file = FilesModel? file = message.fileLocal ??
message.file == null ? null : FilesModel(message.file.toString()); (message.file == null ? null : FilesModel(message.file.toString()));
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet( MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
pPadding: const EdgeInsets.all(0.8), pPadding: const EdgeInsets.all(0.8),
@ -418,13 +419,13 @@ class _AiChatPageState extends State<AiChatPage> {
children: [ children: [
if (message.role.toString().contains('user') && if (message.role.toString().contains('user') &&
file != null) file != null)
file.isAudio file.isAudio()
? Padding( ? Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8), horizontal: 16, vertical: 8),
child: AudioWave(file: file.path), child: AudioWave(file: file.path),
) )
: file.isImage : file.isImage()
? Padding( ? Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: messageImage(file), child: messageImage(file),
@ -605,7 +606,7 @@ class _AiChatPageState extends State<AiChatPage> {
stop: const Duration(seconds: 3), stop: const Duration(seconds: 3),
), ),
), ),
if (state.file != null) if (state.file != null && !kIsWeb)
FutureBuilder( FutureBuilder(
future: state.file!.main.length(), future: state.file!.main.length(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -627,14 +628,18 @@ class _AiChatPageState extends State<AiChatPage> {
Widget messageImage(FilesModel file) { Widget messageImage(FilesModel file) {
return GestureDetector( return GestureDetector(
onTap: () => ActionSheetUtils(context).openInteractiveViewer( onTap: () => ActionSheetUtils(context)
context, file.path, !file.path.startsWith('/uploads')), .openInteractiveViewer(context, file.path, !file.isNetwork()),
child: file.path.startsWith('/uploads') child: file.isNetwork()
? SkeletonImage( ? file.path.startsWith('blob:')
pWidth: MediaQuery.sizeOf(context).width / 1, ? ClipRRect(
pHeight: MediaQuery.sizeOf(context).height / 6, borderRadius: DesignConfig.lowBorderRadius,
imageUrl: file.path, child: Image.network(file.path))
) : SkeletonImage(
pWidth: MediaQuery.sizeOf(context).width / 1,
pHeight: MediaQuery.sizeOf(context).height / 6,
imageUrl: file.path,
)
: ClipRRect( : ClipRRect(
borderRadius: DesignConfig.lowBorderRadius, borderRadius: DesignConfig.lowBorderRadius,
child: Image.file(file.main)), child: Image.file(file.main)),

View File

@ -1,6 +1,11 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:didvan/main.dart'; import 'package:didvan/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:didvan/models/ai/bots_model.dart'; import 'package:didvan/models/ai/bots_model.dart';
import 'package:didvan/models/ai/chats_model.dart'; import 'package:didvan/models/ai/chats_model.dart';
import 'package:didvan/models/ai/files_model.dart'; import 'package:didvan/models/ai/files_model.dart';
@ -14,15 +19,10 @@ 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/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
class AiChatState extends CoreProvier { class AiChatState extends CoreProvier {
ValueNotifier<Stream<String>> messageOnstream = ValueNotifier<Stream<String>> messageOnstream =
ValueNotifier(const Stream.empty()); ValueNotifier(const Stream<String>.empty().asBroadcastStream());
List<MessageModel> messages = []; List<MessageModel> messages = [];
bool onResponsing = false; bool onResponsing = false;
bool loading = false; bool loading = false;
@ -34,6 +34,11 @@ class AiChatState extends CoreProvier {
FilesModel? file; FilesModel? file;
TextEditingController message = TextEditingController(); TextEditingController message = TextEditingController();
@override
void dispose() {
super.dispose();
}
Future<void> _scrolledEnd() async { Future<void> _scrolledEnd() async {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await scrollController.animateTo( await scrollController.animateTo(
@ -53,7 +58,6 @@ class AiChatState extends CoreProvier {
await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData( await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData(
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error)); message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
update(); update();
} }
@ -94,21 +98,16 @@ class AiChatState extends CoreProvier {
messages.add(MessageModel( messages.add(MessageModel(
dateTime: prompt.createdAt.toString(), prompts: [prompt])); dateTime: prompt.createdAt.toString(), prompts: [prompt]));
} }
// chats.add("chats: $chats");
} }
appState = AppState.idle; appState = AppState.idle;
loading = false; loading = false;
onResponsing = false; onResponsing = false;
// Add this code to scroll to maxScrollExtent after the ListView is built
update(); update();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
loading = false; loading = false;
onResponsing = false; onResponsing = false;
update(); update();
} }
@ -122,28 +121,22 @@ class AiChatState extends CoreProvier {
await service.put(); await service.put();
if (service.isSuccess) { if (service.isSuccess) {
appState = AppState.idle; appState = AppState.idle;
// Add this code to scroll to maxScrollExtent after the ListView is built
if (chat == null) { if (chat == null) {
chat = ChatsModel(id: chatId, placeholder: placeholder); chat = ChatsModel(id: chatId, placeholder: placeholder);
} else { } else {
chat = chat!.copyWith(placeholder: placeholder); chat = chat!.copyWith(placeholder: placeholder);
} }
changingPlaceHolder.value = false; changingPlaceHolder.value = false;
update(); update();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
changingPlaceHolder.value = false; changingPlaceHolder.value = false;
update(); update();
} }
Future<void> postMessage(BotsModel bot) async { Future<void> postMessage(BotsModel bot) async {
onResponsing = true; onResponsing = true;
update(); update();
String message = messages.last.prompts.last.text!; String message = messages.last.prompts.last.text!;
@ -161,27 +154,50 @@ 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?.main, file: file,
edite: isEdite); edite: isEdite);
final res = await AiApiService.getResponse(req).catchError((e) { final res = await AiApiService.getResponse(req).catchError((e) {
_onError(e); _onError(e);
return e; // return e;
}); });
String responseMessgae = ''; String responseMessgae = '';
String dataMessgae = ''; String dataMessgae = '';
file = null; file = null;
update(); update();
var r = res.listen((value) async {
var str = utf8.decode(value);
if (str.contains('{{{')) {
dataMessgae += str;
update();
return;
}
responseMessgae += str;
messageOnstream.value = Stream.value(responseMessgae);
final stream = res
.transform(utf8.decoder)
.transform(const LineSplitter()); // <--- Add this line
final r = stream.listen((str) {
// var str = utf8.decode(value);
if (!kIsWeb) {
if (str.contains('{{{')) {
dataMessgae += str;
update();
return;
}
}
responseMessgae += str;
if (kIsWeb) {
try {
int startIndex = responseMessgae.indexOf('{{{');
// + 3 to include the }}} characters
String slicedText =
responseMessgae.substring(startIndex, responseMessgae.length);
dataMessgae = slicedText;
responseMessgae = responseMessgae.replaceAll(dataMessgae, '');
print(
"slicedText: $slicedText"); // Output: {{{ HUMAN_MESSAGE_ID: 2028, AI_MESSAGE_ID: 2029 }}}
} catch (e) {
e.printError();
}
}
messageOnstream.value = Stream.value(responseMessgae);
print("responseMessgae: $str");
// update(); // update();
}); });
@ -189,7 +205,6 @@ class AiChatState extends CoreProvier {
if (chatId == null) { if (chatId == null) {
final service = await getChatId(); final service = await getChatId();
navigatorKey.currentContext!.read<HistoryAiChatState>().getChats(); navigatorKey.currentContext!.read<HistoryAiChatState>().getChats();
if (!service.isSuccess) { if (!service.isSuccess) {
_onError(null); _onError(null);
return; return;
@ -201,7 +216,6 @@ class AiChatState extends CoreProvier {
messages.last.prompts.last = messages.last.prompts.last =
messages.last.prompts.last.copyWith(error: true); messages.last.prompts.last.copyWith(error: true);
messageOnstream.value = const Stream.empty(); messageOnstream.value = const Stream.empty();
update(); update();
_scrolledEnd(); _scrolledEnd();
return; return;
@ -214,10 +228,8 @@ class AiChatState extends CoreProvier {
aiMessageId = data['AI_MESSAGE_ID']; aiMessageId = data['AI_MESSAGE_ID'];
} catch (e) { } catch (e) {
e.printError(); e.printError();
return; return;
} }
// Access the values
messages.last.prompts.last = messages.last.prompts.last messages.last.prompts.last = messages.last.prompts.last
.copyWith(finished: true, text: responseMessgae, id: aiMessageId); .copyWith(finished: true, text: responseMessgae, id: aiMessageId);
if (messages.last.prompts.length > 2) { if (messages.last.prompts.length > 2) {
@ -241,7 +253,7 @@ class AiChatState extends CoreProvier {
_scrolledEnd(); _scrolledEnd();
}); });
r.onError(_onError); r.onError((e) => _onError(e));
} }
Future<void> deleteMessage(int id, int mIndex, int index) async { Future<void> deleteMessage(int id, int mIndex, int index) async {
@ -253,17 +265,13 @@ class AiChatState extends CoreProvier {
} else { } else {
messages[mIndex].prompts.removeAt(index); messages[mIndex].prompts.removeAt(index);
} }
appState = AppState.idle; appState = AppState.idle;
update(); update();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData( await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData(
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error)); message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
update(); update();
} }
} }

View File

@ -27,6 +27,7 @@ import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart'; import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.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:record/record.dart'; import 'package:record/record.dart';
@ -80,6 +81,7 @@ class AiMessageBar extends StatefulWidget {
class _AiMessageBarState extends State<AiMessageBar> { class _AiMessageBarState extends State<AiMessageBar> {
final ValueNotifier<String> messageText = ValueNotifier(''); final ValueNotifier<String> messageText = ValueNotifier('');
bool openAttach = false; bool openAttach = false;
String? path;
@override @override
void initState() { void initState() {
@ -159,8 +161,24 @@ class _AiMessageBarState extends State<AiMessageBar> {
FilePickerResult? result = FilePickerResult? result =
await MediaService.pickPdfFile(); await MediaService.pickPdfFile();
if (result != null) { if (result != null) {
state.file = // if (kIsWeb) {
FilesModel(result.files.single.path!); // Uint8List bytes = result.files.first
// .bytes!; // Access the bytes property
// File file = File.fromRawPath(bytes);
// state.file = FilesModel(file.path,
// name: result.files.first.name,
// bytes: bytes,
// audio: false,
// image: false);
// print(result.files.first.name);
// } else {
state.file = FilesModel(
result.files.single.path!,
audio: false,
image: false);
// }
openAttach = false; openAttach = false;
} }
Future.delayed( Future.delayed(
@ -214,8 +232,10 @@ class _AiMessageBarState extends State<AiMessageBar> {
return; return;
} }
state.file = kIsWeb state.file = kIsWeb
? FilesModel(pickedFile.path) ? FilesModel(pickedFile.path,
: FilesModel(file!.path); image: true, audio: false)
: FilesModel(file!.path,
image: true, audio: false);
openAttach = false; openAttach = false;
await Future.delayed( await Future.delayed(
Duration.zero, Duration.zero,
@ -231,11 +251,28 @@ class _AiMessageBarState extends State<AiMessageBar> {
color: Colors.indigoAccent, color: Colors.indigoAccent,
click: () async { click: () async {
MediaService.onLoadingPickFile(context); MediaService.onLoadingPickFile(context);
FilePickerResult? result = FilePickerResult? result =
await MediaService.pickAudioFile(); await MediaService.pickAudioFile();
if (result != null) { if (result != null) {
state.file = if (kIsWeb) {
FilesModel(result.files.single.path!); Uint8List bytes = result.files.first
.bytes!; // Access the bytes property
File file = File.fromRawPath(bytes);
state.file = FilesModel(file.path,
name: result.files.first.name,
bytes: bytes,
audio: true,
image: false);
print(result.files.first.name);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: true,
image: false);
}
openAttach = false; openAttach = false;
} }
await Future.delayed( await Future.delayed(
@ -290,13 +327,16 @@ class _AiMessageBarState extends State<AiMessageBar> {
icon: DidvanIcons icon: DidvanIcons
.stop_circle_solid, .stop_circle_solid,
click: () async { click: () async {
final path = path = await record
await record .stop();
.stop(); print(
"pathhhhhhh is: $path");
state.file = FilesModel( state.file = FilesModel(
path.toString(), path.toString(),
isRecorded: true); isRecorded: true,
audio: true,
image: false);
_timer.cancel(); _timer.cancel();
_countTimer.value = 0; _countTimer.value = 0;
state.update(); state.update();
@ -318,15 +358,29 @@ class _AiMessageBarState extends State<AiMessageBar> {
click: () async { click: () async {
if (await record if (await record
.hasPermission()) { .hasPermission()) {
Directory? try {
downloadDir = String path =
await getApplicationDocumentsDirectory(); '';
//
if (!kIsWeb) {
Directory?
downloadDir =
await getApplicationDocumentsDirectory();
path = p.join(
downloadDir
.path,
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.wav');
}
record.start( record.start(
const RecordConfig(), const RecordConfig(),
path: path:
'${downloadDir.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a'); path);
startTimer(); startTimer();
} catch (e) {
print(
'Error starting recording: $e');
}
} }
}, },
) )
@ -381,12 +435,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
.file .file
?.path, ?.path,
fileName: state fileName: state
.file == .file
null ?.basename,
? null
: p.basename(state
.file!
.path),
finished: finished:
true, true,
role: 'user', role: 'user',
@ -417,10 +467,11 @@ class _AiMessageBarState extends State<AiMessageBar> {
file: state file: state
.file .file
?.path, ?.path,
fileName: state.file == fileName: state
null .file
? null ?.basename,
: p.basename(state.file!.path), fileLocal:
state.file,
role: role:
'user', 'user',
createdAt: DateTime.now() createdAt: DateTime.now()
@ -715,10 +766,6 @@ class _AiMessageBarState extends State<AiMessageBar> {
AnimatedVisibility fileContainer() { AnimatedVisibility fileContainer() {
final state = context.watch<AiChatState>(); final state = context.watch<AiChatState>();
String basename = '';
if (state.file != null) {
basename = p.basename(state.file!.path);
}
return AnimatedVisibility( return AnimatedVisibility(
isVisible: state.file != null && !state.file!.isRecorded, isVisible: state.file != null && !state.file!.isRecorded,
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
@ -731,16 +778,21 @@ class _AiMessageBarState extends State<AiMessageBar> {
margin: const EdgeInsets.only(bottom: 8, left: 12, right: 12), margin: const EdgeInsets.only(bottom: 8, left: 12, right: 12),
child: Row( child: Row(
children: [ children: [
state.file != null && state.file!.isImage state.file != null && state.file!.isImage()
? SizedBox( ? SizedBox(
width: 32, width: 32,
height: 42, height: 42,
child: ClipRRect( child: ClipRRect(
borderRadius: DesignConfig.lowBorderRadius, borderRadius: DesignConfig.lowBorderRadius,
child: Image.file( child: state.file!.isNetwork()
state.file!.main, ? Image.network(
fit: BoxFit.cover, state.file!.path,
))) fit: BoxFit.cover,
)
: Image.file(
state.file!.main,
fit: BoxFit.cover,
)))
: const Icon(Icons.file_copy), : const Icon(Icons.file_copy),
const SizedBox( const SizedBox(
width: 12, width: 12,
@ -752,12 +804,12 @@ class _AiMessageBarState extends State<AiMessageBar> {
SizedBox( SizedBox(
height: 24, height: 24,
child: MarqueeText( child: MarqueeText(
text: basename, text: state.file != null ? state.file!.basename : '',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
stop: const Duration(seconds: 3), stop: const Duration(seconds: 3),
), ),
), ),
if (state.file != null) if (state.file != null && !kIsWeb)
FutureBuilder( FutureBuilder(
future: state.file!.main.length(), future: state.file!.main.length(),
builder: (context, snapshot) { builder: (context, snapshot) {

View File

@ -27,7 +27,7 @@ class AudioWave extends StatefulWidget {
class _AudioWaveState extends State<AudioWave> { class _AudioWaveState extends State<AudioWave> {
final int itemCount = 35; final int itemCount = 35;
final AudioPlayer audioPlayer = AudioPlayer(); final AudioPlayer audioPlayer = AudioPlayer()..setPitch(1);
final ValueNotifier<List<double>> randoms = ValueNotifier([]); final ValueNotifier<List<double>> randoms = ValueNotifier([]);
final ValueNotifier<List<double>> randomsDisable = ValueNotifier([]); final ValueNotifier<List<double>> randomsDisable = ValueNotifier([]);
@ -40,7 +40,9 @@ class _AudioWaveState extends State<AudioWave> {
void initState() { void initState() {
super.initState(); super.initState();
try { try {
init(); WidgetsBinding.instance.addPostFrameCallback((_) async {
await init();
});
listeners(); listeners();
} catch (e) { } catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -54,19 +56,23 @@ class _AudioWaveState extends State<AudioWave> {
void setRandoms() { void setRandoms() {
for (var i = 0; i < itemCount; i++) { for (var i = 0; i < itemCount; i++) {
randoms.value.add(0); randoms.value.add(0);
randomsDisable.value.add(5.74.w() * Random().nextDouble() + .26.w()); randomsDisable.value.add(2 + Random().nextDouble() * (42 - 2));
} }
} }
Future<void> init() async { Future<void> init() async {
try { try {
final path = widget.file; final path = widget.file;
if (widget.file.startsWith('/uploads')) { if (widget.file.startsWith('blob:')) {
final audioSource = LockCachingAudioSource(Uri.parse( totalDuration = await audioPlayer
.setAudioSource(AudioSource.uri(Uri.parse(path))) ??
Duration.zero;
} else if (widget.file.startsWith('/uploads')) {
final audioSource = AudioSource.uri(Uri.parse(
'${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}')); '${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}'));
await audioSource.request(); totalDuration = await audioPlayer.setUrl(
totalDuration = '${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}') ??
await audioPlayer.setAudioSource(audioSource) ?? Duration.zero; Duration.zero;
} else { } else {
totalDuration = await audioPlayer.setFilePath(path) ?? Duration.zero; totalDuration = await audioPlayer.setFilePath(path) ?? Duration.zero;
} }
@ -114,7 +120,8 @@ class _AudioWaveState extends State<AudioWave> {
} }
@override @override
void dispose() { void dispose() async {
await audioPlayer.stop();
audioPlayer.dispose(); audioPlayer.dispose();
super.dispose(); super.dispose();
} }
@ -215,7 +222,7 @@ class _AudioWaveState extends State<AudioWave> {
Opacity( Opacity(
opacity: 0, opacity: 0,
child: Container( child: Container(
width: 50.5.w(), width: 12,
color: Colors.transparent color: Colors.transparent
.withOpacity(1), .withOpacity(1),
child: Theme( child: Theme(
@ -302,8 +309,8 @@ class _AudioWaveState extends State<AudioWave> {
children: values children: values
.map( .map(
(e) => Container( (e) => Container(
margin: EdgeInsets.symmetric(horizontal: .2.w()), margin: const EdgeInsets.symmetric(horizontal: 1),
width: .56.w(), width: 2,
height: e, height: e,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1000), borderRadius: BorderRadius.circular(1000),

View File

@ -6,6 +6,7 @@ import 'package:didvan/providers/user.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:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/foundation.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
class AuthenticationState extends CoreProvier { class AuthenticationState extends CoreProvier {
@ -52,7 +53,9 @@ class AuthenticationState extends CoreProvier {
final token = service.result['token']; final token = service.result['token'];
await userProvider.setAndGetToken(newToken: token); await userProvider.setAndGetToken(newToken: token);
RequestService.token = token; RequestService.token = token;
HomeWidget.saveWidgetData("token", token.toString()); if (!kIsWeb) {
HomeWidget.saveWidgetData("token", token.toString());
}
await userProvider.getUserInfo(); await userProvider.getUserInfo();
appState = AppState.idle; appState = AppState.idle;
return token; return token;

View File

@ -79,7 +79,7 @@ class _UsernameInputState extends State<UsernameInput> {
.bodySmall! .bodySmall!
.copyWith(color: Theme.of(context).colorScheme.primary), .copyWith(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => launchUrlString( ..onTap = () => openInWebView(
'https://didvan.app/terms-of-use#conditions', 'https://didvan.app/terms-of-use#conditions',
), ),
), ),
@ -91,7 +91,7 @@ class _UsernameInputState extends State<UsernameInput> {
.bodySmall! .bodySmall!
.copyWith(color: Theme.of(context).colorScheme.primary), .copyWith(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => launchUrlString( ..onTap = () => openInWebView(
'https://didvan.app/terms-of-use#privacy', 'https://didvan.app/terms-of-use#privacy',
), ),
), ),

View File

@ -1,6 +1,7 @@
import 'package:didvan/models/customize_categories/favorites_response.dart'; import 'package:didvan/models/customize_categories/favorites_response.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import '../../models/view/alert_data.dart'; import '../../models/view/alert_data.dart';
import '../../providers/core.dart'; import '../../providers/core.dart';
@ -100,7 +101,9 @@ class CustomizeCategoryState extends CoreProvier {
); );
appState = AppState.idle; appState = AppState.idle;
await HomeWidgetRepository.fetchWidget(); if (!kIsWeb) {
await HomeWidgetRepository.fetchWidget();
}
return; return;
} }
@ -134,7 +137,9 @@ class CustomizeCategoryState extends CoreProvier {
); );
appState = AppState.idle; appState = AppState.idle;
await HomeWidgetRepository.fetchWidget(); if (!kIsWeb) {
await HomeWidgetRepository.fetchWidget();
}
return; return;
} }

View File

@ -32,6 +32,7 @@ import 'package:didvan/views/widgets/didvan/bnb.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.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:provider/provider.dart'; import 'package:provider/provider.dart';
@ -52,7 +53,9 @@ class _HomeState extends State<Home>
@override @override
void initState() { void initState() {
NotificationService.startListeningNotificationEvents(); if (!kIsWeb) {
NotificationService.startListeningNotificationEvents();
}
final state = context.read<HomeState>(); final state = context.read<HomeState>();
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();
@ -67,16 +70,17 @@ class _HomeState extends State<Home>
context.read<HistoryAiChatState>().getBots(); context.read<HistoryAiChatState>().getBots();
} }
}); });
if (!kIsWeb) {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
HomeWidgetRepository.fetchWidget(); HomeWidgetRepository.fetchWidget();
HomeWidgetRepository.decideWhereToGo(); HomeWidgetRepository.decideWhereToGo();
NotificationMessage? data = HomeWidgetRepository.data; NotificationMessage? data = HomeWidgetRepository.data;
if (data != null) { if (data != null) {
HomeWidgetRepository.decideWhereToGoNotif(); HomeWidgetRepository.decideWhereToGoNotif();
} }
AppInitializer.handleCLick(state, _tabController); AppInitializer.handleCLick(state, _tabController);
}); });
}
state.refresh(); state.refresh();
context.read<ThemeProvider>().addListener(() { context.read<ThemeProvider>().addListener(() {
state.refresh(); state.refresh();
@ -120,8 +124,12 @@ class _HomeState extends State<Home>
Icon( Icon(
DidvanIcons.ai_solid, DidvanIcons.ai_solid,
size: MediaQuery.sizeOf(context).width / 5, size: MediaQuery.sizeOf(context).width / 5,
color: Theme.of(context).colorScheme.title,
),
DidvanText(
'هوشان',
color: Theme.of(context).colorScheme.title,
), ),
const DidvanText('هوشان'),
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
@ -569,7 +577,7 @@ class _HomeState extends State<Home>
Icon( Icon(
icon, icon,
color: enable color: enable
? null ? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.disabledText, : Theme.of(context).colorScheme.disabledText,
), ),
const SizedBox( const SizedBox(
@ -580,7 +588,7 @@ class _HomeState extends State<Home>
DidvanText(text, DidvanText(text,
fontSize: 16, fontSize: 16,
color: enable color: enable
? null ? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.disabledText), : Theme.of(context).colorScheme.disabledText),
// if (!enable) Text('در حال توسعه ...') // if (!enable) Text('در حال توسعه ...')
], ],

View File

@ -126,7 +126,7 @@ class _MainPageSection extends StatelessWidget {
void _moreHandler(BuildContext context) { void _moreHandler(BuildContext context) {
if (list.link.startsWith('http')) { if (list.link.startsWith('http')) {
launchUrlString( openInWebView(
'${list.link}?accessToken=${RequestService.token}', '${list.link}?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -99,7 +99,7 @@ class MainPageState extends CoreProvier {
return; return;
} }
if (link.startsWith('http')) { if (link.startsWith('http')) {
launchUrlString( openInWebView(
'$link?accessToken=${RequestService.token}', '$link?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -23,7 +23,7 @@ class MainPageBanner extends StatelessWidget {
onTap: () => item.link == null || item.link!.isEmpty onTap: () => item.link == null || item.link!.isEmpty
? ActionSheetUtils(context) ? ActionSheetUtils(context)
.openInteractiveViewer(context, item.image, false) .openInteractiveViewer(context, item.image, false)
: launchUrlString(item.link!, mode: LaunchMode.inAppWebView), : openInWebView(item.link!, mode: LaunchMode.inAppWebView),
child: SkeletonImage( child: SkeletonImage(
imageUrl: item.image, imageUrl: item.image,
), ),

View File

@ -138,7 +138,7 @@ class _NewStatisticState extends State<NewStatistic> {
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
physics: const ScrollPhysics(), physics: const BouncingScrollPhysics(),
itemCount: state.contents[id].contents.length, itemCount: state.contents[id].contents.length,
itemBuilder: (context, index) => StatMainCard( itemBuilder: (context, index) => StatMainCard(
id: id, id: id,

View File

@ -90,7 +90,7 @@ class SearchResultItem extends StatelessWidget {
return; return;
} }
if (_targetPageRouteName == null && item.link != null) { if (_targetPageRouteName == null && item.link != null) {
launchUrlString( openInWebView(
'${item.link!}?accessToken=${RequestService.token}', '${item.link!}?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -13,7 +13,7 @@ class MainCategories extends StatelessWidget {
void _onTap(String link, BuildContext context) { void _onTap(String link, BuildContext context) {
if (link.startsWith('http')) { if (link.startsWith('http')) {
launchUrlString( openInWebView(
'$link?accessToken=${RequestService.token}', '$link?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -65,7 +65,7 @@ class StudioDetailsWidget extends StatelessWidget {
key: ValueKey(state.studio.id), key: ValueKey(state.studio.id),
data: state.studio.description, data: state.studio.description,
onAnchorTap: (href, _, __) => onAnchorTap: (href, _, __) =>
launchUrlString(href!), openInWebView(href!),
style: { style: {
'*': Style( '*': Style(
direction: TextDirection.rtl, direction: TextDirection.rtl,

View File

@ -222,7 +222,7 @@ class _ProfilePageState extends State<ProfilePage> {
icon: DidvanIcons.info_circle_regular, icon: DidvanIcons.info_circle_regular,
title: 'معرفی دیدوان', title: 'معرفی دیدوان',
onTap: () => onTap: () =>
launchUrlString('https://didvan.app/#info'), openInWebView('https://didvan.app/#info'),
), ),
const DidvanDivider(), const DidvanDivider(),
MenuOption( MenuOption(
@ -338,7 +338,7 @@ class _ProfilePageState extends State<ProfilePage> {
MenuOption( MenuOption(
icon: DidvanIcons.alert_regular, icon: DidvanIcons.alert_regular,
title: 'حریم خصوصی', title: 'حریم خصوصی',
onTap: () => launchUrlString( onTap: () => openInWebView(
'https://didvan.app/terms-of-use#privacy', 'https://didvan.app/terms-of-use#privacy',
), ),
), ),

View File

@ -310,7 +310,7 @@ class _DidvanPageViewState extends State<DidvanPageView> {
), ),
); );
} else { } else {
launchUrlString(href); openInWebView(href);
} }
}, },
style: { style: {

View File

@ -105,7 +105,7 @@ class MultitypeOverview extends StatelessWidget {
return; return;
} }
if (_targetPageRouteName == null && item.link != null) { if (_targetPageRouteName == null && item.link != null) {
launchUrlString( openInWebView(
'${item.link!}?accessToken=${RequestService.token}', '${item.link!}?accessToken=${RequestService.token}',
mode: LaunchMode.inAppWebView, mode: LaunchMode.inAppWebView,
); );

View File

@ -1138,6 +1138,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:

View File

@ -92,6 +92,7 @@ dependencies:
mime: ^1.0.2 mime: ^1.0.2
path: any path: any
flutter_cache_manager: any flutter_cache_manager: any
url_launcher: ^6.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: