From 4a8c14ad09d8c93578d9ed637b0a3a795a02bb6f Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Mon, 18 Nov 2024 15:43:32 +0330 Subject: [PATCH] "Modified code in multiple files, including AI chat models, info page, Hoshan drawer, and studio details, with changes to widgets, layouts, and controllers." --- lib/models/ai/chats_model.dart | 4 + lib/views/ai/ai_chat_state.dart | 5 +- lib/views/ai/bot_assistants_page.dart | 34 +- lib/views/ai/create_bot_assistants_page.dart | 214 ++++++----- lib/views/ai/create_bot_assistants_state.dart | 8 +- lib/views/ai/info_page.dart | 361 +++++++++--------- lib/views/ai/widgets/hoshan_drawer.dart | 16 - .../studio_details/studio_details.mobile.dart | 21 +- lib/views/widgets/video/primary_controls.dart | 354 +++++++++++++++++ 9 files changed, 707 insertions(+), 310 deletions(-) create mode 100644 lib/views/widgets/video/primary_controls.dart diff --git a/lib/models/ai/chats_model.dart b/lib/models/ai/chats_model.dart index 707e778..2ac87d8 100644 --- a/lib/models/ai/chats_model.dart +++ b/lib/models/ai/chats_model.dart @@ -10,6 +10,7 @@ class ChatsModel { String? placeholder; String? createdAt; String? updatedAt; + String? responseType; BotsModel? bot; BotAssistants? userBot; List? prompts; @@ -28,6 +29,7 @@ class ChatsModel { this.prompts, this.isEditing = false, this.assistantsName, + this.responseType, this.userBot}); ChatsModel.fromJson(Map json) { @@ -38,6 +40,7 @@ class ChatsModel { placeholder = json['placeholder']; createdAt = json['createdAt']; updatedAt = json['updatedAt']; + responseType = json['responseType']; userBot = json['userBot'] != null ? BotAssistants.fromJson(json['userBot']) : null; @@ -67,6 +70,7 @@ class ChatsModel { data['placeholder'] = placeholder; data['createdAt'] = createdAt; data['updatedAt'] = updatedAt; + data['responseType'] = responseType; if (bot != null) { data['bot'] = bot!.toJson(); } diff --git a/lib/views/ai/ai_chat_state.dart b/lib/views/ai/ai_chat_state.dart index 3b02358..a131527 100644 --- a/lib/views/ai/ai_chat_state.dart +++ b/lib/views/ai/ai_chat_state.dart @@ -157,8 +157,9 @@ class AiChatState extends CoreProvier { // } final req = await AiApiService.initial( - url: '${isAssistants ? '/user' : ''}/${bot.id}/${bot.name}' - .toLowerCase(), + url: + '${isAssistants ? '/user/${bot.responseType}' : ''}/${bot.id}/${bot.name}' + .toLowerCase(), message: message, chatId: chatId, file: uploadedFile, diff --git a/lib/views/ai/bot_assistants_page.dart b/lib/views/ai/bot_assistants_page.dart index 2608d3a..680dc2a 100644 --- a/lib/views/ai/bot_assistants_page.dart +++ b/lib/views/ai/bot_assistants_page.dart @@ -51,23 +51,30 @@ class _BotAssistantsPageState extends State { (BuildContext context, BotAssistantsState state, Widget? child) => CustomScrollView( slivers: [ - SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.only(top: 32, bottom: 24), - child: DidvanText( - 'انتخاب بات‌ها', - fontSize: 20, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.checkFav, - ), - ), + // SliverToBoxAdapter( + // child: Center( + // child: Padding( + // padding: const EdgeInsets.only(top: 32, bottom: 24), + // child: DidvanText( + // 'انتخاب بات‌ها', + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: Theme.of(context).colorScheme.checkFav, + // ), + // ), + // ), + // ), + const SliverToBoxAdapter( + child: SizedBox( + height: 32, ), ), if (state.appState != AppState.failed) SliverToBoxAdapter( child: Padding( - padding: const EdgeInsets.only(bottom: 12.0), + padding: const EdgeInsets.only( + bottom: 12.0, + ), child: switchAssistants(context, state), ), ), @@ -273,6 +280,7 @@ class _BotAssistantsPageState extends State { arguments: assistants.id); }, child: const Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ Icon( DidvanIcons.user_edit_light, @@ -348,6 +356,7 @@ class _BotAssistantsPageState extends State { Expanded( child: InkWell( onTap: () { + if (state.appState == AppState.busy) return; state.isMyAssistants = true; state.getMyAssissmant(); state.update(); @@ -390,6 +399,7 @@ class _BotAssistantsPageState extends State { Expanded( child: InkWell( onTap: () { + if (state.appState == AppState.busy) return; state.isMyAssistants = false; state.getGlobalAssissmant(); state.update(); diff --git a/lib/views/ai/create_bot_assistants_page.dart b/lib/views/ai/create_bot_assistants_page.dart index ed23803..7ff6264 100644 --- a/lib/views/ai/create_bot_assistants_page.dart +++ b/lib/views/ai/create_bot_assistants_page.dart @@ -29,8 +29,10 @@ import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; @@ -59,8 +61,6 @@ class _CreateBotAssistantsPageState extends State { @override void initState() { super.initState(); - - context.read().getImageToolsBots(); } void onConfirm(CreateBotAssistantsState state, int selectedBotId) async { @@ -137,7 +137,7 @@ class _CreateBotAssistantsPageState extends State { CreateBotAssistantsState state, Widget? child) { int selectedBotId = state.selectedBotType == 'text' ? state.allBots.first.id! - : context.read().imageBots.first.id!; + : state.imageBots.first.id!; return state.loading ? Center( child: SpinKitThreeBounce( @@ -159,7 +159,9 @@ class _CreateBotAssistantsPageState extends State { closedHeaderPadding: const EdgeInsets.all(12), items: state.botModels, enabled: state.assistant == null, - initialItem: state.botModels[0], + initialItem: state.selectedBotType == 'text' + ? state.botModels.first + : state.botModels.last, hideSelectedFieldWhenExpanded: false, decoration: CustomDropdownDecoration( listItemDecoration: ListItemDecoration( @@ -170,7 +172,7 @@ class _CreateBotAssistantsPageState extends State { ), closedBorder: Border.all(color: Colors.grey), closedFillColor: - Colors.grey.shade100.withOpacity(0.1), + Theme.of(context).colorScheme.surface, expandedFillColor: Theme.of(context).colorScheme.surface), // hintText: "انتخاب کنید", @@ -178,6 +180,7 @@ class _CreateBotAssistantsPageState extends State { final index = state.botModels.indexOf(value!); state.selectedBotType = index == 0 ? 'text' : 'image'; + state.update(); }, ), ), @@ -211,9 +214,27 @@ class _CreateBotAssistantsPageState extends State { state.image.value = null; return; } - state.image.value = + final pickedFile = await MediaService.pickImage( source: ImageSource.gallery); + File? file; + if (pickedFile != null && !kIsWeb) { + file = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + androidUiSettings: + const AndroidUiSettings( + toolbarTitle: 'برش تصویر'), + iosUiSettings: const IOSUiSettings( + title: 'برش تصویر', + doneButtonTitle: 'تایید', + cancelButtonTitle: 'بازگشت', + ), + compressQuality: 30, + ); + if (file != null) { + state.image.value = XFile(file.path); + } + } }, ), img != null @@ -314,8 +335,8 @@ class _CreateBotAssistantsPageState extends State { key: _formDescKey, child: DidvanTextField( initialValue: state.desc, - // hintText: - // 'به ربات خود بگویید که چگونه رفتار کند و چگونه به پیام‌های کاربر پاسخ دهد. سعی کنید تا حد امکان واضح و مشخص باشید.', + hintText: + 'توضیح دهید چه کارهایی از این ربات بر می‌آید.', textInputType: TextInputType.multiline, minLine: 4, maxLine: 4, @@ -374,8 +395,6 @@ class _CreateBotAssistantsPageState extends State { ), closedBorder: Border.all(color: Colors.grey), - closedFillColor: - Colors.grey.shade100.withOpacity(0.1), expandedFillColor: Theme.of(context) .colorScheme .surface), @@ -394,7 +413,7 @@ class _CreateBotAssistantsPageState extends State { child: DidvanTextField( initialValue: state.prompt, hintText: - 'به ربات خود بگویید که چگونه رفتار کند و چگونه به پیام‌های کاربر پاسخ دهد. سعی کنید تا حد امکان واضح و مشخص باشید.', + 'به ربات خود بگویید که چگونه رفتار کند و چگونه به پیام‌های کاربر پاسخ دهد. سعی کنید تا حد امکان پیام واضح و مشخص باشد.', textInputType: TextInputType.multiline, minLine: 6, maxLine: 6, @@ -618,6 +637,96 @@ class _CreateBotAssistantsPageState extends State { const SizedBox( height: 24, ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + title( + text: 'لینک وب سایت', + isRequired: false), + Row( + children: [ + if (state.countOfLink.length > 1) + DidvanIconButton( + icon: CupertinoIcons + .minus_circle_fill, + onPressed: () { + state.countOfLink + .removeLast(); + state.update(); + }, + ), + if (state.countOfLink.length != 3) + DidvanIconButton( + icon: CupertinoIcons + .plus_circle_fill, + onPressed: () { + if (state.countOfLink.last + .isNotEmpty) { + state.countOfLink.add(''); + state.update(); + } + }, + ), + ], + ) + ], + ), + ListView.builder( + shrinkWrap: true, + itemCount: state.countOfLink.length, + physics: + const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => Column( + children: [ + DidvanTextField( + onChanged: (value) { + state.countOfLink[index] = value; + if (state.countOfLink[index] + .isEmpty && + state.countOfLink.length != + 1) { + state.countOfLink + .removeAt(index); + } + state.update(); + }, + // validator: (value) {}, + hintText: + 'https://www.weforum.org/agenda/2024/08', + ), + const SizedBox( + height: 8, + ), + ], + ), + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Icon( + DidvanIcons.info_circle_light, + color: Theme.of(context) + .colorScheme + .caption, + ), + const SizedBox(width: 4), + Expanded( + child: DidvanText( + 'دستیار شما با استناد بر اطلاعات ارائه شده در پایگاه دانش، پیام کاربران را ارزیابی می‌کند.', + textAlign: TextAlign.right, + fontSize: 12, + color: Theme.of(context) + .colorScheme + .caption, + ), + ), + ], + ), + const SizedBox( + height: 24, + ), ], ), // title(text: 'لینک یوتیوب', isRequired: false), @@ -638,86 +747,6 @@ class _CreateBotAssistantsPageState extends State { // const SizedBox( // height: 24, // ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - title( - text: 'لینک وب سایت', isRequired: false), - Row( - children: [ - if (state.countOfLink.length > 1) - DidvanIconButton( - icon: - CupertinoIcons.minus_circle_fill, - onPressed: () { - state.countOfLink.removeLast(); - state.update(); - }, - ), - if (state.countOfLink.length != 3) - DidvanIconButton( - icon: CupertinoIcons.plus_circle_fill, - onPressed: () { - if (state - .countOfLink.last.isNotEmpty) { - state.countOfLink.add(''); - state.update(); - } - }, - ), - ], - ) - ], - ), - ListView.builder( - shrinkWrap: true, - itemCount: state.countOfLink.length, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => Column( - children: [ - DidvanTextField( - onChanged: (value) { - state.countOfLink[index] = value; - if (state.countOfLink[index].isEmpty && - state.countOfLink.length != 1) { - state.countOfLink.removeAt(index); - } - state.update(); - }, - // validator: (value) {}, - hintText: - 'https://www.weforum.org/agenda/2024/08', - ), - const SizedBox( - height: 8, - ), - ], - ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - DidvanIcons.info_circle_light, - color: - Theme.of(context).colorScheme.caption, - ), - const SizedBox(width: 4), - Expanded( - child: DidvanText( - 'دستیار شما با استناد بر اطلاعات ارائه شده در پایگاه دانش، پیام کاربران را ارزیابی می‌کند.', - textAlign: TextAlign.right, - fontSize: 12, - color: - Theme.of(context).colorScheme.caption, - ), - ), - ], - ), - const SizedBox( - height: 24, - ), ], ), Row( @@ -870,6 +899,9 @@ class _CreateBotAssistantsPageState extends State { Container botRow(BotsModel bot, BuildContext context) { return Container( alignment: Alignment.center, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: DesignConfig.highBorderRadius), child: Row( children: [ SkeletonImage( diff --git a/lib/views/ai/create_bot_assistants_state.dart b/lib/views/ai/create_bot_assistants_state.dart index c9d1328..6c9508a 100644 --- a/lib/views/ai/create_bot_assistants_state.dart +++ b/lib/views/ai/create_bot_assistants_state.dart @@ -37,7 +37,7 @@ class CreateBotAssistantsState extends CoreProvier { String selectedBotType = 'text'; bool isPrivate = true; - void getImageToolsBots() async { + Future getImageToolsBots() async { loadingImageBots = true; final service = RequestService( @@ -91,7 +91,7 @@ class CreateBotAssistantsState extends CoreProvier { Future getAnAssistant({required final int id}) async { loading = true; update(); - + await getImageToolsBots(); final service = RequestService( RequestHelper.getAssistant(id), ); @@ -113,15 +113,15 @@ class CreateBotAssistantsState extends CoreProvier { } } countOfLink = assistant!.websites ?? ['']; - selectedBotType = assistant!.type ?? 'text'; - final list = selectedBotType == 'text' ? allBots : imageBots; + final list = [...allBots, ...imageBots]; for (var bot in list) { if (bot.id == assistant!.botId) { initialBot = bot; break; } } + selectedBotType = initialBot?.responseType ?? 'text'; } appState = AppState.idle; loading = false; diff --git a/lib/views/ai/info_page.dart b/lib/views/ai/info_page.dart index 057388a..fe1605c 100644 --- a/lib/views/ai/info_page.dart +++ b/lib/views/ai/info_page.dart @@ -23,205 +23,214 @@ class _InfoPageState extends State { withActions: false, onBack: () => Navigator.pop(context), ), - body: SingleChildScrollView( - child: Column( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.only(top: 32, bottom: 24), - child: DidvanText( - 'آموزش پرامپت نویسی اصولی', - fontSize: 20, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.checkFav, - ), - ), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: ClipRRect( - borderRadius: DesignConfig.lowBorderRadius, - child: ChatVideoPlayer( - src: - 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', - showOptions: true, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(20.0), + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( - children: [ - DidvanText( - 'آنچه در این ویدیو خواهید دید:', - fontSize: 16, + Center( + child: Padding( + padding: const EdgeInsets.only(top: 32, bottom: 24), + child: DidvanText( + 'آموزش پرامپت نویسی اصولی', + fontSize: 20, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.checkFav, ), - ], + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: ClipRRect( + borderRadius: DesignConfig.lowBorderRadius, + child: ChatVideoPlayer( + src: + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + showOptions: true, + ), + ), ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(20.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( + const Row( children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), - ), - ), - const DidvanText( - 'انتخاب کلمات کلیدی مناسب', + DidvanText( + 'آنچه در این ویدیو خواهید دید:', fontSize: 16, - fontWeight: FontWeight.bold, ), ], ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'انتخاب کلمات کلیدی مناسب', + fontSize: 14, + ), + ], ), - ), - const DidvanText( - 'ساختار و قالب‌بندی پرامپت‌ها', - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ], - ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), + const SizedBox( + height: 4, ), - ), - const DidvanText( - 'تعیین سبک و استایل در پرامپت', - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ], - ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'ساختار و قالب‌بندی پرامپت‌ها', + fontSize: 14, + ), + ], ), - ), - const DidvanText( - 'استفاده از جزییات و صفت‌ها', - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ], - ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), + const SizedBox( + height: 4, ), - ), - const DidvanText( - 'بهینه‌سازی پرامپت‌ها و تکرار', - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ], - ), - const SizedBox( - height: 4, - ), - Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 8), - decoration: const ShapeDecoration( - shape: CircleBorder( - side: BorderSide( - width: 3, color: Colors.black)), + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'تعیین سبک و استایل در پرامپت', + fontSize: 14, + ), + ], ), - ), - const DidvanText( - 'اشتباهات رایج در پرامپت‌نویسی', - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ], - ), + const SizedBox( + height: 4, + ), + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'استفاده از جزییات و صفت‌ها', + fontSize: 14, + ), + ], + ), + const SizedBox( + height: 4, + ), + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'بهینه‌سازی پرامپت‌ها و تکرار', + fontSize: 14, + ), + ], + ), + const SizedBox( + height: 4, + ), + Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 8), + decoration: const ShapeDecoration( + shape: CircleBorder( + side: BorderSide( + width: 3, color: Colors.black)), + ), + ), + const DidvanText( + 'اشتباهات رایج در پرامپت‌نویسی', + fontSize: 14, + ), + ], + ), + ], + ), + ) ], ), + ), + ], + ), + ), + ), + Column( + children: [ + const Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: DidvanDivider( + verticalPadding: 12, + height: 4, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + DidvanIcons.support_solid, + size: 32, + ), + const SizedBox( + width: 8, + ), + const DidvanText('هنوز سوالی دارید؟'), + TextButton( + onPressed: () { + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': 'پشتیبانی اپلیکیشن'}, + ); + }, + child: const DidvanText( + ' پیام به پشتیبانی', + color: Color(0xff007EA7), + ), ) ], ), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 20.0), - child: DidvanDivider( - verticalPadding: 12, - height: 4, + const SizedBox( + height: 32, ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - DidvanIcons.support_solid, - size: 32, - ), - const SizedBox( - width: 8, - ), - const DidvanText('هنوز سوالی دارید؟'), - TextButton( - onPressed: () { - Navigator.of(context).pushNamed( - Routes.direct, - arguments: {'type': 'پشتیبانی اپلیکیشن'}, - ); - }, - child: const DidvanText( - ' پیام به پشتیبانی', - color: Color(0xff007EA7), - ), - ) - ], - ) - ], - ), + ], + ) + ], ), ); } diff --git a/lib/views/ai/widgets/hoshan_drawer.dart b/lib/views/ai/widgets/hoshan_drawer.dart index 71203d0..6849ad7 100644 --- a/lib/views/ai/widgets/hoshan_drawer.dart +++ b/lib/views/ai/widgets/hoshan_drawer.dart @@ -15,7 +15,6 @@ 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:didvan/views/widgets/skeleton_image.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; @@ -73,21 +72,6 @@ class _HoshanDrawerState extends State { 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, diff --git a/lib/views/podcasts/studio_details/studio_details.mobile.dart b/lib/views/podcasts/studio_details/studio_details.mobile.dart index 5f5d8f3..32aafc8 100644 --- a/lib/views/podcasts/studio_details/studio_details.mobile.dart +++ b/lib/views/podcasts/studio_details/studio_details.mobile.dart @@ -9,6 +9,7 @@ import 'package:didvan/views/podcasts/studio_details/widgets/studio_details_widg import 'package:didvan/views/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/didvan/app_bar.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:didvan/views/widgets/video/primary_controls.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:didvan/routes/routes.dart'; @@ -26,14 +27,7 @@ class StudioDetails extends StatefulWidget { class _StudioDetailsState extends State { int _currentlyPlayingId = 0; late VideoPlayerController _videoPlayerController; - late final ChewieController _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoPlay: true, - looping: true, - aspectRatio: 16 / 9, - materialProgressColors: ChewieProgressColors( - playedColor: Theme.of(context).colorScheme.title, - handleColor: Theme.of(context).colorScheme.title)); + @override void initState() { final state = context.read(); @@ -66,6 +60,15 @@ class _StudioDetailsState extends State { @override Widget build(BuildContext context) { + late final ChewieController _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + customControls: const PrimaryControls(), + autoPlay: true, + looping: true, + aspectRatio: 16 / 9, + materialProgressColors: ChewieProgressColors( + playedColor: Theme.of(context).colorScheme.title, + handleColor: Theme.of(context).colorScheme.title)); final d = MediaQuery.of(context); return Consumer( builder: (context, state, child) => StateHandler( @@ -151,7 +154,7 @@ class _StudioDetailsState extends State { void dispose() { _videoPlayerController.pause(); _videoPlayerController.dispose(); - _chewieController.dispose(); + // _chewieController.dispose(); super.dispose(); } } diff --git a/lib/views/widgets/video/primary_controls.dart b/lib/views/widgets/video/primary_controls.dart new file mode 100644 index 0000000..043dfe2 --- /dev/null +++ b/lib/views/widgets/video/primary_controls.dart @@ -0,0 +1,354 @@ +import 'dart:async'; + +import 'package:animated_custom_dropdown/custom_dropdown.dart'; +import 'package:chewie/chewie.dart'; +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/video/play_btn_animation.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PrimaryControls extends StatefulWidget { + const PrimaryControls({super.key}); + + @override + State createState() => _PrimaryControlsState(); +} + +class _PrimaryControlsState extends State { + late ChewieController chewieController; + bool isAnimating = false; + // bool isSpeedMenuOpen = false; + + double opacity = 1; + Timer? _hideControlsTimer; + ValueNotifier position = ValueNotifier(Duration.zero); + final GlobalKey _popupMenuKey = GlobalKey(); + final GlobalKey _popupMenuSpeedKey = GlobalKey(); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + chewieController = ChewieController.of(context); + chewieController.videoPlayerController.addListener( + () { + position.value = chewieController.videoPlayerController.value.position; + }, + ); + } + + void _startHideControlsTimer() { + _hideControlsTimer?.cancel(); + _hideControlsTimer = Timer(const Duration(seconds: 5), () { + setState(() { + opacity = 0; + isAnimating = false; + // if (isSpeedMenuOpen) { + // Navigator.pop(context); + // } + }); + }); + } + + @override + void dispose() { + _hideControlsTimer?.cancel(); // Clean up the timer + super.dispose(); + } + + void _handlePlay() { + { + setState(() { + if (chewieController.isPlaying) { + chewieController.pause(); + opacity = 1; + } else { + chewieController.play(); + opacity = 0; + } + isAnimating = true; + + isAnimating = true; + }); + _startHideControlsTimer(); // Restart the timer on tap + } + } + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 400), + opacity: opacity, + child: InkWell( + onTap: () { + if (opacity == 0) { + setState(() { + opacity = 1; + }); + _startHideControlsTimer(); // Restart the timer on tap + } else { + setState(() { + opacity = 0; + }); + } + }, + child: IgnorePointer( + ignoring: opacity == 0, + child: Stack( + children: [ + Positioned( + top: 0, + right: 0, + left: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 12), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + Colors.black87, + Colors.black54, + Colors.black45, + Colors.black26, + Color.fromARGB(10, 0, 0, 0) + ])), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: 120, + child: CustomDropdown( + closedHeaderPadding: EdgeInsets.zero, + itemsListPadding: EdgeInsets.zero, + items: const [ + 0.25, + 0.5, + 0.75, + 1, + 1.25, + 1.5, + 1.75, + 2 + ], + initialItem: 1, + listItemPadding: EdgeInsets.zero, + expandedHeaderPadding: EdgeInsets.zero, + + hideSelectedFieldWhenExpanded: false, + // overlayHeight: + // chewieController.isFullScreen ? null : 54 * 8, + decoration: const CustomDropdownDecoration( + closedSuffixIcon: SizedBox(), + closedFillColor: Colors.transparent, + expandedBorderRadius: + DesignConfig.lowBorderRadius), + // hintText: "سرعت ویدیو", + listItemBuilder: + (context, item, isSelected, onItemSelect) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + child: Column( + children: [ + DidvanText('x$item'), + if (item != 2) + const DidvanDivider( + verticalPadding: 8, + ) + ], + )); + }, + headerBuilder: (context, selectedItem, enabled) => + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.more_vert_rounded, + size: 32, + color: Colors.white, + ), + ], + ), + hintBuilder: (context, hint, enabled) => + const SizedBox(), + onChanged: (value) async { + // isSpeedMenuOpen = false; + await chewieController.videoPlayerController + .setPlaybackSpeed(value!); + _startHideControlsTimer(); + }, + ), + ), + ], + ), + )), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 12), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.black, + Colors.black87, + Colors.black54, + Colors.black45, + Colors.black26, + Color.fromARGB(10, 0, 0, 0) + ])), + child: Row( + children: [ + // _buildPlayPause(), + _buildProgressIndicator(), + _buildFullScreenToggle(), + ], + ), + ), + ), + Positioned.fill( + child: Center( + child: InkWell( + onTap: _handlePlay, + child: PlayBtnAnimation( + alwaysAnimate: false, + isAnimating: isAnimating, + onEnd: () => setState( + () => isAnimating = false, + ), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.black.withOpacity(0.4)), + child: Icon( + chewieController.isPlaying + ? CupertinoIcons.pause_fill + : CupertinoIcons.play_fill, + color: Colors.white, + size: 32, + ), + ), + ), + ))) + ], + ), + ), + ), + ), + ); + } + + Widget _buildPlayPause() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: InkWell( + onTap: _handlePlay, + child: PlayBtnAnimation( + alwaysAnimate: true, + isAnimating: isAnimating, + onEnd: () => setState( + () => isAnimating = false, + ), + child: Icon( + chewieController.isPlaying + ? CupertinoIcons.pause_fill + : CupertinoIcons.play_fill, + color: Colors.white, + size: 24, + ), + ), + ), + ); + } + + Widget _buildProgressIndicator() { + return Expanded( + child: ValueListenableBuilder( + valueListenable: position, + builder: (context, p, _) { + Duration duration = + chewieController.videoPlayerController.value.duration; + + return Column( + children: [ + SliderTheme( + data: SliderThemeData( + trackHeight: 2, + // thumbColor: Colors.transparent, + overlayShape: SliderComponentShape.noOverlay, + thumbShape: const RoundSliderThumbShape( + // elevation: 0, + // pressedElevation: 0, + enabledThumbRadius: 8)), + child: Slider( + min: 0, + max: duration.inMilliseconds.toDouble(), + value: p.inMilliseconds.toDouble(), + onChanged: (value) async { + await chewieController.pause(); + position.value = Duration(milliseconds: value.round()); + _startHideControlsTimer(); + }, + onChangeEnd: (value) async { + await chewieController + .seekTo(Duration(milliseconds: value.round())); + await chewieController.play(); + + setState(() {}); + }, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DidvanText( + DateTimeUtils.normalizeTimeDuration(p), + color: Colors.white, + fontSize: 16, + ), + DidvanText( + DateTimeUtils.normalizeTimeDuration(duration), + color: Colors.white, + fontSize: 16, + ) + ], + ) + ], + ); + }, + ), + ); + } + + Widget _buildFullScreenToggle() { + return Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 8, 12), + child: InkWell( + onTap: () => setState(() { + chewieController.toggleFullScreen(); + _startHideControlsTimer(); // Restart the timer on tap + }), + child: Icon( + chewieController.isFullScreen + ? Icons.fullscreen_exit + : Icons.fullscreen, + color: Colors.white, + size: 30, + ), + ), + ); + } +}