D1APP-99 studio (beta 2)
This commit is contained in:
parent
0738072d5d
commit
5dd6c4205f
|
|
@ -3,6 +3,7 @@ import 'package:didvan/config/theme_data.dart';
|
|||
import 'package:didvan/providers/theme_provider.dart';
|
||||
import 'package:didvan/providers/user_provider.dart';
|
||||
import 'package:didvan/routes/route_generator.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -26,6 +27,9 @@ class Didvan extends StatelessWidget {
|
|||
ChangeNotifierProvider<ThemeProvider>(
|
||||
create: (context) => ThemeProvider(),
|
||||
),
|
||||
ChangeNotifierProvider<StudioDetailsState>(
|
||||
create: (context) => StudioDetailsState(),
|
||||
),
|
||||
],
|
||||
child: Consumer<ThemeProvider>(
|
||||
builder: (context, themeProvider, child) => MaterialApp(
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class OverviewData {
|
|||
createdAt: json['createdAt'],
|
||||
duration: json['duration'],
|
||||
type: json['type'] ?? '',
|
||||
marked: json['marked'] ?? false,
|
||||
marked: json['marked'] ?? true,
|
||||
media: json['media'],
|
||||
categories: json['categories'] != null
|
||||
? List<CategoryData>.from(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
class SliderData {
|
||||
final int id;
|
||||
final String title;
|
||||
final String image;
|
||||
final String media;
|
||||
|
||||
const SliderData({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.image,
|
||||
required this.media,
|
||||
});
|
||||
|
||||
factory SliderData.fromJson(Map<String, dynamic> json) => SliderData(
|
||||
id: json['id'],
|
||||
title: json['title'],
|
||||
image: json['image'],
|
||||
media: json['media'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'image': image,
|
||||
'media': media,
|
||||
};
|
||||
}
|
||||
|
|
@ -132,7 +132,7 @@ class UserProvider extends CoreProvier {
|
|||
final MapEntry? lastChange =
|
||||
_radarMarkQueue.lastWhereOrNull((item) => item.key == id);
|
||||
if (lastChange == null) return;
|
||||
final service = RequestService(RequestHelper.markRadar(id));
|
||||
final service = RequestService(RequestHelper.mark(id, 'radar'));
|
||||
if (lastChange.value) {
|
||||
await service.post();
|
||||
} else {
|
||||
|
|
@ -148,7 +148,7 @@ class UserProvider extends CoreProvier {
|
|||
final MapEntry? lastChange =
|
||||
_studioMarkQueue.lastWhereOrNull((item) => item.key == id);
|
||||
if (lastChange == null) return;
|
||||
final service = RequestService(RequestHelper.markStudio(id));
|
||||
final service = RequestService(RequestHelper.mark(id, 'studio'));
|
||||
if (lastChange.value) {
|
||||
await service.post();
|
||||
} else {
|
||||
|
|
@ -164,7 +164,7 @@ class UserProvider extends CoreProvier {
|
|||
final MapEntry? lastChange =
|
||||
_newsMarkQueue.lastWhereOrNull((item) => item.key == id);
|
||||
if (lastChange == null) return;
|
||||
final service = RequestService(RequestHelper.markNews(id));
|
||||
final service = RequestService(RequestHelper.mark(id, 'news'));
|
||||
if (lastChange.value) {
|
||||
await service.post();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart';
|
|||
import 'package:didvan/views/home/settings/general_settings/settings.dart';
|
||||
import 'package:didvan/views/home/settings/general_settings/settings_state.dart';
|
||||
import 'package:didvan/views/home/settings/profile/profile.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart'
|
||||
if (dart.library.io) 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart'
|
||||
if (dart.library.html) 'package:didvan/views/home/studio/studio_details/studio_details.web.dart';
|
||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||
import 'package:didvan/views/splash/splash.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
|
|
@ -64,9 +65,6 @@ class RouteGenerator {
|
|||
ChangeNotifierProvider<StudioState>(
|
||||
create: (context) => StudioState(),
|
||||
),
|
||||
ChangeNotifierProvider<StudioDetailsState>(
|
||||
create: (context) => StudioDetailsState(),
|
||||
),
|
||||
],
|
||||
child: const Home(),
|
||||
),
|
||||
|
|
@ -106,11 +104,8 @@ class RouteGenerator {
|
|||
);
|
||||
case Routes.studioDetails:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<StudioDetailsState>.value(
|
||||
value: (settings.arguments as Map<String, dynamic>)['state'],
|
||||
child: StudioDetails(
|
||||
pageData: settings.arguments as Map<String, dynamic>,
|
||||
),
|
||||
StudioDetails(
|
||||
pageData: settings.arguments as Map<String, dynamic>,
|
||||
),
|
||||
);
|
||||
case Routes.directList:
|
||||
|
|
@ -150,14 +145,15 @@ class RouteGenerator {
|
|||
child: Hashtag(tag: settings.arguments as Tag),
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.filteredBookmarks:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<FilteredBookmarksState>(
|
||||
create: (context) => FilteredBookmarksState(
|
||||
settings.arguments as String,
|
||||
(settings.arguments as Map<String, dynamic>)['type'],
|
||||
),
|
||||
child: const FilteredBookmarks(),
|
||||
child: FilteredBookmarks(
|
||||
onDeleted:
|
||||
(settings.arguments as Map<String, dynamic>)['onDeleted']),
|
||||
),
|
||||
);
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@ class RequestHelper {
|
|||
MapEntry('tags', _urlListConcatGenerator(ids)),
|
||||
]);
|
||||
|
||||
static String markRadar(int id) => _baseRadarUrl + '/$id/mark';
|
||||
static String radarComments(int id) => _baseRadarUrl + '/$id/comments';
|
||||
static String addRadarComment(int id) => _baseRadarUrl + '/$id/comments/add';
|
||||
static String feedbackRadarComment(int radarId, int id) =>
|
||||
_baseRadarUrl + '/$radarId/comments/$id/feedback';
|
||||
static String radarDetails(int id, RadarRequestArgs args) =>
|
||||
_baseRadarUrl +
|
||||
'/$id' +
|
||||
|
|
@ -79,11 +74,6 @@ class RequestHelper {
|
|||
MapEntry('categories', _urlListConcatGenerator(args.categories)),
|
||||
]);
|
||||
|
||||
static String markNews(int id) => _baseNewsUrl + '/$id/mark';
|
||||
static String newsComments(int id) => _baseNewsUrl + '/$id/comments';
|
||||
static String addNewsComment(int id) => _baseNewsUrl + '/$id/comments/add';
|
||||
static String feedbackNewsComment(int radarId, int id) =>
|
||||
_baseNewsUrl + '/$radarId/comments/$id/feedback';
|
||||
static String newsDetails(int id, NewsRequestArgs args) =>
|
||||
_baseNewsUrl +
|
||||
'/$id' +
|
||||
|
|
@ -102,12 +92,10 @@ class RequestHelper {
|
|||
MapEntry('search', args.search),
|
||||
]);
|
||||
|
||||
static String markStudio(int id) => _baseStudioUrl + '/$id/mark';
|
||||
static String studioComments(int id) => _baseStudioUrl + '/$id/comments';
|
||||
static String addStudioComment(int id) =>
|
||||
_baseStudioUrl + '/$id/comments/add';
|
||||
static String feedbackStudioComment(int studioId, int id) =>
|
||||
_baseStudioUrl + '/$studioId/comments/$id/feedback';
|
||||
static String sudioSlider(String type) =>
|
||||
_baseStudioUrl +
|
||||
'/slider' +
|
||||
_urlConcatGenerator([MapEntry('type', type)]);
|
||||
static String studioDetails(int id, StudioRequestArgs args) =>
|
||||
_baseStudioUrl +
|
||||
'/$id' +
|
||||
|
|
@ -126,6 +114,14 @@ class RequestHelper {
|
|||
MapEntry('search', args.search),
|
||||
]);
|
||||
|
||||
static String mark(int id, String type) => baseUrl + '/$type/$id/mark';
|
||||
static String comments(int id, String type) =>
|
||||
baseUrl + '/$type/$id/comments';
|
||||
static String feedback(int id, int commentId, String type) =>
|
||||
baseUrl + '/$type/$id/comments/$commentId/feedback';
|
||||
static String addComment(int id, String type) =>
|
||||
baseUrl + '/$type/$id/comments/add';
|
||||
|
||||
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
|
||||
String result = '';
|
||||
additions.removeWhere(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/constants/assets.dart';
|
||||
import 'package:didvan/models/view/app_bar_data.dart';
|
||||
import 'package:didvan/views/home/comments/comments_state.dart';
|
||||
import 'package:didvan/views/home/comments/widgets/comment_item.dart';
|
||||
|
|
@ -9,6 +10,7 @@ import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
|||
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -32,7 +34,7 @@ class _CommentsState extends State<Comments> {
|
|||
void initState() {
|
||||
final state = context.read<CommentsState>();
|
||||
state.itemId = widget.pageData['id'];
|
||||
state.isRadar = widget.pageData['isRadar'];
|
||||
state.type = widget.pageData['type'];
|
||||
state.onCommentsChanged = widget.pageData['onCommentsChanged'];
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
|
|
@ -41,6 +43,8 @@ class _CommentsState extends State<Comments> {
|
|||
super.initState();
|
||||
}
|
||||
|
||||
bool get _isPage => widget.pageData['isPage'] != false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomViewInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
|
@ -56,11 +60,13 @@ class _CommentsState extends State<Comments> {
|
|||
children: [
|
||||
DidvanScaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
appBarData: AppBarData(
|
||||
hasBack: true,
|
||||
title: 'نظرات',
|
||||
subtitle: widget.pageData['title'],
|
||||
),
|
||||
appBarData: _isPage
|
||||
? AppBarData(
|
||||
hasBack: true,
|
||||
title: 'نظرات',
|
||||
subtitle: widget.pageData['title'],
|
||||
)
|
||||
: null,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
|
||||
slivers: [
|
||||
Consumer<CommentsState>(
|
||||
|
|
@ -71,6 +77,11 @@ class _CommentsState extends State<Comments> {
|
|||
itemPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
childCount: state.comments.length,
|
||||
placeholder: const _CommentPlaceholder(),
|
||||
enableEmptyState: state.comments.isEmpty,
|
||||
emptyState: EmptyState(
|
||||
asset: Assets.emptyChat,
|
||||
title: 'اولین نظر را بنویسید...',
|
||||
),
|
||||
builder: (context, state, index) => Comment(
|
||||
focusNode: _focusNode,
|
||||
comment: state.comments[index],
|
||||
|
|
|
|||
|
|
@ -17,19 +17,17 @@ class CommentsState extends CoreProvier {
|
|||
bool showReplyBox = false;
|
||||
late void Function(int count) onCommentsChanged;
|
||||
int _count = 0;
|
||||
late String type;
|
||||
|
||||
final List<CommentData> comments = [];
|
||||
final Map<int, MapEntry<bool, bool>> _feedbackQueue = {};
|
||||
|
||||
bool isRadar = true;
|
||||
int itemId = 0;
|
||||
|
||||
Future<void> getComments() async {
|
||||
appState = AppState.busy;
|
||||
final service = RequestService(
|
||||
isRadar
|
||||
? RequestHelper.radarComments(itemId)
|
||||
: RequestHelper.newsComments(itemId),
|
||||
RequestHelper.comments(itemId, type),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
|
|
@ -52,13 +50,12 @@ class CommentsState extends CoreProvier {
|
|||
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||
if (!_feedbackQueue.containsKey(id)) return;
|
||||
final service = RequestService(
|
||||
isRadar
|
||||
? RequestHelper.feedbackRadarComment(itemId, id)
|
||||
: RequestHelper.feedbackNewsComment(itemId, id),
|
||||
body: {
|
||||
'like': _feedbackQueue[id]!.key,
|
||||
'dislike': _feedbackQueue[id]!.value,
|
||||
});
|
||||
RequestHelper.feedback(itemId, id, type),
|
||||
body: {
|
||||
'like': _feedbackQueue[id]!.key,
|
||||
'dislike': _feedbackQueue[id]!.value,
|
||||
},
|
||||
);
|
||||
await service.put();
|
||||
_feedbackQueue.remove(id);
|
||||
});
|
||||
|
|
@ -119,10 +116,9 @@ class CommentsState extends CoreProvier {
|
|||
update();
|
||||
body.addAll({'text': text});
|
||||
final service = RequestService(
|
||||
isRadar
|
||||
? RequestHelper.addRadarComment(itemId)
|
||||
: RequestHelper.addNewsComment(itemId),
|
||||
body: body);
|
||||
RequestHelper.addComment(itemId, type),
|
||||
body: body,
|
||||
);
|
||||
|
||||
await service.post();
|
||||
if (service.isSuccess) {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,15 @@ class _BookmarksState extends State<Bookmarks> {
|
|||
|
||||
void _onCategorySelected(String type) {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type);
|
||||
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: {
|
||||
'type': type,
|
||||
'onDeleted': (int id) {
|
||||
final state = context.read<BookmarksState>();
|
||||
state.bookmarks
|
||||
.removeWhere((element) => element.id == id && element.type == type);
|
||||
state.update();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void _onChanged(String value) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:didvan/models/requests/studio.dart';
|
||||
import 'package:didvan/models/view/app_bar_data.dart';
|
||||
import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/news.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/podcast.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/radar.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/video.dart';
|
||||
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
||||
|
|
@ -9,7 +12,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilteredBookmarks extends StatefulWidget {
|
||||
const FilteredBookmarks({Key? key}) : super(key: key);
|
||||
final void Function(int id)? onDeleted;
|
||||
const FilteredBookmarks({Key? key, this.onDeleted}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FilteredBookmarksState createState() => _FilteredBookmarksState();
|
||||
|
|
@ -67,8 +71,26 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
|
|||
hasUnmarkConfirmation: true,
|
||||
);
|
||||
}
|
||||
return NewsOverview(
|
||||
news: state.bookmarks[index],
|
||||
if (state.type == 'news') {
|
||||
return NewsOverview(
|
||||
news: state.bookmarks[index],
|
||||
onMarkChanged: _onBookmarkChanged,
|
||||
hasUnmarkConfirmation: true,
|
||||
);
|
||||
}
|
||||
if (state.type == 'podcast') {
|
||||
return PodcastOverview(
|
||||
studioRequestArgs:
|
||||
const StudioRequestArgs(page: 0, type: 'podcast'),
|
||||
podcast: state.bookmarks[index],
|
||||
onMarkChanged: _onBookmarkChanged,
|
||||
hasUnmarkConfirmation: true,
|
||||
);
|
||||
}
|
||||
return VideoOverview(
|
||||
studioRequestArgs:
|
||||
const StudioRequestArgs(page: 0, type: 'video'),
|
||||
video: state.bookmarks[index],
|
||||
onMarkChanged: _onBookmarkChanged,
|
||||
hasUnmarkConfirmation: true,
|
||||
);
|
||||
|
|
@ -85,5 +107,6 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
|
|||
if (value) return;
|
||||
final state = context.read<FilteredBookmarksState>();
|
||||
state.onMarkChanged(id, false);
|
||||
widget.onDeleted?.call(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,14 +56,12 @@ class FilteredBookmarksState extends CoreProvier {
|
|||
}
|
||||
|
||||
void onMarkChanged(int id, bool value) {
|
||||
switch (type) {
|
||||
case 'radar':
|
||||
UserProvider.changeRadarMark(id, value);
|
||||
break;
|
||||
case 'news':
|
||||
UserProvider.changeNewsMark(id, value);
|
||||
break;
|
||||
default:
|
||||
if (type == 'radar') {
|
||||
UserProvider.changeRadarMark(id, value);
|
||||
} else if (type == 'news') {
|
||||
UserProvider.changeNewsMark(id, value);
|
||||
} else {
|
||||
UserProvider.changeStudioMark(id, value);
|
||||
}
|
||||
bookmarks.removeWhere((element) => element.id == id);
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/requests/studio.dart';
|
||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||
import 'package:didvan/views/home/studio/widgets/slider.dart';
|
||||
|
|
@ -9,6 +13,7 @@ import 'package:didvan/views/home/widgets/logo_app_bar.dart';
|
|||
import 'package:didvan/views/home/widgets/overview/podcast.dart';
|
||||
import 'package:didvan/views/home/widgets/overview/video.dart';
|
||||
import 'package:didvan/views/home/widgets/search_field.dart';
|
||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/radial_button.dart';
|
||||
|
|
@ -26,6 +31,7 @@ class Studio extends StatefulWidget {
|
|||
|
||||
class _StudioState extends State<Studio> {
|
||||
final _focusNode = FocusNode();
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -46,7 +52,10 @@ class _StudioState extends State<Studio> {
|
|||
EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
||||
child: DidvanIconButton(
|
||||
icon: DidvanIcons.bookmark_regular,
|
||||
onPressed: () {},
|
||||
onPressed: () => Navigator.of(context).pushNamed(
|
||||
Routes.filteredBookmarks,
|
||||
arguments: context.read<StudioState>().type,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -59,14 +68,20 @@ class _StudioState extends State<Studio> {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SearchField(
|
||||
title: 'جستجو در استودیو',
|
||||
onChanged: (value) {},
|
||||
title: 'استودیو',
|
||||
onChanged: _onChanged,
|
||||
focusNode: _focusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: StudioSlider(),
|
||||
SliverToBoxAdapter(
|
||||
child: Consumer<StudioState>(
|
||||
builder: (context, state, child) => AnimatedVisibility(
|
||||
isVisible: !state.searching,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
child: const StudioSlider(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
|
|
@ -82,7 +97,13 @@ class _StudioState extends State<Studio> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const ItemTitle(title: 'تازهترینها'),
|
||||
Consumer<StudioState>(
|
||||
builder: (context, state, child) => AnimatedVisibility(
|
||||
isVisible: !state.searching,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
child: ItemTitle(title: state.orderString),
|
||||
),
|
||||
),
|
||||
DidvanIconButton(
|
||||
gestureSize: 36,
|
||||
icon: DidvanIcons.sort_regular,
|
||||
|
|
@ -108,7 +129,6 @@ class _StudioState extends State<Studio> {
|
|||
onMarkChanged: state.changeMark,
|
||||
hasUnmarkConfirmation: false,
|
||||
video: state.studios[index],
|
||||
onCommentsChanged: state.onCommentsChanged,
|
||||
studioRequestArgs: StudioRequestArgs(
|
||||
page: state.page,
|
||||
order: state.order,
|
||||
|
|
@ -127,13 +147,25 @@ class _StudioState extends State<Studio> {
|
|||
),
|
||||
),
|
||||
childCount: state.studios.length,
|
||||
onRetry: () => state.getStudioOverviews(page: 1),
|
||||
onRetry: () => state.getStudios(page: 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onChanged(String value) {
|
||||
final state = context.read<StudioState>();
|
||||
if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) {
|
||||
return;
|
||||
}
|
||||
_timer?.cancel();
|
||||
_timer = Timer(const Duration(seconds: 1), () {
|
||||
state.search = value;
|
||||
state.getStudios(page: 1);
|
||||
});
|
||||
}
|
||||
|
||||
void _showSortDialog() {
|
||||
final state = context.read<StudioState>();
|
||||
ActionSheetUtils.showBottomSheet(
|
||||
|
|
@ -142,7 +174,7 @@ class _StudioState extends State<Studio> {
|
|||
builder: (context, setState) => Column(
|
||||
children: [
|
||||
DidvanRadialButton(
|
||||
title: 'جدیدترینها',
|
||||
title: 'تازهترینها',
|
||||
onSelected: () => setState(
|
||||
() => state.selectedSortTypeIndex = 0,
|
||||
),
|
||||
|
|
@ -171,7 +203,7 @@ class _StudioState extends State<Studio> {
|
|||
titleIcon: DidvanIcons.sort_regular,
|
||||
hasDismissButton: false,
|
||||
confrimTitle: 'مرتب سازی',
|
||||
onConfirmed: () => state.getStudioOverviews(page: 1),
|
||||
onConfirmed: () => state.getStudios(page: 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/studio_details_widget.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:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class StudioDetails extends StatefulWidget {
|
||||
|
|
@ -20,6 +24,7 @@ class StudioDetails extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _StudioDetailsState extends State<StudioDetails> {
|
||||
final _scrollController = ScrollController();
|
||||
bool _isFullScreen = false;
|
||||
bool _isInit = true;
|
||||
|
||||
|
|
@ -29,14 +34,12 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
@override
|
||||
void initState() {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => state.getStudioDetails(widget.pageData['id']),
|
||||
);
|
||||
|
||||
state.args = widget.pageData['args'];
|
||||
if (Platform.isAndroid) WebView.platform = AndroidWebView();
|
||||
if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -77,39 +80,44 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
_scaleInPortrait = _dwInPortrait / 576;
|
||||
_isInit = false;
|
||||
}
|
||||
|
||||
return Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
state: state,
|
||||
onRetry: () => state.getStudioDetails(state.currentStudio.id),
|
||||
builder: (context, state) => state.studios.isEmpty
|
||||
? const SizedBox()
|
||||
: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_isFullScreen) {
|
||||
await _changeFullSceen(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: DidvanScaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
padding: EdgeInsets.zero,
|
||||
appBarData: _isFullScreen
|
||||
? null
|
||||
: AppBarData(
|
||||
isSmall: true,
|
||||
title: state.currentStudio.title,
|
||||
),
|
||||
children: [
|
||||
SizedBox(
|
||||
width: ds.width,
|
||||
height: _isFullScreen ? ds.height : ds.width * 9 / 16,
|
||||
child: Stack(
|
||||
children: [
|
||||
WebView(
|
||||
allowsInlineMediaPlayback: true,
|
||||
initialUrl: Uri.dataFromString(
|
||||
'''
|
||||
builder: (context, state) {
|
||||
if (state.studios.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_isFullScreen) {
|
||||
await _changeFullSceen(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: DidvanScaffold(
|
||||
scrollController: _scrollController,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
padding: EdgeInsets.zero,
|
||||
appBarData: _isFullScreen
|
||||
? null
|
||||
: AppBarData(
|
||||
isSmall: true,
|
||||
title: state.currentStudio.title,
|
||||
),
|
||||
children: [
|
||||
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(
|
||||
'''
|
||||
<html>
|
||||
<head>
|
||||
<meta
|
||||
|
|
@ -142,28 +150,38 @@ class _StudioDetailsState extends State<StudioDetails> {
|
|||
</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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
StudioDetailsWidget(
|
||||
onCommentsTabSelected: () => _scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
studio: state.currentStudio,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
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/studio_details_widget.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<String, dynamic> pageData;
|
||||
|
||||
const StudioDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StudioDetails> createState() => _StudioDetailsState();
|
||||
}
|
||||
|
||||
class _StudioDetailsState extends State<StudioDetails> {
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
bool _isFullScreen = false;
|
||||
bool _isInit = true;
|
||||
|
||||
double _dwInPortrait = 0;
|
||||
double _scaleInPortrait = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => state.getStudioDetails(widget.pageData['id']),
|
||||
);
|
||||
state.args = widget.pageData['args'];
|
||||
if (!kIsWeb && Platform.isAndroid) WebView.platform = AndroidWebView();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _changeFullSceen(bool value) async {
|
||||
if (value) {
|
||||
await SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: [],
|
||||
);
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.black,
|
||||
),
|
||||
);
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.landscapeLeft],
|
||||
);
|
||||
} else {
|
||||
await SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top],
|
||||
);
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp],
|
||||
);
|
||||
DesignConfig.updateSystemUiOverlayStyle();
|
||||
}
|
||||
setState(() {
|
||||
_isFullScreen = value;
|
||||
});
|
||||
}
|
||||
|
||||
@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<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
state: state,
|
||||
onRetry: () => state.getStudioDetails(state.currentStudio.id),
|
||||
builder: (context, state) {
|
||||
if (state.studios.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (kIsWeb) {
|
||||
// ignore: undefined_prefixed_name
|
||||
ui.platformViewRegistry.registerViewFactory(
|
||||
"video",
|
||||
(int viewId) => html.IFrameElement()
|
||||
..allowFullscreen = true
|
||||
..src = Uri.dataFromString(
|
||||
'<style>*{padding: 0 ; margin: 0; background: black;}</style>' +
|
||||
state.currentStudio.media,
|
||||
mimeType: 'text/html',
|
||||
).toString()
|
||||
..style.border = 'none',
|
||||
);
|
||||
}
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_isFullScreen) {
|
||||
await _changeFullSceen(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: DidvanScaffold(
|
||||
scrollController: _scrollController,
|
||||
padding: EdgeInsets.zero,
|
||||
appBarData: _isFullScreen
|
||||
? null
|
||||
: AppBarData(
|
||||
isSmall: true,
|
||||
title: state.currentStudio.title,
|
||||
),
|
||||
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(
|
||||
'''
|
||||
<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.currentStudio.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(
|
||||
onCommentsTabSelected: () => _scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
studio: state.currentStudio,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ class StudioDetailsState extends CoreProvier {
|
|||
int _selectedDetailsIndex = 0;
|
||||
bool isFetchingNewItem = false;
|
||||
final List<int> relatedQueue = [];
|
||||
bool currentTypeIsVideo = true;
|
||||
|
||||
int _currentIndex = 0;
|
||||
int get currentIndex => _currentIndex;
|
||||
|
|
@ -24,6 +25,9 @@ class StudioDetailsState extends CoreProvier {
|
|||
int get selectedDetailsIndex => _selectedDetailsIndex;
|
||||
set selectedDetailsIndex(int value) {
|
||||
_selectedDetailsIndex = value;
|
||||
if (value == 2) {
|
||||
getRelatedContents();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
@ -35,12 +39,16 @@ class StudioDetailsState extends CoreProvier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> getStudioDetails(int id,
|
||||
{bool? isForward, StudioRequestArgs? args}) async {
|
||||
Future<void> getStudioDetails(
|
||||
int id, {
|
||||
bool? isForward,
|
||||
StudioRequestArgs? args,
|
||||
}) async {
|
||||
if (args != null) {
|
||||
this.args = args;
|
||||
}
|
||||
if (isForward == null) {
|
||||
_selectedDetailsIndex = 0;
|
||||
appState = AppState.busy;
|
||||
} else {
|
||||
isFetchingNewItem = true;
|
||||
|
|
@ -49,22 +57,16 @@ class StudioDetailsState extends CoreProvier {
|
|||
final service = RequestService(RequestHelper.studioDetails(id, this.args));
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
studios.clear();
|
||||
final result = service.result;
|
||||
final studio = StudioDetailsData.fromJson(result['studio']);
|
||||
await _handlePodcastPlayback(studio);
|
||||
if (this.args.page == 0) {
|
||||
studios.add(studio);
|
||||
initialIndex = 0;
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
if (this.args.type == 'podcast') {
|
||||
MediaService.currentPodcast = studio;
|
||||
MediaService.podcastPlaylistArgs = args;
|
||||
await MediaService.handleAudioPlayback(
|
||||
audioSource: studio.media,
|
||||
isVoiceMessage: false,
|
||||
);
|
||||
}
|
||||
|
||||
StudioDetailsData? prevStudio;
|
||||
if (result['prevStudio'].isNotEmpty) {
|
||||
|
|
@ -108,13 +110,24 @@ class StudioDetailsState extends CoreProvier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _handlePodcastPlayback(StudioDetailsData studio) async {
|
||||
if (args.type == 'podcast') {
|
||||
MediaService.currentPodcast = studio;
|
||||
MediaService.podcastPlaylistArgs = args;
|
||||
await MediaService.handleAudioPlayback(
|
||||
audioSource: studio.media,
|
||||
isVoiceMessage: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getRelatedContents() async {
|
||||
if (currentStudio.relatedContents.isNotEmpty) return;
|
||||
relatedQueue.add(currentStudio.id);
|
||||
final service = RequestService(RequestHelper.tag(
|
||||
ids: currentStudio.tags.map((tag) => tag.id).toList(),
|
||||
itemId: currentStudio.id,
|
||||
type: 'studio',
|
||||
type: currentStudio.media.contains('iframe') ? 'video' : 'podcast',
|
||||
));
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
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:didvan/views/widgets/state_handlers/state_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class StudioDetailsWidget extends StatelessWidget {
|
||||
const StudioDetailsWidget({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
onRetry: () {},
|
||||
state: state,
|
||||
builder: (context, state) => Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_TabItem(
|
||||
icon: DidvanIcons.description_solid,
|
||||
title: 'توضیحات',
|
||||
onTap: () => state.selectedDetailsIndex = 0,
|
||||
isSelected: state.selectedDetailsIndex == 0,
|
||||
),
|
||||
_TabItem(
|
||||
icon: DidvanIcons.chats_solid,
|
||||
title: 'نظرات',
|
||||
onTap: () => state.selectedDetailsIndex = 1,
|
||||
isSelected: state.selectedDetailsIndex == 1,
|
||||
),
|
||||
_TabItem(
|
||||
icon: DidvanIcons.puzzle_solid,
|
||||
title: 'مطالب مرتبط',
|
||||
onTap: () => state.selectedDetailsIndex = 2,
|
||||
isSelected: state.selectedDetailsIndex == 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TabItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final VoidCallback onTap;
|
||||
final bool isSelected;
|
||||
const _TabItem({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
required this.isSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
Color? _color(context) =>
|
||||
isSelected ? Theme.of(context).colorScheme.focusedBorder : null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: _color(context),
|
||||
),
|
||||
Container(
|
||||
width: 64,
|
||||
height: 1,
|
||||
color: _color(context),
|
||||
),
|
||||
DidvanText(
|
||||
title,
|
||||
color: _color(context),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
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/widgets/overview/multitype.dart';
|
||||
import 'package:didvan/views/home/widgets/tag_item.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class StudioDetailsWidget extends StatelessWidget {
|
||||
final StudioDetailsData studio;
|
||||
final VoidCallback onCommentsTabSelected;
|
||||
const StudioDetailsWidget({
|
||||
Key? key,
|
||||
required this.studio,
|
||||
required this.onCommentsTabSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
bool get _isVideo => studio.media.contains('ifram');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ds = MediaQuery.of(context).size;
|
||||
return Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
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),
|
||||
StateHandler<StudioDetailsState>(
|
||||
onRetry: () {},
|
||||
state: state,
|
||||
builder: (context, state) {
|
||||
if (state.selectedDetailsIndex == 0) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DidvanText(state.currentStudio.description),
|
||||
if (studio.tags.isNotEmpty) const SizedBox(height: 20),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (var i = 0; i < studio.tags.length; i++)
|
||||
TagItem(tag: studio.tags[i]),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
if (state.studios.length != state.currentIndex + 1)
|
||||
_StudioPreview(
|
||||
isNext: true,
|
||||
studio: state.studios[state.currentIndex + 1]!,
|
||||
),
|
||||
if (state.currentIndex != 0)
|
||||
_StudioPreview(
|
||||
isNext: false,
|
||||
studio: state.studios[state.currentIndex - 1]!,
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state.selectedDetailsIndex == 1) {
|
||||
return ChangeNotifierProvider<CommentsState>(
|
||||
create: (context) => CommentsState(),
|
||||
child: SizedBox(
|
||||
height: ds.height - 180,
|
||||
child: Comments(
|
||||
pageData: {
|
||||
'id': studio.id,
|
||||
'type': 'studio',
|
||||
'title': studio.title,
|
||||
'onCommentsChanged': state.onCommentsChanged,
|
||||
'isPage': false,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
if (studio.relatedContents.isEmpty)
|
||||
for (var i = 0; i < 3; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: MultitypeOverview.placeholder,
|
||||
),
|
||||
for (var i = 0; i < studio.relatedContents.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: MultitypeOverview(
|
||||
item: studio.relatedContents[i],
|
||||
onMarkChanged: (id, value) {},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const _StudioPreview({Key? key, required this.isNext, required this.studio})
|
||||
: super(key: key);
|
||||
|
||||
String get _previewTitle {
|
||||
if (studio.media.contains('iframe')) {
|
||||
return 'ویدئو ${isNext ? 'بعدی' : 'قبلی'} ';
|
||||
}
|
||||
return 'پادکست ${isNext ? 'بعدی' : 'قبلی'} ';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
state.getStudioDetails(studio.id, args: state.args);
|
||||
},
|
||||
child: Container(
|
||||
width: 88,
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: studio.image,
|
||||
aspectRatio: 1 / 1,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Icon(
|
||||
isNext
|
||||
? DidvanIcons.angle_right_regular
|
||||
: DidvanIcons.angle_left_regular,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DidvanText(
|
||||
_previewTitle,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DidvanText(
|
||||
studio.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.overline,
|
||||
color: Theme.of(context).colorScheme.caption,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/overview_data.dart';
|
||||
import 'package:didvan/models/requests/studio.dart';
|
||||
import 'package:didvan/models/slider_data.dart';
|
||||
import 'package:didvan/providers/core_provider.dart';
|
||||
import 'package:didvan/providers/user_provider.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
|
|
@ -8,9 +9,10 @@ import 'package:didvan/services/network/request_helper.dart';
|
|||
|
||||
class StudioState extends CoreProvier {
|
||||
final List<OverviewData> studios = [];
|
||||
final List<SliderData> sliders = [];
|
||||
|
||||
String? search;
|
||||
String? lastSearch;
|
||||
String search = '';
|
||||
String lastSearch = '';
|
||||
int page = 1;
|
||||
int lastPage = 1;
|
||||
|
||||
|
|
@ -20,21 +22,13 @@ class StudioState extends CoreProvier {
|
|||
|
||||
bool get videosSelected => _videosSelected;
|
||||
|
||||
bool get searching => search.isNotEmpty;
|
||||
|
||||
set videosSelected(bool value) {
|
||||
if (_videosSelected == value) return;
|
||||
_videosSelected = value;
|
||||
studios.clear();
|
||||
getStudioOverviews(page: page);
|
||||
}
|
||||
|
||||
void init() {
|
||||
search = '';
|
||||
lastSearch = '';
|
||||
_videosSelected = true;
|
||||
selectedSortTypeIndex = 0;
|
||||
Future.delayed(Duration.zero, () {
|
||||
getStudioOverviews(page: 1);
|
||||
});
|
||||
_getSliders();
|
||||
getStudios(page: page);
|
||||
}
|
||||
|
||||
String get order {
|
||||
|
|
@ -43,13 +37,46 @@ class StudioState extends CoreProvier {
|
|||
return 'comment';
|
||||
}
|
||||
|
||||
String get orderString {
|
||||
if (selectedSortTypeIndex == 0) return 'تازهترینها';
|
||||
if (selectedSortTypeIndex == 1) return 'پربازدیدترینها';
|
||||
return 'پربحثنرینها';
|
||||
}
|
||||
|
||||
String get type {
|
||||
if (videosSelected) return 'video';
|
||||
return 'podcast';
|
||||
}
|
||||
|
||||
Future<void> getStudioOverviews({required int page}) async {
|
||||
void init() {
|
||||
search = '';
|
||||
lastSearch = '';
|
||||
_videosSelected = true;
|
||||
selectedSortTypeIndex = 0;
|
||||
Future.delayed(Duration.zero, () {
|
||||
_getSliders();
|
||||
getStudios(page: 1);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _getSliders() async {
|
||||
final service = RequestService(
|
||||
RequestHelper.sudioSlider(type),
|
||||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
sliders.clear();
|
||||
final sliderItems = service.result['studios'];
|
||||
for (var i = 0; i < sliderItems.length; i++) {
|
||||
sliders.add(SliderData.fromJson(sliderItems[i]));
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getStudios({required int page}) async {
|
||||
this.page = page;
|
||||
lastSearch = search;
|
||||
if (page == 1) {
|
||||
appState = AppState.busy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,92 @@
|
|||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class StudioSlider extends StatelessWidget {
|
||||
class StudioSlider extends StatefulWidget {
|
||||
const StudioSlider({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StudioSlider> createState() => _StudioSliderState();
|
||||
}
|
||||
|
||||
class _StudioSliderState extends State<StudioSlider> {
|
||||
int selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.watch<StudioState>();
|
||||
return Column(
|
||||
children: [
|
||||
CarouselSlider(
|
||||
items: [
|
||||
Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
|
||||
Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
|
||||
Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
|
||||
Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
|
||||
for (var i = 0; i < state.sliders.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: state.appState == AppState.busy
|
||||
? const ShimmerPlaceholder()
|
||||
: Stack(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
borderRadius: DesignConfig.mediumBorderRadius,
|
||||
imageUrl: state.sliders[i].image,
|
||||
width: double.infinity,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: (state.videosSelected
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryDisabled
|
||||
: Theme.of(context).colorScheme.focused)
|
||||
.withOpacity(0.9),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
bottom: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
child: DidvanText(
|
||||
state.sliders[i].title,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
options: CarouselOptions(
|
||||
onPageChanged: (index, reason) => setState(
|
||||
() => selectedIndex = index,
|
||||
),
|
||||
viewportFraction: 0.94,
|
||||
aspectRatio: 16 / 9,
|
||||
autoPlay: true,
|
||||
),
|
||||
),
|
||||
Row(),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
for (var i = 0; i < state.sliders.length; i++)
|
||||
_SliderIndicator(isCurrentIndex: selectedIndex == i),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -35,9 +99,11 @@ class _SliderIndicator extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return AnimatedContainer(
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
height: 8,
|
||||
width: 8,
|
||||
margin: const EdgeInsets.only(left: 4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.focusedBorder,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/studio_details_data.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
|
||||
import 'package:didvan/views/home/widgets/bookmark_button.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
|
|
@ -10,6 +13,7 @@ import 'package:didvan/views/widgets/didvan/text.dart';
|
|||
import 'package:didvan/views/widgets/ink_wrapper.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AudioPlayerWidget extends StatelessWidget {
|
||||
final StudioDetailsData podcast;
|
||||
|
|
@ -77,8 +81,13 @@ class AudioPlayerWidget extends StatelessWidget {
|
|||
const DidvanText('30', isEnglishFont: true),
|
||||
],
|
||||
),
|
||||
_PlayPouseAnimatedIcon(
|
||||
audioSource: podcast.media,
|
||||
StreamBuilder<bool>(
|
||||
stream: MediaService.audioPlayer.playingStream,
|
||||
builder: (context, snapshot) {
|
||||
return _PlayPouseAnimatedIcon(
|
||||
audioSource: podcast.media,
|
||||
);
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
|
|
@ -88,8 +97,10 @@ class AudioPlayerWidget extends StatelessWidget {
|
|||
onPressed: () {
|
||||
MediaService.audioPlayer.seek(
|
||||
Duration(
|
||||
seconds:
|
||||
MediaService.audioPlayer.position.inSeconds - 10,
|
||||
seconds: max(
|
||||
0,
|
||||
MediaService.audioPlayer.position.inSeconds - 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -100,7 +111,8 @@ class AudioPlayerWidget extends StatelessWidget {
|
|||
BookmarkButton(
|
||||
gestureSize: 48,
|
||||
value: podcast.marked,
|
||||
onMarkChanged: (value) {},
|
||||
onMarkChanged: (value) =>
|
||||
context.read<StudioState>().changeMark(podcast.id, value),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -123,6 +135,12 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
|
|||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _PlayPouseAnimatedIcon oldWidget) {
|
||||
_handleAnimation();
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -130,8 +148,13 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
|
|||
vsync: this,
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleAnimation() {
|
||||
if (MediaService.audioPlayer.playing) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,11 +167,7 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
|
|||
audioSource: widget.audioSource,
|
||||
isVoiceMessage: false,
|
||||
);
|
||||
if (MediaService.audioPlayer.playing) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_animationController.reverse();
|
||||
}
|
||||
_handleAnimation();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/widgets/studio_details.dart';
|
||||
import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart';
|
||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_player_widget.dart';
|
||||
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
|
|
@ -179,46 +181,65 @@ class DidvanBNB extends StatelessWidget {
|
|||
final sheetKey = GlobalKey<ExpandableBottomSheetState>();
|
||||
bool isExpanded = false;
|
||||
final detailsState = context.read<StudioDetailsState>();
|
||||
final state = context.read<StudioState>();
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ChangeNotifierProvider<StudioDetailsState>.value(
|
||||
value: detailsState,
|
||||
child: ExpandableBottomSheet(
|
||||
key: sheetKey,
|
||||
background: const SizedBox(),
|
||||
persistentHeader: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
AudioPlayerWidget(
|
||||
podcast: MediaService.currentPodcast!,
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
builder: (context) => ChangeNotifierProvider<StudioState>.value(
|
||||
value: state,
|
||||
child: Consumer<StudioDetailsState>(
|
||||
builder: (context, state, child) => ExpandableBottomSheet(
|
||||
key: sheetKey,
|
||||
background: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
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: 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()
|
||||
: StudioDetailsWidget(
|
||||
studio: detailsState.currentStudio,
|
||||
onCommentsTabSelected: () {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
sheetKey.currentState?.expand,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
expandableContent: const StudioDetailsWidget(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
|||
Routes.comments,
|
||||
arguments: {
|
||||
'id': widget.item.id,
|
||||
'isRadar': widget.isRadar,
|
||||
'type': widget.isRadar ? 'radar' : 'news',
|
||||
'title': widget.item.title,
|
||||
'onCommentsChanged': widget.onCommentsChanged,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ import 'package:didvan/constants/app_icons.dart';
|
|||
import 'package:didvan/models/overview_data.dart';
|
||||
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/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';
|
||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MultitypeOverview extends StatelessWidget {
|
||||
final OverviewData item;
|
||||
|
|
@ -23,20 +26,60 @@ class MultitypeOverview extends StatelessWidget {
|
|||
this.hasUnmarkConfirmation = false,
|
||||
}) : super(key: key);
|
||||
|
||||
get _targetPageArgs {
|
||||
if (item.type == 'radar') {
|
||||
return const RadarRequestArgs(page: 0);
|
||||
}
|
||||
if (item.type == 'news') {
|
||||
return const NewsRequestArgs(page: 0);
|
||||
}
|
||||
return StudioRequestArgs(page: 0, type: item.type);
|
||||
}
|
||||
|
||||
String get _targetPageRouteName {
|
||||
if (item.type == 'radar') {
|
||||
return Routes.radarDetails;
|
||||
}
|
||||
if (item.type == 'news') {
|
||||
return Routes.newsDetails;
|
||||
}
|
||||
return Routes.studioDetails;
|
||||
}
|
||||
|
||||
IconData get _icon {
|
||||
if (item.type == 'radar') {
|
||||
return DidvanIcons.radar_light;
|
||||
}
|
||||
if (item.type == 'news') {
|
||||
return DidvanIcons.news_light;
|
||||
}
|
||||
if (item.type == 'video') {
|
||||
return DidvanIcons.video_light;
|
||||
}
|
||||
return DidvanIcons.podcast_light;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
item.type == 'radar' ? Routes.radarDetails : Routes.newsDetails,
|
||||
arguments: {
|
||||
'onMarkChanged': onMarkChanged,
|
||||
'id': item.id,
|
||||
'args': item.type == 'radar'
|
||||
? const RadarRequestArgs(page: 0)
|
||||
: const NewsRequestArgs(page: 0),
|
||||
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
if (item.type == 'podcast') {
|
||||
context.read<StudioDetailsState>().getStudioDetails(
|
||||
item.id,
|
||||
args: StudioRequestArgs(page: 0, type: item.type),
|
||||
);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
_targetPageRouteName,
|
||||
arguments: {
|
||||
'onMarkChanged': onMarkChanged,
|
||||
'id': item.id,
|
||||
'args': _targetPageArgs,
|
||||
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
|
@ -52,9 +95,7 @@ class MultitypeOverview extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
child: Icon(
|
||||
item.type == 'radar'
|
||||
? DidvanIcons.radar_light
|
||||
: DidvanIcons.news_light,
|
||||
_icon,
|
||||
color: Theme.of(context).colorScheme.white,
|
||||
size: 18,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ class PodcastOverview extends StatelessWidget {
|
|||
final OverviewData podcast;
|
||||
final void Function(int id, bool value) onMarkChanged;
|
||||
final StudioRequestArgs? studioRequestArgs;
|
||||
final bool hasUnmarkConfirmation;
|
||||
const PodcastOverview({
|
||||
Key? key,
|
||||
required this.podcast,
|
||||
required this.onMarkChanged,
|
||||
this.studioRequestArgs,
|
||||
this.hasUnmarkConfirmation = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -82,6 +84,7 @@ class PodcastOverview extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(width: 16),
|
||||
BookmarkButton(
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
gestureSize: 24,
|
||||
value: podcast.marked,
|
||||
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class RadarOverview extends StatelessWidget {
|
|||
onPressed: () => Navigator.of(context).pushNamed(
|
||||
Routes.comments,
|
||||
arguments: {
|
||||
'isRadar': true,
|
||||
'type': 'radar',
|
||||
'title': radar.title,
|
||||
'id': radar.id,
|
||||
'onCommentsChanged': (count) =>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:didvan/models/overview_data.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/home/widgets/bookmark_button.dart';
|
||||
import 'package:didvan/views/home/widgets/duration_widget.dart';
|
||||
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||
|
|
@ -14,18 +13,15 @@ import 'package:didvan/views/widgets/didvan/text.dart';
|
|||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class VideoOverview extends StatelessWidget {
|
||||
final OverviewData video;
|
||||
final void Function(int id, int count) onCommentsChanged;
|
||||
final void Function(int id, bool value) onMarkChanged;
|
||||
final bool hasUnmarkConfirmation;
|
||||
final StudioRequestArgs? studioRequestArgs;
|
||||
const VideoOverview({
|
||||
Key? key,
|
||||
required this.video,
|
||||
required this.onCommentsChanged,
|
||||
required this.onMarkChanged,
|
||||
required this.hasUnmarkConfirmation,
|
||||
this.studioRequestArgs,
|
||||
|
|
@ -38,12 +34,10 @@ class VideoOverview extends StatelessWidget {
|
|||
Routes.studioDetails,
|
||||
arguments: {
|
||||
'onMarkChanged': onMarkChanged,
|
||||
'onCommentsChanged': onCommentsChanged,
|
||||
'id': video.id,
|
||||
'args': studioRequestArgs,
|
||||
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||
'isVideo': true,
|
||||
'state': context.read<StudioDetailsState>(),
|
||||
},
|
||||
),
|
||||
child: Row(
|
||||
|
|
@ -115,6 +109,7 @@ class VideoOverview extends StatelessWidget {
|
|||
gestureSize: 24,
|
||||
value: video.marked,
|
||||
onMarkChanged: (value) => onMarkChanged(video.id, value),
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class DidvanScaffold extends StatefulWidget {
|
|||
final EdgeInsets padding;
|
||||
final Color? backgroundColor;
|
||||
final bool reverse;
|
||||
final ScrollController? scrollController;
|
||||
|
||||
const DidvanScaffold({
|
||||
Key? key,
|
||||
|
|
@ -18,6 +19,7 @@ class DidvanScaffold extends StatefulWidget {
|
|||
this.padding = const EdgeInsets.symmetric(horizontal: 16),
|
||||
this.backgroundColor,
|
||||
this.reverse = false,
|
||||
this.scrollController,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -25,7 +27,13 @@ class DidvanScaffold extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DidvanScaffoldState extends State<DidvanScaffold> {
|
||||
final _scrollController = ScrollController();
|
||||
late final ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_scrollController = widget.scrollController ?? ScrollController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -33,7 +41,9 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
|||
return Scaffold(
|
||||
backgroundColor: widget.backgroundColor,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(top: statusBarHeight),
|
||||
padding: widget.appBarData == null
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(top: statusBarHeight),
|
||||
child: Stack(
|
||||
children: [
|
||||
CustomScrollView(
|
||||
|
|
|
|||
Loading…
Reference in New Issue