D1APP-99 studio updates
This commit is contained in:
parent
9c096bcd0c
commit
ddc707bbe9
|
|
@ -6,6 +6,7 @@ class OverviewData {
|
||||||
final String image;
|
final String image;
|
||||||
final String description;
|
final String description;
|
||||||
final int? timeToRead;
|
final int? timeToRead;
|
||||||
|
final int? duration;
|
||||||
final String? reference;
|
final String? reference;
|
||||||
final bool forManagers;
|
final bool forManagers;
|
||||||
final String createdAt;
|
final String createdAt;
|
||||||
|
|
@ -24,6 +25,7 @@ class OverviewData {
|
||||||
required this.marked,
|
required this.marked,
|
||||||
required this.comments,
|
required this.comments,
|
||||||
required this.forManagers,
|
required this.forManagers,
|
||||||
|
this.duration,
|
||||||
this.timeToRead,
|
this.timeToRead,
|
||||||
this.reference,
|
this.reference,
|
||||||
this.categories,
|
this.categories,
|
||||||
|
|
@ -39,11 +41,16 @@ class OverviewData {
|
||||||
forManagers: json['forManagers'] ?? false,
|
forManagers: json['forManagers'] ?? false,
|
||||||
comments: json['comments'] ?? 0,
|
comments: json['comments'] ?? 0,
|
||||||
createdAt: json['createdAt'],
|
createdAt: json['createdAt'],
|
||||||
|
duration: json['duration'],
|
||||||
type: json['type'] ?? '',
|
type: json['type'] ?? '',
|
||||||
marked: json['marked'] ?? false,
|
marked: json['marked'] ?? false,
|
||||||
categories: (json['categories'] as List<dynamic>?)
|
categories: json['categories'] != null
|
||||||
?.map((e) => CategoryData.fromJson(e as Map<String, dynamic>))
|
? List<CategoryData>.from(
|
||||||
.toList(),
|
json['categories'].map(
|
||||||
|
(e) => CategoryData.fromJson(e),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
class StudioData {
|
|
||||||
final int id;
|
|
||||||
final String title;
|
|
||||||
final String image;
|
|
||||||
final String duration;
|
|
||||||
final String createdAt;
|
|
||||||
bool marked;
|
|
||||||
|
|
||||||
StudioData({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.image,
|
|
||||||
required this.duration,
|
|
||||||
required this.createdAt,
|
|
||||||
required this.marked,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory StudioData.fromJson(Map<String, dynamic> json) {
|
|
||||||
return StudioData(
|
|
||||||
id: json['id'],
|
|
||||||
title: json['title'],
|
|
||||||
image: json['image'],
|
|
||||||
duration: json['duration'],
|
|
||||||
createdAt: json['createdAt'],
|
|
||||||
marked: json['marked'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'id': id,
|
|
||||||
'title': title,
|
|
||||||
'image': image,
|
|
||||||
'duration': duration,
|
|
||||||
'createdAt': createdAt,
|
|
||||||
'marked': marked,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:didvan/models/overview_data.dart';
|
||||||
|
import 'package:didvan/models/tag.dart';
|
||||||
|
|
||||||
|
class StudioDetailsData {
|
||||||
|
final int id;
|
||||||
|
final int duration;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final String image;
|
||||||
|
final String media;
|
||||||
|
final String createdAt;
|
||||||
|
final int order;
|
||||||
|
bool marked;
|
||||||
|
int comments;
|
||||||
|
final List<Tag> tags;
|
||||||
|
final List<OverviewData> relatedContents = [];
|
||||||
|
|
||||||
|
StudioDetailsData({
|
||||||
|
required this.id,
|
||||||
|
required this.duration,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.image,
|
||||||
|
required this.media,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.order,
|
||||||
|
required this.marked,
|
||||||
|
required this.comments,
|
||||||
|
required this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory StudioDetailsData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return StudioDetailsData(
|
||||||
|
id: json['id'],
|
||||||
|
duration: json['duration'],
|
||||||
|
title: json['title'],
|
||||||
|
description: json['description'],
|
||||||
|
image: json['image'],
|
||||||
|
media: json['media'],
|
||||||
|
createdAt: json['createdAt'],
|
||||||
|
order: json['order'],
|
||||||
|
marked: json['marked'],
|
||||||
|
comments: json['comments'],
|
||||||
|
tags: List<Tag>.from(json['tags'].map((e) => Tag.fromJson(e))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'duration': duration,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'image': image,
|
||||||
|
'media': media,
|
||||||
|
'createdAt': createdAt,
|
||||||
|
'order': order,
|
||||||
|
'marked': marked,
|
||||||
|
'comments': comments,
|
||||||
|
'tags': tags.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,13 @@ class AppBarData {
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
final bool hasBack;
|
final bool hasBack;
|
||||||
final Widget? trailing;
|
final Widget? trailing;
|
||||||
|
final bool isSmall;
|
||||||
|
|
||||||
AppBarData({this.title, this.subtitle, this.hasBack = false, this.trailing});
|
AppBarData({
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.hasBack = false,
|
||||||
|
this.trailing,
|
||||||
|
this.isSmall = false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class UserProvider extends CoreProvier {
|
||||||
|
|
||||||
static final List<MapEntry> _radarMarkQueue = [];
|
static final List<MapEntry> _radarMarkQueue = [];
|
||||||
static final List<MapEntry> _newsMarkQueue = [];
|
static final List<MapEntry> _newsMarkQueue = [];
|
||||||
|
static final List<MapEntry> _studioMarkQueue = [];
|
||||||
|
|
||||||
Future<String?> setAndGetToken({String? newToken}) async {
|
Future<String?> setAndGetToken({String? newToken}) async {
|
||||||
if (newToken == null) {
|
if (newToken == null) {
|
||||||
|
|
@ -141,6 +142,22 @@ class UserProvider extends CoreProvier {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> changeStudioMark(int id, bool value) async {
|
||||||
|
_studioMarkQueue.add(MapEntry(id, value));
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||||
|
final MapEntry? lastChange =
|
||||||
|
_studioMarkQueue.lastWhereOrNull((item) => item.key == id);
|
||||||
|
if (lastChange == null) return;
|
||||||
|
final service = RequestService(RequestHelper.markStudio(id));
|
||||||
|
if (lastChange.value) {
|
||||||
|
await service.post();
|
||||||
|
} else {
|
||||||
|
await service.delete();
|
||||||
|
}
|
||||||
|
_studioMarkQueue.removeWhere((element) => element.key == id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> changeNewsMark(int id, bool value) async {
|
static Future<void> changeNewsMark(int id, bool value) async {
|
||||||
_newsMarkQueue.add(MapEntry(id, value));
|
_newsMarkQueue.add(MapEntry(id, value));
|
||||||
Future.delayed(const Duration(milliseconds: 500), () async {
|
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ 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.dart';
|
||||||
import 'package:didvan/views/home/settings/general_settings/settings_state.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/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_state.dart';
|
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||||
import 'package:didvan/views/splash/splash.dart';
|
import 'package:didvan/views/splash/splash.dart';
|
||||||
import 'package:didvan/routes/routes.dart';
|
import 'package:didvan/routes/routes.dart';
|
||||||
|
|
@ -99,6 +101,15 @@ class RouteGenerator {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
case Routes.studioDetails:
|
||||||
|
return _createRoute(
|
||||||
|
ChangeNotifierProvider<StudioDetailsState>(
|
||||||
|
create: (context) => StudioDetailsState(),
|
||||||
|
child: StudioDetails(
|
||||||
|
pageData: settings.arguments as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
case Routes.directList:
|
case Routes.directList:
|
||||||
return _createRoute(
|
return _createRoute(
|
||||||
ChangeNotifierProvider<DirectListState>(
|
ChangeNotifierProvider<DirectListState>(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class Routes {
|
||||||
static const String generalSettings = '/general-settings';
|
static const String generalSettings = '/general-settings';
|
||||||
static const String radarDetails = '/radar-details';
|
static const String radarDetails = '/radar-details';
|
||||||
static const String newsDetails = '/news-details';
|
static const String newsDetails = '/news-details';
|
||||||
|
static const String studioDetails = '/studio-details';
|
||||||
static const String directList = '/direct-list';
|
static const String directList = '/direct-list';
|
||||||
static const String direct = '/direct';
|
static const String direct = '/direct';
|
||||||
static const String comments = '/comments';
|
static const String comments = '/comments';
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,19 @@ class RequestHelper {
|
||||||
static const String checkUsername = _baseUserUrl + '/CheckUsername';
|
static const String checkUsername = _baseUserUrl + '/CheckUsername';
|
||||||
static const String updateProfile = _baseUserUrl + '/profile/edit';
|
static const String updateProfile = _baseUserUrl + '/profile/edit';
|
||||||
static const String otp = _baseUserUrl + '/otp';
|
static const String otp = _baseUserUrl + '/otp';
|
||||||
static String bookmarks({String? type}) =>
|
static String bookmarks({
|
||||||
_baseUserUrl + '/marked/${type ?? ''}';
|
required int page,
|
||||||
|
String? search,
|
||||||
|
String? type,
|
||||||
|
String? studioType,
|
||||||
|
}) =>
|
||||||
|
_baseUserUrl +
|
||||||
|
'/marked/${type ?? ''}' +
|
||||||
|
_urlConcatGenerator([
|
||||||
|
MapEntry('page', page),
|
||||||
|
MapEntry('type', studioType),
|
||||||
|
MapEntry('search', search),
|
||||||
|
]);
|
||||||
|
|
||||||
static const String directTypes = baseUrl + '/direct/types';
|
static const String directTypes = baseUrl + '/direct/types';
|
||||||
static String direct(int id) => _baseDirectUrl + '/$id';
|
static String direct(int id) => _baseDirectUrl + '/$id';
|
||||||
|
|
@ -36,10 +47,10 @@ class RequestHelper {
|
||||||
baseUrl +
|
baseUrl +
|
||||||
'/tag' +
|
'/tag' +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', page?.toString()),
|
MapEntry('page', page),
|
||||||
MapEntry('limit', limit?.toString() ?? '3'),
|
MapEntry('limit', limit ?? '3'),
|
||||||
MapEntry('type', type),
|
MapEntry('type', type),
|
||||||
MapEntry('id', itemId?.toString() ?? '1'),
|
MapEntry('id', itemId ?? '1'),
|
||||||
MapEntry('tags', _urlListConcatGenerator(ids)),
|
MapEntry('tags', _urlListConcatGenerator(ids)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -52,7 +63,7 @@ class RequestHelper {
|
||||||
_baseRadarUrl +
|
_baseRadarUrl +
|
||||||
'/$id' +
|
'/$id' +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('start', args.startDate),
|
MapEntry('start', args.startDate),
|
||||||
MapEntry('end', args.endDate),
|
MapEntry('end', args.endDate),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
|
|
@ -61,7 +72,7 @@ class RequestHelper {
|
||||||
static String radarOverviews({required RadarRequestArgs args}) =>
|
static String radarOverviews({required RadarRequestArgs args}) =>
|
||||||
_baseRadarUrl +
|
_baseRadarUrl +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('start', args.startDate),
|
MapEntry('start', args.startDate),
|
||||||
MapEntry('end', args.endDate),
|
MapEntry('end', args.endDate),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
|
|
@ -77,7 +88,7 @@ class RequestHelper {
|
||||||
_baseNewsUrl +
|
_baseNewsUrl +
|
||||||
'/$id' +
|
'/$id' +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('start', args.startDate),
|
MapEntry('start', args.startDate),
|
||||||
MapEntry('end', args.endDate),
|
MapEntry('end', args.endDate),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
|
|
@ -85,7 +96,7 @@ class RequestHelper {
|
||||||
static String newsOverviews({required NewsRequestArgs args}) =>
|
static String newsOverviews({required NewsRequestArgs args}) =>
|
||||||
_baseNewsUrl +
|
_baseNewsUrl +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('start', args.startDate),
|
MapEntry('start', args.startDate),
|
||||||
MapEntry('end', args.endDate),
|
MapEntry('end', args.endDate),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
|
|
@ -101,27 +112,29 @@ class RequestHelper {
|
||||||
_baseStudioUrl +
|
_baseStudioUrl +
|
||||||
'/$id' +
|
'/$id' +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('type', args.type),
|
MapEntry('type', args.type),
|
||||||
MapEntry('order', args.order),
|
MapEntry('order', args.order),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
]);
|
]);
|
||||||
static String studioOverviews({required StudioRequestArgs args}) =>
|
static String studioOverviews({required StudioRequestArgs args}) =>
|
||||||
_baseNewsUrl +
|
_baseStudioUrl +
|
||||||
_urlConcatGenerator([
|
_urlConcatGenerator([
|
||||||
MapEntry('page', args.page.toString()),
|
MapEntry('page', args.page),
|
||||||
MapEntry('type', args.type),
|
MapEntry('type', args.type),
|
||||||
MapEntry('order', args.order),
|
MapEntry('order', args.order),
|
||||||
MapEntry('search', args.search),
|
MapEntry('search', args.search),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
static String _urlConcatGenerator(List<MapEntry<String, String?>> additions) {
|
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
|
||||||
String result = '';
|
String result = '';
|
||||||
additions.removeWhere((element) => element.value == null);
|
additions.removeWhere(
|
||||||
|
(element) => element.value == null || element.value.toString().isEmpty,
|
||||||
|
);
|
||||||
if (additions.isNotEmpty) {
|
if (additions.isNotEmpty) {
|
||||||
result += '?';
|
result += '?';
|
||||||
for (var i = 0; i < additions.length; i++) {
|
for (var i = 0; i < additions.length; i++) {
|
||||||
result += (additions[i].key + '=' + additions[i].value!);
|
result += (additions[i].key + '=' + additions[i].value!.toString());
|
||||||
if (i != additions.length - 1) {
|
if (i != additions.length - 1) {
|
||||||
result += '&';
|
result += '&';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import 'package:didvan/constants/app_icons.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/models/view/action_sheet_data.dart';
|
||||||
import 'package:didvan/utils/action_sheet.dart';
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:didvan/views/home/studio/studio_state.dart';
|
import 'package:didvan/views/home/studio/studio_state.dart';
|
||||||
import 'package:didvan/views/home/studio/widgets/slider.dart';
|
import 'package:didvan/views/home/studio/widgets/slider.dart';
|
||||||
import 'package:didvan/views/home/studio/widgets/tab_bar.dart';
|
import 'package:didvan/views/home/studio/widgets/tab_bar.dart';
|
||||||
import 'package:didvan/views/home/widgets/logo_app_bar.dart';
|
import 'package:didvan/views/home/widgets/logo_app_bar.dart';
|
||||||
|
import 'package:didvan/views/home/widgets/podcast_overview.dart';
|
||||||
import 'package:didvan/views/home/widgets/search_field.dart';
|
import 'package:didvan/views/home/widgets/search_field.dart';
|
||||||
|
import 'package:didvan/views/home/widgets/video_overview.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/divider.dart';
|
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/radial_button.dart';
|
import 'package:didvan/views/widgets/didvan/radial_button.dart';
|
||||||
|
|
@ -24,6 +27,12 @@ class Studio extends StatefulWidget {
|
||||||
class _StudioState extends State<Studio> {
|
class _StudioState extends State<Studio> {
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
context.read<StudioState>().init();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
|
|
@ -86,9 +95,41 @@ class _StudioState extends State<Studio> {
|
||||||
Consumer<StudioState>(
|
Consumer<StudioState>(
|
||||||
builder: (context, state, child) => SliverStateHandler<StudioState>(
|
builder: (context, state, child) => SliverStateHandler<StudioState>(
|
||||||
state: state,
|
state: state,
|
||||||
builder: (context, state, index) => Container(),
|
itemPadding: const EdgeInsets.only(
|
||||||
|
bottom: 8,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
),
|
||||||
|
placeholder: state.videosSelected
|
||||||
|
? VideoOverview.placeHolder
|
||||||
|
: PodcastOverview.placeholder,
|
||||||
|
builder: (context, state, index) => state.videosSelected
|
||||||
|
? VideoOverview(
|
||||||
|
onMarkChanged: state.changeMark,
|
||||||
|
hasUnmarkConfirmation: false,
|
||||||
|
video: state.studios[index],
|
||||||
|
onCommentsChanged: state.onCommentsChanged,
|
||||||
|
studioRequestArgs: StudioRequestArgs(
|
||||||
|
page: state.page,
|
||||||
|
order: state.order,
|
||||||
|
search: state.search,
|
||||||
|
type: state.type,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: PodcastOverview(
|
||||||
|
podcast: state.studios[index],
|
||||||
|
onMarkChanged: state.changeMark,
|
||||||
|
hasUnmarkConfirmation: false,
|
||||||
|
onCommentsChanged: state.onCommentsChanged,
|
||||||
|
studioRequestArgs: StudioRequestArgs(
|
||||||
|
page: state.page,
|
||||||
|
order: state.order,
|
||||||
|
search: state.search,
|
||||||
|
type: state.type,
|
||||||
|
),
|
||||||
|
),
|
||||||
childCount: state.studios.length,
|
childCount: state.studios.length,
|
||||||
onRetry: () {},
|
onRetry: () => state.getStudioOverviews(page: 1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -132,7 +173,7 @@ class _StudioState extends State<Studio> {
|
||||||
titleIcon: DidvanIcons.sort_regular,
|
titleIcon: DidvanIcons.sort_regular,
|
||||||
hasDismissButton: false,
|
hasDismissButton: false,
|
||||||
confrimTitle: 'مرتب سازی',
|
confrimTitle: 'مرتب سازی',
|
||||||
onConfirmed: () {},
|
onConfirmed: () => state.getStudioOverviews(page: 1),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
import 'package:didvan/models/studio_details_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/widgets/audio_slider.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/scaffold.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:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
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> {
|
||||||
|
bool _isFullScreen = false;
|
||||||
|
bool _isInit = true;
|
||||||
|
|
||||||
|
double _dwInPortrait = 0;
|
||||||
|
double _scaleInPortrait = 1;
|
||||||
|
|
||||||
|
bool get _isVideo => widget.pageData['isVideo'];
|
||||||
|
|
||||||
|
@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();
|
||||||
|
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) => state.studios.isEmpty
|
||||||
|
? const SizedBox()
|
||||||
|
: WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (_isFullScreen) {
|
||||||
|
await _changeFullSceen(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: DidvanScaffold(
|
||||||
|
appBarData: _isFullScreen
|
||||||
|
? null
|
||||||
|
: AppBarData(
|
||||||
|
isSmall: true,
|
||||||
|
title: state.currentStudio.title,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
if (_isVideo)
|
||||||
|
SizedBox(
|
||||||
|
width: ds.width,
|
||||||
|
height: _isFullScreen ? ds.height : ds.width * 9 / 16,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
WebView(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 42,
|
||||||
|
bottom: 8,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _changeFullSceen(!_isFullScreen),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
width: 24,
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!_isVideo)
|
||||||
|
AudioPlayerWidget(podcast: state.currentStudio),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioPlayerWidget extends StatelessWidget {
|
||||||
|
final StudioDetailsData podcast;
|
||||||
|
const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Hero(
|
||||||
|
tag: podcast.media,
|
||||||
|
child: SkeletonImage(
|
||||||
|
imageUrl: podcast.image,
|
||||||
|
aspectRatio: 1 / 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DidvanText(
|
||||||
|
podcast.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: AudioSlider(
|
||||||
|
tag: podcast.media,
|
||||||
|
showTimer: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:didvan/models/enums.dart';
|
||||||
|
import 'package:didvan/models/overview_data.dart';
|
||||||
|
import 'package:didvan/models/requests/studio.dart';
|
||||||
|
import 'package:didvan/models/studio_details_data.dart';
|
||||||
|
import 'package:didvan/providers/core_provider.dart';
|
||||||
|
import 'package:didvan/services/network/request.dart';
|
||||||
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
|
|
||||||
|
class StudioDetailsState extends CoreProvier {
|
||||||
|
final List<StudioDetailsData?> studios = [];
|
||||||
|
late Timer _trackingTimer;
|
||||||
|
int _trackingTimerCounter = 0;
|
||||||
|
late final int initialIndex;
|
||||||
|
late final StudioRequestArgs args;
|
||||||
|
bool isFetchingNewItem = false;
|
||||||
|
final List<int> relatedQueue = [];
|
||||||
|
|
||||||
|
int _currentIndex = 0;
|
||||||
|
int get currentIndex => _currentIndex;
|
||||||
|
|
||||||
|
StudioDetailsData get currentStudio {
|
||||||
|
try {
|
||||||
|
return studios[_currentIndex]!;
|
||||||
|
} catch (e) {
|
||||||
|
return studios[_currentIndex + 1]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getStudioDetails(int id, {bool? isForward}) async {
|
||||||
|
if (isForward == null) {
|
||||||
|
appState = AppState.busy;
|
||||||
|
} else {
|
||||||
|
isFetchingNewItem = true;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
final service = RequestService(RequestHelper.studioDetails(id, args));
|
||||||
|
await service.httpGet();
|
||||||
|
_handleTracking(sendRequest: isForward != null);
|
||||||
|
if (service.isSuccess) {
|
||||||
|
final result = service.result;
|
||||||
|
final studio = StudioDetailsData.fromJson(result['studio']);
|
||||||
|
if (args.page == 0) {
|
||||||
|
studios.add(studio);
|
||||||
|
initialIndex = 0;
|
||||||
|
appState = AppState.idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioDetailsData? prevStudio;
|
||||||
|
if (result['prevStudio'].isNotEmpty) {
|
||||||
|
prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
|
||||||
|
}
|
||||||
|
|
||||||
|
StudioDetailsData? nextStudio;
|
||||||
|
if (result['nextStudio'].isNotEmpty) {
|
||||||
|
nextStudio = StudioDetailsData.fromJson(result['nextStudio']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isForward == null) {
|
||||||
|
studios
|
||||||
|
.addAll(List.generate(max(studio.order - 2, 0), (index) => null));
|
||||||
|
if (prevStudio != null) {
|
||||||
|
studios.add(prevStudio);
|
||||||
|
}
|
||||||
|
studios.add(studio);
|
||||||
|
if (nextStudio != null) {
|
||||||
|
studios.add(nextStudio);
|
||||||
|
}
|
||||||
|
_currentIndex = initialIndex = studio.order - 1;
|
||||||
|
} else if (isForward) {
|
||||||
|
if (!exists(nextStudio) && nextStudio != null) {
|
||||||
|
studios.add(nextStudio);
|
||||||
|
}
|
||||||
|
_currentIndex++;
|
||||||
|
} else if (!isForward) {
|
||||||
|
if (!exists(prevStudio) && prevStudio != null) {
|
||||||
|
studios[_currentIndex - 2] = prevStudio;
|
||||||
|
}
|
||||||
|
_currentIndex--;
|
||||||
|
}
|
||||||
|
isFetchingNewItem = false;
|
||||||
|
appState = AppState.idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//why? total page state shouldn't die!
|
||||||
|
if (isForward == null) {
|
||||||
|
appState = AppState.failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
));
|
||||||
|
await service.httpGet();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
final relateds = service.result['contents'];
|
||||||
|
for (var i = 0; i < relateds.length; i++) {
|
||||||
|
studios
|
||||||
|
.where((element) => element != null)
|
||||||
|
.firstWhere((element) => element!.id == currentStudio.id)!
|
||||||
|
.relatedContents
|
||||||
|
.add(OverviewData.fromJson(relateds[i]));
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exists(StudioDetailsData? studio) =>
|
||||||
|
studios.any((r) => studio != null && r != null && r.id == studio.id);
|
||||||
|
|
||||||
|
void onCommentsChanged(int count) {
|
||||||
|
studios.firstWhere((studio) => studio?.id == currentStudio.id)!.comments =
|
||||||
|
count;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleTracking({bool sendRequest = true}) async {
|
||||||
|
if (!sendRequest) {
|
||||||
|
_trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
_trackingTimerCounter++;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//send request
|
||||||
|
_trackingTimerCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_trackingTimer.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:didvan/models/enums.dart';
|
import 'package:didvan/models/enums.dart';
|
||||||
|
import 'package:didvan/models/overview_data.dart';
|
||||||
import 'package:didvan/models/requests/studio.dart';
|
import 'package:didvan/models/requests/studio.dart';
|
||||||
import 'package:didvan/models/srudio_data.dart';
|
|
||||||
import 'package:didvan/providers/core_provider.dart';
|
import 'package:didvan/providers/core_provider.dart';
|
||||||
|
import 'package:didvan/providers/user_provider.dart';
|
||||||
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';
|
||||||
|
|
||||||
class StudioState extends CoreProvier {
|
class StudioState extends CoreProvier {
|
||||||
final List<StudioData> studios = [];
|
final List<OverviewData> studios = [];
|
||||||
|
|
||||||
String? search;
|
String? search;
|
||||||
String? lastSearch;
|
String? lastSearch;
|
||||||
|
|
@ -22,52 +23,71 @@ class StudioState extends CoreProvier {
|
||||||
set videosSelected(bool value) {
|
set videosSelected(bool value) {
|
||||||
if (_videosSelected == value) return;
|
if (_videosSelected == value) return;
|
||||||
_videosSelected = value;
|
_videosSelected = value;
|
||||||
|
studios.clear();
|
||||||
getStudioOverviews(page: page);
|
getStudioOverviews(page: page);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
search = '';
|
search = '';
|
||||||
lastSearch = '';
|
lastSearch = '';
|
||||||
videosSelected = true;
|
_videosSelected = true;
|
||||||
selectedSortTypeIndex = 0;
|
selectedSortTypeIndex = 0;
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
getStudioOverviews(page: 1);
|
getStudioOverviews(page: 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String get _order {
|
String get order {
|
||||||
if (selectedSortTypeIndex == 0) return 'date';
|
if (selectedSortTypeIndex == 0) return 'date';
|
||||||
if (selectedSortTypeIndex == 1) return 'view';
|
if (selectedSortTypeIndex == 1) return 'view';
|
||||||
return 'comment';
|
return 'comment';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get type {
|
||||||
|
if (videosSelected) return 'video';
|
||||||
|
return 'podcast';
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getStudioOverviews({required int page}) async {
|
Future<void> getStudioOverviews({required int page}) async {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
appState = AppState.busy;
|
appState = AppState.busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
final service = RequestService(
|
final service = RequestService(
|
||||||
RequestHelper.studioOverviews(
|
RequestHelper.studioOverviews(
|
||||||
args: StudioRequestArgs(
|
args: StudioRequestArgs(
|
||||||
page: page,
|
page: page,
|
||||||
type: videosSelected ? 'video' : 'podcast',
|
type: type,
|
||||||
search: search,
|
search: search,
|
||||||
order: _order,
|
order: order,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await service.httpGet();
|
await service.httpGet();
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
|
if (page == 1) {
|
||||||
|
studios.clear();
|
||||||
|
}
|
||||||
lastPage = service.result['lastPage'];
|
lastPage = service.result['lastPage'];
|
||||||
final studioItems = service.result['studios'];
|
final studioItems = service.result['studios'];
|
||||||
for (var i = 0; i < studioItems.length; i++) {
|
for (var i = 0; i < studioItems.length; i++) {
|
||||||
studios.add(StudioData.fromJson(studioItems[i]));
|
studios.add(OverviewData.fromJson(studioItems[i]));
|
||||||
}
|
}
|
||||||
appState = AppState.idle;
|
appState = AppState.idle;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
appState = AppState.failed;
|
appState = AppState.failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> changeMark(int id, bool value) async {
|
||||||
|
studios.firstWhere((element) => element.id == id).marked = value;
|
||||||
|
notifyListeners();
|
||||||
|
UserProvider.changeStudioMark(id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onCommentsChanged(int id, int count) {
|
||||||
|
studios.firstWhere((radar) => radar.id == id).comments = count;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||||
|
import 'package:didvan/config/design_config.dart';
|
||||||
import 'package:didvan/services/media/media.dart';
|
import 'package:didvan/services/media/media.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AudioSlider extends StatelessWidget {
|
class AudioSlider extends StatelessWidget {
|
||||||
final String tag;
|
final String tag;
|
||||||
const AudioSlider({Key? key, required this.tag}) : super(key: key);
|
final bool showTimer;
|
||||||
|
const AudioSlider({Key? key, required this.tag, this.showTimer = false})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
bool get _isPlaying => MediaService.audioPlayerTag == tag;
|
bool get _isPlaying => MediaService.audioPlayerTag == tag;
|
||||||
|
|
||||||
|
|
@ -24,7 +27,14 @@ class AudioSlider extends StatelessWidget {
|
||||||
_isPlaying ? MediaService.audioPlayer.bufferedPosition : null,
|
_isPlaying ? MediaService.audioPlayer.bufferedPosition : null,
|
||||||
thumbRadius: 6,
|
thumbRadius: 6,
|
||||||
barHeight: 3,
|
barHeight: 3,
|
||||||
timeLabelTextStyle: const TextStyle(fontSize: 0),
|
timeLabelTextStyle: TextStyle(
|
||||||
|
fontSize: showTimer ? null : 0,
|
||||||
|
height: showTimer ? null : 0,
|
||||||
|
fontFamily: DesignConfig.fontFamily.replaceAll(
|
||||||
|
'-FA',
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
),
|
||||||
onSeek: (value) => _onSeek(value.inMilliseconds),
|
onSeek: (value) => _onSeek(value.inMilliseconds),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/utils/date_time.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DurationWidget extends StatelessWidget {
|
||||||
|
final int duration;
|
||||||
|
const DurationWidget({Key? key, required this.duration}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
DidvanIcons.timer_regular,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
DidvanText(
|
||||||
|
DateTimeUtils.normalizeTimeDuration(
|
||||||
|
Duration(seconds: duration),
|
||||||
|
),
|
||||||
|
isEnglishFont: true,
|
||||||
|
color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Icon(
|
||||||
|
DidvanIcons.play_circle_regular,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,38 +1,46 @@
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
import 'package:didvan/models/overview_data.dart';
|
import 'package:didvan/models/overview_data.dart';
|
||||||
import 'package:didvan/models/requests/news.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/utils/date_time.dart';
|
||||||
import 'package:didvan/views/home/widgets/bookmark_button.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';
|
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/divider.dart';
|
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class PodcastOverview extends StatelessWidget {
|
class PodcastOverview extends StatelessWidget {
|
||||||
final OverviewData news;
|
final OverviewData podcast;
|
||||||
final NewsRequestArgs? newsRequestArgs;
|
final void Function(int id, int count) onCommentsChanged;
|
||||||
final void Function(int id, bool value) onMarkChanged;
|
final void Function(int id, bool value) onMarkChanged;
|
||||||
final bool hasUnmarkConfirmation;
|
final bool hasUnmarkConfirmation;
|
||||||
|
final StudioRequestArgs? studioRequestArgs;
|
||||||
const PodcastOverview({
|
const PodcastOverview({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.news,
|
required this.podcast,
|
||||||
|
required this.onCommentsChanged,
|
||||||
required this.onMarkChanged,
|
required this.onMarkChanged,
|
||||||
this.newsRequestArgs,
|
required this.hasUnmarkConfirmation,
|
||||||
this.hasUnmarkConfirmation = false,
|
this.studioRequestArgs,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DidvanCard(
|
return DidvanCard(
|
||||||
onTap: () => Navigator.of(context).pushNamed(
|
onTap: () => Navigator.of(context).pushNamed(
|
||||||
Routes.newsDetails,
|
Routes.studioDetails,
|
||||||
arguments: {
|
arguments: {
|
||||||
'onMarkChanged': onMarkChanged,
|
'onMarkChanged': onMarkChanged,
|
||||||
'id': news.id,
|
'onCommentsChanged': onCommentsChanged,
|
||||||
'args': newsRequestArgs,
|
'id': podcast.id,
|
||||||
|
'args': studioRequestArgs,
|
||||||
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||||
|
'isVideo': false,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -41,47 +49,50 @@ class PodcastOverview extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SkeletonImage(
|
SkeletonImage(
|
||||||
imageUrl: news.image,
|
imageUrl: podcast.image,
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SizedBox(
|
child: Column(
|
||||||
height: 64,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: DidvanText(
|
children: [
|
||||||
news.title,
|
DidvanText(
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
podcast.title,
|
||||||
),
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
DidvanText(
|
||||||
|
DateTimeUtils.momentGenerator(podcast.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.overline,
|
||||||
|
color: Theme.of(context).colorScheme.caption,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
DidvanText(
|
DidvanText(
|
||||||
news.description,
|
podcast.description,
|
||||||
maxLines: 3,
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const DidvanDivider(verticalPadding: 8),
|
const DidvanDivider(verticalPadding: 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
DurationWidget(duration: podcast.duration!),
|
||||||
children: [
|
const Spacer(),
|
||||||
DidvanText(
|
DidvanIconButton(
|
||||||
news.reference!,
|
gestureSize: 28,
|
||||||
style: Theme.of(context).textTheme.caption,
|
icon: DidvanIcons.download_regular,
|
||||||
),
|
onPressed: () {},
|
||||||
DidvanText(
|
|
||||||
' - ' + DateTimeUtils.momentGenerator(news.createdAt),
|
|
||||||
style: Theme.of(context).textTheme.caption,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
BookmarkButton(
|
BookmarkButton(
|
||||||
value: news.marked,
|
value: podcast.marked,
|
||||||
onMarkChanged: (value) => onMarkChanged(news.id, value),
|
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
|
||||||
askForConfirmation: hasUnmarkConfirmation,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -95,7 +106,7 @@ class PodcastOverview extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const ShimmerPlaceholder(height: 64, width: 64),
|
const ShimmerPlaceholder(height: 64, width: 64),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|
@ -109,7 +120,7 @@ class PodcastOverview extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
const ShimmerPlaceholder(
|
const ShimmerPlaceholder(
|
||||||
height: 16,
|
height: 16,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|
@ -117,19 +128,21 @@ class PodcastOverview extends StatelessWidget {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const ShimmerPlaceholder(
|
const ShimmerPlaceholder(
|
||||||
height: 16,
|
height: 16,
|
||||||
width: double.infinity,
|
width: 200,
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
const ShimmerPlaceholder(
|
|
||||||
height: 16,
|
|
||||||
width: 100,
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
const DidvanDivider(verticalPadding: 8),
|
const DidvanDivider(verticalPadding: 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: const [
|
ShimmerPlaceholder(
|
||||||
ShimmerPlaceholder(height: 12, width: 150),
|
height: 36,
|
||||||
ShimmerPlaceholder(height: 24, width: 24),
|
width: 92,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const ShimmerPlaceholder(width: 24, height: 24),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
const ShimmerPlaceholder(width: 24, height: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
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/widgets/bookmark_button.dart';
|
||||||
|
import 'package:didvan/views/home/widgets/duration_widget.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/card.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/icon_button.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';
|
||||||
|
|
||||||
|
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,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DidvanCard(
|
||||||
|
onTap: () => Navigator.of(context).pushNamed(
|
||||||
|
Routes.studioDetails,
|
||||||
|
arguments: {
|
||||||
|
'onMarkChanged': onMarkChanged,
|
||||||
|
'onCommentsChanged': onCommentsChanged,
|
||||||
|
'id': video.id,
|
||||||
|
'args': studioRequestArgs,
|
||||||
|
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||||
|
'isVideo': true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
SkeletonImage(
|
||||||
|
imageUrl: video.image,
|
||||||
|
height: 108,
|
||||||
|
width: 108,
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
height: 28,
|
||||||
|
width: 28,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondary
|
||||||
|
.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
DidvanIcons.play_solid,
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
DidvanText(
|
||||||
|
video.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyText1,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
DidvanIcons.calendar_day_regular,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
DidvanText(
|
||||||
|
DateTimeUtils.momentGenerator(video.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.overline,
|
||||||
|
color: Theme.of(context).colorScheme.caption,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const DidvanDivider(verticalPadding: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
DurationWidget(duration: video.duration!),
|
||||||
|
const Spacer(),
|
||||||
|
DidvanIconButton(
|
||||||
|
gestureSize: 28,
|
||||||
|
icon: DidvanIcons.download_regular,
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
BookmarkButton(
|
||||||
|
value: video.marked,
|
||||||
|
onMarkChanged: (value) => onMarkChanged(video.id, value),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget get placeHolder => DidvanCard(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const ShimmerPlaceholder(height: 108, width: 108),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const ShimmerPlaceholder(height: 20),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const ShimmerPlaceholder(width: 100, height: 16),
|
||||||
|
const DidvanDivider(verticalPadding: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ShimmerPlaceholder(
|
||||||
|
height: 36,
|
||||||
|
width: 92,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const ShimmerPlaceholder(width: 24, height: 24),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
const ShimmerPlaceholder(width: 24, height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -18,11 +18,10 @@ class DidvanAppBar extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
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: const EdgeInsets.only(right: 4, left: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
|
||||||
border: hasBorder
|
border: hasBorder
|
||||||
? Border(
|
? Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
class DidvanScaffold extends StatefulWidget {
|
class DidvanScaffold extends StatefulWidget {
|
||||||
final List<Widget>? slivers;
|
final List<Widget>? slivers;
|
||||||
final List<Widget>? children;
|
final List<Widget>? children;
|
||||||
final AppBarData appBarData;
|
final AppBarData? appBarData;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
final bool reverse;
|
final bool reverse;
|
||||||
|
|
@ -40,14 +40,14 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
reverse: widget.reverse,
|
reverse: widget.reverse,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (!widget.reverse)
|
if (!widget.reverse && widget.appBarData != null)
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
toolbarHeight: kToolbarHeight,
|
toolbarHeight: widget.appBarData!.isSmall ? 56 : 72,
|
||||||
backgroundColor: widget.backgroundColor ??
|
backgroundColor: widget.backgroundColor ??
|
||||||
Theme.of(context).colorScheme.background,
|
Theme.of(context).colorScheme.background,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData),
|
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!),
|
||||||
),
|
),
|
||||||
if (!widget.reverse)
|
if (!widget.reverse)
|
||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
|
|
@ -79,9 +79,9 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (widget.reverse)
|
if (widget.reverse && widget.appBarData != null)
|
||||||
_AppBar(
|
_AppBar(
|
||||||
appBarData: widget.appBarData,
|
appBarData: widget.appBarData!,
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
30
pubspec.lock
30
pubspec.lock
|
|
@ -566,7 +566,7 @@ packages:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.2"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -782,6 +782,34 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
webview_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: webview_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
webview_flutter_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.3"
|
||||||
|
webview_flutter_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.1"
|
||||||
|
webview_flutter_wkwebview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_wkwebview
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ dependencies:
|
||||||
image_cropper: ^1.5.0
|
image_cropper: ^1.5.0
|
||||||
firebase_messaging: ^11.2.8
|
firebase_messaging: ^11.2.8
|
||||||
firebase_core: ^1.13.1
|
firebase_core: ^1.13.1
|
||||||
|
webview_flutter: ^3.0.1
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue