D1APP-99 studio updates
This commit is contained in:
parent
9c096bcd0c
commit
ddc707bbe9
|
|
@ -6,6 +6,7 @@ class OverviewData {
|
|||
final String image;
|
||||
final String description;
|
||||
final int? timeToRead;
|
||||
final int? duration;
|
||||
final String? reference;
|
||||
final bool forManagers;
|
||||
final String createdAt;
|
||||
|
|
@ -24,6 +25,7 @@ class OverviewData {
|
|||
required this.marked,
|
||||
required this.comments,
|
||||
required this.forManagers,
|
||||
this.duration,
|
||||
this.timeToRead,
|
||||
this.reference,
|
||||
this.categories,
|
||||
|
|
@ -39,11 +41,16 @@ class OverviewData {
|
|||
forManagers: json['forManagers'] ?? false,
|
||||
comments: json['comments'] ?? 0,
|
||||
createdAt: json['createdAt'],
|
||||
duration: json['duration'],
|
||||
type: json['type'] ?? '',
|
||||
marked: json['marked'] ?? false,
|
||||
categories: (json['categories'] as List<dynamic>?)
|
||||
?.map((e) => CategoryData.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
categories: json['categories'] != null
|
||||
? List<CategoryData>.from(
|
||||
json['categories'].map(
|
||||
(e) => CategoryData.fromJson(e),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
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 bool hasBack;
|
||||
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> _newsMarkQueue = [];
|
||||
static final List<MapEntry> _studioMarkQueue = [];
|
||||
|
||||
Future<String?> setAndGetToken({String? newToken}) async {
|
||||
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 {
|
||||
_newsMarkQueue.add(MapEntry(id, value));
|
||||
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_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_state.dart';
|
||||
import 'package:didvan/views/splash/splash.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:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<DirectListState>(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class Routes {
|
|||
static const String generalSettings = '/general-settings';
|
||||
static const String radarDetails = '/radar-details';
|
||||
static const String newsDetails = '/news-details';
|
||||
static const String studioDetails = '/studio-details';
|
||||
static const String directList = '/direct-list';
|
||||
static const String direct = '/direct';
|
||||
static const String comments = '/comments';
|
||||
|
|
|
|||
|
|
@ -19,8 +19,19 @@ class RequestHelper {
|
|||
static const String checkUsername = _baseUserUrl + '/CheckUsername';
|
||||
static const String updateProfile = _baseUserUrl + '/profile/edit';
|
||||
static const String otp = _baseUserUrl + '/otp';
|
||||
static String bookmarks({String? type}) =>
|
||||
_baseUserUrl + '/marked/${type ?? ''}';
|
||||
static String bookmarks({
|
||||
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 String direct(int id) => _baseDirectUrl + '/$id';
|
||||
|
|
@ -36,10 +47,10 @@ class RequestHelper {
|
|||
baseUrl +
|
||||
'/tag' +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', page?.toString()),
|
||||
MapEntry('limit', limit?.toString() ?? '3'),
|
||||
MapEntry('page', page),
|
||||
MapEntry('limit', limit ?? '3'),
|
||||
MapEntry('type', type),
|
||||
MapEntry('id', itemId?.toString() ?? '1'),
|
||||
MapEntry('id', itemId ?? '1'),
|
||||
MapEntry('tags', _urlListConcatGenerator(ids)),
|
||||
]);
|
||||
|
||||
|
|
@ -52,7 +63,7 @@ class RequestHelper {
|
|||
_baseRadarUrl +
|
||||
'/$id' +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('start', args.startDate),
|
||||
MapEntry('end', args.endDate),
|
||||
MapEntry('search', args.search),
|
||||
|
|
@ -61,7 +72,7 @@ class RequestHelper {
|
|||
static String radarOverviews({required RadarRequestArgs args}) =>
|
||||
_baseRadarUrl +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('start', args.startDate),
|
||||
MapEntry('end', args.endDate),
|
||||
MapEntry('search', args.search),
|
||||
|
|
@ -77,7 +88,7 @@ class RequestHelper {
|
|||
_baseNewsUrl +
|
||||
'/$id' +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('start', args.startDate),
|
||||
MapEntry('end', args.endDate),
|
||||
MapEntry('search', args.search),
|
||||
|
|
@ -85,7 +96,7 @@ class RequestHelper {
|
|||
static String newsOverviews({required NewsRequestArgs args}) =>
|
||||
_baseNewsUrl +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('start', args.startDate),
|
||||
MapEntry('end', args.endDate),
|
||||
MapEntry('search', args.search),
|
||||
|
|
@ -101,27 +112,29 @@ class RequestHelper {
|
|||
_baseStudioUrl +
|
||||
'/$id' +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('type', args.type),
|
||||
MapEntry('order', args.order),
|
||||
MapEntry('search', args.search),
|
||||
]);
|
||||
static String studioOverviews({required StudioRequestArgs args}) =>
|
||||
_baseNewsUrl +
|
||||
_baseStudioUrl +
|
||||
_urlConcatGenerator([
|
||||
MapEntry('page', args.page.toString()),
|
||||
MapEntry('page', args.page),
|
||||
MapEntry('type', args.type),
|
||||
MapEntry('order', args.order),
|
||||
MapEntry('search', args.search),
|
||||
]);
|
||||
|
||||
static String _urlConcatGenerator(List<MapEntry<String, String?>> additions) {
|
||||
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
|
||||
String result = '';
|
||||
additions.removeWhere((element) => element.value == null);
|
||||
additions.removeWhere(
|
||||
(element) => element.value == null || element.value.toString().isEmpty,
|
||||
);
|
||||
if (additions.isNotEmpty) {
|
||||
result += '?';
|
||||
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) {
|
||||
result += '&';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
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/utils/action_sheet.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/tab_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/video_overview.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';
|
||||
|
|
@ -24,6 +27,12 @@ class Studio extends StatefulWidget {
|
|||
class _StudioState extends State<Studio> {
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
context.read<StudioState>().init();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomScrollView(
|
||||
|
|
@ -86,9 +95,41 @@ class _StudioState extends State<Studio> {
|
|||
Consumer<StudioState>(
|
||||
builder: (context, state, child) => SliverStateHandler<StudioState>(
|
||||
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,
|
||||
onRetry: () {},
|
||||
onRetry: () => state.getStudioOverviews(page: 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -132,7 +173,7 @@ class _StudioState extends State<Studio> {
|
|||
titleIcon: DidvanIcons.sort_regular,
|
||||
hasDismissButton: false,
|
||||
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/overview_data.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/user_provider.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
|
||||
class StudioState extends CoreProvier {
|
||||
final List<StudioData> studios = [];
|
||||
final List<OverviewData> studios = [];
|
||||
|
||||
String? search;
|
||||
String? lastSearch;
|
||||
|
|
@ -22,52 +23,71 @@ class StudioState extends CoreProvier {
|
|||
set videosSelected(bool value) {
|
||||
if (_videosSelected == value) return;
|
||||
_videosSelected = value;
|
||||
studios.clear();
|
||||
getStudioOverviews(page: page);
|
||||
}
|
||||
|
||||
void init() {
|
||||
search = '';
|
||||
lastSearch = '';
|
||||
videosSelected = true;
|
||||
_videosSelected = true;
|
||||
selectedSortTypeIndex = 0;
|
||||
Future.delayed(Duration.zero, () {
|
||||
getStudioOverviews(page: 1);
|
||||
});
|
||||
}
|
||||
|
||||
String get _order {
|
||||
String get order {
|
||||
if (selectedSortTypeIndex == 0) return 'date';
|
||||
if (selectedSortTypeIndex == 1) return 'view';
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
String get type {
|
||||
if (videosSelected) return 'video';
|
||||
return 'podcast';
|
||||
}
|
||||
|
||||
Future<void> getStudioOverviews({required int page}) async {
|
||||
this.page = page;
|
||||
if (page == 1) {
|
||||
appState = AppState.busy;
|
||||
}
|
||||
|
||||
final service = RequestService(
|
||||
RequestHelper.studioOverviews(
|
||||
args: StudioRequestArgs(
|
||||
page: page,
|
||||
type: videosSelected ? 'video' : 'podcast',
|
||||
type: type,
|
||||
search: search,
|
||||
order: _order,
|
||||
order: order,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
if (page == 1) {
|
||||
studios.clear();
|
||||
}
|
||||
lastPage = service.result['lastPage'];
|
||||
final studioItems = service.result['studios'];
|
||||
for (var i = 0; i < studioItems.length; i++) {
|
||||
studios.add(StudioData.fromJson(studioItems[i]));
|
||||
studios.add(OverviewData.fromJson(studioItems[i]));
|
||||
}
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
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:didvan/config/design_config.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AudioSlider extends StatelessWidget {
|
||||
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;
|
||||
|
||||
|
|
@ -24,7 +27,14 @@ class AudioSlider extends StatelessWidget {
|
|||
_isPlaying ? MediaService.audioPlayer.bufferedPosition : null,
|
||||
thumbRadius: 6,
|
||||
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),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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/requests/news.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 PodcastOverview extends StatelessWidget {
|
||||
final OverviewData news;
|
||||
final NewsRequestArgs? newsRequestArgs;
|
||||
final OverviewData podcast;
|
||||
final void Function(int id, int count) onCommentsChanged;
|
||||
final void Function(int id, bool value) onMarkChanged;
|
||||
final bool hasUnmarkConfirmation;
|
||||
final StudioRequestArgs? studioRequestArgs;
|
||||
const PodcastOverview({
|
||||
Key? key,
|
||||
required this.news,
|
||||
required this.podcast,
|
||||
required this.onCommentsChanged,
|
||||
required this.onMarkChanged,
|
||||
this.newsRequestArgs,
|
||||
this.hasUnmarkConfirmation = false,
|
||||
required this.hasUnmarkConfirmation,
|
||||
this.studioRequestArgs,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanCard(
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.newsDetails,
|
||||
Routes.studioDetails,
|
||||
arguments: {
|
||||
'onMarkChanged': onMarkChanged,
|
||||
'id': news.id,
|
||||
'args': newsRequestArgs,
|
||||
'onCommentsChanged': onCommentsChanged,
|
||||
'id': podcast.id,
|
||||
'args': studioRequestArgs,
|
||||
'hasUnmarkConfirmation': hasUnmarkConfirmation,
|
||||
'isVideo': false,
|
||||
},
|
||||
),
|
||||
child: Column(
|
||||
|
|
@ -41,47 +49,50 @@ class PodcastOverview extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: news.image,
|
||||
imageUrl: podcast.image,
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 64,
|
||||
child: DidvanText(
|
||||
news.title,
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DidvanText(
|
||||
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),
|
||||
DidvanText(
|
||||
news.description,
|
||||
maxLines: 3,
|
||||
podcast.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const DidvanDivider(verticalPadding: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
DidvanText(
|
||||
news.reference!,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
DidvanText(
|
||||
' - ' + DateTimeUtils.momentGenerator(news.createdAt),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
],
|
||||
DurationWidget(duration: podcast.duration!),
|
||||
const Spacer(),
|
||||
DidvanIconButton(
|
||||
gestureSize: 28,
|
||||
icon: DidvanIcons.download_regular,
|
||||
onPressed: () {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
BookmarkButton(
|
||||
value: news.marked,
|
||||
onMarkChanged: (value) => onMarkChanged(news.id, value),
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
value: podcast.marked,
|
||||
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -95,7 +106,7 @@ class PodcastOverview extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const ShimmerPlaceholder(height: 64, width: 64),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -109,7 +120,7 @@ class PodcastOverview extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 16),
|
||||
const ShimmerPlaceholder(
|
||||
height: 16,
|
||||
width: double.infinity,
|
||||
|
|
@ -117,19 +128,21 @@ class PodcastOverview extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
const ShimmerPlaceholder(
|
||||
height: 16,
|
||||
width: double.infinity,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const ShimmerPlaceholder(
|
||||
height: 16,
|
||||
width: 100,
|
||||
width: 200,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const DidvanDivider(verticalPadding: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
ShimmerPlaceholder(height: 12, width: 150),
|
||||
ShimmerPlaceholder(height: 24, width: 24),
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
height: appBarData.isSmall ? 56 : 72,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: const EdgeInsets.only(right: 4, left: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
border: hasBorder
|
||||
? Border(
|
||||
bottom: BorderSide(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
|||
class DidvanScaffold extends StatefulWidget {
|
||||
final List<Widget>? slivers;
|
||||
final List<Widget>? children;
|
||||
final AppBarData appBarData;
|
||||
final AppBarData? appBarData;
|
||||
final EdgeInsets padding;
|
||||
final Color? backgroundColor;
|
||||
final bool reverse;
|
||||
|
|
@ -40,14 +40,14 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
|||
controller: _scrollController,
|
||||
reverse: widget.reverse,
|
||||
slivers: [
|
||||
if (!widget.reverse)
|
||||
if (!widget.reverse && widget.appBarData != null)
|
||||
SliverAppBar(
|
||||
toolbarHeight: kToolbarHeight,
|
||||
toolbarHeight: widget.appBarData!.isSmall ? 56 : 72,
|
||||
backgroundColor: widget.backgroundColor ??
|
||||
Theme.of(context).colorScheme.background,
|
||||
automaticallyImplyLeading: false,
|
||||
pinned: true,
|
||||
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData),
|
||||
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!),
|
||||
),
|
||||
if (!widget.reverse)
|
||||
const SliverToBoxAdapter(
|
||||
|
|
@ -79,9 +79,9 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (widget.reverse)
|
||||
if (widget.reverse && widget.appBarData != null)
|
||||
_AppBar(
|
||||
appBarData: widget.appBarData,
|
||||
appBarData: widget.appBarData!,
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
30
pubspec.lock
30
pubspec.lock
|
|
@ -566,7 +566,7 @@ packages:
|
|||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.2"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -782,6 +782,34 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ dependencies:
|
|||
image_cropper: ^1.5.0
|
||||
firebase_messaging: ^11.2.8
|
||||
firebase_core: ^1.13.1
|
||||
webview_flutter: ^3.0.1
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue