Skip to content

Commit 6c83b85

Browse files
committed
feat: support for player in local media
1 parent 8092f80 commit 6c83b85

File tree

9 files changed

+2090
-91
lines changed

9 files changed

+2090
-91
lines changed

lib/screens/local_source/player/offline_player_old.dart

Lines changed: 1711 additions & 0 deletions
Large diffs are not rendered by default.

lib/screens/manga/controller/reader_controller.dart

Lines changed: 139 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:math' as math;
23
import 'package:anymex/utils/logger.dart';
34
import 'package:anymex/controllers/offline/offline_storage_controller.dart';
45
import 'package:anymex/controllers/service_handler/params.dart';
@@ -88,6 +89,13 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
8889
RxBool canGoNext = false.obs;
8990
RxBool canGoPrev = false.obs;
9091

92+
final RxBool isOverscrolling = false.obs;
93+
final RxDouble overscrollProgress = 0.0.obs;
94+
final RxBool isOverscrollingNext = true.obs;
95+
double _overscrollStartOffset = 0.0;
96+
final double _maxOverscrollDistance = 50.0;
97+
Timer? _overscrollResetTimer;
98+
9199
bool _isNavigating = false;
92100

93101
@override
@@ -106,6 +114,7 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
106114
_performFinalSave();
107115
});
108116

117+
_overscrollResetTimer?.cancel();
109118
pageController?.dispose();
110119
super.onClose();
111120
}
@@ -217,6 +226,7 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
217226
scrollOffsetListener = ScrollOffsetListener.create();
218227
pageController = PreloadPageController(initialPage: 0);
219228
_setupPositionListener();
229+
_setupScrollListener();
220230
}
221231

222232
void _getPreferences() {
@@ -263,16 +273,140 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
263273
}
264274
}
265275

276+
void _setupScrollListener() {
277+
if (scrollOffsetListener != null) {
278+
scrollOffsetListener!.changes.listen(_onScrollChanged);
279+
}
280+
}
281+
282+
void _onScrollChanged(double offset) {
283+
if (!overscrollToChapter.value ||
284+
readingLayout.value != MangaPageViewMode.continuous ||
285+
pageList.isEmpty ||
286+
_isNavigating) {
287+
return;
288+
}
289+
290+
final positions = itemPositionsListener?.itemPositions.value;
291+
if (positions == null || positions.isEmpty) return;
292+
293+
final lastPosition = positions.firstWhere(
294+
(pos) => pos.index == pageList.length - 1,
295+
orElse: () => positions.first,
296+
);
297+
298+
final isAtLastPage = lastPosition.index == pageList.length - 1;
299+
300+
final firstPosition = positions.firstWhere(
301+
(pos) => pos.index == 0,
302+
orElse: () => positions.first,
303+
);
304+
305+
final isAtFirstPage = firstPosition.index == 0;
306+
307+
// if (isAtLastPage &&
308+
// canGoNext.value &&
309+
// lastPosition.itemTrailingEdge <= 1.0) {
310+
// if (!isOverscrolling.value) {
311+
// _startOverscroll(true, offset);
312+
// } else {
313+
// _updateOverscroll(offset);
314+
// }
315+
// } else if (isAtFirstPage &&
316+
// canGoPrev.value &&
317+
// firstPosition.itemLeadingEdge >= 0.0) {
318+
// if (!isOverscrolling.value) {
319+
// _startOverscroll(false, offset);
320+
// } else {
321+
// _updateOverscroll(offset);
322+
// }
323+
// } else if (isOverscrolling.value) {
324+
// _resetOverscroll();
325+
// }
326+
}
327+
328+
void _startOverscroll(bool isNext, double offset) {
329+
isOverscrolling.value = true;
330+
isOverscrollingNext.value = isNext;
331+
_overscrollStartOffset = offset;
332+
overscrollProgress.value = 0.0;
333+
334+
if (showControls.value) {
335+
showControls.value = false;
336+
}
337+
}
338+
339+
void _updateOverscroll(double currentOffset) {
340+
final scrollDelta = (currentOffset - _overscrollStartOffset).abs();
341+
final progress = (scrollDelta / _maxOverscrollDistance).clamp(0.0, 1.0);
342+
343+
overscrollProgress.value = progress;
344+
345+
if (progress >= 1.0) {
346+
_triggerChapterChange();
347+
}
348+
349+
_overscrollResetTimer?.cancel();
350+
_overscrollResetTimer = Timer(const Duration(milliseconds: 1000), () {
351+
if (overscrollProgress.value < 1.0) {
352+
_resetOverscroll();
353+
}
354+
});
355+
}
356+
357+
void _resetOverscroll() {
358+
isOverscrolling.value = false;
359+
overscrollProgress.value = 0.0;
360+
_overscrollStartOffset = 0.0;
361+
_overscrollResetTimer?.cancel();
362+
}
363+
364+
void _triggerChapterChange() {
365+
_resetOverscroll();
366+
chapterNavigator(isOverscrollingNext.value);
367+
}
368+
266369
void _onPositionChanged() async {
267370
if (itemPositionsListener == null || pageList.isEmpty) return;
268371

269372
final positions = itemPositionsListener!.itemPositions.value;
270373
if (positions.isEmpty || _isNavigating) return;
271374

272-
final topItem = currentPageIndex.value >= (pageList.length - 2)
273-
? positions.last
274-
: positions.first;
275-
final number = topItem.index + 1;
375+
ItemPosition? mostVisibleItem;
376+
double maxVisibleExtent = 0.0;
377+
378+
final lastItemPosition = positions.firstWhere(
379+
(pos) => pos.index == pageList.length - 1,
380+
orElse: () => positions.first,
381+
);
382+
383+
final isAtEnd = lastItemPosition.index == pageList.length - 1 &&
384+
lastItemPosition.itemTrailingEdge <= 1.0;
385+
386+
for (final position in positions) {
387+
final leadingEdge = position.itemLeadingEdge;
388+
final trailingEdge = position.itemTrailingEdge;
389+
390+
final visibleExtent =
391+
(math.min(1.0, trailingEdge) - math.max(0.0, leadingEdge))
392+
.clamp(0.0, 1.0);
393+
394+
if (isAtEnd && position.index == pageList.length - 1) {
395+
if (visibleExtent > 0.3) {
396+
mostVisibleItem = position;
397+
break;
398+
}
399+
}
400+
401+
if (visibleExtent > maxVisibleExtent) {
402+
maxVisibleExtent = visibleExtent;
403+
mostVisibleItem = position;
404+
}
405+
}
406+
407+
if (mostVisibleItem == null) return;
408+
409+
final number = mostVisibleItem.index + 1;
276410

277411
if (!_isValidPageNumber(number)) return;
278412

@@ -397,16 +531,6 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
397531
final current = currentChapter.value;
398532
if (current == null || current.number == null) return;
399533

400-
// final targetNumber = next ? current.number! + 1 : current.number! - 1;
401-
402-
// final numberMatchIndex =
403-
// chapterList.indexWhere((chapter) => chapter.number == targetNumber);
404-
405-
// if (numberMatchIndex != -1) {
406-
// navigateToChapter(numberMatchIndex);
407-
// return;
408-
// }
409-
410534
final index = chapterList.indexOf(current);
411535
if (index == -1) return;
412536

@@ -431,6 +555,7 @@ class ReaderController extends GetxController with WidgetsBindingObserver {
431555

432556
Future<void> fetchImages(String url) async {
433557
_isNavigating = true;
558+
_resetOverscroll();
434559
WidgetsBinding.instance.addPostFrameCallback((_) => _initTracking());
435560
currentPageIndex.value = 1;
436561
_syncAvailability();

lib/screens/manga/details_page.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,11 @@ class _MangaDetailsPageState extends State<MangaDetailsPage> {
6969
// Current Manga
7070
RxDouble mangaScore = 0.0.obs;
7171
RxInt mangaProgress = 0.obs;
72-
RxString mangaStatus = "CURRENT".obs;
72+
RxString mangaStatus = "".obs;
7373

7474
// Tracker's Controller
7575
PageController controller = PageController();
7676

77-
// List Editor Vars
78-
final selectedScore = 0.0.obs;
79-
final selectedStatus = "CURRENT";
80-
8177
void _onPageSelected(int index) {
8278
selectedPage.value = index;
8379
controller.animateToPage(index,
@@ -118,7 +114,7 @@ class _MangaDetailsPageState extends State<MangaDetailsPage> {
118114
Logger.i('[_initListVars] ${currentManga.value?.episodeCount}');
119115
mangaProgress.value = currentManga.value?.episodeCount?.toInt() ?? 0;
120116
mangaScore.value = currentManga.value?.score?.toDouble() ?? 0.0;
121-
mangaStatus.value = currentManga.value?.watchingStatus ?? "CURRENT";
117+
mangaStatus.value = currentManga.value?.watchingStatus ?? "";
122118
}
123119

124120
Future<void> _fetchAnilistData() async {

lib/screens/manga/widgets/manga_stats.dart

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// ignore_for_file: prefer_const_constructors
2-
31
import 'package:anymex/models/Media/media.dart';
42
import 'package:anymex/models/mangaupdates/anime_adaptation.dart';
53
import 'package:anymex/screens/home_page.dart';
@@ -10,6 +8,7 @@ import 'package:anymex/utils/function.dart';
108
import 'package:anymex/utils/anime_adaptation_util.dart';
119
import 'package:anymex/widgets/helper/platform_builder.dart';
1210
import 'package:anymex/widgets/custom_widgets/custom_text.dart';
11+
import 'package:expressive_loading_indicator/expressive_loading_indicator.dart';
1312
import 'package:flutter/material.dart';
1413
import 'package:flutter_html/flutter_html.dart';
1514

@@ -90,17 +89,16 @@ class MangaStats extends StatelessWidget {
9089
future: MangaAnimeUtil.getAnimeAdaptation(data.romajiTitle),
9190
builder: (context, snapshot) {
9291
if (snapshot.connectionState == ConnectionState.waiting) {
93-
return CircularProgressIndicator(); // Show loading indicator
92+
return const ExpressiveLoadingIndicator();
9493
}
9594
if (snapshot.hasError) {
96-
return SizedBox.shrink(); // Hide entirely on error
95+
return const SizedBox.shrink();
9796
}
9897
final adaptation = snapshot.data!;
9998
if (adaptation.error != null || !adaptation.hasAdaptation) {
100-
return SizedBox.shrink(); // Hide entirely if no adaptation
99+
return const SizedBox.shrink();
101100
}
102101

103-
// Show the adaptation details only if adaptation exists
104102
return Column(
105103
crossAxisAlignment: CrossAxisAlignment.start,
106104
children: [
@@ -120,7 +118,7 @@ class MangaStats extends StatelessWidget {
120118
AdaptationInfoColumn(
121119
input: adaptation.animeStart ?? 'Unknown',
122120
),
123-
SizedBox(height: 10),
121+
const SizedBox(height: 10),
124122
const StateItem(label: "Anime End", value: ''),
125123
AdaptationInfoColumn(
126124
input: adaptation.animeEnd ?? 'Unknown',
@@ -140,8 +138,8 @@ class MangaStats extends StatelessWidget {
140138
size: 17,
141139
),
142140
GridView.builder(
143-
physics: NeverScrollableScrollPhysics(),
144-
padding: EdgeInsets.only(top: 15),
141+
physics: const NeverScrollableScrollPhysics(),
142+
padding: const EdgeInsets.only(top: 15),
145143
shrinkWrap: true,
146144
itemCount: data.genres.length,
147145
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(

0 commit comments

Comments
 (0)