mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-16 18:56:00 -07:00
Init repo
This commit is contained in:
commit
764b6034e3
566 changed files with 212335 additions and 0 deletions
270
lib/screens/book_viewer/book_viewer_screen.dart
Normal file
270
lib/screens/book_viewer/book_viewer_screen.dart
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/providers/book_viewer_provider.dart';
|
||||
import 'package:fladder/providers/items/book_details_provider.dart';
|
||||
import 'package:fladder/providers/settings/book_viewer_settings_provider.dart';
|
||||
import 'package:fladder/screens/book_viewer/book_viewer_controls.dart';
|
||||
import 'package:fladder/screens/book_viewer/book_viewer_reader.dart'
|
||||
if (dart.library.html) 'package:fladder/screens/book_viewer/book_viewer_reader_web.dart';
|
||||
import 'package:fladder/util/themes_data.dart';
|
||||
import 'package:fladder/util/throttler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
Future<void> openBookViewer(
|
||||
BuildContext context,
|
||||
AutoDisposeStateNotifierProvider<BookDetailsProviderNotifier, BookProviderModel> provider, {
|
||||
int? initialPage,
|
||||
}) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
useSafeArea: false,
|
||||
builder: (context) => Dialog.fullscreen(
|
||||
child: BookViewerScreen(
|
||||
initialPage: initialPage ?? 0,
|
||||
provider: provider,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class BookViewerScreen extends ConsumerStatefulWidget {
|
||||
final int initialPage;
|
||||
final AutoDisposeStateNotifierProvider<BookDetailsProviderNotifier, BookProviderModel> provider;
|
||||
const BookViewerScreen({required this.provider, this.initialPage = 0, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _BookViewerScreenState();
|
||||
}
|
||||
|
||||
class _BookViewerScreenState extends ConsumerState<BookViewerScreen> {
|
||||
final Throttler throttler = Throttler(duration: const Duration(milliseconds: 130));
|
||||
final Duration pageAnimDuration = const Duration(milliseconds: 125);
|
||||
final Curve pageAnimCurve = Curves.easeInCubic;
|
||||
late final ExtendedPageController extendedController = ExtendedPageController(initialPage: widget.initialPage);
|
||||
late final BookViewController viewController = BookViewController();
|
||||
bool outOfRange = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() => ref.read(bookViewerSettingsProvider.notifier).setSavedBrightness());
|
||||
}
|
||||
|
||||
late double lastScale = switch (ref.read(bookViewerSettingsProvider).initZoomState) {
|
||||
InitZoomState.contained => 1.0,
|
||||
InitZoomState.covered => 1.75,
|
||||
};
|
||||
|
||||
late double lastPosition = 0.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bookViewerDetails = ref.watch(bookViewerProvider);
|
||||
final loading = bookViewerDetails.loading;
|
||||
final pages = bookViewerDetails.pages;
|
||||
final book = bookViewerDetails.book;
|
||||
final bookViewSettings = ref.watch(bookViewerSettingsProvider);
|
||||
ref.listen(
|
||||
bookViewerProvider.select((value) => value.loading),
|
||||
(previous, next) {
|
||||
if (previous == true && next == false) {
|
||||
ref.read(bookViewerProvider.notifier).updatePlayback((widget.initialPage.toDouble()).toInt());
|
||||
}
|
||||
},
|
||||
);
|
||||
return Theme(
|
||||
data: ThemesData.of(context).dark,
|
||||
child: PopScope(
|
||||
canPop: true,
|
||||
onPopInvoked: (didPop) async {
|
||||
await ref.read(bookViewerProvider.notifier).stopPlayback();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (!loading)
|
||||
ExtendedImageGesturePageView.builder(
|
||||
itemCount: pages.length + 2,
|
||||
controller: extendedController,
|
||||
canScrollPage: (gestureDetails) {
|
||||
return bookViewSettings.disableScrollOnZoom
|
||||
? gestureDetails != null
|
||||
? !(gestureDetails.totalScale! > 1.0)
|
||||
: true
|
||||
: true;
|
||||
},
|
||||
onPageChanged: (value) {
|
||||
final newRange = pages.length + 1 == value || value == 0;
|
||||
if (outOfRange != newRange) {
|
||||
viewController.toggleControls(value: newRange);
|
||||
outOfRange = newRange;
|
||||
}
|
||||
ref.read(bookViewerProvider.notifier).updatePlayback(value);
|
||||
},
|
||||
reverse: bookViewSettings.readDirection == ReadDirection.rightToLeft,
|
||||
itemBuilder: (context, index) {
|
||||
if (pages.length + 1 == index || index == 0) {
|
||||
final atEnd = index >= pages.length;
|
||||
final details = ref.read(widget.provider);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: bookViewSettings.readDirection != ReadDirection.leftToRight
|
||||
? CrossAxisAlignment.start
|
||||
: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (atEnd) ...{
|
||||
Flexible(
|
||||
child: Text(
|
||||
"End: \n${book?.name}",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
if (details.nextChapter(bookViewerDetails.book) != null) ...{
|
||||
const SizedBox(height: 32),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Next: ",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)),
|
||||
onPressed: () async =>
|
||||
await loadNextBook(details.nextChapter(bookViewerDetails.book)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.keyboard_arrow_left_rounded),
|
||||
Text(
|
||||
details.nextChapter(bookViewerDetails.book)!.name,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
} else ...{
|
||||
const SizedBox(height: 32),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.info_rounded),
|
||||
const SizedBox(width: 16),
|
||||
Text("No next chapter"),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else ...{
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Start: \n${book?.name}",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
if (details.previousChapter(bookViewerDetails.book) != null) ...{
|
||||
const SizedBox(height: 32),
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Previous:",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 8)),
|
||||
onPressed: () async =>
|
||||
await loadNextBook(details.previousChapter(bookViewerDetails.book)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
details.previousChapter(bookViewerDetails.book)!.name,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onPrimary),
|
||||
),
|
||||
const Icon(Icons.keyboard_arrow_right_rounded),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
} else ...{
|
||||
const SizedBox(height: 32),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.info_rounded),
|
||||
const SizedBox(width: 16),
|
||||
Text("First chapter"),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return BookViewerReader(
|
||||
index: index,
|
||||
pages: pages,
|
||||
bookViewSettings: bookViewSettings,
|
||||
previousPage: previousPage,
|
||||
nextPage: nextPage,
|
||||
viewController: viewController,
|
||||
lastScale: lastScale,
|
||||
newScale: (value) => lastScale = value,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
BookViewerControls(
|
||||
provider: widget.provider,
|
||||
viewController: viewController,
|
||||
controller: extendedController,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> nextPage() async =>
|
||||
throttler.run(() async => await extendedController.nextPage(duration: pageAnimDuration, curve: pageAnimCurve));
|
||||
|
||||
Future<void> previousPage() async => throttler
|
||||
.run(() async => await extendedController.previousPage(duration: pageAnimDuration, curve: pageAnimCurve));
|
||||
|
||||
Future<void> loadNextBook(BookModel? book) async {
|
||||
await ref.read(bookViewerProvider.notifier).fetchBook(book);
|
||||
extendedController.jumpToPage(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue