diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f4ee5ba..beb7c61 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -77,6 +77,10 @@ PODS: - Flutter - flutter_secure_storage (6.0.0): - Flutter + - flutter_sound (9.6.0): + - Flutter + - flutter_sound_core (= 9.6.0) + - flutter_sound_core (9.6.0) - flutter_vibrate (0.0.1): - Flutter - GoogleDataTransport (9.4.1): @@ -164,6 +168,7 @@ DEPENDENCIES: - flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - flutter_sound (from `.symlinks/plugins/flutter_sound/ios`) - flutter_vibrate (from `.symlinks/plugins/flutter_vibrate/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) @@ -189,6 +194,7 @@ SPEC REPOS: - FirebaseCoreInternal - FirebaseInstallations - FirebaseMessaging + - flutter_sound_core - GoogleDataTransport - GoogleUtilities - IosAwnCore @@ -217,6 +223,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + flutter_sound: + :path: ".symlinks/plugins/flutter_sound/ios" flutter_vibrate: :path: ".symlinks/plugins/flutter_vibrate/ios" home_widget: @@ -265,6 +273,8 @@ SPEC CHECKSUMS: flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + flutter_sound: dde9a913063b65a27ba8fdc2039036b99b136c79 + flutter_sound_core: 0c6eb9d5268adc70ff159b3d65fd3d98a82d3a27 flutter_vibrate: 9f4c2ab57008965f78969472367c329dd77eb801 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 65f0b71..3ca83d6 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -59,7 +59,7 @@ NSAppleMusicUsageDescription This app requires access to Apple Music to [explain specific reason]. NSMicrophoneUsageDescription - ... explain why the app uses the microphone here ... + To send voice text this app requires Mic's permission. NSAppTransportSecurity NSAllowsArbitraryLoads @@ -71,9 +71,9 @@ NSAppTransportSecurity - NSAllowsArbitraryLoads - + NSAllowsArbitraryLoads + - + \ No newline at end of file diff --git a/lib/models/ai/ai_chat_args.dart b/lib/models/ai/ai_chat_args.dart index 1445425..194681d 100644 --- a/lib/models/ai/ai_chat_args.dart +++ b/lib/models/ai/ai_chat_args.dart @@ -5,6 +5,7 @@ class AiChatArgs { final BotsModel bot; final ChatsModel? chat; final Prompts? prompts; + final bool? attach; - AiChatArgs({required this.bot, this.chat, this.prompts}); + AiChatArgs({required this.bot, this.chat, this.prompts, this.attach}); } diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index 39ed646..e313283 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -163,7 +163,7 @@ class MediaService { print("Cannot get download folder path $err"); } } - String path = "${dir?.path}$basename"; + String path = "${dir?.path}${Platform.isIOS ? '/' : ''}$basename"; File file = File(path); diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index bfa0ee3..401c964 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -382,7 +382,12 @@ class ActionSheetUtils { ), ), const SizedBox(width: 12), - Text(bot.name.toString()) + Expanded( + child: DidvanText( + bot.name.toString(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + )) ], ), ), diff --git a/lib/views/ai/ai.dart b/lib/views/ai/ai.dart index e27a244..02a85e5 100644 --- a/lib/views/ai/ai.dart +++ b/lib/views/ai/ai.dart @@ -235,7 +235,18 @@ class _AiState extends State { const SizedBox( width: 8, ), - const MessageBarBtn( + MessageBarBtn( + click: () { + Navigator.of(context) + .pushNamed( + Routes.aiChat, + arguments: + AiChatArgs( + bot: + bot, + attach: + true)); + }, enable: false, icon: Icons .attach_file_rounded), diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index d634e40..b1fba3e 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -23,6 +23,7 @@ import 'package:didvan/views/ai/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/ai/widgets/audio_wave.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/marquee_text.dart'; @@ -125,96 +126,160 @@ class _AiChatPageState extends State { ActionSheetUtils(context).openDialog( data: ActionSheetData( hasConfirmButtonClose: false, - onConfirmed: () async { - final state = context.read(); - await state - .changePlaceHolder(placeholder.text); - Future.delayed( - Duration.zero, - () => ActionSheetUtils(context).pop(), - ); - }, + hasConfirmButton: false, + hasDismissButton: false, content: ValueListenableBuilder( valueListenable: state.changingPlaceHolder, - builder: (context, value, child) => Column( - children: [ - Stack( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - DidvanText( - 'شخصی‌سازی دستورات', - style: Theme.of(context) - .textTheme - .titleMedium, - ), - ], - ), - Positioned( - right: 0, - top: 0, - bottom: 0, - child: Center( - child: InkWell( - onTap: () { - ActionSheetUtils(context) - .pop(); - }, - child: const Icon( - DidvanIcons.close_solid, - size: 24, - ), + builder: (context, value, child) => + Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context) + .height / + 3), + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Stack( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .center, + children: [ + DidvanText( + 'شخصی‌سازی دستورات', + style: Theme.of( + context) + .textTheme + .titleMedium, + ), + ], + ), + Positioned( + right: 0, + top: 0, + bottom: 0, + child: Center( + child: InkWell( + onTap: () { + ActionSheetUtils( + context) + .pop(); + }, + child: const Icon( + DidvanIcons + .close_solid, + size: 24, + ), + ), + )), + ], ), - )), - ], - ), - const SizedBox( - height: 12, - ), - const DidvanText( - 'دوست دارید هوشان چه چیزهایی را درباره شما بداند تا بتواند پاسخ‌های بهتری ارائه دهد؟ '), - const SizedBox( - height: 12, - ), - value - ? Center( - child: Image.asset( - Assets.loadingAnimation, - width: 60, - height: 60, + const SizedBox( + height: 12, + ), + const DidvanText( + 'دوست دارید هوشان چه چیزهایی را درباره شما بداند تا بتواند پاسخ‌های بهتری ارائه دهد؟ '), + const SizedBox( + height: 12, + ), + value + ? Center( + child: Image.asset( + Assets + .loadingAnimation, + width: 60, + height: 60, + ), + ) + : TextField( + controller: + placeholder, + style: (Theme.of( + context) + .textTheme + .bodyMedium)! + .copyWith( + fontFamily: DesignConfig + .fontFamily + .padRight( + 3)), + minLines: 5, + maxLines: 5, + keyboardType: + TextInputType + .multiline, + decoration: + InputDecoration( + filled: true, + fillColor: Theme.of( + context) + .colorScheme + .secondCTA, + contentPadding: + const EdgeInsets + .fromLTRB( + 10, + 18, + 10, + 0), + border: const OutlineInputBorder( + borderRadius: + DesignConfig + .lowBorderRadius), + errorStyle: + const TextStyle( + height: + 0.01), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + Expanded( + child: DidvanButton( + onPressed: () { + Navigator.of(context).pop(); + }, + title: 'بازگشت', + style: + ButtonStyleMode.secondary, ), - ) - : TextField( - controller: placeholder, - style: (Theme.of(context) - .textTheme - .bodyMedium)! - .copyWith( - fontFamily: DesignConfig - .fontFamily - .padRight(3)), - minLines: 4, - maxLines: 4, - keyboardType: - TextInputType.multiline, - decoration: InputDecoration( - filled: true, - fillColor: Theme.of(context) - .colorScheme - .secondCTA, - contentPadding: - const EdgeInsets.fromLTRB( - 10, 10, 10, 0), - border: const OutlineInputBorder( - borderRadius: DesignConfig - .lowBorderRadius), - errorStyle: const TextStyle( - height: 0.01), + ), + const SizedBox(width: 20), + Expanded( + child: DidvanButton( + style: + ButtonStyleMode.primary, + onPressed: () async { + final state = context + .read(); + await state + .changePlaceHolder( + placeholder.text); + Future.delayed( + Duration.zero, + () => ActionSheetUtils( + context) + .pop(), + ); + }, + title: 'تایید', ), - ) - ], + ), + ], + ), + ], + ), ), ))); }, @@ -321,6 +386,7 @@ class _AiChatPageState extends State { // : AiMessageBar( bot: widget.args.bot, + attch: widget.args.attach, ), ], )), @@ -394,7 +460,7 @@ class _AiChatPageState extends State { children: [ Container( constraints: BoxConstraints( - maxWidth: MediaQuery.sizeOf(context).width / 1.5), + maxWidth: MediaQuery.sizeOf(context).width / 1.3), decoration: BoxDecoration( borderRadius: DesignConfig.mediumBorderRadius.copyWith( bottomLeft: !message.role.toString().contains('user') @@ -513,24 +579,43 @@ class _AiChatPageState extends State { value: historyAiChatState .bots[index], height: 72, - child: Row( - children: [ - ClipOval( - child: CachedNetworkImage( - imageUrl: + child: Container( + constraints: + const BoxConstraints( + maxWidth: 200), + child: Row( + children: [ + ClipOval( + child: + CachedNetworkImage( + imageUrl: + historyAiChatState + .bots[index] + .image + .toString(), + width: 42, + height: 42, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Directionality( + textDirection: + TextDirection.ltr, + child: DidvanText( historyAiChatState .bots[index] - .image + .name .toString(), - width: 42, - height: 42, + maxLines: 1, + overflow: + TextOverflow + .ellipsis, + ), + ), ), - ), - const SizedBox(width: 12), - DidvanText( - '${historyAiChatState.bots[index].name}X', - ), - ], + ], + ), ), ), ) @@ -542,7 +627,7 @@ class _AiChatPageState extends State { padding: const EdgeInsets.symmetric( horizontal: 8), constraints: const BoxConstraints( - maxWidth: 120), + maxWidth: 100), decoration: BoxDecoration( borderRadius: DesignConfig.lowBorderRadius, @@ -553,11 +638,15 @@ class _AiChatPageState extends State { child: Row( children: [ Expanded( - child: DidvanText( - '${widget.args.bot.name}', - maxLines: 1, - overflow: - TextOverflow.ellipsis, + child: Directionality( + textDirection: + TextDirection.ltr, + child: DidvanText( + '${widget.args.bot.name}', + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), ), ), const Icon( diff --git a/lib/views/ai/widgets/ai_message_bar.dart b/lib/views/ai/widgets/ai_message_bar.dart index 7aaa2ec..112c6b4 100644 --- a/lib/views/ai/widgets/ai_message_bar.dart +++ b/lib/views/ai/widgets/ai_message_bar.dart @@ -1,6 +1,7 @@ // ignore_for_file: library_private_types_in_public_api, avoid_web_libraries_in_flutter import 'dart:async'; +import 'package:record/record.dart'; import 'package:universal_html/html.dart' as html; import 'dart:io'; import 'package:audio_session/audio_session.dart'; @@ -39,7 +40,9 @@ typedef _Fn = void Function(); class AiMessageBar extends StatefulWidget { final BotsModel bot; - const AiMessageBar({Key? key, required this.bot}) : super(key: key); + final bool? attch; + const AiMessageBar({Key? key, required this.bot, this.attch}) + : super(key: key); @override _AiMessageBarState createState() => _AiMessageBarState(); @@ -55,7 +58,7 @@ class _AiMessageBarState extends State { bool _mPlayerIsInited = false; bool _mRecorderIsInited = false; bool _mplaybackReady = false; - bool openAttach = false; + late bool openAttach = widget.attch ?? false; Timer? _timer; final theSource = AudioSource.microphone; @@ -91,9 +94,13 @@ class _AiMessageBarState extends State { Future openTheRecorder() async { if (!kIsWeb) { - var status = await Permission.microphone.request(); + var status = await Permission.microphone.status; + await AudioRecorder().hasPermission(); if (status != PermissionStatus.granted) { - throw RecordingPermissionException('Microphone permission not granted'); + if (!Platform.isIOS) { + throw RecordingPermissionException( + 'Microphone permission not granted'); + } } } await _mRecorder!.openRecorder(); @@ -253,7 +260,7 @@ class _AiMessageBarState extends State { builder: (context, value, child) => Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 8), child: SizedBox( - width: 46, + width: 50, child: Center( child: DidvanText( DateTimeUtils.normalizeTimeDuration(value)), diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index 3932a46..6f3ec85 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -348,7 +348,7 @@ class _ProfilePageState extends State { ), const SizedBox(height: 16), DidvanText( - 'نسخه نرم‌افزار: 3.3.4', + 'نسخه نرم‌افزار: 3.3.5', style: Theme.of(context).textTheme.bodySmall, ), ], diff --git a/pubspec.yaml b/pubspec.yaml index 233402b..b7970d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.3.4+3340 +version: 3.3.5+3350 environment: sdk: ">=2.19.0 <3.0.0"