From 1d401b3ba0efa0c47f48a6d1026783bbf0850d37 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Tue, 29 Mar 2022 11:58:12 +0430 Subject: [PATCH] design updates + bug fixes --- lib/providers/core_provider.dart | 2 +- lib/providers/server_data_provider.dart | 6 +- lib/providers/user_provider.dart | 3 +- lib/routes/route_generator.dart | 24 ++- lib/views/home/comments/comments_state.dart | 1 - .../home/news/news_details/news_details.dart | 52 +++--- .../radar/radar_details/radar_details.dart | 66 ++++---- .../filtered_bookmarks_state.dart | 11 -- .../direct_list/direct_list_state.dart | 1 - .../studio_details/studio_details.mobile.dart | 156 +++++++++++------- .../studio_details/studio_details.web.dart | 128 +++++--------- .../studio_details/studio_details_state.dart | 2 +- .../widgets/details_tab_bar.dart | 134 +++++++++++++++ .../widgets/studio_details_widget.dart | 109 ++---------- lib/views/home/studio/widgets/slider.dart | 25 ++- lib/views/home/studio/widgets/tab_bar.dart | 28 ++-- lib/views/home/widgets/bookmark_button.dart | 6 +- .../home/widgets/floating_navigation_bar.dart | 6 + .../home/widgets/overview/multitype.dart | 18 +- lib/views/widgets/didvan/app_bar.dart | 3 +- lib/views/widgets/didvan/bnb.dart | 63 +++---- lib/views/widgets/didvan/scaffold.dart | 14 +- .../state_handlers/sliver_state_handler.dart | 4 +- 23 files changed, 472 insertions(+), 390 deletions(-) create mode 100644 lib/views/home/studio/studio_details/widgets/details_tab_bar.dart diff --git a/lib/providers/core_provider.dart b/lib/providers/core_provider.dart index 820387d..b9887ef 100644 --- a/lib/providers/core_provider.dart +++ b/lib/providers/core_provider.dart @@ -3,7 +3,7 @@ import 'package:didvan/utils/action_sheet.dart'; import 'package:flutter/cupertino.dart'; class CoreProvier with ChangeNotifier { - AppState _appState = AppState.idle; + AppState _appState = AppState.busy; set appState(AppState newState) { if (newState == AppState.isolatedBusy) { diff --git a/lib/providers/server_data_provider.dart b/lib/providers/server_data_provider.dart index 532bb5e..4ef71bb 100644 --- a/lib/providers/server_data_provider.dart +++ b/lib/providers/server_data_provider.dart @@ -1,5 +1,6 @@ import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; +import 'package:collection/collection.dart'; class ServerDataProvider { static final List directTypes = []; @@ -10,7 +11,10 @@ class ServerDataProvider { static int labelToTypeId(String label) => label.contains('پشتیبانی') ? 7 - : directTypes.firstWhere((element) => element.value.contains(label)).key; + : directTypes + .firstWhereOrNull((element) => element.value.contains(label)) + ?.key ?? + 7; static Future _getDirectTypes() async { final service = RequestService(RequestHelper.directTypes); diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 6748251..ad4a4fb 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -29,7 +29,8 @@ class UserProvider extends CoreProvier { isAuthenticated = true; final RequestService service = RequestService(RequestHelper.userInfo); await service.httpGet(); - if (service.statusCode == 401 || service.result['user'] == null) { + if (service.statusCode == 401 || + (service.isSuccess && service.result['user'] == null)) { return false; } if (service.isSuccess) { diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index a4f2222..2716aaf 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -180,17 +180,23 @@ class RouteGenerator { final shortestSide = MediaQuery.of(context).size.shortestSide; final bool useMobileLayout = shortestSide < 600; if (kIsWeb && !useMobileLayout) { - return Container( - color: Theme.of(context).colorScheme.background, - alignment: Alignment.center, - child: AspectRatio(aspectRatio: 9 / 16, child: page), + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + child: Container( + color: Theme.of(context).colorScheme.background, + alignment: Alignment.center, + child: AspectRatio(aspectRatio: 9 / 16, child: page), + ), ); } - return Container( - color: Theme.of(context).colorScheme.surface, - child: SafeArea( - child: page, - top: false, + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + child: Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + child: page, + top: false, + ), ), ); }, diff --git a/lib/views/home/comments/comments_state.dart b/lib/views/home/comments/comments_state.dart index cb52186..5982f54 100644 --- a/lib/views/home/comments/comments_state.dart +++ b/lib/views/home/comments/comments_state.dart @@ -25,7 +25,6 @@ class CommentsState extends CoreProvier { int itemId = 0; Future getComments() async { - appState = AppState.busy; final service = RequestService( RequestHelper.comments(itemId, type), ); diff --git a/lib/views/home/news/news_details/news_details.dart b/lib/views/home/news/news_details/news_details.dart index 55d09d4..08d5a11 100644 --- a/lib/views/home/news/news_details/news_details.dart +++ b/lib/views/home/news/news_details/news_details.dart @@ -43,36 +43,34 @@ class _NewsDetailsState extends State { state: state, builder: (context, state) => Stack( children: [ - if (state.news.isNotEmpty) - IgnorePointer( - ignoring: state.isFetchingNewItem, - child: DidvanPageView( - isRadar: false, - initialIndex: state.initialIndex, - onPageChanged: _onPageChnaged, - scrollController: _scrollController, - items: state.news, - currentIndex: state.currentIndex, - ), + IgnorePointer( + ignoring: state.isFetchingNewItem, + child: DidvanPageView( + isRadar: false, + initialIndex: state.initialIndex, + onPageChanged: _onPageChnaged, + scrollController: _scrollController, + items: state.news, + currentIndex: state.currentIndex, ), - if (state.news.isNotEmpty) - Positioned( - bottom: 0, - left: 0, - right: 0, - child: FloatingNavigationBar( - hasUnmarkConfirmation: - widget.pageData['hasUnmarkConfirmation'], - scrollController: _scrollController, - item: state.currentNews, - onCommentsChanged: state.onCommentsChanged, - onMarkChanged: (value) => widget.pageData['onMarkChanged']( - state.currentNews.id, - value, - ), - isRadar: false, + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: FloatingNavigationBar( + hasUnmarkConfirmation: + widget.pageData['hasUnmarkConfirmation'], + scrollController: _scrollController, + item: state.currentNews, + onCommentsChanged: state.onCommentsChanged, + onMarkChanged: (value) => widget.pageData['onMarkChanged']( + state.currentNews.id, + value, ), + isRadar: false, ), + ), ], ), ), diff --git a/lib/views/home/radar/radar_details/radar_details.dart b/lib/views/home/radar/radar_details/radar_details.dart index 1c9eb11..d88f98e 100644 --- a/lib/views/home/radar/radar_details/radar_details.dart +++ b/lib/views/home/radar/radar_details/radar_details.dart @@ -43,43 +43,41 @@ class _RadarDetailsState extends State { state: state, builder: (context, state) => Stack( children: [ - if (state.radars.isNotEmpty) - IgnorePointer( - ignoring: state.isFetchingNewItem, - child: DidvanPageView( - isRadar: true, - initialIndex: state.initialIndex, - onPageChanged: _onPageChanged, - scrollController: _scrollController, - items: state.radars, - currentIndex: state.currentIndex, - ), + IgnorePointer( + ignoring: state.isFetchingNewItem, + child: DidvanPageView( + isRadar: true, + initialIndex: state.initialIndex, + onPageChanged: _onPageChanged, + scrollController: _scrollController, + items: state.radars, + currentIndex: state.currentIndex, ), - if (state.radars.isNotEmpty) - Positioned( - bottom: 0, - left: 0, - right: 0, - child: FloatingNavigationBar( - hasUnmarkConfirmation: - widget.pageData['hasUnmarkConfirmation'], - isRadar: true, - scrollController: _scrollController, - onMarkChanged: (value) => - widget.pageData['onMarkChanged']?.call( + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: FloatingNavigationBar( + hasUnmarkConfirmation: + widget.pageData['hasUnmarkConfirmation'], + isRadar: true, + scrollController: _scrollController, + onMarkChanged: (value) => + widget.pageData['onMarkChanged']?.call( + state.currentRadar.id, + value, + ), + item: state.currentRadar, + onCommentsChanged: (count) { + state.onCommentsChanged(count); + widget.pageData['onCommentsChanged']?.call( state.currentRadar.id, - value, - ), - item: state.currentRadar, - onCommentsChanged: (count) { - state.onCommentsChanged(count); - widget.pageData['onCommentsChanged']?.call( - state.currentRadar.id, - count, - ); - }, - ), + count, + ); + }, ), + ), ], ), ), diff --git a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart index 9cfc4d1..1fb2353 100644 --- a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart @@ -6,8 +6,6 @@ import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class FilteredBookmarksState extends CoreProvier { - String search = ''; - String lastSearch = ''; final List bookmarks = []; final String type; int page = 1; @@ -15,17 +13,8 @@ class FilteredBookmarksState extends CoreProvier { FilteredBookmarksState(this.type); - bool get searching => search != ''; - Future getBookmarks({required int page}) async { - if (search != '') { - lastSearch = search; - } - if (page == 1) { - bookmarks.clear(); - } this.page = page; - appState = AppState.busy; String typeString = ''; if (type == 'video' || type == 'podcast') { typeString = 'studios'; diff --git a/lib/views/home/settings/direct_list/direct_list_state.dart b/lib/views/home/settings/direct_list/direct_list_state.dart index 728305a..c6efe26 100644 --- a/lib/views/home/settings/direct_list/direct_list_state.dart +++ b/lib/views/home/settings/direct_list/direct_list_state.dart @@ -15,7 +15,6 @@ class DirectListState extends CoreProvier { } Future getDirectsList() async { - appState = AppState.busy; final RequestService service = RequestService(RequestHelper.directs); await service.httpGet(); if (service.isSuccess) { diff --git a/lib/views/home/studio/studio_details/studio_details.mobile.dart b/lib/views/home/studio/studio_details/studio_details.mobile.dart index a8d51f1..5c726fb 100644 --- a/lib/views/home/studio/studio_details/studio_details.mobile.dart +++ b/lib/views/home/studio/studio_details/studio_details.mobile.dart @@ -4,7 +4,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/foundation.dart'; @@ -39,7 +41,7 @@ class _StudioDetailsState extends State { () => state.getStudioDetails(widget.pageData['id']), ); state.args = widget.pageData['args']; - if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView(); + if (Platform.isAndroid) WebView.platform = AndroidWebView(); super.initState(); } @@ -107,82 +109,108 @@ class _StudioDetailsState extends State { appBarData: _isFullScreen ? null : AppBarData( + trailing: BookmarkButton( + value: state.studio.marked, + onMarkChanged: (value) => widget + .pageData['onMarkChanged'](state.studio.id, value), + gestureSize: 48, + ), isSmall: true, title: state.studio.title, ), - children: [ - SizedBox( - width: ds.width, - height: _isFullScreen ? ds.height : ds.width * 9 / 16, - child: Stack( + showSliversFirst: true, + slivers: [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + backgroundColor: Theme.of(context).colorScheme.surface, + toolbarHeight: + (_isFullScreen ? ds.height : ds.width * 9 / 16) + + 72 - + MediaQuery.of(context).padding.top, + elevation: 0, + flexibleSpace: Column( children: [ - WebView( - backgroundColor: Theme.of(context).colorScheme.black, - allowsInlineMediaPlayback: true, - initialUrl: Uri.dataFromString( - ''' - - - - - - - ${state.studio.media} - - - ''', - mimeType: 'text/html', - ).toString(), - javascriptMode: JavascriptMode.unrestricted, + SizedBox( + width: ds.width, + height: _isFullScreen ? ds.height : ds.width * 9 / 16, + child: Stack( + children: [ + WebView( + backgroundColor: + Theme.of(context).colorScheme.black, + allowsInlineMediaPlayback: true, + initialUrl: Uri.dataFromString( + ''' + + + + + + + ${state.studio.media} + + + ''', + mimeType: 'text/html', + ).toString(), + javascriptMode: JavascriptMode.unrestricted, + ), + Positioned( + right: 42, + bottom: 8, + child: GestureDetector( + onTap: () => _changeFullSceen(!_isFullScreen), + child: Container( + color: Colors.transparent, + width: 24, + height: 30, + ), + ), + ), + ], + ), ), - Positioned( - right: 42, - bottom: 8, - child: GestureDetector( - onTap: () => _changeFullSceen(!_isFullScreen), - child: Container( - color: Colors.transparent, - width: 24, - height: 30, + DetailsTabBar( + isVideo: true, + onCommentsTabSelected: () => Future.delayed( + const Duration(milliseconds: 100), + () => _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: DesignConfig.lowAnimationDuration, + curve: Curves.easeIn, ), ), ), ], ), ), - const SizedBox(height: 20), + ], + children: [ StudioDetailsWidget( scrollController: _scrollController, - onCommentsTabSelected: () => Future.delayed( - const Duration(milliseconds: 100), - () => _scrollController.animateTo( - _scrollController.position.maxScrollExtent, - duration: DesignConfig.lowAnimationDuration, - curve: Curves.easeIn, - ), - ), studio: state.studio, ), ], diff --git a/lib/views/home/studio/studio_details/studio_details.web.dart b/lib/views/home/studio/studio_details/studio_details.web.dart index c3040ff..65b1da5 100644 --- a/lib/views/home/studio/studio_details/studio_details.web.dart +++ b/lib/views/home/studio/studio_details/studio_details.web.dart @@ -2,18 +2,17 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:universal_html/html.dart' as html; -import 'package:webview_flutter/webview_flutter.dart'; class StudioDetails extends StatefulWidget { final Map pageData; @@ -28,10 +27,6 @@ class _StudioDetailsState extends State { final _scrollController = ScrollController(); bool _isFullScreen = false; - bool _isInit = true; - - double _dwInPortrait = 0; - double _scaleInPortrait = 1; @override void initState() { @@ -41,7 +36,6 @@ class _StudioDetailsState extends State { () => state.getStudioDetails(widget.pageData['id']), ); state.args = widget.pageData['args']; - if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView(); super.initState(); } @@ -77,12 +71,6 @@ class _StudioDetailsState extends State { @override Widget build(BuildContext context) { final ds = MediaQuery.of(context).size; - if (_isInit) { - _dwInPortrait = MediaQuery.of(context).size.width; - _scaleInPortrait = _dwInPortrait / 576; - _isInit = false; - } - return Consumer( builder: (context, state, child) => StateHandler( state: state, @@ -121,87 +109,47 @@ class _StudioDetailsState extends State { : AppBarData( isSmall: true, title: state.studio.title, + trailing: BookmarkButton( + value: state.studio.marked, + onMarkChanged: (value) => widget + .pageData['onMarkChanged'](state.studio.id, value), + gestureSize: 48, + ), ), - children: [ - if (kIsWeb) - const AspectRatio( - aspectRatio: 16 / 9, - child: HtmlElementView(viewType: 'video'), - ), - if (!kIsWeb) - SizedBox( - width: ds.width, - height: _isFullScreen ? ds.height : ds.width * 9 / 16, - child: Stack( - children: [ - WebView( - backgroundColor: Theme.of(context).colorScheme.black, - allowsInlineMediaPlayback: true, - initialUrl: Uri.dataFromString( - ''' - - - - - - - ${state.studio.media} - - - ''', - mimeType: 'text/html', - ).toString(), - javascriptMode: JavascriptMode.unrestricted, - ), - if (!kIsWeb) - Positioned( - right: 42, - bottom: 8, - child: GestureDetector( - onTap: () => _changeFullSceen(!_isFullScreen), - child: Container( - color: Colors.transparent, - width: 24, - height: 30, - ), - ), + showSliversFirst: true, + slivers: [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + elevation: 0, + toolbarHeight: + (_isFullScreen ? ds.height : ds.width * 9 / 16) + + 72 - + MediaQuery.of(context).padding.top, + flexibleSpace: Column( + children: [ + const AspectRatio( + aspectRatio: 16 / 9, + child: HtmlElementView(viewType: 'video'), + ), + DetailsTabBar( + isVideo: true, + onCommentsTabSelected: () => Future.delayed( + const Duration(milliseconds: 100), + () => _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: DesignConfig.lowAnimationDuration, + curve: Curves.easeIn, ), - ], - ), + ), + ), + ], ), - const SizedBox(height: 20), + ), + ], + children: [ StudioDetailsWidget( scrollController: _scrollController, - onCommentsTabSelected: () => Future.delayed( - const Duration(milliseconds: 100), - () => _scrollController.animateTo( - _scrollController.position.maxScrollExtent, - duration: DesignConfig.lowAnimationDuration, - curve: Curves.easeIn, - ), - ), studio: state.studio, ), ], diff --git a/lib/views/home/studio/studio_details/studio_details_state.dart b/lib/views/home/studio/studio_details/studio_details_state.dart index 49f13d1..2f8d867 100644 --- a/lib/views/home/studio/studio_details/studio_details_state.dart +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -110,7 +110,7 @@ class StudioDetailsState extends CoreProvier { final service = RequestService(RequestHelper.tag( ids: studio.tags.map((tag) => tag.id).toList(), itemId: studio.id, - type: studio.media.contains('iframe') ? 'video' : 'podcast', + type: args.type, )); await service.httpGet(); if (service.isSuccess) { diff --git a/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart b/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart new file mode 100644 index 0000000..ce2a4a2 --- /dev/null +++ b/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart @@ -0,0 +1,134 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class DetailsTabBar extends StatelessWidget { + final bool isVideo; + final VoidCallback onCommentsTabSelected; + + const DetailsTabBar({ + Key? key, + required this.isVideo, + required this.onCommentsTabSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final state = context.read(); + return WillPopScope( + onWillPop: () async { + state.selectedDetailsIndex = 0; + return true; + }, + child: Container( + height: 72, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + boxShadow: [ + BoxShadow( + color: const Color(0XFF1B3C59).withOpacity(0.15), + offset: const Offset(0, 8), + blurRadius: 8, + spreadRadius: 0, + ) + ], + ), + child: FittedBox( + fit: BoxFit.scaleDown, + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + _TabItem( + icon: DidvanIcons.description_solid, + title: 'توضیحات', + onTap: () => state.selectedDetailsIndex = 0, + isSelected: state.selectedDetailsIndex == 0, + isVideo: isVideo, + ), + _TabItem( + icon: DidvanIcons.chats_solid, + title: 'نظرات', + onTap: () { + state.selectedDetailsIndex = 1; + onCommentsTabSelected(); + }, + isSelected: state.selectedDetailsIndex == 1, + isVideo: isVideo, + ), + _TabItem( + icon: DidvanIcons.puzzle_solid, + title: 'مطالب مرتبط', + onTap: () => state.selectedDetailsIndex = 2, + isSelected: state.selectedDetailsIndex == 2, + isVideo: isVideo, + ), + ], + ), + ), + ), + ), + ); + } +} + +class _TabItem extends StatelessWidget { + final IconData icon; + final String title; + final VoidCallback onTap; + final bool isSelected; + final bool isVideo; + const _TabItem({ + Key? key, + required this.icon, + required this.title, + required this.onTap, + required this.isSelected, + required this.isVideo, + }) : super(key: key); + + Color? _color(context) { + if (isSelected) { + if (isVideo) { + return Theme.of(context).colorScheme.secondary; + } + return Theme.of(context).colorScheme.focusedBorder; + } + return Theme.of(context).colorScheme.border; + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: GestureDetector( + onTap: onTap, + child: Container( + color: Colors.transparent, + child: Column( + children: [ + Icon( + icon, + color: _color(context), + ), + AnimatedContainer( + duration: DesignConfig.lowAnimationDuration, + width: isSelected ? 64 : 0, + height: 1, + color: _color(context), + ), + DidvanText( + title, + color: _color(context), + style: Theme.of(context).textTheme.caption, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart index 6c99823..6edbac4 100644 --- a/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart +++ b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart @@ -5,6 +5,7 @@ import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/views/home/comments/comments.dart'; import 'package:didvan/views/home/comments/comments_state.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/widgets/overview/multitype.dart'; import 'package:didvan/views/home/widgets/tag_item.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -16,15 +17,15 @@ import 'package:provider/provider.dart'; class StudioDetailsWidget extends StatelessWidget { final StudioDetailsData studio; final ScrollController? scrollController; - final VoidCallback onCommentsTabSelected; + final VoidCallback? onCommentsTabSelected; const StudioDetailsWidget({ Key? key, required this.studio, - required this.onCommentsTabSelected, + this.onCommentsTabSelected, this.scrollController, }) : super(key: key); - bool get _isVideo => studio.media.contains('ifram'); + bool get _isVideo => studio.media.contains('iframe'); @override Widget build(BuildContext context) { @@ -35,38 +36,12 @@ class StudioDetailsWidget extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - const SizedBox(), - _TabItem( - icon: DidvanIcons.description_solid, - title: 'توضیحات', - onTap: () => state.selectedDetailsIndex = 0, - isSelected: state.selectedDetailsIndex == 0, - isVideo: _isVideo, - ), - _TabItem( - icon: DidvanIcons.chats_solid, - title: 'نظرات', - onTap: () { - state.selectedDetailsIndex = 1; - onCommentsTabSelected(); - }, - isSelected: state.selectedDetailsIndex == 1, - isVideo: _isVideo, - ), - _TabItem( - icon: DidvanIcons.puzzle_solid, - title: 'مطالب مرتبط', - onTap: () => state.selectedDetailsIndex = 2, - isSelected: state.selectedDetailsIndex == 2, - isVideo: _isVideo, - ), - const SizedBox(), - ], - ), - const SizedBox(height: 24), + if (!_isVideo) + DetailsTabBar( + isVideo: _isVideo, + onCommentsTabSelected: onCommentsTabSelected ?? () {}, + ), + if (state.selectedDetailsIndex != 1) const SizedBox(height: 16), StateHandler( onRetry: () {}, state: state, @@ -116,7 +91,10 @@ class StudioDetailsWidget extends StatelessWidget { return ChangeNotifierProvider( create: (context) => CommentsState(), child: SizedBox( - height: ds.height - 180, + height: ds.height - + ds.width * 9 / 16 - + 128 - + MediaQuery.of(context).padding.top, child: Comments( pageData: { 'id': studio.id, @@ -164,63 +142,6 @@ class StudioDetailsWidget extends StatelessWidget { } } -class _TabItem extends StatelessWidget { - final IconData icon; - final String title; - final VoidCallback onTap; - final bool isSelected; - final bool isVideo; - const _TabItem({ - Key? key, - required this.icon, - required this.title, - required this.onTap, - required this.isSelected, - required this.isVideo, - }) : super(key: key); - - Color? _color(context) { - if (isSelected) { - if (isVideo) { - return Theme.of(context).colorScheme.secondary; - } - return Theme.of(context).colorScheme.focusedBorder; - } - return Theme.of(context).colorScheme.border; - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - color: Colors.transparent, - child: Column( - children: [ - Icon( - icon, - color: _color(context), - ), - if (isSelected) const SizedBox(height: 8), - if (isSelected) - Container( - width: 64, - height: 1, - color: _color(context), - ), - if (isSelected) - DidvanText( - title, - color: _color(context), - style: Theme.of(context).textTheme.caption, - ) - ], - ), - ), - ); - } -} - class _StudioPreview extends StatelessWidget { final bool isNext; final StudioDetailsData studio; @@ -279,7 +200,7 @@ class _StudioPreview extends StatelessWidget { const SizedBox(height: 8), DidvanText( studio.title, - maxLines: 3, + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: Theme.of(context).textTheme.overline, diff --git a/lib/views/home/studio/widgets/slider.dart b/lib/views/home/studio/widgets/slider.dart index ea8cbdd..c072168 100644 --- a/lib/views/home/studio/widgets/slider.dart +++ b/lib/views/home/studio/widgets/slider.dart @@ -114,7 +114,10 @@ class _StudioSliderState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ for (var i = 0; i < state.sliders.length; i++) - _SliderIndicator(isCurrentIndex: selectedIndex == i), + _SliderIndicator( + isCurrentIndex: selectedIndex == i, + isVideo: state.videosSelected, + ), ], ), const SizedBox(height: 16), @@ -125,8 +128,19 @@ class _StudioSliderState extends State { class _SliderIndicator extends StatelessWidget { final bool isCurrentIndex; - const _SliderIndicator({Key? key, required this.isCurrentIndex}) - : super(key: key); + final bool isVideo; + const _SliderIndicator({ + Key? key, + required this.isCurrentIndex, + required this.isVideo, + }) : super(key: key); + + Color _color(BuildContext context) { + if (isVideo) { + return Theme.of(context).colorScheme.secondary; + } + return Theme.of(context).colorScheme.focusedBorder; + } @override Widget build(BuildContext context) { @@ -137,11 +151,10 @@ class _SliderIndicator extends StatelessWidget { margin: const EdgeInsets.only(left: 4), decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).colorScheme.focusedBorder, + color: _color(context), ), shape: BoxShape.circle, - color: - isCurrentIndex ? Theme.of(context).colorScheme.focusedBorder : null, + color: isCurrentIndex ? _color(context) : null, ), ); } diff --git a/lib/views/home/studio/widgets/tab_bar.dart b/lib/views/home/studio/widgets/tab_bar.dart index 3d915a8..b1ed37b 100644 --- a/lib/views/home/studio/widgets/tab_bar.dart +++ b/lib/views/home/studio/widgets/tab_bar.dart @@ -20,9 +20,7 @@ class StudioTabBar extends StatelessWidget { padding: const EdgeInsets.all(4), decoration: BoxDecoration( border: Border.all( - color: state.videosSelected - ? Theme.of(context).colorScheme.secondary - : Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.border, ), borderRadius: DesignConfig.lowBorderRadius, ), @@ -88,19 +86,17 @@ class _StudioTypeButton extends StatelessWidget { size: 32, color: _color(context), ), - if (!isSelected) const SizedBox(height: 18), - if (isSelected) - Container( - width: 88, - height: 1, - color: _color(context), - ), - if (isSelected) - DidvanText( - title, - style: Theme.of(context).textTheme.overline, - color: _color(context), - ) + AnimatedContainer( + duration: DesignConfig.lowAnimationDuration, + width: isSelected ? 88 : 0, + height: 1, + color: _color(context), + ), + DidvanText( + title, + style: Theme.of(context).textTheme.overline, + color: _color(context), + ) ], ), ), diff --git a/lib/views/home/widgets/bookmark_button.dart b/lib/views/home/widgets/bookmark_button.dart index 8f1d5aa..2387ca0 100644 --- a/lib/views/home/widgets/bookmark_button.dart +++ b/lib/views/home/widgets/bookmark_button.dart @@ -1,3 +1,4 @@ +import 'package:didvan/config/design_config.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/utils/action_sheet.dart'; @@ -43,7 +44,10 @@ class _BookmarkButtonState extends State { Widget build(BuildContext context) { return DidvanIconButton( gestureSize: widget.gestureSize, - color: widget.color, + color: widget.color ?? + (DesignConfig.isDark || !_value + ? null + : Theme.of(context).colorScheme.primary), icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular, onPressed: () async { bool confirm = false; diff --git a/lib/views/home/widgets/floating_navigation_bar.dart b/lib/views/home/widgets/floating_navigation_bar.dart index dd453a9..aac4297 100644 --- a/lib/views/home/widgets/floating_navigation_bar.dart +++ b/lib/views/home/widgets/floating_navigation_bar.dart @@ -104,6 +104,9 @@ class _FloatingNavigationBarState extends State { const Spacer(), if (widget.isRadar) BookmarkButton( + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.focusedBorder + : Theme.of(context).colorScheme.focused, askForConfirmation: widget.hasUnmarkConfirmation, value: widget.item.marked, onMarkChanged: (value) { @@ -143,6 +146,9 @@ class _FloatingNavigationBarState extends State { if (!widget.isRadar) const SizedBox(width: 12), if (!widget.isRadar) BookmarkButton( + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.focusedBorder + : Theme.of(context).colorScheme.focused, askForConfirmation: widget.hasUnmarkConfirmation, value: widget.item.marked, onMarkChanged: (value) { diff --git a/lib/views/home/widgets/overview/multitype.dart b/lib/views/home/widgets/overview/multitype.dart index 6e5a5aa..136d78e 100644 --- a/lib/views/home/widgets/overview/multitype.dart +++ b/lib/views/home/widgets/overview/multitype.dart @@ -5,6 +5,7 @@ import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.dart'; +import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -127,7 +128,22 @@ class MultitypeOverview extends StatelessWidget { DateTime.parse(item.createdAt).toPersianDateStr(), style: Theme.of(context).textTheme.overline, ), - // DidvanText('text'), + const Spacer(), + if ((item.timeToRead ?? item.duration) != null) ...[ + const Icon( + DidvanIcons.timer_light, + size: 18, + ), + const SizedBox(width: 4), + DidvanText( + item.timeToRead != null + ? 'خواندن در ${item.timeToRead} دقیقه' + : DateTimeUtils.normalizeTimeDuration( + Duration(seconds: item.duration!), + ), + style: Theme.of(context).textTheme.overline, + ), + ] ], ), ], diff --git a/lib/views/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart index f7f00e3..1f1e6ed 100644 --- a/lib/views/widgets/didvan/app_bar.dart +++ b/lib/views/widgets/didvan/app_bar.dart @@ -20,7 +20,8 @@ class DidvanAppBar extends StatelessWidget { return Container( height: appBarData.isSmall ? 56 : 72, width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.only(right: 4, left: 20), + padding: + EdgeInsets.only(right: 4, left: appBarData.trailing == null ? 20 : 0), decoration: BoxDecoration( border: hasBorder ? Border( diff --git a/lib/views/widgets/didvan/bnb.dart b/lib/views/widgets/didvan/bnb.dart index c5f4ec5..78af00d 100644 --- a/lib/views/widgets/didvan/bnb.dart +++ b/lib/views/widgets/didvan/bnb.dart @@ -198,35 +198,42 @@ class DidvanBNB extends StatelessWidget { color: Theme.of(context).colorScheme.surface, ), ), - persistentHeader: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - AudioPlayerWidget( - podcast: MediaService.currentPodcast!, - ), - Container( - width: MediaQuery.of(context).size.width, - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - DidvanIconButton( - size: 32, - icon: DidvanIcons.angle_down_regular, - onPressed: () { - if (!isExpanded) { - sheetKey.currentState?.expand(); - isExpanded = true; - return; - } - isExpanded = false; - sheetKey.currentState?.contract(); - }, - ), - const SizedBox(height: 16), - ], + persistentHeader: GestureDetector( + onVerticalDragUpdate: (details) { + if (details.delta.dy > 10) { + Navigator.of(context).pop(); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AudioPlayerWidget( + podcast: MediaService.currentPodcast!, ), - ), - ], + Container( + width: MediaQuery.of(context).size.width, + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + DidvanIconButton( + size: 32, + icon: DidvanIcons.angle_down_regular, + onPressed: () { + if (!isExpanded) { + sheetKey.currentState?.expand(); + isExpanded = true; + return; + } + isExpanded = false; + sheetKey.currentState?.contract(); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ], + ), ), expandableContent: state.appState == AppState.busy ? const SizedBox() diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart index bca06ac..8e31440 100644 --- a/lib/views/widgets/didvan/scaffold.dart +++ b/lib/views/widgets/didvan/scaffold.dart @@ -10,6 +10,7 @@ class DidvanScaffold extends StatefulWidget { final Color? backgroundColor; final bool reverse; final ScrollController? scrollController; + final bool showSliversFirst; const DidvanScaffold({ Key? key, @@ -20,6 +21,7 @@ class DidvanScaffold extends StatefulWidget { this.backgroundColor, this.reverse = false, this.scrollController, + this.showSliversFirst = false, }) : super(key: key); @override @@ -60,7 +62,7 @@ class _DidvanScaffoldState extends State { pinned: true, flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!), ), - if (widget.children != null) + if (widget.children != null && !widget.showSliversFirst) SliverPadding( padding: widget.padding, sliver: SliverList( @@ -76,6 +78,16 @@ class _DidvanScaffoldState extends State { padding: widget.padding, sliver: widget.slivers![i], ), + if (widget.children != null && widget.showSliversFirst) + SliverPadding( + padding: widget.padding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.children![index], + childCount: widget.children!.length, + ), + ), + ), if (widget.reverse) SliverToBoxAdapter( child: SizedBox( diff --git a/lib/views/widgets/state_handlers/sliver_state_handler.dart b/lib/views/widgets/state_handlers/sliver_state_handler.dart index cb762f5..f27ed14 100644 --- a/lib/views/widgets/state_handlers/sliver_state_handler.dart +++ b/lib/views/widgets/state_handlers/sliver_state_handler.dart @@ -40,7 +40,9 @@ class SliverStateHandler extends SliverList { if (enableEmptyState && state.appState == AppState.idle) { return Padding( padding: EdgeInsets.only( - top: centerEmptyState ? 120 : 20, + top: centerEmptyState + ? MediaQuery.of(context).size.height / 4 + : 20, bottom: 20, ), child: emptyState,