diff --git a/lib/assets/animations/onlinegiftools-dark.gif b/lib/assets/animations/onlinegiftools-dark.gif new file mode 100644 index 0000000..40d8c3d Binary files /dev/null and b/lib/assets/animations/onlinegiftools-dark.gif differ diff --git a/lib/assets/animations/onlinegiftools-light.gif b/lib/assets/animations/onlinegiftools-light.gif new file mode 100644 index 0000000..85c9133 Binary files /dev/null and b/lib/assets/animations/onlinegiftools-light.gif differ diff --git a/lib/constants/assets.dart b/lib/constants/assets.dart index 214f320..dd6ffb1 100644 --- a/lib/constants/assets.dart +++ b/lib/constants/assets.dart @@ -43,6 +43,8 @@ class Assets { static String loadingAnimation = '$_baseAnimationsPath/loading.gif'; static String bookmarkAnimation = '$_baseAnimationsPath/bookmark.gif'; + static String boxAnimation = + '$_baseAnimationsPath/onlinegiftools-$_themeSuffix.gif'; static String get businessCategoryIcon => '$_baseCategoriesPath/business-$_themeSuffix.svg'; diff --git a/lib/main.dart b/lib/main.dart index 40038ef..5dbcbcc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,9 @@ import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/notification/firebase_api.dart'; import 'package:didvan/services/notification/notification_service.dart'; import 'package:didvan/utils/my_custom_scroll_behavior.dart'; +import 'package:didvan/views/ai/ai_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; +import 'package:didvan/views/ai/tools_state.dart'; import 'package:didvan/views/podcasts/podcasts_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -170,6 +172,12 @@ class _DidvanState extends State with WidgetsBindingObserver { ChangeNotifierProvider( create: (context) => HistoryAiChatState(), ), + ChangeNotifierProvider( + create: (context) => AiState(), + ), + ChangeNotifierProvider( + create: (context) => ToolsState(), + ), ], child: Consumer( builder: (context, themeProvider, child) => Container( diff --git a/lib/models/ai/tools_model.dart b/lib/models/ai/tools_model.dart new file mode 100644 index 0000000..cd12dce --- /dev/null +++ b/lib/models/ai/tools_model.dart @@ -0,0 +1,72 @@ +import 'package:didvan/models/ai/bots_model.dart'; + +class ToolsModel { + List? tools; + + ToolsModel({this.tools}); + + ToolsModel.fromJson(Map json) { + if (json['tools'] != null) { + tools = []; + json['tools'].forEach((v) { + tools!.add(Tools.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + if (tools != null) { + data['tools'] = tools!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Tools { + int? id; + String? name; + String? image; + String? description; + String? guide; + int? order; + List? bots; + + Tools( + {this.id, + this.name, + this.image, + this.description, + this.guide, + this.order, + this.bots}); + + Tools.fromJson(Map json) { + id = json['id']; + name = json['name']; + image = json['image']; + description = json['description']; + guide = json['guide']; + order = json['order']; + if (json['bots'] != null) { + bots = []; + json['bots'].forEach((v) { + bots!.add(BotsModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['image'] = image; + data['description'] = description; + data['guide'] = guide; + data['order'] = order; + if (bots != null) { + data['bots'] = bots!.map((v) => v.toJson()).toList(); + } + return data; + } +} diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 9ce41cc..5c8c9fc 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -205,7 +205,7 @@ class RequestHelper { static String aiArchived() => '$baseUrl/ai/chat${_urlConcatGenerator([ const MapEntry('archived', true), ])}'; - static String aiBots() => '$baseUrl/ai/bot'; + static String aiBots() => '$baseUrl/ai/bot/v2'; static String aiSearchBots(String q) => '$baseUrl/ai/bot${_urlConcatGenerator([ MapEntry('q', q), @@ -229,6 +229,7 @@ class RequestHelper { static String deleteAllChats() => '$baseUrl/ai/chat/all'; static String archivedChat(int id) => '$baseUrl/ai/chat/$id/archive'; static String placeholder(int id) => '$baseUrl/ai/chat/$id/placeholder'; + static String tools() => '$baseUrl/ai/tool'; static String _urlConcatGenerator(List> additions) { String result = ''; diff --git a/lib/views/ai/ai.dart b/lib/views/ai/ai.dart index 778aed6..b13c671 100644 --- a/lib/views/ai/ai.dart +++ b/lib/views/ai/ai.dart @@ -10,7 +10,10 @@ import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/ai/ai_chat_args.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/views/ai/ai_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; +import 'package:didvan/views/ai/tool_screen.dart'; +import 'package:didvan/views/ai/tools_screen.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/home/home.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -44,255 +47,281 @@ class _AiState extends State { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, state, child) { - if (state.bots.isEmpty) { - return Center( - child: Image.asset( - Assets.loadingAnimation, - width: 60, - height: 60, - ), - ); - } - final bot = state.bot!; - return Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only(bottom: 24), - child: Column( - children: [ - const SizedBox( - height: 24, - ), - Icon( - DidvanIcons.ai_solid, - size: MediaQuery.sizeOf(context).width / 5, - color: Theme.of(context).colorScheme.title, - ), - DidvanText( - 'هوشان', - color: Theme.of(context).colorScheme.title, - ), - const SizedBox( - height: 24, - ), - InkWell( - onTap: () => ActionSheetUtils(context) - .botsDialogSelect( - context: context, state: state), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(360), - border: Border.all( - width: 1, - color: - Theme.of(context).colorScheme.text)), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, horizontal: 18), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Column( - children: [ - Transform.rotate( - angle: 180 * pi / 180, - child: Icon( - DidvanIcons.caret_down_solid, - color: Theme.of(context) - .colorScheme - .title, - ), - ), - Icon( - DidvanIcons.caret_down_solid, + return Stack( + children: [ + Consumer( + builder: (BuildContext context, state, Widget? child) { + switch (state.page) { + case 0: + return Consumer( + builder: (context, state, child) { + if (state.bots.isEmpty) { + return Center( + child: Image.asset( + Assets.loadingAnimation, + width: 60, + height: 60, + ), + ); + } + final bot = state.bot!; + return Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Column( + children: [ + const SizedBox( + height: 24, + ), + Icon( + DidvanIcons.ai_solid, + size: MediaQuery.sizeOf(context).width / 5, + color: Theme.of(context).colorScheme.title, + ), + DidvanText( + 'هوشان', + color: Theme.of(context).colorScheme.title, + ), + const SizedBox( + height: 24, + ), + InkWell( + onTap: () => ActionSheetUtils(context) + .botsDialogSelect( + context: context, state: state), + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(360), + border: Border.all( + width: 1, color: Theme.of(context) .colorScheme - .title, + .text)), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 18), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Column( + children: [ + Transform.rotate( + angle: 180 * pi / 180, + child: Icon( + DidvanIcons + .caret_down_solid, + color: Theme.of(context) + .colorScheme + .title, + ), + ), + Icon( + DidvanIcons + .caret_down_solid, + color: Theme.of(context) + .colorScheme + .title, + ), + ], + ), + const SizedBox( + width: 12, + ), + DidvanText(bot.name.toString(), + color: Theme.of(context) + .colorScheme + .title), + ], + ), + const SizedBox( + width: 12, + ), + ClipOval( + child: CachedNetworkImage( + width: 46, + height: 46, + imageUrl: bot.image.toString(), + ), ), ], ), - const SizedBox( - width: 12, - ), - DidvanText(bot.name.toString(), - color: Theme.of(context) - .colorScheme - .title), - ], - ), - const SizedBox( - width: 12, - ), - ClipOval( - child: CachedNetworkImage( - width: 46, - height: 46, - imageUrl: bot.image.toString(), ), ), - ], - ), + ), + const SizedBox( + height: 24, + ), + const Padding( + padding: + EdgeInsets.symmetric(horizontal: 20.0), + child: Text( + "به هوشان؛ هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", + textAlign: TextAlign.center, + ), + ) + ], ), ), ), - const SizedBox( - height: 24, - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: Text( - "به هوشان؛ هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", - textAlign: TextAlign.center, - ), - ) - ], - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 0, 20, 12), - child: InkWell( - onTap: () => - Navigator.of(context).pushNamed(Routes.aiChat, - arguments: AiChatArgs( - bot: bot, - )), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - boxShadow: DesignConfig.defaultShadow, - color: - Theme.of(context).colorScheme.surface, - border: Border.all( - color: Theme.of(context) - .colorScheme - .border), - borderRadius: - DesignConfig.highBorderRadius), - child: Row( + ), + Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 12), + child: InkWell( + onTap: () => + Navigator.of(context).pushNamed(Routes.aiChat, + arguments: AiChatArgs( + bot: bot, + )), + child: Column( + children: [ + Row( children: [ Expanded( - child: Padding( - padding: - const EdgeInsets.symmetric( - horizontal: 8.0, - ), - child: Form( - child: Row( - children: [ - const MessageBarBtn( - enable: true, - icon: DidvanIcons - .mic_regular), - const SizedBox( - width: 8, - ), - Expanded( - child: TextFormField( - textInputAction: - TextInputAction - .newline, - style: Theme.of(context) - .textTheme - .bodyMedium, - minLines: 1, - enabled: false, - decoration: - InputDecoration( - border: - InputBorder.none, - hintText: 'بنویسید...', - hintStyle: Theme.of( - context) - .textTheme - .bodySmall! - .copyWith( - color: Theme.of( - context) - .colorScheme - .disabledText), + child: Container( + decoration: BoxDecoration( + boxShadow: + DesignConfig.defaultShadow, + color: Theme.of(context) + .colorScheme + .surface, + border: Border.all( + color: Theme.of(context) + .colorScheme + .border), + borderRadius: DesignConfig + .highBorderRadius), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets + .symmetric( + horizontal: 8.0, ), - ), - ), - const SizedBox( - width: 8, - ), - MessageBarBtn( - click: () { - Navigator.of(context) - .pushNamed( - Routes.aiChat, - arguments: - AiChatArgs( + child: Form( + child: Row( + children: [ + const MessageBarBtn( + enable: true, + icon: DidvanIcons + .mic_regular), + const SizedBox( + width: 8, + ), + Expanded( + child: + TextFormField( + textInputAction: + TextInputAction + .newline, + style: Theme.of( + context) + .textTheme + .bodyMedium, + minLines: 1, + enabled: false, + decoration: + InputDecoration( + border: + InputBorder + .none, + hintText: + 'بنویسید...', + hintStyle: Theme.of( + context) + .textTheme + .bodySmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .disabledText), + ), + ), + ), + const SizedBox( + width: 8, + ), + MessageBarBtn( + click: () { + Navigator.of(context).pushNamed( + Routes + .aiChat, + arguments: AiChatArgs( bot: bot, attach: true)); - }, - enable: false, - icon: Icons - .attach_file_rounded), - ], - )))) + }, + enable: false, + icon: Icons + .attach_file_rounded), + ], + )))) + ], + ), + ), + ), ], ), - ), - ), - ], - ), - const Padding( - padding: EdgeInsets.fromLTRB(8, 8, 8, 4), - child: DidvanText( - 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.', - fontSize: 12, - ), - ) - ], - )), + const Padding( + padding: EdgeInsets.fromLTRB(8, 8, 8, 4), + child: DidvanText( + 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.', + fontSize: 12, + ), + ) + ], + )), + ), + ], + ); + }, + ); + + case 1: + return const ToolsScreen(); + case 2: + return const ToolScreen(); + + default: + return const SizedBox(); + } + }, + ), + Positioned( + top: 32, + right: 0, + child: InkWell( + onTap: () => homeScaffKey.currentState!.openDrawer(), + child: Container( + width: 46, + height: 46, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12)), + boxShadow: DesignConfig.defaultShadow), + child: Icon( + DidvanIcons.angle_left_light, + color: Theme.of(context).colorScheme.title, ), - ], - ), - Positioned( - top: 32, - right: 0, - child: InkWell( - onTap: () => homeScaffKey.currentState!.openDrawer(), - child: Container( - width: 46, - height: 46, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - bottomLeft: Radius.circular(12)), - boxShadow: DesignConfig.defaultShadow), - child: Icon( - DidvanIcons.angle_left_light, - color: Theme.of(context).colorScheme.title, - ), - )), - ) - ], - ); - }, + )), + ) + ], ); } } diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index e96c618..63e2d89 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -13,7 +13,6 @@ import 'package:didvan/models/ai/chats_model.dart'; import 'package:didvan/models/ai/files_model.dart'; import 'package:didvan/models/ai/messages_model.dart'; import 'package:didvan/models/enums.dart'; -import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/media/media.dart'; @@ -25,9 +24,9 @@ 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/ai/widgets/hoshan_drawer.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/hoshan_app_bar.dart'; import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/video/chat_video_player.dart'; @@ -49,7 +48,9 @@ class AiChatPage extends StatefulWidget { } class _AiChatPageState extends State { + final GlobalKey scaffKey = GlobalKey(); FocusNode focusNode = FocusNode(); + @override void initState() { final state = context.read(); @@ -101,196 +102,13 @@ class _AiChatPageState extends State { }, child: Consumer( builder: (context, state, child) => Scaffold( - appBar: AppBar( - shadowColor: Theme.of(context).colorScheme.border, - title: Text(widget.args.bot.name.toString()), - leading: Row( - children: [ - DidvanIconButton( - icon: DidvanIcons.angle_right_solid, - onPressed: () { - Navigator.of(context).pop(); - if (context.read().refresh) { - context.read().getChats(); - context.read().refresh = false; - } - }, - ), - ], - ), - actions: [ - if (state.chatId != null) - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: InkWell( - onTap: () { - final TextEditingController placeholder = - TextEditingController( - text: state.chat?.placeholder); - ActionSheetUtils(context).openDialog( - data: ActionSheetData( - hasConfirmButtonClose: false, - hasConfirmButton: false, - hasDismissButton: false, - content: ValueListenableBuilder( - valueListenable: state.changingPlaceHolder, - 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, - ), - ) - : 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, - ), - ), - const SizedBox(width: 20), - Expanded( - child: DidvanButton( - style: - ButtonStyleMode.primary, - onPressed: () async { - await state - .changePlaceHolder( - placeholder.text); - Future.delayed( - Duration.zero, - () => ActionSheetUtils( - context) - .pop(), - ); - }, - title: 'تایید', - ), - ), - ], - ), - ], - ), - ), - ))); - }, - child: const Icon(DidvanIcons.note_regular)), - ) - ], - centerTitle: true, - automaticallyImplyLeading: false, + appBar: HoshanAppBar( + onBack: () { + Navigator.pop(context); + }, ), + key: scaffKey, + drawer: const HoshanDrawer(), body: state.loading ? Center( child: Image.asset( @@ -299,85 +117,110 @@ class _AiChatPageState extends State { height: 60, ), ) - : state.messages.isEmpty - ? Column( - crossAxisAlignment: CrossAxisAlignment.center, + : Stack( + children: [ + Column( children: [ - const SizedBox( - height: 12, - ), - Center( - child: Icon( - DidvanIcons.ai_solid, - size: MediaQuery.sizeOf(context).width / 5, - ), - ), - const DidvanText('هوشان'), - const SizedBox( - height: 24, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - DidvanText(widget.args.bot.name.toString()), const SizedBox( - width: 12, + height: 24, ), ClipOval( child: CachedNetworkImage( - width: 46, - height: 46, + width: 75, + height: 75, imageUrl: widget.args.bot.image.toString(), ), ), + const SizedBox( + height: 12, + ), + DidvanText( + widget.args.bot.name.toString(), + fontSize: 17, + fontWeight: FontWeight.bold, + ), + if (state.messages.isEmpty) + Column( + children: [ + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0), + child: Center( + child: DidvanText( + widget.args.bot.description ?? + '')), + ) + ], + ) ], ), - const SizedBox( - height: 24, - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: Text( - "به هوشان؛ هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.", - textAlign: TextAlign.center, - ), - ) - ], - ) - : SingleChildScrollView( - reverse: true, - controller: state.scrollController, - child: ListView.builder( - itemCount: state.messages.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.only( - bottom: state.file != null && - !(state.file!.isRecorded) - ? 180 - : 100), - itemBuilder: (context, mIndex) { - final prompts = state.messages[mIndex].prompts; - final time = state.messages[mIndex].dateTime; - return Column( - children: [ - timeLabel(context, time), - ListView.builder( - itemCount: prompts.length, - shrinkWrap: true, - physics: - const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - final message = prompts[index]; + if (state.messages.isNotEmpty) + SingleChildScrollView( + reverse: true, + controller: state.scrollController, + child: ListView.builder( + itemCount: state.messages.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.only( + bottom: state.file != null && + !(state.file!.isRecorded) + ? 180 + : 100), + itemBuilder: (context, mIndex) { + final prompts = + state.messages[mIndex].prompts; + final time = + state.messages[mIndex].dateTime; + return Column( + children: [ + timeLabel(context, time), + ListView.builder( + itemCount: prompts.length, + shrinkWrap: true, + physics: + const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final message = prompts[index]; - return messageBubble(message, context, - state, index, mIndex); - }, - ), - ], - ); - }), + return messageBubble(message, + context, state, index, mIndex); + }, + ), + ], + ); + }), + ), + ], ), + Positioned( + top: 32, + right: 0, + child: InkWell( + onTap: () => scaffKey.currentState!.openDrawer(), + child: Container( + width: 46, + height: 46, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12)), + boxShadow: DesignConfig.defaultShadow), + child: Icon( + DidvanIcons.angle_left_light, + color: Theme.of(context).colorScheme.title, + ), + )), + ) + ], + ), bottomSheet: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/views/ai/ai_state.dart b/lib/views/ai/ai_state.dart new file mode 100644 index 0000000..8993a5f --- /dev/null +++ b/lib/views/ai/ai_state.dart @@ -0,0 +1,24 @@ +import 'package:didvan/models/ai/tools_model.dart'; +import 'package:didvan/providers/core.dart'; + +class AiState extends CoreProvier { + int page = 0; + Tools? tool; + + void goToAi() { + page = 0; + update(); + } + + void goToTools() { + page = 1; + tool = null; + update(); + } + + void goToToolBox({required final Tools tool}) { + page = 2; + this.tool = tool; + update(); + } +} diff --git a/lib/views/ai/tool_screen.dart b/lib/views/ai/tool_screen.dart new file mode 100644 index 0000000..e41c897 --- /dev/null +++ b/lib/views/ai/tool_screen.dart @@ -0,0 +1,119 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/models/ai/ai_chat_args.dart'; +import 'package:didvan/models/ai/tools_model.dart'; +import 'package:didvan/routes/routes.dart'; +import 'package:didvan/views/ai/ai_state.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class ToolScreen extends StatefulWidget { + const ToolScreen({Key? key}) : super(key: key); + + @override + State createState() => _ToolScreenState(); +} + +class _ToolScreenState extends State { + late Tools tool = context.read().tool!; + + get itemBuilder => null; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + children: [ + const SizedBox(height: 32), + SvgPicture.network( + tool.image!, + width: 64, + height: 64, + ), + const SizedBox( + height: 4, + ), + DidvanText( + tool.name!, + fontSize: 20, + fontWeight: FontWeight.bold, + color: const Color(0xff1B3C59), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: DidvanText( + tool.guide!, + fontSize: 12, + color: const Color(0xff666666), + ), + ), + const SizedBox(height: 24), + GridView.builder( + shrinkWrap: true, + itemCount: tool.bots!.length, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 32), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1 / 1, + crossAxisSpacing: 18, + mainAxisSpacing: 18), + itemBuilder: (context, index) { + final bot = tool.bots![index]; + return InkWell( + onTap: () => Navigator.of(context).pushNamed(Routes.aiChat, + arguments: AiChatArgs( + bot: bot, + )), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all(color: const Color(0xffbbbbbb))), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SkeletonImage( + imageUrl: bot.image!, + width: 72, + height: 72, + borderRadius: BorderRadius.circular(360), + ), + const SizedBox( + height: 4, + ), + DidvanText( + bot.name!, + fontSize: 16, + fontWeight: FontWeight.bold, + color: const Color(0xff2A282F), + ), + if (bot.description != null) + Column( + children: [ + const SizedBox( + height: 4, + ), + DidvanText( + bot.description!, + fontSize: 14, + fontWeight: FontWeight.bold, + color: const Color(0xffA8A6AC), + ), + ], + ) + ], + ), + ), + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/views/ai/tools_screen.dart b/lib/views/ai/tools_screen.dart new file mode 100644 index 0000000..d5d9338 --- /dev/null +++ b/lib/views/ai/tools_screen.dart @@ -0,0 +1,215 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/views/ai/ai_state.dart'; +import 'package:didvan/views/ai/tools_state.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class ToolsScreen extends StatefulWidget { + const ToolsScreen({Key? key}) : super(key: key); + + @override + State createState() => _ToolsScreenState(); +} + +class _ToolsScreenState extends State { + @override + void initState() { + super.initState(); + + context.read().getTools(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + physics: context.watch().loading + ? const NeverScrollableScrollPhysics() + : const BouncingScrollPhysics(), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 46, bottom: 24), + child: DidvanText( + 'انتخاب بات‌ها', + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xff1B3C59), + ), + ), + Consumer( + builder: (BuildContext context, ToolsState state, Widget? child) { + final tools = state.tools; + if (tools != null && tools.isEmpty) { + return EmptyState( + asset: Assets.emptyResult, + title: 'لیست خالی است', + ); + } + if (state.loading) { + return toolsPlaceHolder(); + } + + return ListView.builder( + itemCount: tools!.length, + shrinkWrap: true, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24) + .copyWith(bottom: 46), + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final tool = tools[index]; + return InkWell( + onTap: () => + context.read().goToToolBox(tool: tool), + child: Container( + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all( + color: const Color(0xffB8B8B8), + ), + ), + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + SvgPicture.network( + tool.image!, + width: 75, + height: 75, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + DidvanText( + tool.name!, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(4), + color: Theme.of(context) + .colorScheme + .primary), + child: DidvanText( + '${tool.bots!.length} مدل', + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ) + ], + ), + DidvanText( + '${tool.bots!.map( + (e) => e.name!, + )}' + .replaceAll('(', '') + .replaceAll(')', ''), + fontSize: 12, + color: Theme.of(context).colorScheme.primary, + ), + DidvanText(tool.description!, + fontSize: 12, color: const Color(0xffA8A6AC)) + ], + ), + ) + ], + ), + ), + ); + }, + ); + }, + ) + ], + ), + ); + } + + ListView toolsPlaceHolder() { + return ListView.builder( + itemCount: 10, + shrinkWrap: true, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24), + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Container( + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all( + color: const Color(0xffB8B8B8), + ), + ), + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + ShimmerPlaceholder( + width: 75, + height: 75, + borderRadius: BorderRadius.circular(360), + ), + const SizedBox( + width: 8, + ), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ShimmerPlaceholder( + width: 120, + height: 24, + borderRadius: DesignConfig.mediumBorderRadius, + ), + ShimmerPlaceholder( + width: 60, + height: 24, + borderRadius: DesignConfig.mediumBorderRadius, + ), + ], + ), + SizedBox( + height: 8, + ), + ShimmerPlaceholder( + width: 120, + height: 24, + borderRadius: DesignConfig.mediumBorderRadius, + ), + SizedBox( + height: 8, + ), + ShimmerPlaceholder( + width: 240, + height: 46, + borderRadius: DesignConfig.mediumBorderRadius, + ), + ], + ), + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/views/ai/tools_state.dart b/lib/views/ai/tools_state.dart new file mode 100644 index 0000000..33b6a63 --- /dev/null +++ b/lib/views/ai/tools_state.dart @@ -0,0 +1,29 @@ +import 'package:didvan/models/ai/tools_model.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/providers/core.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; + +class ToolsState extends CoreProvier { + bool loading = true; + List? tools; + + void getTools() async { + final service = RequestService( + RequestHelper.tools(), + ); + await service.httpGet(); + if (service.isSuccess) { + final ToolsModel toolsModel = ToolsModel.fromJson(service.result); + tools = toolsModel.tools!; + tools ??= []; + appState = AppState.idle; + loading = false; + update(); + return; + } + appState = AppState.failed; + loading = false; + update(); + } +} diff --git a/lib/views/ai/widgets/hoshan_drawer.dart b/lib/views/ai/widgets/hoshan_drawer.dart new file mode 100644 index 0000000..51c6588 --- /dev/null +++ b/lib/views/ai/widgets/hoshan_drawer.dart @@ -0,0 +1,475 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/main.dart'; +import 'package:didvan/models/ai/ai_chat_args.dart'; +import 'package:didvan/models/ai/chats_model.dart'; +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; +import 'package:didvan/models/view/alert_data.dart'; +import 'package:didvan/routes/routes.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/views/ai/history_ai_chat_state.dart'; +import 'package:didvan/views/home/home.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; + +class HoshanDrawer extends StatefulWidget { + const HoshanDrawer({Key? key}) : super(key: key); + + @override + State createState() => _HoshanDrawerState(); +} + +class _HoshanDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + child: Consumer( + builder: (context, state, child) { + return Column( + children: [ + const SizedBox( + height: 8, + ), + Padding( + padding: const EdgeInsets.only(left: 20.0, top: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () => homeScaffKey.currentState!.closeDrawer(), + child: const Icon( + DidvanIcons.close_regular, + ), + ) + ], + ), + ), + Icon( + DidvanIcons.ai_solid, + size: MediaQuery.sizeOf(context).width / 5, + color: Theme.of(context).colorScheme.title, + ), + DidvanText( + 'هوشان', + color: Theme.of(context).colorScheme.title, + ), + const SizedBox( + height: 24, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + children: [ + drawerBtn( + icon: Icons.handshake_rounded, + text: 'ساخت دستیار شخصی', + crossAxisAlignment: CrossAxisAlignment.start, + enable: false), + const DidvanDivider(), + drawerBtn( + icon: CupertinoIcons.doc_text_search, + text: 'جستجو در مدل‌ها', + click: () { + ActionSheetUtils(context).botsDialogSelect( + context: context, state: state); + homeScaffKey.currentState!.closeDrawer(); + }, + enable: false), + const DidvanDivider(), + drawerBtn( + icon: DidvanIcons.chats_regular, + text: 'تاریخچه همه گفتگوها', + label: 'حذف همه', + click: () { + Navigator.of(context) + .pushNamed(Routes.aiHistory); + }, + labelClick: state.chats.isEmpty + ? null + : () async { + await ActionSheetUtils(context) + .openDialog( + data: ActionSheetData( + onConfirmed: () async { + await state.deleteAllChat( + refresh: false); + }, + content: Column( + children: [ + Row( + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + Icon( + DidvanIcons + .trash_solid, + color: Theme.of( + context) + .colorScheme + .error, + ), + const SizedBox( + width: 8, + ), + DidvanText( + 'پاک کردن همه گفت‌وگوها', + color: Theme.of( + context) + .colorScheme + .error, + fontSize: 20, + ), + ], + ), + const SizedBox( + height: 12, + ), + const DidvanText( + 'آیا از پاک کردن تمامی گفت‌وگوهای انجام شده با هوشان اطمینان دارید؟'), + ], + ))); + }, + ), + const SizedBox( + height: 12, + ), + // SearchField( + // title: 'title', + // onChanged: (value) {}, + // focusNode: FocusNode()), + // SizedBox( + // height: 12, + // ), + Expanded( + child: state.loadingdeleteAll || + state.appState == AppState.busy + ? ListView.builder( + shrinkWrap: true, + itemCount: 10, + padding: 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, + ), + ], + ), + ); + }, + ) + : 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, + itemCount: state.chats.length, + padding: const EdgeInsets.symmetric( + horizontal: 12), + physics: + const BouncingScrollPhysics(), + itemBuilder: (context, index) { + final chat = state.chats[index]; + TextEditingController title = + TextEditingController( + text: chat.title); + + return chatRow( + chat, title, state, index); + }, + ), + ), + // SizedBox( + // height: 12, + // ), + // Text('نمایش قدیمی‌ترها') + ], + ), + ), + Column( + children: [ + const DidvanDivider(), + drawerBtn( + icon: Icons.folder_copy_outlined, + text: 'گفت‌وگوهای آرشیو شده', + click: () { + Navigator.of(context) + .pushNamed(Routes.aiHistory, arguments: true); + }, + ), + const SizedBox( + height: 12, + ), + drawerBtn( + icon: DidvanIcons.support_regular, + text: 'پیام به پشتیبانی', + click: () { + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': 'پشتیبانی اپلیکیشن'}, + ); + }, + ), + ], + ) + ], + ), + ), + ), + const SizedBox( + height: 32, + ) + ], + ); + }, + ), + ); + } + + Widget drawerBtn( + {final CrossAxisAlignment? crossAxisAlignment, + required final IconData icon, + required final String text, + final bool enable = true, + final String? label, + final Function()? labelClick, + final Function()? click}) { + return InkWell( + onTap: enable + ? click + : () { + ActionSheetUtils(context).showAlert( + AlertData(message: 'درحال توسعه', aLertType: ALertType.info)); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: + crossAxisAlignment ?? CrossAxisAlignment.center, + children: [ + Icon( + icon, + color: enable + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.disabledText, + ), + const SizedBox( + width: 8, + ), + Column( + children: [ + DidvanText(text, + fontSize: 16, + color: enable + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.disabledText), + // if (!enable) Text('در حال توسعه ...') + ], + ) + ], + ), + if (label != null) + InkWell( + onTap: labelClick, + child: DidvanText( + label, + color: Theme.of(context).colorScheme.primary, + fontSize: 12, + ), + ) + ], + ), + ), + ); + } + + Padding chatRow(ChatsModel chat, TextEditingController title, + HistoryAiChatState state, int index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: InkWell( + onTap: () { + navigatorKey.currentState!.pushNamed(Routes.aiChat, + arguments: AiChatArgs(bot: chat.bot!, chat: chat)); + }, + child: Row( + children: [ + ClipOval( + child: CachedNetworkImage( + imageUrl: chat.bot!.image.toString(), + width: 24, + height: 24, + )), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + chat.title.toString(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox( + width: 24, + ), + Row( + children: [ + 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: 4, + ), + PopupMenuButton( + onSelected: (value) async { + switch (value) { + case 'حذف پیام': + await state.deleteChat(chat.id!, index, refresh: false); + break; + + case 'آرشیو': + await state.archivedChat(chat.id!, index, + refresh: false); + break; + default: + } + + state.update(); + }, + itemBuilder: (BuildContext context) { + return [ + ActionSheetUtils.popUpBtns( + value: 'حذف پیام', + icon: DidvanIcons.trash_regular, + color: Theme.of(context).colorScheme.error, + height: 32, + size: 16), + ActionSheetUtils.popUpBtns( + value: 'آرشیو', + icon: Icons.folder_copy, + height: 32, + size: 16, + ), + ]; + }, + offset: const Offset(0, 0), + position: PopupMenuPosition.under, + useRootNavigator: true, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.more_vert, + size: 20, + ), + ), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart index 53c7212..e0095dd 100644 --- a/lib/views/home/home.dart +++ b/lib/views/home/home.dart @@ -1,40 +1,33 @@ // ignore_for_file: deprecated_member_use -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'; import 'package:didvan/main.dart'; -import 'package:didvan/models/ai/ai_chat_args.dart'; -import 'package:didvan/models/ai/chats_model.dart'; -import 'package:didvan/models/enums.dart'; import 'package:didvan/models/notification_message.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/providers/theme.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/notification/notification_service.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/ai/ai.dart'; +import 'package:didvan/views/ai/ai_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart'; +import 'package:didvan/views/ai/widgets/hoshan_drawer.dart'; import 'package:didvan/views/home/categories/categories_page.dart'; import 'package:didvan/views/home/main/main_page.dart'; import 'package:didvan/views/home/home_state.dart'; import 'package:didvan/views/home/new_statistic/new_statistic.dart'; import 'package:didvan/views/home/search/search.dart'; -import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/hoshan_app_bar.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/logo_app_bar.dart'; import 'package:didvan/views/widgets/didvan/bnb.dart'; -import 'package:didvan/views/widgets/shimmer_placeholder.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import '../../services/app_home_widget/home_widget_repository.dart'; @@ -172,290 +165,32 @@ class _HomeState extends State super.initState(); } + PreferredSizeWidget getAppBar() { + PreferredSizeWidget result = const LogoAppBar(); + if (context.watch().tabController.index == 2) { + result = HoshanAppBar( + onBack: () { + final state = context.read(); + if (state.page == 1) { + state.goToAi(); + } else { + state.goToTools(); + } + }, + ); + } + + return result; + } + @override Widget build(BuildContext context) { return Scaffold( key: homeScaffKey, - appBar: LogoAppBar( - canSearch: context.watch().tabController.index != 2, - ), + appBar: getAppBar(), resizeToAvoidBottomInset: false, drawer: context.watch().tabController.index == 2 - ? Drawer( - child: Consumer( - builder: (context, state, child) { - return Column( - children: [ - const SizedBox( - height: 8, - ), - Padding( - padding: const EdgeInsets.only(left: 20.0, top: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - InkWell( - onTap: () => - homeScaffKey.currentState!.closeDrawer(), - child: const Icon( - DidvanIcons.close_regular, - ), - ) - ], - ), - ), - Icon( - DidvanIcons.ai_solid, - size: MediaQuery.sizeOf(context).width / 5, - color: Theme.of(context).colorScheme.title, - ), - DidvanText( - 'هوشان', - color: Theme.of(context).colorScheme.title, - ), - const SizedBox( - height: 24, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - children: [ - drawerBtn( - icon: Icons.handshake_rounded, - text: 'ساخت دستیار شخصی', - crossAxisAlignment: - CrossAxisAlignment.start, - enable: false), - const DidvanDivider(), - drawerBtn( - icon: CupertinoIcons.doc_text_search, - text: 'جستجو در مدل‌ها', - click: () { - ActionSheetUtils(context) - .botsDialogSelect( - context: context, - state: state); - homeScaffKey.currentState! - .closeDrawer(); - }, - enable: false), - const DidvanDivider(), - drawerBtn( - icon: DidvanIcons.chats_regular, - text: 'تاریخچه همه گفتگوها', - label: 'حذف همه', - click: () { - Navigator.of(context) - .pushNamed(Routes.aiHistory); - }, - labelClick: state.chats.isEmpty - ? null - : () async { - await ActionSheetUtils(context) - .openDialog( - data: ActionSheetData( - onConfirmed: - () async { - await state - .deleteAllChat( - refresh: - false); - }, - content: Column( - children: [ - Row( - crossAxisAlignment: - CrossAxisAlignment - .center, - children: [ - Icon( - DidvanIcons - .trash_solid, - color: Theme.of( - context) - .colorScheme - .error, - ), - const SizedBox( - width: 8, - ), - DidvanText( - 'پاک کردن همه گفت‌وگوها', - color: Theme.of( - context) - .colorScheme - .error, - fontSize: - 20, - ), - ], - ), - const SizedBox( - height: 12, - ), - const DidvanText( - 'آیا از پاک کردن تمامی گفت‌وگوهای انجام شده با هوشان اطمینان دارید؟'), - ], - ))); - }, - ), - const SizedBox( - height: 12, - ), - // SearchField( - // title: 'title', - // onChanged: (value) {}, - // focusNode: FocusNode()), - // SizedBox( - // height: 12, - // ), - Expanded( - child: state.loadingdeleteAll || - state.appState == AppState.busy - ? ListView.builder( - shrinkWrap: true, - itemCount: 10, - padding: - 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, - ), - ], - ), - ); - }, - ) - : 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, - itemCount: state.chats.length, - padding: const EdgeInsets - .symmetric( - horizontal: 12), - physics: - const BouncingScrollPhysics(), - itemBuilder: - (context, index) { - final chat = - state.chats[index]; - TextEditingController - title = - TextEditingController( - text: chat.title); - - return chatRow(chat, title, - state, index); - }, - ), - ), - // SizedBox( - // height: 12, - // ), - // Text('نمایش قدیمی‌ترها') - ], - ), - ), - Column( - children: [ - const DidvanDivider(), - drawerBtn( - icon: Icons.folder_copy_outlined, - text: 'گفت‌وگوهای آرشیو شده', - click: () { - Navigator.of(context).pushNamed( - Routes.aiHistory, - arguments: true); - }, - ), - const SizedBox( - height: 12, - ), - drawerBtn( - icon: DidvanIcons.support_regular, - text: 'پیام به پشتیبانی', - click: () { - Navigator.of(context).pushNamed( - Routes.direct, - arguments: { - 'type': 'پشتیبانی اپلیکیشن' - }, - ); - }, - ), - ], - ) - ], - ), - ), - ), - const SizedBox( - height: 32, - ) - ], - ); - }, - ), - ) + ? const HoshanDrawer() : null, body: WillPopScope( onWillPop: () async { @@ -519,193 +254,4 @@ class _HomeState extends State ), ); } - - Padding chatRow(ChatsModel chat, TextEditingController title, - HistoryAiChatState state, int index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: InkWell( - onTap: () { - navigatorKey.currentState!.pushNamed(Routes.aiChat, - arguments: AiChatArgs(bot: chat.bot!, chat: chat)); - }, - child: Row( - children: [ - ClipOval( - child: CachedNetworkImage( - imageUrl: chat.bot!.image.toString(), - width: 24, - height: 24, - )), - const SizedBox( - width: 12, - ), - Expanded( - child: Text( - chat.title.toString(), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const SizedBox( - width: 24, - ), - Row( - children: [ - 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: 4, - ), - PopupMenuButton( - onSelected: (value) async { - switch (value) { - case 'حذف پیام': - await state.deleteChat(chat.id!, index, refresh: false); - break; - - case 'آرشیو': - await state.archivedChat(chat.id!, index, - refresh: false); - break; - default: - } - - state.update(); - }, - itemBuilder: (BuildContext context) { - return [ - ActionSheetUtils.popUpBtns( - value: 'حذف پیام', - icon: DidvanIcons.trash_regular, - color: Theme.of(context).colorScheme.error, - height: 32, - size: 16), - ActionSheetUtils.popUpBtns( - value: 'آرشیو', - icon: Icons.folder_copy, - height: 32, - size: 16, - ), - ]; - }, - offset: const Offset(0, 0), - position: PopupMenuPosition.under, - useRootNavigator: true, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon( - Icons.more_vert, - size: 20, - ), - ), - ), - ], - ) - ], - ), - ), - ); - } - - Widget drawerBtn( - {final CrossAxisAlignment? crossAxisAlignment, - required final IconData icon, - required final String text, - final bool enable = true, - final String? label, - final Function()? labelClick, - final Function()? click}) { - return InkWell( - onTap: enable - ? click - : () { - ActionSheetUtils(context).showAlert( - AlertData(message: 'درحال توسعه', aLertType: ALertType.info)); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: - crossAxisAlignment ?? CrossAxisAlignment.center, - children: [ - Icon( - icon, - color: enable - ? Theme.of(context).colorScheme.title - : Theme.of(context).colorScheme.disabledText, - ), - const SizedBox( - width: 8, - ), - Column( - children: [ - DidvanText(text, - fontSize: 16, - color: enable - ? Theme.of(context).colorScheme.title - : Theme.of(context).colorScheme.disabledText), - // if (!enable) Text('در حال توسعه ...') - ], - ) - ], - ), - if (label != null) - InkWell( - onTap: labelClick, - child: DidvanText( - label, - color: Theme.of(context).colorScheme.primary, - fontSize: 12, - ), - ) - ], - ), - ), - ); - } } diff --git a/lib/views/widgets/hoshan_app_bar.dart b/lib/views/widgets/hoshan_app_bar.dart new file mode 100644 index 0000000..e68e017 --- /dev/null +++ b/lib/views/widgets/hoshan_app_bar.dart @@ -0,0 +1,90 @@ +import 'dart:math'; + +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/constants/assets.dart'; +import 'package:didvan/views/ai/ai_state.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class HoshanAppBar extends StatelessWidget implements PreferredSizeWidget { + final Function()? onBack; + const HoshanAppBar({Key? key, this.onBack}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), + color: Theme.of(context).colorScheme.surface, + boxShadow: [ + BoxShadow( + color: const Color(0XFF1B3C59).withOpacity(0.15), + blurRadius: 8, + spreadRadius: 0, + offset: const Offset(0, 8), + ) + ], + ), + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Row( + children: [ + Icon( + DidvanIcons.ai_solid, + size: 40, + ), + DidvanText( + 'هوشان', + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ], + ), + Row( + children: [ + DidvanIconButton( + icon: DidvanIcons.info_circle_light, + size: 32, + onPressed: () {}), + DidvanIconButton( + icon: DidvanIcons.antenna_light, + size: 32, + onPressed: () {}, + ), + context.watch().page != 0 + ? Transform.rotate( + angle: 180 * pi / 180, + child: DidvanIconButton( + icon: DidvanIcons.back_light, + size: 32, + onPressed: () => onBack?.call(), + ), + ) + : InkWell( + onTap: () { + context.read().goToTools(); + }, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Image.asset( + Assets.boxAnimation, + width: 38, + height: 38, + ), + ), + ), + ], + ) + ], + )); + } + + @override + Size get preferredSize => const Size(double.infinity, 144); +}