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,18 +180,24 @@ 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(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: Container(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
alignment: Alignment.center, alignment: Alignment.center,
child: AspectRatio(aspectRatio: 9 / 16, child: page), child: AspectRatio(aspectRatio: 9 / 16, child: page),
),
); );
} }
return Container( return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: Container(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: SafeArea( child: SafeArea(
child: page, child: page,
top: false, 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,7 +43,6 @@ 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(
@ -55,7 +54,6 @@ class _NewsDetailsState extends State<NewsDetails> {
currentIndex: state.currentIndex, currentIndex: state.currentIndex,
), ),
), ),
if (state.news.isNotEmpty)
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,

View File

@ -43,7 +43,6 @@ 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(
@ -55,7 +54,6 @@ class _RadarDetailsState extends State<RadarDetails> {
currentIndex: state.currentIndex, currentIndex: state.currentIndex,
), ),
), ),
if (state.radars.isNotEmpty)
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,

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,9 +109,27 @@ 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,
), ),
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: [ children: [
SizedBox( SizedBox(
width: ds.width, width: ds.width,
@ -117,7 +137,8 @@ class _StudioDetailsState extends State<StudioDetails> {
child: Stack( child: Stack(
children: [ children: [
WebView( WebView(
backgroundColor: Theme.of(context).colorScheme.black, backgroundColor:
Theme.of(context).colorScheme.black,
allowsInlineMediaPlayback: true, allowsInlineMediaPlayback: true,
initialUrl: Uri.dataFromString( initialUrl: Uri.dataFromString(
''' '''
@ -172,9 +193,8 @@ class _StudioDetailsState extends State<StudioDetails> {
], ],
), ),
), ),
const SizedBox(height: 20), DetailsTabBar(
StudioDetailsWidget( isVideo: true,
scrollController: _scrollController,
onCommentsTabSelected: () => Future.delayed( onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100), const Duration(milliseconds: 100),
() => _scrollController.animateTo( () => _scrollController.animateTo(
@ -183,6 +203,14 @@ class _StudioDetailsState extends State<StudioDetails> {
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
), ),
),
],
),
),
],
children: [
StudioDetailsWidget(
scrollController: _scrollController,
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,79 +109,31 @@ 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,
), ),
),
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: [ children: [
if (kIsWeb)
const AspectRatio( const AspectRatio(
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
child: HtmlElementView(viewType: 'video'), child: HtmlElementView(viewType: 'video'),
), ),
if (!kIsWeb) DetailsTabBar(
SizedBox( isVideo: true,
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(
'''
<html>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=$_scaleInPortrait"
/>
<style>
* {
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),
StudioDetailsWidget(
scrollController: _scrollController,
onCommentsTabSelected: () => Future.delayed( onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100), const Duration(milliseconds: 100),
() => _scrollController.animateTo( () => _scrollController.animateTo(
@ -202,6 +142,14 @@ class _StudioDetailsState extends State<StudioDetails> {
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
), ),
),
],
),
),
],
children: [
StudioDetailsWidget(
scrollController: _scrollController,
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: [
const SizedBox(),
_TabItem(
icon: DidvanIcons.description_solid,
title: 'توضیحات',
onTap: () => state.selectedDetailsIndex = 0,
isSelected: state.selectedDetailsIndex == 0,
isVideo: _isVideo, isVideo: _isVideo,
onCommentsTabSelected: onCommentsTabSelected ?? () {},
), ),
_TabItem( if (state.selectedDetailsIndex != 1) const SizedBox(height: 16),
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,14 +86,12 @@ 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),
), ),
if (isSelected)
DidvanText( DidvanText(
title, title,
style: Theme.of(context).textTheme.overline, style: Theme.of(context).textTheme.overline,

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,7 +198,13 @@ class DidvanBNB extends StatelessWidget {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
), ),
), ),
persistentHeader: Column( persistentHeader: GestureDetector(
onVerticalDragUpdate: (details) {
if (details.delta.dy > 10) {
Navigator.of(context).pop();
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
AudioPlayerWidget( AudioPlayerWidget(
@ -228,6 +234,7 @@ class DidvanBNB extends StatelessWidget {
), ),
], ],
), ),
),
expandableContent: state.appState == AppState.busy expandableContent: state.appState == AppState.busy
? const SizedBox() ? const SizedBox()
: StudioDetailsWidget( : StudioDetailsWidget(

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,