design updates + bug fixes

This commit is contained in:
MohammadTaha Basiri 2022-03-29 11:58:12 +04:30
parent 9b221c4463
commit 1d401b3ba0
23 changed files with 472 additions and 390 deletions

View File

@ -3,7 +3,7 @@ import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class CoreProvier with ChangeNotifier { class CoreProvier with ChangeNotifier {
AppState _appState = AppState.idle; AppState _appState = AppState.busy;
set appState(AppState newState) { set appState(AppState newState) {
if (newState == AppState.isolatedBusy) { if (newState == AppState.isolatedBusy) {

View File

@ -1,5 +1,6 @@
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:collection/collection.dart';
class ServerDataProvider { class ServerDataProvider {
static final List<MapEntry> directTypes = []; static final List<MapEntry> directTypes = [];
@ -10,7 +11,10 @@ class ServerDataProvider {
static int labelToTypeId(String label) => label.contains('پشتیبانی') static int labelToTypeId(String label) => label.contains('پشتیبانی')
? 7 ? 7
: directTypes.firstWhere((element) => element.value.contains(label)).key; : directTypes
.firstWhereOrNull((element) => element.value.contains(label))
?.key ??
7;
static Future<void> _getDirectTypes() async { static Future<void> _getDirectTypes() async {
final service = RequestService(RequestHelper.directTypes); final service = RequestService(RequestHelper.directTypes);

View File

@ -29,7 +29,8 @@ class UserProvider extends CoreProvier {
isAuthenticated = true; isAuthenticated = true;
final RequestService service = RequestService(RequestHelper.userInfo); final RequestService service = RequestService(RequestHelper.userInfo);
await service.httpGet(); await service.httpGet();
if (service.statusCode == 401 || service.result['user'] == null) { if (service.statusCode == 401 ||
(service.isSuccess && service.result['user'] == null)) {
return false; return false;
} }
if (service.isSuccess) { if (service.isSuccess) {

View File

@ -180,17 +180,23 @@ class RouteGenerator {
final shortestSide = MediaQuery.of(context).size.shortestSide; final shortestSide = MediaQuery.of(context).size.shortestSide;
final bool useMobileLayout = shortestSide < 600; final bool useMobileLayout = shortestSide < 600;
if (kIsWeb && !useMobileLayout) { if (kIsWeb && !useMobileLayout) {
return Container( return MediaQuery(
color: Theme.of(context).colorScheme.background, data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
alignment: Alignment.center, child: Container(
child: AspectRatio(aspectRatio: 9 / 16, child: page), color: Theme.of(context).colorScheme.background,
alignment: Alignment.center,
child: AspectRatio(aspectRatio: 9 / 16, child: page),
),
); );
} }
return Container( return MediaQuery(
color: Theme.of(context).colorScheme.surface, data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: SafeArea( child: Container(
child: page, color: Theme.of(context).colorScheme.surface,
top: false, child: SafeArea(
child: page,
top: false,
),
), ),
); );
}, },

View File

@ -25,7 +25,6 @@ class CommentsState extends CoreProvier {
int itemId = 0; int itemId = 0;
Future<void> getComments() async { Future<void> getComments() async {
appState = AppState.busy;
final service = RequestService( final service = RequestService(
RequestHelper.comments(itemId, type), RequestHelper.comments(itemId, type),
); );

View File

@ -43,36 +43,34 @@ class _NewsDetailsState extends State<NewsDetails> {
state: state, state: state,
builder: (context, state) => Stack( builder: (context, state) => Stack(
children: [ children: [
if (state.news.isNotEmpty) IgnorePointer(
IgnorePointer( ignoring: state.isFetchingNewItem,
ignoring: state.isFetchingNewItem, child: DidvanPageView(
child: DidvanPageView( isRadar: false,
isRadar: false, initialIndex: state.initialIndex,
initialIndex: state.initialIndex, onPageChanged: _onPageChnaged,
onPageChanged: _onPageChnaged, scrollController: _scrollController,
scrollController: _scrollController, items: state.news,
items: state.news, currentIndex: state.currentIndex,
currentIndex: state.currentIndex,
),
), ),
if (state.news.isNotEmpty) ),
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
child: FloatingNavigationBar( child: FloatingNavigationBar(
hasUnmarkConfirmation: hasUnmarkConfirmation:
widget.pageData['hasUnmarkConfirmation'], widget.pageData['hasUnmarkConfirmation'],
scrollController: _scrollController, scrollController: _scrollController,
item: state.currentNews, item: state.currentNews,
onCommentsChanged: state.onCommentsChanged, onCommentsChanged: state.onCommentsChanged,
onMarkChanged: (value) => widget.pageData['onMarkChanged']( onMarkChanged: (value) => widget.pageData['onMarkChanged'](
state.currentNews.id, state.currentNews.id,
value, value,
),
isRadar: false,
), ),
isRadar: false,
), ),
),
], ],
), ),
), ),

View File

@ -43,43 +43,41 @@ class _RadarDetailsState extends State<RadarDetails> {
state: state, state: state,
builder: (context, state) => Stack( builder: (context, state) => Stack(
children: [ children: [
if (state.radars.isNotEmpty) IgnorePointer(
IgnorePointer( ignoring: state.isFetchingNewItem,
ignoring: state.isFetchingNewItem, child: DidvanPageView(
child: DidvanPageView( isRadar: true,
isRadar: true, initialIndex: state.initialIndex,
initialIndex: state.initialIndex, onPageChanged: _onPageChanged,
onPageChanged: _onPageChanged, scrollController: _scrollController,
scrollController: _scrollController, items: state.radars,
items: state.radars, currentIndex: state.currentIndex,
currentIndex: state.currentIndex,
),
), ),
if (state.radars.isNotEmpty) ),
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
child: FloatingNavigationBar( child: FloatingNavigationBar(
hasUnmarkConfirmation: hasUnmarkConfirmation:
widget.pageData['hasUnmarkConfirmation'], widget.pageData['hasUnmarkConfirmation'],
isRadar: true, isRadar: true,
scrollController: _scrollController, scrollController: _scrollController,
onMarkChanged: (value) => onMarkChanged: (value) =>
widget.pageData['onMarkChanged']?.call( widget.pageData['onMarkChanged']?.call(
state.currentRadar.id,
value,
),
item: state.currentRadar,
onCommentsChanged: (count) {
state.onCommentsChanged(count);
widget.pageData['onCommentsChanged']?.call(
state.currentRadar.id, state.currentRadar.id,
value, count,
), );
item: state.currentRadar, },
onCommentsChanged: (count) {
state.onCommentsChanged(count);
widget.pageData['onCommentsChanged']?.call(
state.currentRadar.id,
count,
);
},
),
), ),
),
], ],
), ),
), ),

View File

@ -6,8 +6,6 @@ import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
class FilteredBookmarksState extends CoreProvier { class FilteredBookmarksState extends CoreProvier {
String search = '';
String lastSearch = '';
final List<OverviewData> bookmarks = []; final List<OverviewData> bookmarks = [];
final String type; final String type;
int page = 1; int page = 1;
@ -15,17 +13,8 @@ class FilteredBookmarksState extends CoreProvier {
FilteredBookmarksState(this.type); FilteredBookmarksState(this.type);
bool get searching => search != '';
Future<void> getBookmarks({required int page}) async { Future<void> getBookmarks({required int page}) async {
if (search != '') {
lastSearch = search;
}
if (page == 1) {
bookmarks.clear();
}
this.page = page; this.page = page;
appState = AppState.busy;
String typeString = ''; String typeString = '';
if (type == 'video' || type == 'podcast') { if (type == 'video' || type == 'podcast') {
typeString = 'studios'; typeString = 'studios';

View File

@ -15,7 +15,6 @@ class DirectListState extends CoreProvier {
} }
Future<void> getDirectsList() async { Future<void> getDirectsList() async {
appState = AppState.busy;
final RequestService service = RequestService(RequestHelper.directs); final RequestService service = RequestService(RequestHelper.directs);
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {

View File

@ -4,7 +4,9 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/view/app_bar_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/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/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/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -39,7 +41,7 @@ class _StudioDetailsState extends State<StudioDetails> {
() => state.getStudioDetails(widget.pageData['id']), () => state.getStudioDetails(widget.pageData['id']),
); );
state.args = widget.pageData['args']; state.args = widget.pageData['args'];
if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView(); if (Platform.isAndroid) WebView.platform = AndroidWebView();
super.initState(); super.initState();
} }
@ -107,82 +109,108 @@ class _StudioDetailsState extends State<StudioDetails> {
appBarData: _isFullScreen appBarData: _isFullScreen
? null ? null
: AppBarData( : AppBarData(
trailing: BookmarkButton(
value: state.studio.marked,
onMarkChanged: (value) => widget
.pageData['onMarkChanged'](state.studio.id, value),
gestureSize: 48,
),
isSmall: true, isSmall: true,
title: state.studio.title, title: state.studio.title,
), ),
children: [ showSliversFirst: true,
SizedBox( slivers: [
width: ds.width, SliverAppBar(
height: _isFullScreen ? ds.height : ds.width * 9 / 16, automaticallyImplyLeading: false,
child: Stack( 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: [ children: [
WebView( SizedBox(
backgroundColor: Theme.of(context).colorScheme.black, width: ds.width,
allowsInlineMediaPlayback: true, height: _isFullScreen ? ds.height : ds.width * 9 / 16,
initialUrl: Uri.dataFromString( child: Stack(
''' children: [
<html> WebView(
<head> backgroundColor:
<meta Theme.of(context).colorScheme.black,
name="viewport" allowsInlineMediaPlayback: true,
content="width=device-width, initial-scale=$_scaleInPortrait" initialUrl: Uri.dataFromString(
/> '''
<style> <html>
* { <head>
padding: 0; <meta
margin: 0; name="viewport"
overflow: hidden; content="width=device-width, initial-scale=$_scaleInPortrait"
} />
iframe { <style>
max-height: 100vh; * {
} padding: 0;
.r1_iframe_embed { margin: 0;
height: ${MediaQuery.of(context).size.width / _scaleInPortrait}px !important; overflow: hidden;
padding-top: 0 !important; }
} iframe {
@media(max-width:580px){ max-height: 100vh;
.r1_iframe_embed { }
height: ${_dwInPortrait * 9 / 16 / _scaleInPortrait}px !important; .r1_iframe_embed {
padding-top: 0 !important; height: ${MediaQuery.of(context).size.width / _scaleInPortrait}px !important;
} padding-top: 0 !important;
} }
</style> @media(max-width:580px){
</head> .r1_iframe_embed {
<body> height: ${_dwInPortrait * 9 / 16 / _scaleInPortrait}px !important;
${state.studio.media} padding-top: 0 !important;
</body> }
</html> }
''', </style>
mimeType: 'text/html', </head>
).toString(), <body>
javascriptMode: JavascriptMode.unrestricted, ${state.studio.media}
</body>
</html>
''',
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( DetailsTabBar(
right: 42, isVideo: true,
bottom: 8, onCommentsTabSelected: () => Future.delayed(
child: GestureDetector( const Duration(milliseconds: 100),
onTap: () => _changeFullSceen(!_isFullScreen), () => _scrollController.animateTo(
child: Container( _scrollController.position.maxScrollExtent,
color: Colors.transparent, duration: DesignConfig.lowAnimationDuration,
width: 24, curve: Curves.easeIn,
height: 30,
), ),
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 20), ],
children: [
StudioDetailsWidget( StudioDetailsWidget(
scrollController: _scrollController, scrollController: _scrollController,
onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
),
studio: state.studio, studio: state.studio,
), ),
], ],

View File

@ -2,18 +2,17 @@ import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:didvan/config/design_config.dart'; 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/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/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/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/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:webview_flutter/webview_flutter.dart';
class StudioDetails extends StatefulWidget { class StudioDetails extends StatefulWidget {
final Map<String, dynamic> pageData; final Map<String, dynamic> pageData;
@ -28,10 +27,6 @@ class _StudioDetailsState extends State<StudioDetails> {
final _scrollController = ScrollController(); final _scrollController = ScrollController();
bool _isFullScreen = false; bool _isFullScreen = false;
bool _isInit = true;
double _dwInPortrait = 0;
double _scaleInPortrait = 1;
@override @override
void initState() { void initState() {
@ -41,7 +36,6 @@ class _StudioDetailsState extends State<StudioDetails> {
() => state.getStudioDetails(widget.pageData['id']), () => state.getStudioDetails(widget.pageData['id']),
); );
state.args = widget.pageData['args']; state.args = widget.pageData['args'];
if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView();
super.initState(); super.initState();
} }
@ -77,12 +71,6 @@ class _StudioDetailsState extends State<StudioDetails> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ds = MediaQuery.of(context).size; final ds = MediaQuery.of(context).size;
if (_isInit) {
_dwInPortrait = MediaQuery.of(context).size.width;
_scaleInPortrait = _dwInPortrait / 576;
_isInit = false;
}
return Consumer<StudioDetailsState>( return Consumer<StudioDetailsState>(
builder: (context, state, child) => StateHandler<StudioDetailsState>( builder: (context, state, child) => StateHandler<StudioDetailsState>(
state: state, state: state,
@ -121,87 +109,47 @@ class _StudioDetailsState extends State<StudioDetails> {
: AppBarData( : AppBarData(
isSmall: true, isSmall: true,
title: state.studio.title, title: state.studio.title,
trailing: BookmarkButton(
value: state.studio.marked,
onMarkChanged: (value) => widget
.pageData['onMarkChanged'](state.studio.id, value),
gestureSize: 48,
),
), ),
children: [ showSliversFirst: true,
if (kIsWeb) slivers: [
const AspectRatio( SliverAppBar(
aspectRatio: 16 / 9, automaticallyImplyLeading: false,
child: HtmlElementView(viewType: 'video'), pinned: true,
), elevation: 0,
if (!kIsWeb) toolbarHeight:
SizedBox( (_isFullScreen ? ds.height : ds.width * 9 / 16) +
width: ds.width, 72 -
height: _isFullScreen ? ds.height : ds.width * 9 / 16, MediaQuery.of(context).padding.top,
child: Stack( flexibleSpace: Column(
children: [ children: [
WebView( const AspectRatio(
backgroundColor: Theme.of(context).colorScheme.black, aspectRatio: 16 / 9,
allowsInlineMediaPlayback: true, child: HtmlElementView(viewType: 'video'),
initialUrl: Uri.dataFromString( ),
''' DetailsTabBar(
<html> isVideo: true,
<head> onCommentsTabSelected: () => Future.delayed(
<meta const Duration(milliseconds: 100),
name="viewport" () => _scrollController.animateTo(
content="width=device-width, initial-scale=$_scaleInPortrait" _scrollController.position.maxScrollExtent,
/> duration: DesignConfig.lowAnimationDuration,
<style> curve: Curves.easeIn,
* {
padding: 0;
margin: 0;
overflow: hidden;
}
iframe {
max-height: 100vh;
}
.r1_iframe_embed {
height: ${MediaQuery.of(context).size.width / _scaleInPortrait}px !important;
padding-top: 0 !important;
}
@media(max-width:580px){
.r1_iframe_embed {
height: ${_dwInPortrait * 9 / 16 / _scaleInPortrait}px !important;
padding-top: 0 !important;
}
}
</style>
</head>
<body>
${state.studio.media}
</body>
</html>
''',
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,
),
),
), ),
], ),
), ),
],
), ),
const SizedBox(height: 20), ),
],
children: [
StudioDetailsWidget( StudioDetailsWidget(
scrollController: _scrollController, scrollController: _scrollController,
onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
),
studio: state.studio, studio: state.studio,
), ),
], ],

View File

@ -110,7 +110,7 @@ class StudioDetailsState extends CoreProvier {
final service = RequestService(RequestHelper.tag( final service = RequestService(RequestHelper.tag(
ids: studio.tags.map((tag) => tag.id).toList(), ids: studio.tags.map((tag) => tag.id).toList(),
itemId: studio.id, itemId: studio.id,
type: studio.media.contains('iframe') ? 'video' : 'podcast', type: args.type,
)); ));
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {

View File

@ -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<StudioDetailsState>();
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,
)
],
),
),
),
);
}
}

View File

@ -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.dart';
import 'package:didvan/views/home/comments/comments_state.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/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/overview/multitype.dart';
import 'package:didvan/views/home/widgets/tag_item.dart'; import 'package:didvan/views/home/widgets/tag_item.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
@ -16,15 +17,15 @@ import 'package:provider/provider.dart';
class StudioDetailsWidget extends StatelessWidget { class StudioDetailsWidget extends StatelessWidget {
final StudioDetailsData studio; final StudioDetailsData studio;
final ScrollController? scrollController; final ScrollController? scrollController;
final VoidCallback onCommentsTabSelected; final VoidCallback? onCommentsTabSelected;
const StudioDetailsWidget({ const StudioDetailsWidget({
Key? key, Key? key,
required this.studio, required this.studio,
required this.onCommentsTabSelected, this.onCommentsTabSelected,
this.scrollController, this.scrollController,
}) : super(key: key); }) : super(key: key);
bool get _isVideo => studio.media.contains('ifram'); bool get _isVideo => studio.media.contains('iframe');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -35,38 +36,12 @@ class StudioDetailsWidget extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( if (!_isVideo)
mainAxisAlignment: MainAxisAlignment.spaceAround, DetailsTabBar(
children: [ isVideo: _isVideo,
const SizedBox(), onCommentsTabSelected: onCommentsTabSelected ?? () {},
_TabItem( ),
icon: DidvanIcons.description_solid, if (state.selectedDetailsIndex != 1) const SizedBox(height: 16),
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),
StateHandler<StudioDetailsState>( StateHandler<StudioDetailsState>(
onRetry: () {}, onRetry: () {},
state: state, state: state,
@ -116,7 +91,10 @@ class StudioDetailsWidget extends StatelessWidget {
return ChangeNotifierProvider<CommentsState>( return ChangeNotifierProvider<CommentsState>(
create: (context) => CommentsState(), create: (context) => CommentsState(),
child: SizedBox( child: SizedBox(
height: ds.height - 180, height: ds.height -
ds.width * 9 / 16 -
128 -
MediaQuery.of(context).padding.top,
child: Comments( child: Comments(
pageData: { pageData: {
'id': studio.id, '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 { class _StudioPreview extends StatelessWidget {
final bool isNext; final bool isNext;
final StudioDetailsData studio; final StudioDetailsData studio;
@ -279,7 +200,7 @@ class _StudioPreview extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
DidvanText( DidvanText(
studio.title, studio.title,
maxLines: 3, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.overline, style: Theme.of(context).textTheme.overline,

View File

@ -114,7 +114,10 @@ class _StudioSliderState extends State<StudioSlider> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
for (var i = 0; i < state.sliders.length; i++) for (var i = 0; i < state.sliders.length; i++)
_SliderIndicator(isCurrentIndex: selectedIndex == i), _SliderIndicator(
isCurrentIndex: selectedIndex == i,
isVideo: state.videosSelected,
),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -125,8 +128,19 @@ class _StudioSliderState extends State<StudioSlider> {
class _SliderIndicator extends StatelessWidget { class _SliderIndicator extends StatelessWidget {
final bool isCurrentIndex; final bool isCurrentIndex;
const _SliderIndicator({Key? key, required this.isCurrentIndex}) final bool isVideo;
: super(key: key); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -137,11 +151,10 @@ class _SliderIndicator extends StatelessWidget {
margin: const EdgeInsets.only(left: 4), margin: const EdgeInsets.only(left: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: Theme.of(context).colorScheme.focusedBorder, color: _color(context),
), ),
shape: BoxShape.circle, shape: BoxShape.circle,
color: color: isCurrentIndex ? _color(context) : null,
isCurrentIndex ? Theme.of(context).colorScheme.focusedBorder : null,
), ),
); );
} }

View File

@ -20,9 +20,7 @@ class StudioTabBar extends StatelessWidget {
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: state.videosSelected color: Theme.of(context).colorScheme.border,
? Theme.of(context).colorScheme.secondary
: Theme.of(context).colorScheme.primary,
), ),
borderRadius: DesignConfig.lowBorderRadius, borderRadius: DesignConfig.lowBorderRadius,
), ),
@ -88,19 +86,17 @@ class _StudioTypeButton extends StatelessWidget {
size: 32, size: 32,
color: _color(context), color: _color(context),
), ),
if (!isSelected) const SizedBox(height: 18), AnimatedContainer(
if (isSelected) duration: DesignConfig.lowAnimationDuration,
Container( width: isSelected ? 88 : 0,
width: 88, height: 1,
height: 1, color: _color(context),
color: _color(context), ),
), DidvanText(
if (isSelected) title,
DidvanText( style: Theme.of(context).textTheme.overline,
title, color: _color(context),
style: Theme.of(context).textTheme.overline, )
color: _color(context),
)
], ],
), ),
), ),

View File

@ -1,3 +1,4 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
@ -43,7 +44,10 @@ class _BookmarkButtonState extends State<BookmarkButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanIconButton( return DidvanIconButton(
gestureSize: widget.gestureSize, 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, icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () async { onPressed: () async {
bool confirm = false; bool confirm = false;

View File

@ -104,6 +104,9 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
const Spacer(), const Spacer(),
if (widget.isRadar) if (widget.isRadar)
BookmarkButton( BookmarkButton(
color: DesignConfig.isDark
? Theme.of(context).colorScheme.focusedBorder
: Theme.of(context).colorScheme.focused,
askForConfirmation: widget.hasUnmarkConfirmation, askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.item.marked, value: widget.item.marked,
onMarkChanged: (value) { onMarkChanged: (value) {
@ -143,6 +146,9 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
if (!widget.isRadar) const SizedBox(width: 12), if (!widget.isRadar) const SizedBox(width: 12),
if (!widget.isRadar) if (!widget.isRadar)
BookmarkButton( BookmarkButton(
color: DesignConfig.isDark
? Theme.of(context).colorScheme.focusedBorder
: Theme.of(context).colorScheme.focused,
askForConfirmation: widget.hasUnmarkConfirmation, askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.item.marked, value: widget.item.marked,
onMarkChanged: (value) { onMarkChanged: (value) {

View File

@ -5,6 +5,7 @@ import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.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/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
@ -127,7 +128,22 @@ class MultitypeOverview extends StatelessWidget {
DateTime.parse(item.createdAt).toPersianDateStr(), DateTime.parse(item.createdAt).toPersianDateStr(),
style: Theme.of(context).textTheme.overline, 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,
),
]
], ],
), ),
], ],

View File

@ -20,7 +20,8 @@ class DidvanAppBar extends StatelessWidget {
return Container( return Container(
height: appBarData.isSmall ? 56 : 72, height: appBarData.isSmall ? 56 : 72,
width: MediaQuery.of(context).size.width, 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( decoration: BoxDecoration(
border: hasBorder border: hasBorder
? Border( ? Border(

View File

@ -198,35 +198,42 @@ class DidvanBNB extends StatelessWidget {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
), ),
), ),
persistentHeader: Column( persistentHeader: GestureDetector(
crossAxisAlignment: CrossAxisAlignment.center, onVerticalDragUpdate: (details) {
children: [ if (details.delta.dy > 10) {
AudioPlayerWidget( Navigator.of(context).pop();
podcast: MediaService.currentPodcast!, }
), },
Container( child: Column(
width: MediaQuery.of(context).size.width, crossAxisAlignment: CrossAxisAlignment.center,
color: Theme.of(context).colorScheme.surface, children: [
child: Column( AudioPlayerWidget(
children: [ podcast: MediaService.currentPodcast!,
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),
],
), ),
), 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 expandableContent: state.appState == AppState.busy
? const SizedBox() ? const SizedBox()

View File

@ -10,6 +10,7 @@ class DidvanScaffold extends StatefulWidget {
final Color? backgroundColor; final Color? backgroundColor;
final bool reverse; final bool reverse;
final ScrollController? scrollController; final ScrollController? scrollController;
final bool showSliversFirst;
const DidvanScaffold({ const DidvanScaffold({
Key? key, Key? key,
@ -20,6 +21,7 @@ class DidvanScaffold extends StatefulWidget {
this.backgroundColor, this.backgroundColor,
this.reverse = false, this.reverse = false,
this.scrollController, this.scrollController,
this.showSliversFirst = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -60,7 +62,7 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
pinned: true, pinned: true,
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!), flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!),
), ),
if (widget.children != null) if (widget.children != null && !widget.showSliversFirst)
SliverPadding( SliverPadding(
padding: widget.padding, padding: widget.padding,
sliver: SliverList( sliver: SliverList(
@ -76,6 +78,16 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
padding: widget.padding, padding: widget.padding,
sliver: widget.slivers![i], 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) if (widget.reverse)
SliverToBoxAdapter( SliverToBoxAdapter(
child: SizedBox( child: SizedBox(

View File

@ -40,7 +40,9 @@ class SliverStateHandler<T extends CoreProvier> extends SliverList {
if (enableEmptyState && state.appState == AppState.idle) { if (enableEmptyState && state.appState == AppState.idle) {
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: centerEmptyState ? 120 : 20, top: centerEmptyState
? MediaQuery.of(context).size.height / 4
: 20,
bottom: 20, bottom: 20,
), ),
child: emptyState, child: emptyState,