diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 43fc3c0..79aac85 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -39,6 +40,40 @@ + + + + + + + + + + + + + + + + navigatorKey = GlobalKey(); @@ -66,6 +67,12 @@ void main() async { HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); await NotificationService.initializeNotification(); + await FlutterDownloader.initialize( + debug: + true, // optional: set to false to disable printing logs to console (default: true) + ignoreSsl: + true // option: set to false to disable working with http links (default: false) + ); } // FirebaseMessaging.onBackgroundMessage(_initPushNotification); diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index e313283..777fc41 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -11,13 +11,14 @@ import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:just_audio/just_audio.dart'; import 'package:path/path.dart' as p; import 'package:file_picker/file_picker.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:http/http.dart' as http; +import 'package:permission_handler/permission_handler.dart'; import 'package:universal_html/html.dart' as html; class MediaService { @@ -148,8 +149,8 @@ class MediaService { } } - static Future downloadFile(String url) async { - final basename = p.basename(url).split('?accessToken=').first; + static Future downloadFile(String url, {final String? name}) async { + final basename = name ?? p.basename(url).split('?accessToken=').first; Directory? dir; try { if (Platform.isIOS) { @@ -163,18 +164,24 @@ class MediaService { print("Cannot get download folder path $err"); } } - String path = "${dir?.path}${Platform.isIOS ? '/' : ''}$basename"; - - File file = File(path); - - try { - final http.Response audioResponse = await http.get(Uri.parse(url)); - final bytes = audioResponse.bodyBytes; - file.writeAsBytes(bytes); - } catch (e) { - if (kDebugMode) { - print("Exception$e"); + String path = "${dir?.path}${Platform.isIOS ? '/' : ''}"; + final status = await Permission.storage.request(); + if (status.isGranted) { + try { + await FlutterDownloader.enqueue( + url: url, + savedDir: path, + fileName: basename, + showNotification: true, + openFileFromNotification: true, + ); + } catch (e) { + if (kDebugMode) { + print("Exception$e"); + } + return null; } + } else { return null; } diff --git a/lib/views/ai/ai.dart b/lib/views/ai/ai.dart index 02a85e5..778aed6 100644 --- a/lib/views/ai/ai.dart +++ b/lib/views/ai/ai.dart @@ -261,7 +261,7 @@ class _AiState extends State { const Padding( padding: EdgeInsets.fromLTRB(8, 8, 8, 4), child: DidvanText( - 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.', fontSize: 12, ), ) diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index b1fba3e..20e414e 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -261,8 +261,6 @@ class _AiChatPageState extends State { style: ButtonStyleMode.primary, onPressed: () async { - final state = context - .read(); await state .changePlaceHolder( placeholder.text); @@ -688,25 +686,12 @@ class _AiChatPageState extends State { onTap: () async { final url = '${RequestHelper.baseUrl + message.file.toString()}?accessToken=${RequestService.token}'; - final download = kIsWeb + kIsWeb ? MediaService .downloadFileFromWeb(url) : await MediaService.downloadFile( - url); - AlertData alertData = AlertData( - message: 'دانلود موفقیت آمیز بود', - aLertType: ALertType.success); - if (download == null) { - alertData = AlertData( - message: - 'دانلود موفقیت آمیز نبود', - aLertType: ALertType.error); - } - Future.delayed( - Duration.zero, - () => ActionSheetUtils(context) - .showAlert(alertData), - ); + url, + name: message.fileName); }, child: Icon( DidvanIcons.download_solid, diff --git a/lib/views/ai/history_ai_chat_page.dart b/lib/views/ai/history_ai_chat_page.dart index beef175..069c722 100644 --- a/lib/views/ai/history_ai_chat_page.dart +++ b/lib/views/ai/history_ai_chat_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; @@ -184,14 +185,21 @@ class _HistoryAiChatPageState extends State { style: TextStyle( color: Theme.of(context) .colorScheme - .text), + .text, + fontFamily: + DesignConfig.fontFamily), children: [ TextSpan( text: "\"${chat.title}\"", - style: const TextStyle( + style: TextStyle( + fontFamily: DesignConfig + .fontFamily, fontWeight: FontWeight.bold)), - const TextSpan( + TextSpan( + style: TextStyle( + fontFamily: DesignConfig + .fontFamily), text: ' با هوشان اطمینان دارید؟ '), ]), @@ -371,12 +379,15 @@ class _HistoryAiChatPageState extends State { // fontWeight: FontWeight.bold, // fontSize: 16, ), - DidvanText( - chat.prompts![0].text.toString(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - fontSize: 12, - ), + if (chat.prompts != null && + chat.prompts!.isNotEmpty && + chat.prompts![0].text != null) + DidvanText( + chat.prompts![0].text.toString(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + fontSize: 12, + ), ], ), ), diff --git a/lib/views/ai/widgets/ai_message_bar.dart b/lib/views/ai/widgets/ai_message_bar.dart index 112c6b4..006180f 100644 --- a/lib/views/ai/widgets/ai_message_bar.dart +++ b/lib/views/ai/widgets/ai_message_bar.dart @@ -296,7 +296,7 @@ class _AiMessageBarState extends State { ? const Padding( padding: EdgeInsets.fromLTRB(8, 8, 8, 4), child: DidvanText( - 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.', fontSize: 12, ), ) @@ -680,10 +680,11 @@ class _AiMessageBarState extends State { MediaService.onLoadingPickFile(context); FilePickerResult? result = await MediaService.pickPdfFile(); if (result != null) { + String? name = result.files.first.name; + if (kIsWeb) { Uint8List? bytes = result.files.first.bytes; // Access the bytes property - String? name = result.files.first.name; // Store bytes and file name directly in your state or model state.file = FilesModel( @@ -695,7 +696,7 @@ class _AiMessageBarState extends State { ); } else { state.file = FilesModel(result.files.single.path!, - audio: false, image: false); + audio: false, image: false, name: name); } } Future.delayed( @@ -752,7 +753,8 @@ class _AiMessageBarState extends State { state.file = kIsWeb ? FilesModel(pickedFile.path, name: pickedFile.name, image: true, audio: false) - : FilesModel(file!.path, image: true, audio: false); + : FilesModel(file!.path, + name: pickedFile.name, image: true, audio: false); await Future.delayed( Duration.zero, () => ActionSheetUtils(context).pop(), @@ -773,10 +775,11 @@ class _AiMessageBarState extends State { FilePickerResult? result = await MediaService.pickAudioFile(); if (result != null) { + String? name = result.files.first.name; + if (kIsWeb) { Uint8List? bytes = result.files.first.bytes; // Access the bytes property - String? name = result.files.first.name; final blob = html.Blob([bytes]); final blobUrl = html.Url.createObjectUrlFromBlob(blob); @@ -790,7 +793,7 @@ class _AiMessageBarState extends State { ); } else { state.file = FilesModel(result.files.single.path!, - audio: true, image: false); + name: name, audio: true, image: false); } } await Future.delayed( diff --git a/lib/views/ai/widgets/ai_message_bar_ios.dart b/lib/views/ai/widgets/ai_message_bar_ios.dart index 1f6f6b5..34c1a89 100644 --- a/lib/views/ai/widgets/ai_message_bar_ios.dart +++ b/lib/views/ai/widgets/ai_message_bar_ios.dart @@ -561,7 +561,7 @@ class _AiMessageBarIOSState extends State { ? const Padding( padding: EdgeInsets.fromLTRB(8, 8, 8, 4), child: DidvanText( - 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.', + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.', fontSize: 12, ), ) diff --git a/lib/views/authentication/screens/username.dart b/lib/views/authentication/screens/username.dart index 21d6b3c..9058371 100644 --- a/lib/views/authentication/screens/username.dart +++ b/lib/views/authentication/screens/username.dart @@ -1,4 +1,3 @@ -import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/views/authentication/authentication_state.dart'; import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; @@ -7,6 +6,7 @@ import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; // import 'package:url_launcher/url_launcher.dart'; class UsernameInput extends StatefulWidget { @@ -79,10 +79,9 @@ class _UsernameInputState extends State { .bodySmall! .copyWith(color: Theme.of(context).colorScheme.primary), recognizer: TapGestureRecognizer() - ..onTap = () => AppInitializer.openWebLink( - context, - 'https://didvan.com/terms-of-use#conditions', - ), + ..onTap = () => launchUrlString( + 'https://didvan.com/terms-of-use#conditions', + mode: LaunchMode.inAppWebView), ), const TextSpan(text: 'و\n'), TextSpan( @@ -92,10 +91,9 @@ class _UsernameInputState extends State { .bodySmall! .copyWith(color: Theme.of(context).colorScheme.primary), recognizer: TapGestureRecognizer() - ..onTap = () => AppInitializer.openWebLink( - context, - 'https://didvan.com/terms-of-use#privacy', - ), + ..onTap = () => launchUrlString( + 'https://didvan.com/terms-of-use#privacy', + mode: LaunchMode.inAppWebView), ), const TextSpan(text: 'را می‌پذیرم'), ], diff --git a/lib/views/direct/direct_state.dart b/lib/views/direct/direct_state.dart index 9cbd0f4..6847d94 100644 --- a/lib/views/direct/direct_state.dart +++ b/lib/views/direct/direct_state.dart @@ -259,8 +259,7 @@ class DirectState extends CoreProvier { } text = null; - replyRadar = null; - replyNews = null; + notifyListeners(); final service = @@ -271,7 +270,11 @@ class DirectState extends CoreProvier { if (service.isSuccess) { final message = service.result['message']; - messages.insert(0, MessageData.fromJson(message)); + + messages.insert( + 0, + MessageData.fromJson(message) + .copyWith(news: replyNews, radar: replyRadar)); dailyMessages.clear(); @@ -303,8 +306,10 @@ class DirectState extends CoreProvier { _addToDailyGrouped(messages[i]); } } - path = null; } + path = null; + replyRadar = null; + replyNews = null; notifyListeners(); } } diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart index 3e6e124..53c7212 100644 --- a/lib/views/home/home.dart +++ b/lib/views/home/home.dart @@ -315,77 +315,76 @@ class _HomeState extends State // height: 12, // ), Expanded( - child: state.chats.isEmpty - ? Padding( + child: state.loadingdeleteAll || + state.appState == AppState.busy + ? ListView.builder( + shrinkWrap: true, + itemCount: 10, padding: - const EdgeInsets.all(12.0), - child: Column( - children: [ - SvgPicture.asset( - Assets.emptyResult, - height: MediaQuery.sizeOf( - context) - .height / - 10, - ), - const DidvanText( - 'لیست خالی است', - fontSize: 14, - fontWeight: FontWeight.bold, - ) - ], - ), - ) - : state.loadingdeleteAll || - state.appState == - AppState.busy - ? ListView.builder( - shrinkWrap: true, - itemCount: 10, - padding: const EdgeInsets - .symmetric( + const EdgeInsets.symmetric( horizontal: 12), - physics: - const NeverScrollableScrollPhysics(), - itemBuilder: - (context, index) { - return const Padding( - padding: - EdgeInsets.symmetric( - vertical: 12.0), - child: Row( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - ClipOval( - child: - ShimmerPlaceholder( - height: 24, - width: 24, - ), - ), - SizedBox(width: 12), - Expanded( - child: - ShimmerPlaceholder( - height: 24, - ), - ), - SizedBox(width: 12), - ShimmerPlaceholder( - height: 24, - width: 24, - ), - SizedBox(width: 8), - ShimmerPlaceholder( - height: 24, - width: 12, - ), - ], + physics: + const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return const Padding( + padding: EdgeInsets.symmetric( + vertical: 12.0), + child: Row( + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + ClipOval( + child: + ShimmerPlaceholder( + height: 24, + width: 24, + ), ), - ); - }, + SizedBox(width: 12), + Expanded( + child: + ShimmerPlaceholder( + height: 24, + ), + ), + SizedBox(width: 12), + ShimmerPlaceholder( + height: 24, + width: 24, + ), + SizedBox(width: 8), + ShimmerPlaceholder( + height: 24, + width: 12, + ), + ], + ), + ); + }, + ) + : state.chats.isEmpty + ? Padding( + padding: const EdgeInsets.all( + 12.0), + child: Column( + children: [ + SvgPicture.asset( + Assets.emptyResult, + height: + MediaQuery.sizeOf( + context) + .height / + 10, + ), + const DidvanText( + 'لیست خالی است', + fontSize: 14, + fontWeight: + FontWeight.bold, + ) + ], + ), ) : ListView.builder( shrinkWrap: true, @@ -542,62 +541,62 @@ class _HomeState extends State width: 12, ), Expanded( - child: chat.isEditing != null && chat.isEditing! - ? TextFormField( - controller: title, - style: const TextStyle(fontSize: 12), - textAlignVertical: TextAlignVertical.bottom, - maxLines: 1, - decoration: const InputDecoration( - isDense: true, - contentPadding: - EdgeInsets.symmetric(vertical: 5, horizontal: 10), - border: OutlineInputBorder(), - )) - : Text( - chat.title.toString(), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), + child: Text( + chat.title.toString(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ), const SizedBox( width: 24, ), Row( children: [ - chat.isEditing != null && - chat.isEditing! && - state.loadingchangeTitle - ? const SizedBox( - width: 12, - height: 12, - child: CircularProgressIndicator()) - : InkWell( - onTap: () async { - if (title.text.isNotEmpty) { - await state.changeNameChat( - chat.id!, index, title.text, - refresh: false); - title.clear(); - } - if (chat.isEditing != null) { - chat.isEditing = !chat.isEditing!; - state.update(); - return; - } - chat.isEditing = true; - - state.update(); - }, - child: Icon( - chat.isEditing != null && chat.isEditing! - ? Icons.save - : Icons.edit_outlined, - size: 18, - ), + InkWell( + onTap: () async { + ActionSheetUtils(context).openDialog( + data: ActionSheetData( + content: Center( + child: TextFormField( + controller: title, + style: const TextStyle(fontSize: 12), + textAlignVertical: TextAlignVertical.bottom, + maxLines: 3, + decoration: const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric( + vertical: 5, horizontal: 10), + border: OutlineInputBorder(), + )), ), + title: 'تغییر نام', + onConfirmed: () async { + if (title.text.isNotEmpty) { + await state.changeNameChat( + chat.id!, index, title.text, + refresh: false); + title.clear(); + } + if (chat.isEditing != null) { + chat.isEditing = !chat.isEditing!; + state.update(); + return; + } + + state.update(); + }, + )); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.edit_outlined, + size: 20, + ), + ), + ), const SizedBox( - width: 8, + width: 4, ), PopupMenuButton( onSelected: (value) async { @@ -621,22 +620,25 @@ class _HomeState extends State value: 'حذف پیام', icon: DidvanIcons.trash_regular, color: Theme.of(context).colorScheme.error, - height: 24, - size: 12), + height: 32, + size: 16), ActionSheetUtils.popUpBtns( value: 'آرشیو', icon: Icons.folder_copy, - height: 24, - size: 12, + height: 32, + size: 16, ), ]; }, offset: const Offset(0, 0), position: PopupMenuPosition.under, useRootNavigator: true, - child: const Icon( - Icons.more_vert, - size: 18, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.more_vert, + size: 20, + ), ), ), ], diff --git a/lib/views/profile/edit_profile/edit_profile.dart b/lib/views/profile/edit_profile/edit_profile.dart index 7b69649..1204107 100644 --- a/lib/views/profile/edit_profile/edit_profile.dart +++ b/lib/views/profile/edit_profile/edit_profile.dart @@ -93,7 +93,7 @@ class _EditProfileState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: DidvanText( - 'نام کاربری می‌تواند شامل کاراکترهای کوچک و بزرگ انگلیسی و اعداد باشد.', + 'نام کاربری می‌تواند شامل کاراکترهای انگلیسی و اعداد باشد.', style: Theme.of(context).textTheme.labelSmall, ), ), diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index 6f3ec85..7267ebc 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -7,7 +7,6 @@ import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/profile/general_settings/settings_state.dart'; @@ -23,6 +22,7 @@ import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class ProfilePage extends StatefulWidget { const ProfilePage({Key? key}) : super(key: key); @@ -221,8 +221,9 @@ class _ProfilePageState extends State { MenuOption( icon: DidvanIcons.info_circle_regular, title: 'معرفی دیدوان', - onTap: () => AppInitializer.openWebLink( - context, 'https://didvan.com/#info'), + onTap: () => launchUrlString( + 'https://didvan.com/#info', + mode: LaunchMode.inAppWebView), ), const DidvanDivider(), MenuOption( @@ -338,10 +339,9 @@ class _ProfilePageState extends State { MenuOption( icon: DidvanIcons.alert_regular, title: 'حریم خصوصی', - onTap: () => AppInitializer.openWebLink( - context, - 'https://didvan.com/terms-of-use#privacy', - ), + onTap: () => launchUrlString( + 'https://didvan.com/terms-of-use#privacy', + mode: LaunchMode.inAppWebView), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index efbbb59..4b3bcdc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -406,6 +406,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + flutter_downloader: + dependency: "direct main" + description: + name: flutter_downloader + sha256: b6da5495b6258aa7c243d0f0a5281e3430b385bccac11cc508f981e653b25aa6 + url: "https://pub.dev" + source: hosted + version: "1.11.8" flutter_html: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b7970d1..33c1da5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -103,6 +103,7 @@ dependencies: js: any flutter_sound_platform_interface: any + flutter_downloader: ^1.11.8 dev_dependencies: flutter_test: sdk: flutter