bug fixes

This commit is contained in:
MohammadTaha Basiri 2022-03-30 15:36:31 +04:30
parent 873c1f6692
commit 4df58d093a
16 changed files with 481 additions and 447 deletions

View File

@ -1,5 +1,6 @@
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/theme_provider.dart';
import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/routes/route_generator.dart'; import 'package:didvan/routes/route_generator.dart';
@ -21,6 +22,9 @@ class Didvan extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider<MediaProvider>(
create: (context) => MediaProvider(),
),
ChangeNotifierProvider<UserProvider>( ChangeNotifierProvider<UserProvider>(
create: (context) => UserProvider(), create: (context) => UserProvider(),
), ),

60
lib/providers/media.dart Normal file
View File

@ -0,0 +1,60 @@
import 'dart:io';
import 'package:didvan/models/enums.dart';
import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/storage/storage.dart';
class MediaProvider extends CoreProvier {
static final List<int> downloadedItemIds = [];
final List<String> downloadQueue = [];
Future<void> getDownloadsList() async {
downloadedItemIds.clear();
final videosDir = Directory(
StorageService.appDocsDir + ('/videos'),
);
final podcastsDir = Directory(
StorageService.appDocsDir + ('/podcasts'),
);
if (!await videosDir.exists()) {
await videosDir.create();
}
if (!await podcastsDir.exists()) {
await podcastsDir.create();
}
videosDir.list(recursive: false).listen(
(event) {
downloadedItemIds.add(
int.parse(
event.path.split('/').last.split('-').last.split('.').first,
),
);
},
);
podcastsDir.list(recursive: false).listen(
(event) {
downloadedItemIds.add(
int.parse(
event.path.split('/').last.split('-').last.split('.').first,
),
);
},
);
await Future.delayed(const Duration(milliseconds: 300), notifyListeners);
}
Future<void> download({
required String url,
required String fileName,
required bool isVideo,
}) async {
appState = AppState.busy;
downloadQueue.add(url);
notifyListeners();
final service = RequestService(url);
await service.download(fileName, isVideo ? 'videos' : 'podcasts');
downloadQueue.remove(url);
getDownloadsList();
}
}

View File

@ -1,7 +1,9 @@
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/providers/media.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';
import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
@ -30,6 +32,10 @@ class MediaService {
String tag; String tag;
tag = '${isVoiceMessage ? 'message' : 'podcast'}-$id'; tag = '${isVoiceMessage ? 'message' : 'podcast'}-$id';
isNetworkAudio ??= audioSource.runtimeType == String; isNetworkAudio ??= audioSource.runtimeType == String;
if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) {
audioSource = StorageService.appDocsDir + '/podcasts/podcast-$id.mp3';
isNetworkAudio = false;
}
if (audioPlayerTag == tag) { if (audioPlayerTag == tag) {
if (audioPlayer.playing) { if (audioPlayer.playing) {
await audioPlayer.pause(); await audioPlayer.pause();

View File

@ -165,7 +165,7 @@ class RequestService {
} }
Future<void> download(String fileName, String subDirectory) async { Future<void> download(String fileName, String subDirectory) async {
Permission.storage.request(); await Permission.storage.request();
final response = await http.get(Uri.parse(url)); final response = await http.get(Uri.parse(url));
StorageService.createFile( StorageService.createFile(
bytes: response.bodyBytes, bytes: response.bodyBytes,

View File

@ -19,7 +19,7 @@ class StorageService {
await dir.create(recursive: true); await dir.create(recursive: true);
} }
final file = await io.File( final file = await io.File(
appDocsDir + '/$subDirectory/podcast-$name.mp3', appDocsDir + '/$subDirectory/$name',
).create(recursive: true); ).create(recursive: true);
await file.writeAsBytes(bytes); await file.writeAsBytes(bytes);
} }

View File

@ -3,6 +3,7 @@ import 'dart:io';
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/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart';
@ -93,6 +94,9 @@ class _StudioDetailsState extends State<StudioDetails> {
await _changeFullSceen(false); await _changeFullSceen(false);
return false; return false;
} }
if (MediaService.currentPodcast != null) {
state.studio = MediaService.currentPodcast!;
}
return true; return true;
}, },
child: DidvanScaffold( child: DidvanScaffold(
@ -205,7 +209,6 @@ class _StudioDetailsState extends State<StudioDetails> {
children: [ children: [
StudioDetailsWidget( StudioDetailsWidget(
scrollController: _scrollController, scrollController: _scrollController,
studio: state.studio,
), ),
], ],
), ),

View File

@ -2,6 +2,7 @@ import 'dart:ui' as ui;
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.dart';
@ -93,6 +94,9 @@ class _StudioDetailsState extends State<StudioDetails> {
await _changeFullSceen(false); await _changeFullSceen(false);
return false; return false;
} }
if (MediaService.currentPodcast != null) {
state.studio = MediaService.currentPodcast!;
}
return true; return true;
}, },
child: DidvanScaffold( child: DidvanScaffold(
@ -144,7 +148,6 @@ class _StudioDetailsState extends State<StudioDetails> {
children: [ children: [
StudioDetailsWidget( StudioDetailsWidget(
scrollController: _scrollController, scrollController: _scrollController,
studio: state.studio,
), ),
], ],
), ),

View File

@ -6,6 +6,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/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.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';
@ -17,9 +18,9 @@ class StudioDetailsState extends CoreProvier {
StudioDetailsData? prevStudio; StudioDetailsData? prevStudio;
late int initialIndex; late int initialIndex;
late StudioRequestArgs args; late StudioRequestArgs args;
StudioRequestArgs? podcastArgs;
final List<int> relatedQueue = []; final List<int> relatedQueue = [];
bool _positionListenerActivated = false; bool _positionListenerActivated = false;
final List<int> downloadedFileIds = [];
int _selectedDetailsIndex = 0; int _selectedDetailsIndex = 0;
Timer? timer; Timer? timer;
@ -39,15 +40,19 @@ class StudioDetailsState extends CoreProvier {
int id, { int id, {
StudioRequestArgs? args, StudioRequestArgs? args,
bool? isForward, bool? isForward,
bool forceFetch = false,
}) async { }) async {
if (args != null) { if (args != null) {
this.args = args; this.args = args;
} }
if (MediaService.currentPodcast?.id == id && this.args.type == 'podcast') { if (this.args.type == 'podcast') {
podcastArgs = this.args;
}
if (MediaService.currentPodcast?.id == id &&
this.args.type == 'podcast' &&
!forceFetch) {
return; return;
} }
_getDownloadsList();
_selectedDetailsIndex = 0; _selectedDetailsIndex = 0;
if (isForward != null) { if (isForward != null) {
if (isForward) { if (isForward) {
@ -85,7 +90,7 @@ class StudioDetailsState extends CoreProvier {
if (result['prevStudio'].isNotEmpty && this.args.page != 0) { if (result['prevStudio'].isNotEmpty && this.args.page != 0) {
prevStudio = StudioDetailsData.fromJson(result['prevStudio']); prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
} }
if (isForward == null) { if (isForward == null && !forceFetch) {
await _handlePodcastPlayback(studio); await _handlePodcastPlayback(studio);
} }
appState = AppState.idle; appState = AppState.idle;
@ -100,14 +105,10 @@ class StudioDetailsState extends CoreProvier {
if (args.type == 'podcast') { if (args.type == 'podcast') {
MediaService.currentPodcast = studio; MediaService.currentPodcast = studio;
MediaService.podcastPlaylistArgs = args; MediaService.podcastPlaylistArgs = args;
final downloaded = downloadedFileIds.contains(studio.id);
await MediaService.handleAudioPlayback( await MediaService.handleAudioPlayback(
audioSource: downloaded audioSource: studio.media,
? StorageService.appDocsDir + '/podcasts/podcast-${studio.id}.mp3'
: studio.media,
id: studio.id, id: studio.id,
isVoiceMessage: false, isVoiceMessage: false,
isNetworkAudio: !downloaded,
); );
if (nextStudio != null && !_positionListenerActivated) { if (nextStudio != null && !_positionListenerActivated) {
_positionListenerActivated = true; _positionListenerActivated = true;
@ -129,25 +130,6 @@ class StudioDetailsState extends CoreProvier {
} }
} }
Future<void> _getDownloadsList() async {
downloadedFileIds.clear();
final dir = Directory(
StorageService.appDocsDir + ('/${args.type}s'),
);
if (!await dir.exists()) {
await dir.create();
}
dir.list(recursive: false).listen(
(event) {
downloadedFileIds.add(
int.parse(
event.path.split('/').last.split('-').last.split('.').first,
),
);
},
);
}
Future<void> getRelatedContents() async { Future<void> getRelatedContents() async {
if (studio.relatedContents.isNotEmpty) return; if (studio.relatedContents.isNotEmpty) return;
relatedQueue.add(studio.id); relatedQueue.add(studio.id);

View File

@ -15,129 +15,131 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class StudioDetailsWidget extends StatelessWidget { class StudioDetailsWidget extends StatelessWidget {
final StudioDetailsData studio;
final ScrollController? scrollController; final ScrollController? scrollController;
final VoidCallback? onCommentsTabSelected; final VoidCallback? onCommentsTabSelected;
const StudioDetailsWidget({ const StudioDetailsWidget({
Key? key, Key? key,
required this.studio,
this.onCommentsTabSelected, this.onCommentsTabSelected,
this.scrollController, this.scrollController,
}) : super(key: key); }) : super(key: key);
bool get _isVideo => studio.media.contains('iframe');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ds = MediaQuery.of(context).size; final ds = MediaQuery.of(context).size;
return Consumer<StudioDetailsState>( return Consumer<StudioDetailsState>(
builder: (context, state, child) => Container( builder: (context, state, child) {
color: Theme.of(context).colorScheme.surface, bool isVideo = state.studio.media.contains('iframe');
child: Column( return Container(
mainAxisSize: MainAxisSize.min, color: Theme.of(context).colorScheme.surface,
children: [ child: Column(
if (!_isVideo) mainAxisSize: MainAxisSize.min,
DetailsTabBar( children: [
isVideo: _isVideo, if (!isVideo)
onCommentsTabSelected: onCommentsTabSelected ?? () {}, DetailsTabBar(
), isVideo: isVideo,
const SizedBox(height: 16), onCommentsTabSelected: onCommentsTabSelected ?? () {},
StateHandler<StudioDetailsState>( ),
onRetry: () {}, const SizedBox(height: 16),
state: state, StateHandler<StudioDetailsState>(
builder: (context, state) { onRetry: () {},
if (state.selectedDetailsIndex == 0) { state: state,
return SingleChildScrollView( builder: (context, state) {
padding: const EdgeInsets.symmetric(horizontal: 20), if (state.selectedDetailsIndex == 0) {
child: Column( return SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.symmetric(horizontal: 20),
mainAxisSize: MainAxisSize.min, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
DidvanText(state.studio.description), mainAxisSize: MainAxisSize.min,
if (studio.tags.isNotEmpty) const SizedBox(height: 20), children: [
Wrap( DidvanText(state.studio.description),
spacing: 8, if (state.studio.tags.isNotEmpty)
runSpacing: 8, const SizedBox(height: 20),
children: [ Wrap(
for (var i = 0; i < studio.tags.length; i++) spacing: 8,
TagItem(tag: studio.tags[i]), runSpacing: 8,
], children: [
), for (var i = 0; i < state.studio.tags.length; i++)
const SizedBox(height: 20), TagItem(tag: state.studio.tags[i]),
Row( ],
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ const SizedBox(height: 20),
const SizedBox(), Row(
if (state.nextStudio != null) mainAxisAlignment: MainAxisAlignment.spaceBetween,
_StudioPreview( children: [
isNext: true, const SizedBox(),
studio: state.nextStudio!, if (state.nextStudio != null)
scrollController: scrollController, _StudioPreview(
), isNext: true,
if (state.prevStudio != null) studio: state.nextStudio!,
_StudioPreview( scrollController: scrollController,
isNext: false, ),
studio: state.prevStudio!, if (state.prevStudio != null)
scrollController: scrollController, _StudioPreview(
), isNext: false,
const SizedBox(), studio: state.prevStudio!,
], scrollController: scrollController,
) ),
], const SizedBox(),
), ],
); )
} ],
if (state.selectedDetailsIndex == 1) {
return ChangeNotifierProvider<CommentsState>(
create: (context) => CommentsState(),
child: SizedBox(
height: ds.height -
ds.width * 9 / 16 -
144 -
MediaQuery.of(context).padding.top,
child: Comments(
pageData: {
'id': studio.id,
'type': 'studio',
'title': studio.title,
'onCommentsChanged': state.onCommentsChanged,
'isPage': false,
},
), ),
), );
); }
} if (state.selectedDetailsIndex == 1) {
return Column( return ChangeNotifierProvider<CommentsState>(
children: [ create: (context) => CommentsState(),
if (studio.relatedContents.isEmpty) child: SizedBox(
for (var i = 0; i < 3; i++) height: ds.height -
ds.width * 9 / 16 -
144 -
MediaQuery.of(context).padding.top,
child: Comments(
pageData: {
'id': state.studio.id,
'type': 'studio',
'title': state.studio.title,
'onCommentsChanged': state.onCommentsChanged,
'isPage': false,
},
),
),
);
}
return Column(
children: [
if (state.studio.relatedContents.isEmpty)
for (var i = 0; i < 3; i++)
Padding(
padding: const EdgeInsets.only(
bottom: 8,
left: 16,
right: 16,
),
child: MultitypeOverview.placeholder,
),
for (var i = 0;
i < state.studio.relatedContents.length;
i++)
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 8, bottom: 8,
left: 16, left: 16,
right: 16, right: 16,
), ),
child: MultitypeOverview.placeholder, child: MultitypeOverview(
item: state.studio.relatedContents[i],
onMarkChanged: (id, value) {},
),
), ),
for (var i = 0; i < studio.relatedContents.length; i++) ],
Padding( );
padding: const EdgeInsets.only( },
bottom: 8, ),
left: 16, ],
right: 16, ),
), );
child: MultitypeOverview( },
item: studio.relatedContents[i],
onMarkChanged: (id, value) {},
),
),
],
);
},
),
],
),
),
); );
} }
} }

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
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';
@ -9,15 +8,10 @@ import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/providers/user_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';
import 'package:didvan/services/storage/storage.dart';
class StudioState extends CoreProvier { class StudioState extends CoreProvier {
final List<OverviewData> studios = []; final List<OverviewData> studios = [];
final List<SliderData> sliders = []; final List<SliderData> sliders = [];
final List<int> downloadedFileIds = [];
final List<String> downloadQueue = [];
AppState downloadState = AppState.idle;
String search = ''; String search = '';
String lastSearch = ''; String lastSearch = '';
@ -37,29 +31,9 @@ class StudioState extends CoreProvier {
_videosSelected = value; _videosSelected = value;
selectedSortTypeIndex = 0; selectedSortTypeIndex = 0;
_getSliders(); _getSliders();
getDownloadsList();
getStudios(page: page); getStudios(page: page);
} }
Future<void> getDownloadsList() async {
downloadedFileIds.clear();
final dir = Directory(
StorageService.appDocsDir + (videosSelected ? '/videos' : '/podcasts'),
);
if (!await dir.exists()) {
await dir.create();
}
dir.list(recursive: false).listen(
(event) {
downloadedFileIds.add(
int.parse(
event.path.split('/').last.split('-').last.split('.').first,
),
);
},
);
}
String get order { String get order {
if (selectedSortTypeIndex == 0 || selectedSortTypeIndex == 1) return 'date'; if (selectedSortTypeIndex == 0 || selectedSortTypeIndex == 1) return 'date';
if (selectedSortTypeIndex == 2) return 'view'; if (selectedSortTypeIndex == 2) return 'view';
@ -83,7 +57,6 @@ class StudioState extends CoreProvier {
lastSearch = ''; lastSearch = '';
_videosSelected = true; _videosSelected = true;
selectedSortTypeIndex = 0; selectedSortTypeIndex = 0;
getDownloadsList();
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
_getSliders(); _getSliders();
getStudios(page: 1); getStudios(page: 1);
@ -148,15 +121,4 @@ class StudioState extends CoreProvier {
studios.firstWhere((radar) => radar.id == id).comments = count; studios.firstWhere((radar) => radar.id == id).comments = count;
notifyListeners(); notifyListeners();
} }
Future<void> download(String url, String fileName) async {
downloadState = AppState.busy;
downloadQueue.add(url);
notifyListeners();
final service = RequestService(url);
await service.download(fileName, videosSelected ? 'videos' : 'podcasts');
downloadState = AppState.idle;
downloadQueue.remove(url);
notifyListeners();
}
} }

View File

@ -76,7 +76,10 @@ class _StudioTypeButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: () {
onTap();
FocusScope.of(context).unfocus();
},
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: Column( child: Column(

View File

@ -7,6 +7,7 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.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/models/view/action_sheet_data.dart';
import 'package:didvan/providers/media.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/utils/action_sheet.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';
@ -346,10 +347,6 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
audioSource: widget.audioSource, audioSource: widget.audioSource,
isVoiceMessage: false, isVoiceMessage: false,
id: widget.id, id: widget.id,
isNetworkAudio: !context
.read<StudioDetailsState>()
.downloadedFileIds
.contains(widget.id),
); );
_handleAnimation(); _handleAnimation();
}, },

View File

@ -3,9 +3,10 @@ import 'package:didvan/constants/app_icons.dart';
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';
import 'package:didvan/providers/media.dart';
import 'package:didvan/services/media/media.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';
@ -21,19 +22,18 @@ import 'package:provider/provider.dart';
class PodcastOverview extends StatelessWidget { class PodcastOverview extends StatelessWidget {
final OverviewData podcast; final OverviewData podcast;
final void Function(int id, bool value) onMarkChanged; final void Function(int id, bool value) onMarkChanged;
final StudioRequestArgs? studioRequestArgs; final StudioRequestArgs studioRequestArgs;
final bool hasUnmarkConfirmation; final bool hasUnmarkConfirmation;
const PodcastOverview({ const PodcastOverview({
Key? key, Key? key,
required this.podcast, required this.podcast,
required this.onMarkChanged, required this.onMarkChanged,
this.studioRequestArgs, required this.studioRequestArgs,
this.hasUnmarkConfirmation = false, this.hasUnmarkConfirmation = false,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.read<StudioState>();
return DidvanCard( return DidvanCard(
onTap: () { onTap: () {
context context
@ -77,52 +77,57 @@ class PodcastOverview extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const DidvanDivider(verticalPadding: 8), const DidvanDivider(verticalPadding: 8),
Row( Consumer<MediaProvider>(
children: [ builder: (context, state, child) => Row(
DurationWidget(duration: podcast.duration!), children: [
const Spacer(), DurationWidget(duration: podcast.duration!),
if (!kIsWeb) ...[ const Spacer(),
if (state.downloadState == AppState.idle || if (!kIsWeb) ...[
!state.downloadQueue.contains(podcast.media)) if (state.appState == AppState.idle ||
DidvanIconButton( !state.downloadQueue.contains(podcast.media))
gestureSize: 28, DidvanIconButton(
color: _isDownloaded(state) gestureSize: 28,
? Theme.of(context).colorScheme.primary color: _isDownloaded
: null, ? Theme.of(context).colorScheme.primary
icon: _isDownloaded(state) : null,
? DidvanIcons.download_solid icon: _isDownloaded
: DidvanIcons.download_regular, ? DidvanIcons.download_solid
onPressed: _isDownloaded(state) : DidvanIcons.download_regular,
? () {} onPressed: _isDownloaded
: () => state.download( ? () {}
podcast.media!, podcast.id.toString()), : () => state.download(
), fileName: 'podcast-${podcast.id}.mp3',
if (state.downloadState == AppState.busy && isVideo: false,
state.downloadQueue.contains(podcast.media)) url: podcast.media!,
const SizedBox( ),
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
), ),
), if (state.appState == AppState.busy &&
const SizedBox(width: 16), state.downloadQueue.contains(podcast.media))
const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
const SizedBox(width: 16),
],
BookmarkButton(
askForConfirmation: hasUnmarkConfirmation,
gestureSize: 32,
value: podcast.marked,
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
),
], ],
BookmarkButton( ),
askForConfirmation: hasUnmarkConfirmation,
gestureSize: 32,
value: podcast.marked,
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
),
],
), ),
], ],
), ),
); );
} }
bool _isDownloaded(state) { bool get _isDownloaded {
return state.downloadedFileIds.contains(podcast.id); return MediaProvider.downloadedItemIds.contains(podcast.id);
} }
static Widget get placeholder => DidvanCard( static Widget get placeholder => DidvanCard(

View File

@ -102,13 +102,6 @@ class RadarOverview extends StatelessWidget {
const DidvanDivider(), const DidvanDivider(),
Row( Row(
children: [ children: [
BookmarkButton(
gestureSize: 32,
value: radar.marked,
onMarkChanged: (value) => onMarkChanged(radar.id, value),
askForConfirmation: hasUnmarkConfirmation,
),
const Spacer(),
if (radar.comments != 0) DidvanText(radar.comments.toString()), if (radar.comments != 0) DidvanText(radar.comments.toString()),
const SizedBox(width: 4), const SizedBox(width: 4),
DidvanIconButton( DidvanIconButton(
@ -125,10 +118,17 @@ class RadarOverview extends StatelessWidget {
}, },
), ),
), ),
const SizedBox(width: 16), // const SizedBox(width: 16),
// const DidvanText('10'), // const DidvanText('10'),
// const SizedBox(width: 4), // const SizedBox(width: 4),
// const Icon(DidvanIcons.evaluation_regular), // const Icon(DidvanIcons.evaluation_regular),
const Spacer(),
BookmarkButton(
gestureSize: 32,
value: radar.marked,
onMarkChanged: (value) => onMarkChanged(radar.id, value),
askForConfirmation: hasUnmarkConfirmation,
),
], ],
), ),
], ],

View File

@ -2,6 +2,7 @@ import 'dart:developer';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/main.dart'; import 'package:didvan/main.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/server_data_provider.dart';
import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/theme_provider.dart';
import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/providers/user_provider.dart';
@ -109,6 +110,7 @@ class _SplashState extends State<Splash> {
final String? token = await userProvider.setAndGetToken(); final String? token = await userProvider.setAndGetToken();
if (token != null) { if (token != null) {
log(token); log(token);
context.read<MediaProvider>().getDownloadsList();
RequestService.token = token; RequestService.token = token;
final result = await userProvider.getUserInfo(); final result = await userProvider.getUserInfo();
if (!result) { if (!result) {

View File

@ -2,6 +2,7 @@ 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/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/services/storage/storage.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';
@ -26,6 +27,73 @@ class DidvanBNB extends StatelessWidget {
{Key? key, required this.currentTabIndex, required this.onTabChanged}) {Key? key, required this.currentTabIndex, required this.onTabChanged})
: super(key: key); : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
const _PlayerNavBar(),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 72,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: DesignConfig.defaultShadow,
),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
_NavBarItem(
isSelected: currentTabIndex == 0,
title: 'اخبار',
selectedIcon: DidvanIcons.news_solid,
unselectedIcon: DidvanIcons.news_light,
onTap: () => onTabChanged(0),
),
_NavBarItem(
isSelected: currentTabIndex == 1,
title: 'آمار',
selectedIcon: DidvanIcons.chart_solid,
unselectedIcon: DidvanIcons.chart_light,
onTap: () => onTabChanged(1),
),
_NavBarItem(
isSelected: currentTabIndex == 2,
title: 'رادار',
selectedIcon: DidvanIcons.radar_solid,
unselectedIcon: DidvanIcons.radar_light,
onTap: () => onTabChanged(2),
),
_NavBarItem(
isSelected: currentTabIndex == 3,
title: 'استودیو',
selectedIcon: DidvanIcons.play_circle_solid,
unselectedIcon: DidvanIcons.play_circle_light,
onTap: () => onTabChanged(3),
),
_NavBarItem(
isSelected: currentTabIndex == 4,
title: 'تنظیمات',
selectedIcon: DidvanIcons.setting_solid,
unselectedIcon: DidvanIcons.setting_light,
onTap: () => onTabChanged(4),
),
],
),
),
),
],
);
}
}
class _PlayerNavBar extends StatelessWidget {
const _PlayerNavBar({Key? key}) : super(key: key);
bool _enablePlayerController(StudioDetailsState state) => bool _enablePlayerController(StudioDetailsState state) =>
MediaService.currentPodcast != null || MediaService.currentPodcast != null ||
(MediaService.audioPlayerTag?.contains('podcast') ?? false); (MediaService.audioPlayerTag?.contains('podcast') ?? false);
@ -33,227 +101,170 @@ class DidvanBNB extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<bool>( return StreamBuilder<bool>(
stream: MediaService.audioPlayer.playingStream, stream: MediaService.audioPlayer.playingStream,
builder: (context, snapshot) { builder: (context, snapshot) => GestureDetector(
return Stack( onTap: () => MediaService.currentPodcast == null
children: [ ? null
GestureDetector( : _showPlayerBottomSheet(context),
onTap: () => MediaService.currentPodcast == null child: Consumer<StudioDetailsState>(
? null builder: (context, state, child) => AnimatedContainer(
: _showPlayerBottomSheet(context), padding: const EdgeInsets.only(top: 12),
child: Consumer<StudioDetailsState>( duration: DesignConfig.lowAnimationDuration,
builder: (context, state, child) => AnimatedContainer( height: _enablePlayerController(state) ? 128 : 72,
padding: const EdgeInsets.only(top: 12), decoration: BoxDecoration(
duration: DesignConfig.lowAnimationDuration, color: DesignConfig.isDark
height: _enablePlayerController(state) ? 128 : 72, ? Theme.of(context).colorScheme.focused
decoration: BoxDecoration( : Theme.of(context).colorScheme.navigation,
color: DesignConfig.isDark borderRadius: const BorderRadius.vertical(
? Theme.of(context).colorScheme.focused top: Radius.circular(16),
: Theme.of(context).colorScheme.navigation, ),
borderRadius: const BorderRadius.vertical( ),
top: Radius.circular(16), alignment: Alignment.topCenter,
), child: !_enablePlayerController(state)
), ? const SizedBox()
alignment: Alignment.topCenter, : MediaService.currentPodcast == null
child: !_enablePlayerController(state) ? SizedBox(
? const SizedBox() height: 32,
: MediaService.currentPodcast == null child: Center(
? SizedBox( child: SpinKitThreeBounce(
height: 32, size: 18,
child: Center( color: DesignConfig.isDark
child: SpinKitThreeBounce( ? Theme.of(context).colorScheme.title
size: 18, : Theme.of(context).colorScheme.secondCTA,
),
),
)
: SizedBox(
height: 56,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 16,
),
child: DidvanIconButton(
icon: DidvanIcons.close_regular,
color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: MediaService.resetAudioPlayer,
),
),
SkeletonImage(
imageUrl: MediaService.currentPodcast!.image,
width: 32,
height: 32,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
MediaService.currentPodcast!.title,
color: DesignConfig.isDark color: DesignConfig.isDark
? null ? null
: Theme.of(context) : Theme.of(context)
.colorScheme .colorScheme
.secondCTA, .secondCTA,
), ),
), AudioSlider(
) disableThumb: true,
: SizedBox( tag: MediaService.audioPlayerTag!,
height: 56, ),
child: Row( ],
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ ),
Padding( StreamBuilder<PlayerState>(
padding: const EdgeInsets.only( stream:
right: 12, MediaService.audioPlayer.playerStateStream,
left: 16, builder: (context, snapshot) {
), final playerState = MediaService
child: DidvanIconButton( .audioPlayer.playerState.processingState;
icon: DidvanIcons.close_regular, if (playerState == ProcessingState.loading ||
state.appState == AppState.busy) {
return Padding(
padding: const EdgeInsets.only(
top: 4,
left: 16,
right: 16,
),
child: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: DesignConfig.isDark color: DesignConfig.isDark
? null ? Theme.of(context)
.colorScheme
.title
: Theme.of(context) : Theme.of(context)
.colorScheme .colorScheme
.secondCTA, .secondCTA,
gestureSize: 28,
onPressed:
MediaService.resetAudioPlayer,
), ),
), ),
SkeletonImage( );
imageUrl: }
MediaService.currentPodcast!.image, return const SizedBox();
width: 32, },
height: 32, ),
), if (state.appState != AppState.busy &&
const SizedBox(width: 16), MediaService.audioPlayer.playerState
Expanded( .processingState !=
child: Column( ProcessingState.loading)
crossAxisAlignment: Padding(
CrossAxisAlignment.start, padding: const EdgeInsets.only(
children: [ left: 12,
DidvanText( right: 16,
MediaService.currentPodcast!.title, ),
color: DesignConfig.isDark child: DidvanIconButton(
? null gestureSize: 28,
: Theme.of(context) color: DesignConfig.isDark
.colorScheme ? null
.secondCTA, : Theme.of(context).colorScheme.secondCTA,
), icon: snapshot.data!
AudioSlider( ? DidvanIcons.pause_solid
disableThumb: true, : DidvanIcons.play_solid,
tag: MediaService.audioPlayerTag!, onPressed: () {
), if (state.args.type == 'video') {
], state.getStudioDetails(
), MediaService.currentPodcast!.id,
), args: state.podcastArgs,
StreamBuilder<PlayerState>( forceFetch: true,
stream: MediaService );
.audioPlayer.playerStateStream, }
builder: (context, snapshot) { MediaService.handleAudioPlayback(
if (state.appState == AppState.busy || audioSource:
MediaService.audioPlayer.playerState MediaService.currentPodcast!.media,
.processingState == id: MediaService.currentPodcast!.id,
ProcessingState.loading) { isVoiceMessage: false,
return Padding( );
padding: const EdgeInsets.only( },
top: 4,
left: 16,
right: 16,
),
child: SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Theme.of(context)
.colorScheme
.secondCTA,
),
),
);
}
return const SizedBox();
},
),
if (state.appState != AppState.busy &&
MediaService.audioPlayer.playerState
.processingState !=
ProcessingState.loading)
Padding(
padding: const EdgeInsets.only(
left: 12,
right: 16,
),
child: DidvanIconButton(
gestureSize: 28,
color: DesignConfig.isDark
? null
: Theme.of(context)
.colorScheme
.secondCTA,
icon: snapshot.data!
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
onPressed: () {
MediaService.handleAudioPlayback(
audioSource: state
.downloadedFileIds
.contains(state.studio.id)
? StorageService.appDocsDir +
'/podcasts/podcast-${state.studio.id}.mp3'
: state.studio.media,
isNetworkAudio: !state
.downloadedFileIds
.contains(state.studio.id),
id: state.studio.id,
isVoiceMessage: false,
);
},
),
),
],
), ),
), ),
), ],
), ),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 72,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: DesignConfig.defaultShadow,
),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
_NavBarItem(
isSelected: currentTabIndex == 0,
title: 'اخبار',
selectedIcon: DidvanIcons.news_solid,
unselectedIcon: DidvanIcons.news_light,
onTap: () => onTabChanged(0),
), ),
_NavBarItem( ),
isSelected: currentTabIndex == 1, ),
title: 'آمار', ),
selectedIcon: DidvanIcons.chart_solid, );
unselectedIcon: DidvanIcons.chart_light,
onTap: () => onTabChanged(1),
),
_NavBarItem(
isSelected: currentTabIndex == 2,
title: 'رادار',
selectedIcon: DidvanIcons.radar_solid,
unselectedIcon: DidvanIcons.radar_light,
onTap: () => onTabChanged(2),
),
_NavBarItem(
isSelected: currentTabIndex == 3,
title: 'استودیو',
selectedIcon: DidvanIcons.play_circle_solid,
unselectedIcon: DidvanIcons.play_circle_light,
onTap: () => onTabChanged(3),
),
_NavBarItem(
isSelected: currentTabIndex == 4,
title: 'تنظیمات',
selectedIcon: DidvanIcons.setting_solid,
unselectedIcon: DidvanIcons.setting_light,
onTap: () => onTabChanged(4),
),
],
),
),
),
],
);
});
} }
void _showPlayerBottomSheet(BuildContext context) { void _showPlayerBottomSheet(BuildContext context) {
final sheetKey = GlobalKey<ExpandableBottomSheetState>(); final sheetKey = GlobalKey<ExpandableBottomSheetState>();
bool isExpanded = false; bool isExpanded = false;
final detailsState = context.read<StudioDetailsState>(); final detailsState = context.read<StudioDetailsState>();
if (detailsState.args.type == 'video') {
detailsState.getStudioDetails(
MediaService.currentPodcast!.id,
args: detailsState.podcastArgs,
forceFetch: true,
);
}
final state = context.read<StudioState>(); final state = context.read<StudioState>();
showModalBottomSheet( showModalBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -288,23 +299,18 @@ class DidvanBNB extends StatelessWidget {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: Column( child: Column(
children: [ children: [
StatefulBuilder( DidvanIconButton(
builder: (context, setState) => DidvanIconButton( size: 32,
size: 32, icon: DidvanIcons.angle_up_regular,
icon: isExpanded onPressed: () {
? DidvanIcons.angle_down_regular if (!isExpanded) {
: DidvanIcons.angle_up_regular, sheetKey.currentState?.expand();
onPressed: () { isExpanded = true;
if (!isExpanded) { } else {
sheetKey.currentState?.expand(); isExpanded = false;
isExpanded = true; sheetKey.currentState?.contract();
} else { }
isExpanded = false; },
sheetKey.currentState?.contract();
}
setState(() {});
},
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
@ -322,7 +328,6 @@ class DidvanBNB extends StatelessWidget {
), ),
) )
: StudioDetailsWidget( : StudioDetailsWidget(
studio: detailsState.studio,
onCommentsTabSelected: () { onCommentsTabSelected: () {
Future.delayed( Future.delayed(
const Duration(milliseconds: 100), const Duration(milliseconds: 100),