D1APP-99 studio

This commit is contained in:
MohammadTaha Basiri 2022-03-25 19:23:37 +04:30
parent 00e00b0ec5
commit d869f54338
18 changed files with 374 additions and 181 deletions

View File

@ -3,10 +3,14 @@
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:label="Didvan" android:label="Didvan"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View File

@ -1,7 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:io';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart' as parser; import 'package:http_parser/http_parser.dart' as parser;
import 'package:permission_handler/permission_handler.dart';
class RequestService { class RequestService {
static late String token; static late String token;
@ -162,6 +164,13 @@ class RequestService {
} }
} }
Future<void> download() async {
Permission.manageExternalStorage.request();
final response = await http.get(Uri.parse(url));
final file = await File('/storage/emulated/0/Download/file.mp3').create();
await file.writeAsBytes(response.bodyBytes);
}
void _handleResponse(http.Response? response) { void _handleResponse(http.Response? response) {
statusCode = response?.statusCode; statusCode = response?.statusCode;
if (_handleError(response)) { if (_handleError(response)) {

View File

@ -5,7 +5,7 @@ import 'package:didvan/views/home/radar/radar.dart';
import 'package:didvan/views/home/settings/settings.dart'; import 'package:didvan/views/home/settings/settings.dart';
import 'package:didvan/views/home/statistics/statistics.dart'; import 'package:didvan/views/home/statistics/statistics.dart';
import 'package:didvan/views/home/studio/studio.dart'; import 'package:didvan/views/home/studio/studio.dart';
import 'package:didvan/views/home/widgets/bnb.dart'; import 'package:didvan/views/widgets/didvan/bnb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@ -54,7 +54,10 @@ class _StudioState extends State<Studio> {
icon: DidvanIcons.bookmark_regular, icon: DidvanIcons.bookmark_regular,
onPressed: () => Navigator.of(context).pushNamed( onPressed: () => Navigator.of(context).pushNamed(
Routes.filteredBookmarks, Routes.filteredBookmarks,
arguments: context.read<StudioState>().type, arguments: {
'type': context.read<StudioState>().type,
'onDeleted': (_) {}
},
), ),
), ),
), ),

View File

@ -84,9 +84,11 @@ class _StudioDetailsState extends State<StudioDetails> {
return Consumer<StudioDetailsState>( return Consumer<StudioDetailsState>(
builder: (context, state, child) => StateHandler<StudioDetailsState>( builder: (context, state, child) => StateHandler<StudioDetailsState>(
state: state, state: state,
onRetry: () => state.getStudioDetails(state.currentStudio.id), onRetry: () => state.getStudioDetails(state.studio.id),
builder: (context, state) { builder: (context, state) {
if (state.studios.isEmpty) { if (state.prevStudio == null &&
state.nextStudio == null &&
state.args.page != 0) {
return const SizedBox(); return const SizedBox();
} }
return WillPopScope( return WillPopScope(
@ -105,7 +107,7 @@ class _StudioDetailsState extends State<StudioDetails> {
? null ? null
: AppBarData( : AppBarData(
isSmall: true, isSmall: true,
title: state.currentStudio.title, title: state.studio.title,
), ),
children: [ children: [
SizedBox( SizedBox(
@ -146,7 +148,7 @@ class _StudioDetailsState extends State<StudioDetails> {
</style> </style>
</head> </head>
<body> <body>
${state.currentStudio.media} ${state.studio.media}
</body> </body>
</html> </html>
''', ''',
@ -171,12 +173,15 @@ class _StudioDetailsState extends State<StudioDetails> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
StudioDetailsWidget( StudioDetailsWidget(
onCommentsTabSelected: () => _scrollController.animateTo( onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent, _scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
studio: state.currentStudio, ),
studio: state.studio,
), ),
], ],
), ),

View File

@ -86,12 +86,13 @@ class _StudioDetailsState extends State<StudioDetails> {
return Consumer<StudioDetailsState>( return Consumer<StudioDetailsState>(
builder: (context, state, child) => StateHandler<StudioDetailsState>( builder: (context, state, child) => StateHandler<StudioDetailsState>(
state: state, state: state,
onRetry: () => state.getStudioDetails(state.currentStudio.id), onRetry: () => state.getStudioDetails(state.studio.id),
builder: (context, state) { builder: (context, state) {
if (state.studios.isEmpty) { if (state.prevStudio == null &&
state.nextStudio == null &&
state.args.page != 0) {
return const SizedBox(); return const SizedBox();
} }
if (kIsWeb) {
// ignore: undefined_prefixed_name // ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory( ui.platformViewRegistry.registerViewFactory(
"video", "video",
@ -99,12 +100,11 @@ class _StudioDetailsState extends State<StudioDetails> {
..allowFullscreen = true ..allowFullscreen = true
..src = Uri.dataFromString( ..src = Uri.dataFromString(
'<style>*{padding: 0 ; margin: 0; background: black;}</style>' + '<style>*{padding: 0 ; margin: 0; background: black;}</style>' +
state.currentStudio.media, state.studio.media,
mimeType: 'text/html', mimeType: 'text/html',
).toString() ).toString()
..style.border = 'none', ..style.border = 'none',
); );
}
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (_isFullScreen) { if (_isFullScreen) {
@ -120,7 +120,7 @@ class _StudioDetailsState extends State<StudioDetails> {
? null ? null
: AppBarData( : AppBarData(
isSmall: true, isSmall: true,
title: state.currentStudio.title, title: state.studio.title,
), ),
children: [ children: [
if (kIsWeb) if (kIsWeb)
@ -167,7 +167,7 @@ class _StudioDetailsState extends State<StudioDetails> {
</style> </style>
</head> </head>
<body> <body>
${state.currentStudio.media} ${state.studio.media}
</body> </body>
</html> </html>
''', ''',
@ -193,12 +193,15 @@ class _StudioDetailsState extends State<StudioDetails> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
StudioDetailsWidget( StudioDetailsWidget(
onCommentsTabSelected: () => _scrollController.animateTo( onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent, _scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
studio: state.currentStudio, ),
studio: state.studio,
), ),
], ],
), ),

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/overview_data.dart';
@ -11,16 +10,17 @@ import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
class StudioDetailsState extends CoreProvier { class StudioDetailsState extends CoreProvier {
final List<StudioDetailsData?> studios = []; late StudioDetailsData studio;
StudioDetailsData? nextStudio;
StudioDetailsData? prevStudio;
late int initialIndex; late int initialIndex;
late StudioRequestArgs args; late StudioRequestArgs args;
int _selectedDetailsIndex = 0;
bool isFetchingNewItem = false;
final List<int> relatedQueue = []; final List<int> relatedQueue = [];
bool currentTypeIsVideo = true; bool _positionListenerActivated = false;
int _currentIndex = 0; int _selectedDetailsIndex = 0;
int get currentIndex => _currentIndex; Timer? timer;
int timerValue = 10;
int get selectedDetailsIndex => _selectedDetailsIndex; int get selectedDetailsIndex => _selectedDetailsIndex;
set selectedDetailsIndex(int value) { set selectedDetailsIndex(int value) {
@ -31,84 +31,50 @@ class StudioDetailsState extends CoreProvier {
notifyListeners(); notifyListeners();
} }
StudioDetailsData get currentStudio {
try {
return studios[_currentIndex]!;
} catch (e) {
return studios[_currentIndex + 1]!;
}
}
Future<void> getStudioDetails( Future<void> getStudioDetails(
int id, { int id, {
bool? isForward,
StudioRequestArgs? args, StudioRequestArgs? args,
bool? isForward,
}) async { }) async {
if (args != null) { if (args != null) {
this.args = args; this.args = args;
} }
if (isForward == null) { if (MediaService.currentPodcast?.id == id && this.args.type == 'podcast') {
_selectedDetailsIndex = 0; return;
appState = AppState.busy;
} else {
isFetchingNewItem = true;
notifyListeners();
} }
_selectedDetailsIndex = 0;
if (isForward != null) {
if (isForward) {
prevStudio = studio;
studio = nextStudio!;
nextStudio = null;
} else {
nextStudio = studio;
studio = prevStudio!;
prevStudio = null;
}
_handlePodcastPlayback(studio);
}
appState = AppState.busy;
final service = RequestService(RequestHelper.studioDetails(id, this.args)); final service = RequestService(RequestHelper.studioDetails(id, this.args));
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
studios.clear();
final result = service.result; final result = service.result;
final studio = StudioDetailsData.fromJson(result['studio']); studio = StudioDetailsData.fromJson(result['studio']);
await _handlePodcastPlayback(studio); if (result['nextStudio'].isNotEmpty && this.args.page != 0) {
if (this.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']); nextStudio = StudioDetailsData.fromJson(result['nextStudio']);
} }
if (result['prevStudio'].isNotEmpty && this.args.page != 0) {
prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
}
if (isForward == null) { if (isForward == null) {
studios await _handlePodcastPlayback(studio);
.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; appState = AppState.idle;
return; return;
} }
//why? total page state shouldn't die!
if (isForward == null) {
appState = AppState.failed; appState = AppState.failed;
} }
}
Future<void> _handlePodcastPlayback(StudioDetailsData studio) async { Future<void> _handlePodcastPlayback(StudioDetailsData studio) async {
if (args.type == 'podcast') { if (args.type == 'podcast') {
@ -118,37 +84,39 @@ class StudioDetailsState extends CoreProvier {
audioSource: studio.media, audioSource: studio.media,
isVoiceMessage: false, isVoiceMessage: false,
); );
if (nextStudio != null && !_positionListenerActivated) {
_positionListenerActivated = true;
MediaService.audioPlayer.positionStream.listen((event) {
final duration = MediaService.audioPlayer.duration ??
Duration(seconds: studio.duration);
if (event.compareTo(duration) > 0 && nextStudio != null) {
getStudioDetails(nextStudio!.id, isForward: true);
}
});
}
} }
} }
Future<void> getRelatedContents() async { Future<void> getRelatedContents() async {
if (currentStudio.relatedContents.isNotEmpty) return; if (studio.relatedContents.isNotEmpty) return;
relatedQueue.add(currentStudio.id); relatedQueue.add(studio.id);
final service = RequestService(RequestHelper.tag( final service = RequestService(RequestHelper.tag(
ids: currentStudio.tags.map((tag) => tag.id).toList(), ids: studio.tags.map((tag) => tag.id).toList(),
itemId: currentStudio.id, itemId: studio.id,
type: currentStudio.media.contains('iframe') ? 'video' : 'podcast', type: studio.media.contains('iframe') ? 'video' : 'podcast',
)); ));
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
final relateds = service.result['contents']; final relateds = service.result['contents'];
for (var i = 0; i < relateds.length; i++) { for (var i = 0; i < relateds.length; i++) {
studios studio.relatedContents.add(OverviewData.fromJson(relateds[i]));
.where((element) => element != null)
.firstWhere((element) => element!.id == currentStudio.id)!
.relatedContents
.add(OverviewData.fromJson(relateds[i]));
} }
notifyListeners(); notifyListeners();
} }
} }
bool exists(StudioDetailsData? studio) =>
studios.any((r) => studio != null && r != null && r.id == studio.id);
void onCommentsChanged(int count) { void onCommentsChanged(int count) {
studios.firstWhere((studio) => studio?.id == currentStudio.id)!.comments = studio.comments = count;
count;
notifyListeners(); notifyListeners();
} }
} }

View File

@ -75,7 +75,7 @@ class StudioDetailsWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
DidvanText(state.currentStudio.description), DidvanText(state.studio.description),
if (studio.tags.isNotEmpty) const SizedBox(height: 20), if (studio.tags.isNotEmpty) const SizedBox(height: 20),
Wrap( Wrap(
spacing: 8, spacing: 8,
@ -90,15 +90,15 @@ class StudioDetailsWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const SizedBox(), const SizedBox(),
if (state.studios.length != state.currentIndex + 1) if (state.nextStudio != null)
_StudioPreview( _StudioPreview(
isNext: true, isNext: true,
studio: state.studios[state.currentIndex + 1]!, studio: state.nextStudio!,
), ),
if (state.currentIndex != 0) if (state.prevStudio != null)
_StudioPreview( _StudioPreview(
isNext: false, isNext: false,
studio: state.studios[state.currentIndex - 1]!, studio: state.prevStudio!,
), ),
const SizedBox(), const SizedBox(),
], ],
@ -234,7 +234,11 @@ class _StudioPreview extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
final state = context.read<StudioDetailsState>(); final state = context.read<StudioDetailsState>();
state.getStudioDetails(studio.id, args: state.args); state.getStudioDetails(
isNext ? state.nextStudio!.id : state.prevStudio!.id,
args: state.args,
isForward: isNext,
);
}, },
child: Container( child: Container(
width: 88, width: 88,

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
@ -117,4 +119,9 @@ class StudioState extends CoreProvier {
studios.firstWhere((radar) => radar.id == id).comments = count; studios.firstWhere((radar) => radar.id == id).comments = count;
notifyListeners(); notifyListeners();
} }
void download(String url) {
final service = RequestService(url);
service.download();
}
} }

View File

@ -2,6 +2,9 @@ import 'package:carousel_slider/carousel_slider.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/studio/studio_state.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';
@ -26,12 +29,38 @@ class _StudioSliderState extends State<StudioSlider> {
children: [ children: [
CarouselSlider( CarouselSlider(
items: [ items: [
if (state.appState == AppState.busy)
const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: ShimmerPlaceholder(),
),
if (state.appState == AppState.idle)
for (var i = 0; i < state.sliders.length; i++) for (var i = 0; i < state.sliders.length; i++)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),
child: state.appState == AppState.busy child: GestureDetector(
? const ShimmerPlaceholder() onTap: () {
: Stack( if (state.videosSelected) {
Navigator.of(context)
.pushNamed(Routes.studioDetails, arguments: {
'onMarkChanged': state.changeMark,
'id': state.sliders[i].id,
'args':
const StudioRequestArgs(page: 0, type: 'video'),
'hasUnmarkConfirmation': false,
'isVideo': true,
});
return;
}
context.read<StudioDetailsState>().getStudioDetails(
state.sliders[i].id,
args: const StudioRequestArgs(
page: 0,
type: 'podcast',
),
);
},
child: Stack(
children: [ children: [
SkeletonImage( SkeletonImage(
borderRadius: DesignConfig.mediumBorderRadius, borderRadius: DesignConfig.mediumBorderRadius,
@ -68,6 +97,7 @@ class _StudioSliderState extends State<StudioSlider> {
], ],
), ),
), ),
),
], ],
options: CarouselOptions( options: CarouselOptions(
onPageChanged: (index, reason) => setState( onPageChanged: (index, reason) => setState(
@ -75,7 +105,7 @@ class _StudioSliderState extends State<StudioSlider> {
), ),
viewportFraction: 0.94, viewportFraction: 0.94,
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
autoPlay: true, autoPlay: state.appState == AppState.idle,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),

View File

@ -1,16 +1,21 @@
import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/utils/action_sheet.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/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/home/widgets/bookmark_button.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/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/item_title.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';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -21,6 +26,7 @@ class AudioPlayerWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.read<StudioDetailsState>();
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
@ -59,14 +65,35 @@ class AudioPlayerWidget extends StatelessWidget {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
StatefulBuilder(
builder: (context, setState) => Column(
children: [ children: [
DidvanIconButton( DidvanIconButton(
icon: DidvanIcons.sleep_timer_regular, icon: state.timer == null
onPressed: () {}, ? DidvanIcons.sleep_timer_regular
: DidvanIcons.sleep_enabled_regular,
color: Theme.of(context).colorScheme.title,
onPressed: () => _showSleepTimer(
state,
() => setState(() {}),
),
),
if (state.timer != null)
DidvanText(
state.timerValue.toString() + '\'',
isEnglishFont: true,
style: Theme.of(context).textTheme.overline,
color: Theme.of(context).colorScheme.caption,
),
],
),
), ),
Column( Column(
children: [ children: [
DidvanIconButton( DidvanIconButton(
color: Theme.of(context).colorScheme.title,
size: 32, size: 32,
icon: DidvanIcons.media_forward_solid, icon: DidvanIcons.media_forward_solid,
onPressed: () { onPressed: () {
@ -78,7 +105,11 @@ class AudioPlayerWidget extends StatelessWidget {
); );
}, },
), ),
const DidvanText('30', isEnglishFont: true), DidvanText(
'30',
isEnglishFont: true,
color: Theme.of(context).colorScheme.title,
),
], ],
), ),
StreamBuilder<bool>( StreamBuilder<bool>(
@ -94,6 +125,7 @@ class AudioPlayerWidget extends StatelessWidget {
DidvanIconButton( DidvanIconButton(
size: 32, size: 32,
icon: DidvanIcons.media_backward_solid, icon: DidvanIcons.media_backward_solid,
color: Theme.of(context).colorScheme.title,
onPressed: () { onPressed: () {
MediaService.audioPlayer.seek( MediaService.audioPlayer.seek(
Duration( Duration(
@ -105,21 +137,104 @@ class AudioPlayerWidget extends StatelessWidget {
); );
}, },
), ),
const DidvanText('10', isEnglishFont: true), DidvanText(
'10',
isEnglishFont: true,
color: Theme.of(context).colorScheme.title,
),
], ],
), ),
BookmarkButton( BookmarkButton(
gestureSize: 48, gestureSize: 48,
color: Theme.of(context).colorScheme.title,
value: podcast.marked, value: podcast.marked,
onMarkChanged: (value) => onMarkChanged: (value) =>
context.read<StudioState>().changeMark(podcast.id, value), context.read<StudioState>().changeMark(podcast.id, value),
), ),
const SizedBox(),
], ],
), ),
], ],
), ),
); );
} }
Future<void> _showSleepTimer(StudioDetailsState state, update) async {
int timerValue = 10;
final controller = FixedExtentScrollController();
Future.delayed(
const Duration(milliseconds: 100),
() => controller.animateTo(
50 * (state.timerValue / 5 - 2),
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
);
await ActionSheetUtils.showBottomSheet(
data: ActionSheetData(
content: StatefulBuilder(
builder: (context, setState) => Column(
children: [
const ItemTitle(
title: 'زمان خواب',
icon: DidvanIcons.sleep_timer_regular,
),
const SizedBox(height: 24),
DidvanText(
timerValue.toString() + ' دقیقه',
style: Theme.of(context).textTheme.headline3,
),
const SizedBox(height: 12),
const Icon(DidvanIcons.caret_down_solid),
const SizedBox(height: 8),
SizedBox(
height: 50,
child: RotatedBox(
quarterTurns: 3,
child: ListWheelScrollView(
controller: controller,
physics: const FixedExtentScrollPhysics(),
itemExtent: 48,
onSelectedItemChanged: (index) {
final minutes = (index + 2) * 5;
timerValue = minutes;
setState(() {});
},
children: [
for (var i = 0; i < 9; i++)
Center(
child: Container(
color: Theme.of(context).colorScheme.text,
width: 50,
height: 3,
),
),
],
),
),
),
],
),
),
onConfirmed: () {
state.timer = Timer(
Duration(minutes: timerValue),
MediaService.audioPlayer.stop,
);
state.timerValue = timerValue;
update();
},
dismissTitle: 'لغو',
onDismissed: () {
state.timer?.cancel();
state.timer = null;
state.timerValue = 10;
update();
},
),
);
controller.dispose();
}
} }
class _PlayPouseAnimatedIcon extends StatefulWidget { class _PlayPouseAnimatedIcon extends StatefulWidget {

View File

@ -45,6 +45,7 @@ class AudioSlider extends StatelessWidget {
timeLabelTextStyle: TextStyle( timeLabelTextStyle: TextStyle(
fontSize: showTimer ? null : 0, fontSize: showTimer ? null : 0,
height: showTimer ? 3 : 0, height: showTimer ? 3 : 0,
color: Theme.of(context).colorScheme.text,
fontFamily: DesignConfig.fontFamily.replaceAll( fontFamily: DesignConfig.fontFamily.replaceAll(
'-FA', '-FA',
'', '',

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
class BookmarkButton extends StatefulWidget { class BookmarkButton extends StatefulWidget {
final bool value; final bool value;
final Color? color;
final void Function(bool value) onMarkChanged; final void Function(bool value) onMarkChanged;
final bool askForConfirmation; final bool askForConfirmation;
final double gestureSize; final double gestureSize;
@ -16,6 +17,7 @@ class BookmarkButton extends StatefulWidget {
required this.onMarkChanged, required this.onMarkChanged,
this.askForConfirmation = false, this.askForConfirmation = false,
required this.gestureSize, required this.gestureSize,
this.color,
}) : super(key: key); }) : super(key: key);
@override @override
@ -41,6 +43,7 @@ class _BookmarkButtonState extends State<BookmarkButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanIconButton( return DidvanIconButton(
gestureSize: widget.gestureSize, gestureSize: widget.gestureSize,
color: widget.color,
icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular, icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () async { onPressed: () async {
bool confirm = false; bool confirm = false;

View File

@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_state.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/home/widgets/duration_widget.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
@ -77,12 +78,13 @@ class PodcastOverview extends StatelessWidget {
children: [ children: [
DurationWidget(duration: podcast.duration!), DurationWidget(duration: podcast.duration!),
const Spacer(), const Spacer(),
DidvanIconButton( // DidvanIconButton(
gestureSize: 28, // gestureSize: 28,
icon: DidvanIcons.download_regular, // icon: DidvanIcons.download_regular,
onPressed: () {}, // onPressed: () =>
), // context.read<StudioState>().download(podcast.media!),
const SizedBox(width: 16), // ),
// const SizedBox(width: 16),
BookmarkButton( BookmarkButton(
askForConfirmation: hasUnmarkConfirmation, askForConfirmation: hasUnmarkConfirmation,
gestureSize: 24, gestureSize: 24,

View File

@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/studio/studio_state.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/home/widgets/duration_widget.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
@ -13,6 +14,7 @@ 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';
import 'package:provider/provider.dart';
class VideoOverview extends StatelessWidget { class VideoOverview extends StatelessWidget {
final OverviewData video; final OverviewData video;
@ -99,12 +101,13 @@ class VideoOverview extends StatelessWidget {
children: [ children: [
DurationWidget(duration: video.duration!), DurationWidget(duration: video.duration!),
const Spacer(), const Spacer(),
DidvanIconButton( // DidvanIconButton(
gestureSize: 28, // gestureSize: 28,
icon: DidvanIcons.download_regular, // icon: DidvanIcons.download_regular,
onPressed: () {}, // onPressed: () =>
), // context.read<StudioState>().download(video.media!),
const SizedBox(width: 16), // ),
// const SizedBox(width: 16),
BookmarkButton( BookmarkButton(
gestureSize: 24, gestureSize: 24,
value: video.marked, value: video.marked,

View File

@ -231,7 +231,7 @@ class DidvanBNB extends StatelessWidget {
expandableContent: state.appState == AppState.busy expandableContent: state.appState == AppState.busy
? const SizedBox() ? const SizedBox()
: StudioDetailsWidget( : StudioDetailsWidget(
studio: detailsState.currentStudio, studio: detailsState.studio,
onCommentsTabSelected: () { onCommentsTabSelected: () {
Future.delayed( Future.delayed(
const Duration(milliseconds: 100), const Duration(milliseconds: 100),

View File

@ -532,6 +532,41 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.11.1" version: "1.11.1"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
url: "https://pub.dartlang.org"
source: hosted
version: "9.2.0"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
url: "https://pub.dartlang.org"
source: hosted
version: "9.0.2+1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
url: "https://pub.dartlang.org"
source: hosted
version: "9.0.3"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "3.7.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
persian_datetime_picker: persian_datetime_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -840,4 +875,4 @@ packages:
version: "5.3.1" version: "5.3.1"
sdks: sdks:
dart: ">=2.16.0 <3.0.0" dart: ">=2.16.0 <3.0.0"
flutter: ">=2.5.0" flutter: ">=2.8.0"

View File

@ -64,6 +64,7 @@ dependencies:
firebase_core: ^1.13.1 firebase_core: ^1.13.1
webview_flutter: ^3.0.1 webview_flutter: ^3.0.1
expandable_bottom_sheet: ^1.1.1+1 expandable_bottom_sheet: ^1.1.1+1
permission_handler: ^9.2.0
dev_dependencies: dev_dependencies: