Skip to content

Commit 1ec2f07

Browse files
committed
feat(player): save progress
1 parent 5e0d94f commit 1ec2f07

File tree

13 files changed

+211
-46
lines changed

13 files changed

+211
-46
lines changed

lib/Adaptor/Episode/EpisodeGridViewHolder.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:dantotsu/Theme/ThemeProvider.dart';
66
import '../../DataClass/Episode.dart';
77
import '../../DataClass/Media.dart';
88
import '../../Theme/Colors.dart';
9+
import 'Widget/HandleProgress.dart';
910

1011
class EpisodeCardView extends StatelessWidget {
1112
final Episode episode;
@@ -24,8 +25,6 @@ class EpisodeCardView extends StatelessWidget {
2425
@override
2526
Widget build(BuildContext context) {
2627
final theme = Theme.of(context).colorScheme;
27-
final themeManager = Provider.of<ThemeNotifier>(context);
28-
final isDark = themeManager.isDarkMode;
2928

3029
Color cardColor = isWatched
3130
? theme.surface.withOpacity(0.5)
@@ -36,16 +35,24 @@ class EpisodeCardView extends StatelessWidget {
3635
shape: RoundedRectangleBorder(
3736
borderRadius: BorderRadius.circular(16),
3837
),
38+
clipBehavior: Clip.hardEdge,
3939
elevation: 4,
4040
color: cardColor,
4141
child: Stack(
42+
alignment: Alignment.bottomCenter,
4243
children: [
4344
_buildBackgroundImage(context),
4445
if (episode.filler ?? false) _fillerColor(context),
4546
_buildEpisodeInfo(context),
4647
if (isWatched) _buildWatchedOverlay(context),
4748
if (isWatched) _buildWatchedIcon(context),
4849
_buildNumber(context),
50+
handleProgress(
51+
context: context,
52+
mediaId: mediaData.id,
53+
ep: episode.number,
54+
width: 162,
55+
)
4956
],
5057
),
5158
);

lib/Adaptor/Episode/EpisodeListViewHolder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class EpisodeListView extends StatelessWidget {
9797
shape: RoundedRectangleBorder(
9898
borderRadius: BorderRadius.circular(16),
9999
),
100+
clipBehavior: Clip.hardEdge,
100101
elevation: 4,
101102
color: theme.surfaceContainerLowest,
102103
child: Stack(
@@ -161,8 +162,8 @@ class EpisodeListView extends StatelessWidget {
161162
handleProgress(
162163
context: context,
163164
mediaId: mediaData.id,
164-
ep: episode.number.toDouble().toInt(),
165-
width: 142,
165+
ep: episode.number,
166+
width: 162,
166167
)
167168
],
168169
),

lib/Adaptor/Episode/Widget/HandleProgress.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import 'package:flutter/material.dart';
2+
import 'package:provider/provider.dart';
23

34
import '../../../Preferences/PrefManager.dart';
5+
import '../../../Services/ServiceSwitcher.dart';
46

57
Widget handleProgress({
68
required BuildContext context,
79
required int mediaId,
8-
required int ep,
10+
required dynamic ep,
911
required double width,
1012
}) {
11-
var currentProgress = PrefManager.getCustomVal<int>("${mediaId}_$ep");
12-
var maxProgress = PrefManager.getCustomVal<int>("${mediaId}_${ep}_max");
13+
var sourceName= Provider
14+
.of<MediaServiceProvider>(context)
15+
.currentService.getName;
16+
var currentProgress = PrefManager.getCustomVal<int>("$mediaId-$ep-$sourceName-current");
17+
var maxProgress = PrefManager.getCustomVal<int>("$mediaId-$ep-$sourceName-max");
1318
if (currentProgress == null || maxProgress == null || maxProgress == 0) {
1419
return const SizedBox.shrink();
1520
}
@@ -18,7 +23,7 @@ Widget handleProgress({
1823

1924
return SizedBox(
2025
width: width,
21-
height: 3,
26+
height: 3.4,
2227
child: Stack(
2328
children: [
2429
Container(

lib/Screens/Info/Tabs/Watch/Anime/AnimeParser.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class AnimeParser extends BaseParser {
7070
var chapters = m.chapters;
7171
var isFirst = true;
7272
var shouldNormalize = false;
73-
73+
var additionalIndex=0;
7474
episodeList.value = Map.fromEntries(
7575
chapters?.reversed.mapIndexed((index, chapter) {
7676
final episode = MChapterToEpisode(chapter, media);
@@ -83,13 +83,20 @@ class AnimeParser extends BaseParser {
8383
}
8484

8585
if (shouldNormalize) {
86-
episode.number = (index + 1).toString();
86+
if (episode.number.toDouble() % 1 != 0) {
87+
additionalIndex--;
88+
var remainder = (episode.number.toDouble() % 1).toStringAsFixed(2).toDouble();
89+
episode.number = (index + 1 + remainder + additionalIndex).toString();
90+
} else {
91+
episode.number = (index + 1 + additionalIndex).toString();
92+
}
8793
}
8894

8995
return MapEntry(episode.number, episode);
9096
}) ??
9197
[],
9298
);
99+
episodeList.value;
93100
}
94101

95102
var episodeDataLoaded = false.obs;

lib/Screens/Info/Tabs/Watch/Anime/Player/Platform/WindowsPlayer.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ class WindowsPlayer extends BasePlayer {
5858

5959
@override
6060
void listenToPlayerStream() {
61-
6261
videoController.player.stream.position
6362
.listen((e) => currentTime.value = _formatTime(e.inSeconds));
6463
videoController.player.stream.duration

lib/Screens/Info/Tabs/Watch/Anime/Player/Player.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ class MediaPlayer extends StatefulWidget {
3434
final List<v.Video> videos;
3535
final Episode currentEpisode;
3636
final Source source;
37-
37+
final bool isOffline;
3838
const MediaPlayer({
3939
super.key,
4040
required this.media,
4141
required this.index,
4242
required this.videos,
4343
required this.currentEpisode,
4444
required this.source,
45+
this.isOffline = false,
4546
});
4647

4748
@override
@@ -66,7 +67,13 @@ class MediaPlayerState extends State<MediaPlayer>
6667
void initState() {
6768
super.initState();
6869
focusNode.requestFocus();
69-
_loadPlayerSettings();
70+
if (!widget.isOffline) {
71+
_loadPlayerSettings();
72+
} else{
73+
resizeMode = BoxFit.contain.obs;
74+
settings = widget.media.anime!.playerSettings!;
75+
}
76+
7077
_initializePlayer();
7178
_leftAnimationController = AnimationController(
7279
duration: const Duration(milliseconds: 1000),
@@ -118,15 +125,16 @@ class MediaPlayerState extends State<MediaPlayer>
118125

119126
@override
120127
void dispose() {
128+
super.dispose();
129+
videoPlayerController.dispose();
121130
_hideCursorTimer?.cancel();
122131
_leftAnimationController.dispose();
123132
_rightAnimationController.dispose();
124-
videoPlayerController.dispose();
133+
125134
focusNode.dispose();
126135
if (Platform.isAndroid || Platform.isIOS) {
127136
_setLandscapeMode(false);
128137
}
129-
super.dispose();
130138
}
131139

132140
void _setLandscapeMode(bool state) {
@@ -468,6 +476,7 @@ class MediaPlayerState extends State<MediaPlayer>
468476
}
469477

470478
Widget _buildEpisodeList() {
479+
471480
Map<String, Episode> episodeList = widget.media.anime?.episodes ?? {};
472481
var (chunk, selected) =
473482
buildChunks(context, episodeList, widget.media.userProgress.toString());

lib/Screens/Info/Tabs/Watch/Anime/Player/PlayerController.dart

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:dantotsu/Widgets/AlertDialogBuilder.dart';
66
import 'package:file_picker/file_picker.dart';
77
import 'package:flutter/material.dart';
88
import 'package:get/get.dart';
9+
import 'package:provider/provider.dart';
910
import 'package:wakelock_plus/wakelock_plus.dart';
1011
import 'package:window_manager/window_manager.dart';
1112
import 'package:dantotsu/Adaptor/Episode/EpisodeAdaptor.dart';
@@ -16,6 +17,9 @@ import 'package:dantotsu/Preferences/HiveDataClasses/DefaultPlayerSettings/Defau
1617
import 'package:dantotsu/Widgets/CustomBottomDialog.dart';
1718
import 'package:dantotsu/api/Mangayomi/Eval/dart/model/video.dart' as v;
1819
import 'package:dantotsu/api/Mangayomi/Model/Source.dart';
20+
import '../../../../../../Services/ServiceSwitcher.dart';
21+
import '../../../../../../api/Discord/Discord.dart';
22+
import '../../../../../../api/Discord/DiscordService.dart';
1923
import '../../../../../Settings/SettingsPlayerScreen.dart';
2024
import 'Platform/BasePlayer.dart';
2125
import 'Player.dart';
@@ -64,15 +68,67 @@ class _PlayerControllerState extends State<PlayerController> {
6468
_controller = widget.player.videoPlayerController;
6569
currentQuality = videos[widget.player.widget.index];
6670
_controller.listenToPlayerStream();
71+
_onInit(context);
72+
}
73+
74+
Future<void> _onInit(BuildContext context) async {
75+
while (_controller.maxTime.value == '00:00') {
76+
await Future.delayed(const Duration(milliseconds: 100));
77+
}
78+
setRpc();
79+
if (context.mounted) {
80+
var sourceName = Provider.of<MediaServiceProvider>(context, listen: false)
81+
.currentService
82+
.getName;
83+
var currentProgress = PrefManager.getCustomVal<int>(
84+
"${media.id}-${currentEpisode.number}-$sourceName-current");
85+
_controller.seek(Duration(seconds: currentProgress ?? 0));
86+
}
87+
_controller.currentPosition.listen((v) {
88+
if (v.inSeconds > 0) {
89+
_saveProgress(v.inSeconds);
90+
}
91+
});
92+
93+
var list = PrefManager.getCustomVal<List<int>>("continueAnimeList") ?? [];
94+
if (list.contains(media.id)) list.remove(media.id);
95+
96+
list.add(media.id);
97+
PrefManager.setCustomVal("continueAnimeList", list);
98+
}
99+
100+
Future<void> _saveProgress(int currentProgress) async {
101+
var sourceName = Provider.of<MediaServiceProvider>(context, listen: false)
102+
.currentService
103+
.getName;
104+
var maxProgress = _controller.maxTime.value;
105+
PrefManager.setCustomVal<int>(
106+
"${media.id}-${currentEpisode.number}-$sourceName-current",
107+
currentProgress);
108+
PrefManager.setCustomVal<int>(
109+
"${media.id}-${currentEpisode.number}-$sourceName-max",
110+
_timeStringToSeconds(maxProgress).toInt());
111+
}
112+
113+
Future<void> setRpc() async {
114+
Discord.setRpc(
115+
media,
116+
episode: currentEpisode,
117+
eTime: _timeStringToSeconds(
118+
_controller.maxTime.value,
119+
).toInt(),
120+
);
67121
}
68122

69123
@override
70124
void dispose() {
125+
DiscordService.stopRPC();
71126
WakelockPlus.disable();
72127
super.dispose();
73128
}
129+
74130
Future initFullScreen() async =>
75-
isFullScreen.value = await WindowManager.instance.isFullScreen();
131+
isFullScreen.value = await WindowManager.instance.isFullScreen();
76132

77133
String _formatTime(int seconds) {
78134
final hours = seconds ~/ 3600;
@@ -126,8 +182,10 @@ class _PlayerControllerState extends State<PlayerController> {
126182
children: [
127183
Row(
128184
children: [
129-
Text(_controller.currentTime.value,
130-
style: const TextStyle(color: Colors.white),),
185+
Text(
186+
_controller.currentTime.value,
187+
style: const TextStyle(color: Colors.white),
188+
),
131189
Text(
132190
" / ",
133191
style: TextStyle(
@@ -536,8 +594,7 @@ class _PlayerControllerState extends State<PlayerController> {
536594
},
537595
style: ElevatedButton.styleFrom(
538596
padding: const EdgeInsets.symmetric(horizontal: 12),
539-
backgroundColor:
540-
Colors.black.withValues(alpha: 0.2),
597+
backgroundColor: Colors.black.withValues(alpha: 0.2),
541598
shape: RoundedRectangleBorder(
542599
borderRadius: BorderRadius.circular(15),
543600
side: BorderSide(
@@ -612,7 +669,8 @@ class _PlayerControllerState extends State<PlayerController> {
612669
return;
613670
}
614671
currentQuality = videos[index];
615-
_controller.open(currentQuality.url, _controller.currentPosition.value);
672+
_controller.open(
673+
currentQuality.url, _controller.currentPosition.value);
616674
Get.back();
617675
},
618676
);

lib/Screens/Settings/BaseSettingsScreen.dart

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ abstract class BaseSettingsScreen<T extends StatefulWidget> extends State<T> {
1010

1111
Widget icon();
1212

13+
Future<void> onIconPressed() async {}
14+
1315
@override
1416
Widget build(BuildContext context) {
1517
return Scaffold(
@@ -61,19 +63,22 @@ abstract class BaseSettingsScreen<T extends StatefulWidget> extends State<T> {
6163
top: 124.statusBar(),
6264
left: 32,
6365
right: 16,
64-
child: Row(
65-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
66-
children: [
67-
Text(
68-
title,
69-
style: const TextStyle(
70-
fontSize: 28,
71-
fontFamily: 'Poppins',
72-
fontWeight: FontWeight.bold,
66+
child: GestureDetector(
67+
onTap: onIconPressed,
68+
child: Row(
69+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
70+
children: [
71+
Text(
72+
title,
73+
style: const TextStyle(
74+
fontSize: 28,
75+
fontFamily: 'Poppins',
76+
fontWeight: FontWeight.bold,
77+
),
7378
),
74-
),
75-
icon,
76-
],
79+
icon,
80+
],
81+
),
7782
),
7883
),
7984
],

0 commit comments

Comments
 (0)