From 6042c0d2058722551f835b972f40c48cab929e90 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 19:13:20 +0100 Subject: [PATCH 1/8] feat: e-ink mode with auto-detection and disabled animations --- lib/helpers/eink_detection_helper.dart | 42 +++++ lib/providers/eink_mode_provider.dart | 44 ++++++ lib/providers/eink_mode_provider.g.dart | 32 ++++ lib/screens/reader/epub_reader_screen.dart | 6 +- lib/screens/reader/reader_screen.dart | 110 ++++++++++--- lib/screens/reader/two_page_reader.dart | 6 +- .../settings/readers_settings_screen.dart | 22 +++ lib/widgets/next_in_series_overlay.dart | 148 +++++++++--------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 24 +++ pubspec.yaml | 1 + 11 files changed, 345 insertions(+), 92 deletions(-) create mode 100644 lib/helpers/eink_detection_helper.dart create mode 100644 lib/providers/eink_mode_provider.dart create mode 100644 lib/providers/eink_mode_provider.g.dart diff --git a/lib/helpers/eink_detection_helper.dart b/lib/helpers/eink_detection_helper.dart new file mode 100644 index 0000000..2b7b988 --- /dev/null +++ b/lib/helpers/eink_detection_helper.dart @@ -0,0 +1,42 @@ +import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; + +/// Detects whether the current device is a known e-ink device. +class EinkDetectionHelper { + static const _einkManufacturers = { + 'onyx', // BOOX + 'boyue', // Likebook + 'amazon', // Kindle + 'kobo', + 'rakuten', // Kobo + 'pocketbook', + 'remarkable', + 'tolino', + 'bigme', + 'dasung', + }; + + /// Hisense e-ink phone model prefixes (e.g. A5, A7, A9). + static const _hisenseEinkModels = {'a5', 'a7', 'a9'}; + + /// Returns `true` when the device is a known e-ink device. + /// On non-Android platforms this always returns `false`. + static Future isEinkDevice() async { + if (!Platform.isAndroid) return false; + + final info = await DeviceInfoPlugin().androidInfo; + final manufacturer = info.manufacturer.toLowerCase(); + final model = info.model.toLowerCase(); + + if (_einkManufacturers.contains(manufacturer)) return true; + + // Hisense makes both LCD and e-ink phones — match specific model lines. + if (manufacturer == 'hisense') { + for (final prefix in _hisenseEinkModels) { + if (model.contains(prefix)) return true; + } + } + + return false; + } +} diff --git a/lib/providers/eink_mode_provider.dart b/lib/providers/eink_mode_provider.dart new file mode 100644 index 0000000..37dda63 --- /dev/null +++ b/lib/providers/eink_mode_provider.dart @@ -0,0 +1,44 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:worldhopper/helpers/eink_detection_helper.dart'; + +part 'eink_mode_provider.g.dart'; + +/// Key for storing e-ink mode preference in SharedPreferences +const String _einkModeKey = 'eink_mode'; + +/// Provider for e-ink mode state management. +/// +/// On first launch (no stored preference), auto-detects whether the device is +/// a known e-ink device and persists the result. Subsequent launches use the +/// stored value so the user's explicit choice is always respected. +@riverpod +class EinkModeNotifier extends _$EinkModeNotifier { + @override + bool build() { + _loadEinkMode(); + return false; // Default value until loaded + } + + /// Load e-ink mode from SharedPreferences, auto-detecting on first launch. + Future _loadEinkMode() async { + final prefs = await SharedPreferences.getInstance(); + final stored = prefs.getBool(_einkModeKey); + + if (stored != null) { + state = stored; + } else { + // First launch — auto-detect + final detected = await EinkDetectionHelper.isEinkDevice(); + state = detected; + await prefs.setBool(_einkModeKey, detected); + } + } + + /// Set e-ink mode and persist to SharedPreferences. + Future setEinkMode(bool enabled) async { + state = enabled; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_einkModeKey, enabled); + } +} diff --git a/lib/providers/eink_mode_provider.g.dart b/lib/providers/eink_mode_provider.g.dart new file mode 100644 index 0000000..cec308e --- /dev/null +++ b/lib/providers/eink_mode_provider.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'eink_mode_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$einkModeNotifierHash() => r'4a5aa5c980270db066864fe75ebdd2c015725cd6'; + +/// Provider for e-ink mode state management. +/// +/// On first launch (no stored preference), auto-detects whether the device is +/// a known e-ink device and persists the result. Subsequent launches use the +/// stored value so the user's explicit choice is always respected. +/// +/// Copied from [EinkModeNotifier]. +@ProviderFor(EinkModeNotifier) +final einkModeNotifierProvider = + AutoDisposeNotifierProvider.internal( + EinkModeNotifier.new, + name: r'einkModeNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$einkModeNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$EinkModeNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/reader/epub_reader_screen.dart b/lib/screens/reader/epub_reader_screen.dart index c02e979..c99f993 100644 --- a/lib/screens/reader/epub_reader_screen.dart +++ b/lib/screens/reader/epub_reader_screen.dart @@ -8,6 +8,7 @@ import 'package:flutter_epub_viewer/flutter_epub_viewer.dart'; import 'package:go_router/go_router.dart'; import 'package:worldhopper/config/constants.dart'; import 'package:worldhopper/helpers/snackbar_helper.dart'; +import 'package:worldhopper/providers/eink_mode_provider.dart'; import 'package:worldhopper/models/enhanced_metadata.dart'; import 'package:worldhopper/models/opds_entry.dart'; import 'package:worldhopper/models/reading_progress.dart'; @@ -480,6 +481,7 @@ class _EpubReaderScreenState extends ConsumerState { feedUrl: widget.feedUrl, entry: widget.entry, isVisible: _reachedEndThisSession, + einkMode: ref.watch(einkModeNotifierProvider), onBeforeNavigate: _deleteCompletedProgress, ), ], @@ -660,7 +662,9 @@ class _EpubReaderScreenState extends ConsumerState { right: 0, child: AnimatedOpacity( opacity: _showAppBar ? 1.0 : 0.0, - duration: const Duration(milliseconds: 200), + duration: ref.watch(einkModeNotifierProvider) + ? Duration.zero + : const Duration(milliseconds: 200), child: Container( padding: EdgeInsets.only( top: topPadding, diff --git a/lib/screens/reader/reader_screen.dart b/lib/screens/reader/reader_screen.dart index 35f0c82..fe86e20 100644 --- a/lib/screens/reader/reader_screen.dart +++ b/lib/screens/reader/reader_screen.dart @@ -18,6 +18,7 @@ import 'package:worldhopper/providers/connectivity_provider.dart'; import 'package:worldhopper/providers/filter_quality_provider.dart'; import 'package:worldhopper/providers/precache_pages_provider.dart'; import 'package:worldhopper/providers/reading_mode_provider.dart'; +import 'package:worldhopper/providers/eink_mode_provider.dart'; import 'package:worldhopper/providers/two_page_mode_provider.dart'; import 'package:worldhopper/screens/reader/page_grouping.dart'; import 'package:worldhopper/screens/reader/two_page_reader.dart'; @@ -58,6 +59,9 @@ class _ReaderScreenState extends ConsumerState { List _spreads = []; bool _wasTwoPage = false; + // E-ink swipe tracking + double? _einkSwipeStartX; + @override void initState() { super.initState(); @@ -249,6 +253,7 @@ class _ReaderScreenState extends ConsumerState { feedUrl: widget.feedUrl, entry: widget.entry, isVisible: _reachedEndThisSession, + einkMode: ref.watch(einkModeNotifierProvider), onBeforeNavigate: _deleteCompletedProgress, ), ], @@ -259,15 +264,18 @@ class _ReaderScreenState extends ConsumerState { Widget _buildPagedReader(OPDSStreamLink streamLink, dynamic server, {required bool reverse}) { final filterQuality = ref.watch(filterQualityNotifierProvider); + final einkMode = ref.watch(einkModeNotifierProvider); - return GestureDetector( + Widget gallery = GestureDetector( onTap: () { setState(() { _showControls = !_showControls; }); }, child: PhotoViewGallery.builder( - scrollPhysics: const BouncingScrollPhysics(), + scrollPhysics: einkMode + ? const NeverScrollableScrollPhysics() + : const BouncingScrollPhysics(), reverse: reverse, builder: (context, index) { return PhotoViewGalleryPageOptions( @@ -309,6 +317,17 @@ class _ReaderScreenState extends ConsumerState { }, ), ); + + if (einkMode) { + gallery = _wrapWithEinkSwipe( + child: gallery, + controller: _pageController, + itemCount: streamLink.pageCount, + reverse: reverse, + ); + } + + return gallery; } Widget _buildTwoPageReader(OPDSStreamLink streamLink, dynamic server, {required bool rtl, required bool firstPageIsCover}) { @@ -336,13 +355,16 @@ class _ReaderScreenState extends ConsumerState { final authHeaders = AuthService().getAuthHeaders(server); - return TwoPageReader( + final einkMode = ref.watch(einkModeNotifierProvider); + + Widget reader = TwoPageReader( spreads: _spreads, pageController: _twoPageController!, getPageUrl: (index) => streamLink.getPageUrl(index), headers: authHeaders, filterQuality: filterQuality, reverse: rtl, + einkMode: einkMode, onTap: () { setState(() { _showControls = !_showControls; @@ -362,6 +384,17 @@ class _ReaderScreenState extends ConsumerState { _precacheUpcomingPages(lastPage); }, ); + + if (einkMode) { + reader = _wrapWithEinkSwipe( + child: reader, + controller: _twoPageController!, + itemCount: _spreads.length, + reverse: rtl, + ); + } + + return reader; } Widget _buildVerticalReader(OPDSStreamLink streamLink, dynamic server) { @@ -431,6 +464,36 @@ class _ReaderScreenState extends ConsumerState { ); } + /// Wraps [child] with a [Listener] that detects horizontal swipes and calls + /// [jumpToPage] on [controller], giving instant page turns with no animation. + Widget _wrapWithEinkSwipe({ + required Widget child, + required PageController controller, + required int itemCount, + required bool reverse, + }) { + return Listener( + onPointerDown: (e) => _einkSwipeStartX = e.position.dx, + onPointerUp: (e) { + if (_einkSwipeStartX == null) return; + final dx = e.position.dx - _einkSwipeStartX!; + _einkSwipeStartX = null; + if (dx.abs() < 50) return; // below swipe threshold + + // Swipe left (dx < 0) → next page, swipe right (dx > 0) → previous + final direction = dx < 0 ? 1 : -1; + final effectiveDirection = reverse ? -direction : direction; + final currentIdx = controller.page?.round() ?? 0; + final target = currentIdx + effectiveDirection; + if (target >= 0 && target < itemCount) { + controller.jumpToPage(target); + } + }, + onPointerCancel: (_) => _einkSwipeStartX = null, + child: child, + ); + } + Widget _buildTopBar(BuildContext context, OPDSStreamLink streamLink) { return Container( decoration: BoxDecoration( @@ -639,6 +702,7 @@ class _ReaderScreenState extends ConsumerState { } Widget _buildBottomBar(BuildContext context, OPDSStreamLink streamLink) { + final einkMode = ref.watch(einkModeNotifierProvider); final progress = (_currentPage + 1) / streamLink.pageCount; final globalMode = ref.watch(readingModeNotifierProvider); final seriesOverride = widget.feedUrl != null @@ -754,20 +818,24 @@ class _ReaderScreenState extends ConsumerState { IconButton( icon: const Icon(Icons.chevron_right, color: Colors.white), onPressed: currentIdx > 0 - ? () => activeController.previousPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ) + ? () => einkMode + ? activeController.jumpToPage(currentIdx - 1) + : activeController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ) : null, iconSize: 32, ), IconButton( icon: const Icon(Icons.chevron_left, color: Colors.white), onPressed: currentIdx < totalItems - 1 - ? () => activeController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ) + ? () => einkMode + ? activeController.jumpToPage(currentIdx + 1) + : activeController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ) : null, iconSize: 32, ), @@ -790,20 +858,24 @@ class _ReaderScreenState extends ConsumerState { IconButton( icon: const Icon(Icons.chevron_left, color: Colors.white), onPressed: currentIdx > 0 - ? () => activeController.previousPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ) + ? () => einkMode + ? activeController.jumpToPage(currentIdx - 1) + : activeController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ) : null, iconSize: 32, ), IconButton( icon: const Icon(Icons.chevron_right, color: Colors.white), onPressed: currentIdx < totalItems - 1 - ? () => activeController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ) + ? () => einkMode + ? activeController.jumpToPage(currentIdx + 1) + : activeController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ) : null, iconSize: 32, ), diff --git a/lib/screens/reader/two_page_reader.dart b/lib/screens/reader/two_page_reader.dart index 77f629c..9beaad7 100644 --- a/lib/screens/reader/two_page_reader.dart +++ b/lib/screens/reader/two_page_reader.dart @@ -14,6 +14,7 @@ class TwoPageReader extends StatelessWidget { final Map headers; final FilterQuality filterQuality; final bool reverse; + final bool einkMode; final ValueChanged onPageChanged; final VoidCallback onTap; @@ -25,6 +26,7 @@ class TwoPageReader extends StatelessWidget { required this.headers, required this.filterQuality, required this.reverse, + this.einkMode = false, required this.onPageChanged, required this.onTap, }); @@ -38,7 +40,9 @@ class TwoPageReader extends StatelessWidget { reverse: reverse, itemCount: spreads.length, onPageChanged: onPageChanged, - physics: const BouncingScrollPhysics(), + physics: einkMode + ? const NeverScrollableScrollPhysics() + : const BouncingScrollPhysics(), itemBuilder: (context, spreadIndex) { final spread = spreads[spreadIndex]; diff --git a/lib/screens/settings/readers_settings_screen.dart b/lib/screens/settings/readers_settings_screen.dart index c573bd4..e49be36 100644 --- a/lib/screens/settings/readers_settings_screen.dart +++ b/lib/screens/settings/readers_settings_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:worldhopper/providers/eink_mode_provider.dart'; import 'package:worldhopper/providers/filter_quality_provider.dart'; import 'package:worldhopper/providers/precache_pages_provider.dart'; import 'package:worldhopper/providers/reading_mode_provider.dart'; @@ -38,6 +39,7 @@ class ReadersSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final einkMode = ref.watch(einkModeNotifierProvider); final readingMode = ref.watch(readingModeNotifierProvider); final filterQuality = ref.watch(filterQualityNotifierProvider); final precachePages = ref.watch(precachePagesNotifierProvider); @@ -50,6 +52,26 @@ class ReadersSettingsScreen extends ConsumerWidget { ), body: ListView( children: [ + _buildSectionHeader(context, 'General'), + Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + children: [ + SwitchListTile( + secondary: const Icon(Icons.tablet_android), + title: const Text('E-ink mode'), + subtitle: + const Text('Disable animations for e-ink displays'), + value: einkMode, + onChanged: (enabled) { + ref + .read(einkModeNotifierProvider.notifier) + .setEinkMode(enabled); + }, + ), + ], + ), + ), _buildSectionHeader(context, 'Image Reader'), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), diff --git a/lib/widgets/next_in_series_overlay.dart b/lib/widgets/next_in_series_overlay.dart index 4abb94d..6b3b175 100644 --- a/lib/widgets/next_in_series_overlay.dart +++ b/lib/widgets/next_in_series_overlay.dart @@ -19,6 +19,7 @@ class NextInSeriesOverlay extends ConsumerStatefulWidget { final String? feedUrl; final OPDSEntry entry; final bool isVisible; + final bool einkMode; final Future Function()? onBeforeNavigate; const NextInSeriesOverlay({ @@ -27,6 +28,7 @@ class NextInSeriesOverlay extends ConsumerStatefulWidget { this.feedUrl, required this.entry, required this.isVisible, + this.einkMode = false, this.onBeforeNavigate, }); @@ -124,82 +126,86 @@ class _NextInSeriesOverlayState extends ConsumerState { required String title, required IconData trailingIcon, }) { + final barContent = Material( + elevation: 8, + borderRadius: BorderRadius.circular(12), + color: color, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only( + left: 16, top: 12, bottom: 12, right: 4), + child: Row( + children: [ + Icon(icon, color: onColor), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: onColor.withOpacity(0.7), + ), + ), + Text( + title, + style: + Theme.of(context).textTheme.titleSmall?.copyWith( + color: onColor, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 4), + Icon(trailingIcon, color: onColor), + SizedBox( + width: 36, + height: 36, + child: IconButton( + padding: EdgeInsets.zero, + iconSize: 20, + icon: Icon( + Icons.close, + color: onColor.withOpacity(0.7), + ), + onPressed: () { + setState(() { + _dismissed = true; + }); + }, + ), + ), + ], + ), + ), + ), + ); + return Positioned( bottom: MediaQuery.of(context).padding.bottom + 16, left: 16, right: 16, - child: Dismissible( - key: ValueKey(key), - direction: DismissDirection.down, - onDismissed: (_) { - setState(() { - _dismissed = true; - }); - }, - child: Material( - elevation: 8, - borderRadius: BorderRadius.circular(12), - color: color, - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: onTap, - child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 12, bottom: 12, right: 4), - child: Row( - children: [ - Icon(icon, color: onColor), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - label, - style: - Theme.of(context).textTheme.labelSmall?.copyWith( - color: onColor.withOpacity(0.7), - ), - ), - Text( - title, - style: - Theme.of(context).textTheme.titleSmall?.copyWith( - color: onColor, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - const SizedBox(width: 4), - Icon(trailingIcon, color: onColor), - SizedBox( - width: 36, - height: 36, - child: IconButton( - padding: EdgeInsets.zero, - iconSize: 20, - icon: Icon( - Icons.close, - color: onColor.withOpacity(0.7), - ), - onPressed: () { - setState(() { - _dismissed = true; - }); - }, - ), - ), - ], - ), + child: widget.einkMode + ? barContent + : Dismissible( + key: ValueKey(key), + direction: DismissDirection.down, + onDismissed: (_) { + setState(() { + _dismissed = true; + }); + }, + child: barContent, ), - ), - ), - ), ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 43d4f72..3d853b3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import connectivity_plus +import device_info_plus import flutter_inappwebview_macos import package_info_plus import shared_preferences_foundation @@ -15,6 +16,7 @@ import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 94e49e7..9226459 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -265,6 +265,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.dev" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" dio: dependency: "direct main" description: @@ -1197,6 +1213,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5a06e93..286365d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -82,6 +82,7 @@ dependencies: wakelock_plus: ^1.2.8 package_info_plus: ^9.0.0 url_launcher: ^6.3.2 + device_info_plus: ^11.0.0 dev_dependencies: flutter_test: -- 2.52.0 From 5516b559e4abd48d70a3edb34892293ae1b53976 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 19:19:34 +0100 Subject: [PATCH 2/8] chore: dart format --- lib/screens/reader/reader_screen.dart | 293 +++++++++--------- .../settings/readers_settings_screen.dart | 3 +- lib/widgets/next_in_series_overlay.dart | 20 +- 3 files changed, 165 insertions(+), 151 deletions(-) diff --git a/lib/screens/reader/reader_screen.dart b/lib/screens/reader/reader_screen.dart index fe86e20..6519626 100644 --- a/lib/screens/reader/reader_screen.dart +++ b/lib/screens/reader/reader_screen.dart @@ -139,130 +139,137 @@ class _ReaderScreenState extends ConsumerState { child: Scaffold( backgroundColor: Colors.black, body: Stack( - children: [ - // Page viewer - serverAsync.when( - data: (server) { - if (server == null) { - return const Center( - child: Text( - 'Server not found', - style: TextStyle(color: Colors.white), - ), - ); - } + children: [ + // Page viewer + serverAsync.when( + data: (server) { + if (server == null) { + return const Center( + child: Text( + 'Server not found', + style: TextStyle(color: Colors.white), + ), + ); + } - final globalMode = ref.watch(readingModeNotifierProvider); - final seriesOverride = widget.feedUrl != null - ? ref.watch(seriesReadingModeNotifierProvider(widget.feedUrl!)) - : null; - final readingMode = seriesOverride ?? globalMode; + final globalMode = ref.watch(readingModeNotifierProvider); + final seriesOverride = widget.feedUrl != null + ? ref.watch( + seriesReadingModeNotifierProvider(widget.feedUrl!)) + : null; + final readingMode = seriesOverride ?? globalMode; - // Determine if two-page mode should activate - final globalTwoPage = ref.watch(twoPageModeNotifierProvider); - final seriesToPageOverride = widget.feedUrl != null - ? ref.watch(seriesToPageModeNotifierProvider(widget.feedUrl!)) - : null; - final twoPageMode = seriesToPageOverride ?? globalTwoPage; - final screenWidth = MediaQuery.of(context).size.width; - final isPaged = readingMode != ReadingMode.verticalContinuous; - final useTwoPage = isPaged && - twoPageMode == TwoPageMode.auto && - screenWidth >= 768; + // Determine if two-page mode should activate + final globalTwoPage = ref.watch(twoPageModeNotifierProvider); + final seriesToPageOverride = widget.feedUrl != null + ? ref.watch( + seriesToPageModeNotifierProvider(widget.feedUrl!)) + : null; + final twoPageMode = seriesToPageOverride ?? globalTwoPage; + final screenWidth = MediaQuery.of(context).size.width; + final isPaged = readingMode != ReadingMode.verticalContinuous; + final useTwoPage = isPaged && + twoPageMode == TwoPageMode.auto && + screenWidth >= 768; - // Handle mode switching mid-read - if (_previousMode != null && _previousMode != readingMode) { - if (readingMode == ReadingMode.verticalContinuous) { - // Switching to vertical: create new scroll controller - _scrollController?.dispose(); - _scrollController = null; - } else { - // Switching to paged: jump to current page after frame + // Handle mode switching mid-read + if (_previousMode != null && _previousMode != readingMode) { + if (readingMode == ReadingMode.verticalContinuous) { + // Switching to vertical: create new scroll controller + _scrollController?.dispose(); + _scrollController = null; + } else { + // Switching to paged: jump to current page after frame + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_pageController.hasClients) { + _pageController.jumpToPage(_currentPage); + } + }); + } + } + _previousMode = readingMode; + + // Handle transition between single-page and two-page modes + if (_wasTwoPage && !useTwoPage) { + // Transitioning from two-page to single-page + _wasTwoPage = false; WidgetsBinding.instance.addPostFrameCallback((_) { if (_pageController.hasClients) { _pageController.jumpToPage(_currentPage); } }); + } else if (!_wasTwoPage && useTwoPage) { + // Transitioning from single-page to two-page + _wasTwoPage = true; } - } - _previousMode = readingMode; - // Handle transition between single-page and two-page modes - if (_wasTwoPage && !useTwoPage) { - // Transitioning from two-page to single-page - _wasTwoPage = false; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_pageController.hasClients) { - _pageController.jumpToPage(_currentPage); - } - }); - } else if (!_wasTwoPage && useTwoPage) { - // Transitioning from single-page to two-page - _wasTwoPage = true; - } + if (useTwoPage) { + final isRtl = readingMode == ReadingMode.rtl; + final globalCover = ref.watch(coverPageNotifierProvider); + final seriesCoverOverride = widget.feedUrl != null + ? ref.watch( + seriesCoverPageNotifierProvider(widget.feedUrl!)) + : null; + final firstPageIsCover = seriesCoverOverride ?? globalCover; + return _buildTwoPageReader(streamLink, server, + rtl: isRtl, firstPageIsCover: firstPageIsCover); + } - if (useTwoPage) { - final isRtl = readingMode == ReadingMode.rtl; - final globalCover = ref.watch(coverPageNotifierProvider); - final seriesCoverOverride = widget.feedUrl != null - ? ref.watch(seriesCoverPageNotifierProvider(widget.feedUrl!)) - : null; - final firstPageIsCover = seriesCoverOverride ?? globalCover; - return _buildTwoPageReader( - streamLink, server, rtl: isRtl, firstPageIsCover: firstPageIsCover); - } - - return switch (readingMode) { - ReadingMode.ltr => _buildPagedReader(streamLink, server, reverse: false), - ReadingMode.rtl => _buildPagedReader(streamLink, server, reverse: true), - ReadingMode.verticalContinuous => _buildVerticalReader(streamLink, server), - }; - }, - loading: () => const Center( - child: CircularProgressIndicator(color: Colors.white), - ), - error: (error, stack) => Center( - child: Text( - 'Error loading server: $error', - style: const TextStyle(color: Colors.white), + return switch (readingMode) { + ReadingMode.ltr => + _buildPagedReader(streamLink, server, reverse: false), + ReadingMode.rtl => + _buildPagedReader(streamLink, server, reverse: true), + ReadingMode.verticalContinuous => + _buildVerticalReader(streamLink, server), + }; + }, + loading: () => const Center( + child: CircularProgressIndicator(color: Colors.white), + ), + error: (error, stack) => Center( + child: Text( + 'Error loading server: $error', + style: const TextStyle(color: Colors.white), + ), ), ), - ), - // Top controls - if (_showControls) - Positioned( - top: 0, - left: 0, - right: 0, - child: _buildTopBar(context, streamLink), + // Top controls + if (_showControls) + Positioned( + top: 0, + left: 0, + right: 0, + child: _buildTopBar(context, streamLink), + ), + + // Bottom controls + if (_showControls) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: _buildBottomBar(context, streamLink), + ), + + // Next in series overlay + NextInSeriesOverlay( + serverId: widget.serverId, + feedUrl: widget.feedUrl, + entry: widget.entry, + isVisible: _reachedEndThisSession, + einkMode: ref.watch(einkModeNotifierProvider), + onBeforeNavigate: _deleteCompletedProgress, ), - - // Bottom controls - if (_showControls) - Positioned( - bottom: 0, - left: 0, - right: 0, - child: _buildBottomBar(context, streamLink), - ), - - // Next in series overlay - NextInSeriesOverlay( - serverId: widget.serverId, - feedUrl: widget.feedUrl, - entry: widget.entry, - isVisible: _reachedEndThisSession, - einkMode: ref.watch(einkModeNotifierProvider), - onBeforeNavigate: _deleteCompletedProgress, - ), - ], - ), + ], + ), ), ); } - Widget _buildPagedReader(OPDSStreamLink streamLink, dynamic server, {required bool reverse}) { + Widget _buildPagedReader(OPDSStreamLink streamLink, dynamic server, + {required bool reverse}) { final filterQuality = ref.watch(filterQualityNotifierProvider); final einkMode = ref.watch(einkModeNotifierProvider); @@ -296,8 +303,7 @@ class _ReaderScreenState extends ConsumerState { child: CircularProgressIndicator( value: event == null ? 0 - : event.cumulativeBytesLoaded / - (event.expectedTotalBytes ?? 1), + : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1), color: Colors.white, ), ), @@ -330,10 +336,11 @@ class _ReaderScreenState extends ConsumerState { return gallery; } - Widget _buildTwoPageReader(OPDSStreamLink streamLink, dynamic server, {required bool rtl, required bool firstPageIsCover}) { + Widget _buildTwoPageReader(OPDSStreamLink streamLink, dynamic server, + {required bool rtl, required bool firstPageIsCover}) { final filterQuality = ref.watch(filterQualityNotifierProvider); - final dimensions = ref.watch( - pageDimensionsNotifierProvider(widget.entry.id)); + final dimensions = + ref.watch(pageDimensionsNotifierProvider(widget.entry.id)); // Recompute spreads whenever dimensions change _spreads = computePageSpreads( @@ -347,8 +354,7 @@ class _ReaderScreenState extends ConsumerState { final currentSpreadIndex = spreadIndexForPage(_spreads, _currentPage); // Create or update the two-page controller - if (_twoPageController == null || - !_twoPageController!.hasClients) { + if (_twoPageController == null || !_twoPageController!.hasClients) { _twoPageController?.dispose(); _twoPageController = PageController(initialPage: currentSpreadIndex); } @@ -417,7 +423,8 @@ class _ReaderScreenState extends ConsumerState { final maxExtent = notification.metrics.maxScrollExtent; final estimatedPage = (scrollOffset / maxExtent * (streamLink.pageCount - 1)).round(); - final clampedPage = estimatedPage.clamp(0, streamLink.pageCount - 1); + final clampedPage = + estimatedPage.clamp(0, streamLink.pageCount - 1); if (clampedPage != _currentPage) { setState(() { @@ -578,7 +585,8 @@ class _ReaderScreenState extends ConsumerState { void _setReadingMode(WidgetRef ref, ReadingMode mode) { if (widget.feedUrl != null) { - ref.read(seriesReadingModeNotifierProvider(widget.feedUrl!).notifier) + ref + .read(seriesReadingModeNotifierProvider(widget.feedUrl!).notifier) .setReadingMode(mode); } else { ref.read(readingModeNotifierProvider.notifier).setReadingMode(mode); @@ -587,7 +595,8 @@ class _ReaderScreenState extends ConsumerState { void _setTwoPageMode(WidgetRef ref, TwoPageMode mode) { if (widget.feedUrl != null) { - ref.read(seriesToPageModeNotifierProvider(widget.feedUrl!).notifier) + ref + .read(seriesToPageModeNotifierProvider(widget.feedUrl!).notifier) .setTwoPageMode(mode); } else { ref.read(twoPageModeNotifierProvider.notifier).setTwoPageMode(mode); @@ -596,7 +605,8 @@ class _ReaderScreenState extends ConsumerState { void _setCoverPage(WidgetRef ref, bool value) { if (widget.feedUrl != null) { - ref.read(seriesCoverPageNotifierProvider(widget.feedUrl!).notifier) + ref + .read(seriesCoverPageNotifierProvider(widget.feedUrl!).notifier) .setCoverPage(value); } else { ref.read(coverPageNotifierProvider.notifier).setCoverPage(value); @@ -668,7 +678,8 @@ class _ReaderScreenState extends ConsumerState { trailingIcon: readingMode == ReadingMode.verticalContinuous ? const Icon(Icons.check, size: 18) : null, - onPressed: () => _setReadingMode(ref, ReadingMode.verticalContinuous), + onPressed: () => + _setReadingMode(ref, ReadingMode.verticalContinuous), child: const Text('Vertical scroll'), ), ], @@ -691,9 +702,8 @@ class _ReaderScreenState extends ConsumerState { if (isPaged) MenuItemButton( leadingIcon: const Icon(Icons.looks_one), - trailingIcon: firstPageIsCover - ? const Icon(Icons.check, size: 18) - : null, + trailingIcon: + firstPageIsCover ? const Icon(Icons.check, size: 18) : null, onPressed: () => _setCoverPage(ref, !firstPageIsCover), child: const Text('First page is cover'), ), @@ -746,9 +756,8 @@ class _ReaderScreenState extends ConsumerState { ? _twoPageController! : _pageController; final totalItems = isTwoPage ? _spreads.length : streamLink.pageCount; - final currentIdx = isTwoPage - ? spreadIndexForPage(_spreads, _currentPage) - : _currentPage; + final currentIdx = + isTwoPage ? spreadIndexForPage(_spreads, _currentPage) : _currentPage; return Container( decoration: BoxDecoration( @@ -816,7 +825,8 @@ class _ReaderScreenState extends ConsumerState { iconSize: 28, ), IconButton( - icon: const Icon(Icons.chevron_right, color: Colors.white), + icon: const Icon(Icons.chevron_right, + color: Colors.white), onPressed: currentIdx > 0 ? () => einkMode ? activeController.jumpToPage(currentIdx - 1) @@ -828,7 +838,8 @@ class _ReaderScreenState extends ConsumerState { iconSize: 32, ), IconButton( - icon: const Icon(Icons.chevron_left, color: Colors.white), + icon: + const Icon(Icons.chevron_left, color: Colors.white), onPressed: currentIdx < totalItems - 1 ? () => einkMode ? activeController.jumpToPage(currentIdx + 1) @@ -856,7 +867,8 @@ class _ReaderScreenState extends ConsumerState { iconSize: 28, ), IconButton( - icon: const Icon(Icons.chevron_left, color: Colors.white), + icon: + const Icon(Icons.chevron_left, color: Colors.white), onPressed: currentIdx > 0 ? () => einkMode ? activeController.jumpToPage(currentIdx - 1) @@ -868,7 +880,8 @@ class _ReaderScreenState extends ConsumerState { iconSize: 32, ), IconButton( - icon: const Icon(Icons.chevron_right, color: Colors.white), + icon: const Icon(Icons.chevron_right, + color: Colors.white), onPressed: currentIdx < totalItems - 1 ? () => einkMode ? activeController.jumpToPage(currentIdx + 1) @@ -895,7 +908,8 @@ class _ReaderScreenState extends ConsumerState { } Future _deleteCompletedProgress() async { - await ref.read(readingProgressNotifierProvider.notifier) + await ref + .read(readingProgressNotifierProvider.notifier) .deleteProgress(widget.entry.id, widget.serverId); if (mounted) { ref.invalidate(recentlyReadProvider); @@ -906,12 +920,13 @@ class _ReaderScreenState extends ConsumerState { void _initializeProgress() async { // Cache the publication entry - final cachedPub = await ref.read(publicationCacheProvider.notifier).cachePublication( - serverId: widget.serverId, - entry: widget.entry, - metadata: const EnhancedMetadata.empty(), - downloadImages: true, - ); + final cachedPub = + await ref.read(publicationCacheProvider.notifier).cachePublication( + serverId: widget.serverId, + entry: widget.entry, + metadata: const EnhancedMetadata.empty(), + downloadImages: true, + ); final progressKey = ProgressKey( publicationId: widget.entry.id, @@ -971,14 +986,16 @@ class _ReaderScreenState extends ConsumerState { await ref.read(readingProgressProvider(progressKey).future); // Get or cache the publication - final cachedPub = await ref.read(cachedPublicationProvider(widget.serverId, widget.entry.id).future); + final cachedPub = await ref.read( + cachedPublicationProvider(widget.serverId, widget.entry.id).future); if (existingProgress != null) { // Update existing progress with cache ID final updatedProgress = existingProgress.copyWith( currentPage: page, lastReadAt: DateTime.now(), - publicationCacheId: cachedPub?.id ?? existingProgress.publicationCacheId, + publicationCacheId: + cachedPub?.id ?? existingProgress.publicationCacheId, seriesFeedUrl: widget.feedUrl ?? existingProgress.seriesFeedUrl, ); @@ -1023,8 +1040,8 @@ class _ReaderScreenState extends ConsumerState { final authHeaders = AuthService().getAuthHeaders(server); final totalPages = streamLink.pageCount; - final dimNotifier = ref.read( - pageDimensionsNotifierProvider(widget.entry.id).notifier); + final dimNotifier = + ref.read(pageDimensionsNotifierProvider(widget.entry.id).notifier); for (var i = currentPage + 1; i <= currentPage + count && i < totalPages; diff --git a/lib/screens/settings/readers_settings_screen.dart b/lib/screens/settings/readers_settings_screen.dart index 9fdc776..9782a21 100644 --- a/lib/screens/settings/readers_settings_screen.dart +++ b/lib/screens/settings/readers_settings_screen.dart @@ -60,8 +60,7 @@ class ReadersSettingsScreen extends ConsumerWidget { SwitchListTile( secondary: const Icon(Icons.tablet_android), title: const Text('E-ink mode'), - subtitle: - const Text('Disable animations for e-ink displays'), + subtitle: const Text('Disable animations for e-ink displays'), value: einkMode, onChanged: (enabled) { ref diff --git a/lib/widgets/next_in_series_overlay.dart b/lib/widgets/next_in_series_overlay.dart index 6b3b175..f948b70 100644 --- a/lib/widgets/next_in_series_overlay.dart +++ b/lib/widgets/next_in_series_overlay.dart @@ -134,8 +134,8 @@ class _NextInSeriesOverlayState extends ConsumerState { borderRadius: BorderRadius.circular(12), onTap: onTap, child: Padding( - padding: const EdgeInsets.only( - left: 16, top: 12, bottom: 12, right: 4), + padding: + const EdgeInsets.only(left: 16, top: 12, bottom: 12, right: 4), child: Row( children: [ Icon(icon, color: onColor), @@ -147,18 +147,16 @@ class _NextInSeriesOverlayState extends ConsumerState { children: [ Text( label, - style: - Theme.of(context).textTheme.labelSmall?.copyWith( - color: onColor.withOpacity(0.7), - ), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: onColor.withOpacity(0.7), + ), ), Text( title, - style: - Theme.of(context).textTheme.titleSmall?.copyWith( - color: onColor, - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: onColor, + fontWeight: FontWeight.bold, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ), -- 2.52.0 From 576feb3cc701baad88d3f1ac16f4e1e4c58238f0 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 19:29:16 +0100 Subject: [PATCH 3/8] ci: fixed steps --- .woodpecker/pr.yml | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/.woodpecker/pr.yml b/.woodpecker/pr.yml index a163b09..c5c38fe 100644 --- a/.woodpecker/pr.yml +++ b/.woodpecker/pr.yml @@ -6,25 +6,17 @@ when: event: pull_request steps: - # Step 1: Install dependencies and generate code - setup: + # Step 1: Check formatting + lint: + image: ghcr.io/cirruslabs/flutter:stable + commands: + - flutter pub get + - dart format --set-exit-if-changed . + + # Step 2: Static analysis + analyze: image: ghcr.io/cirruslabs/flutter:stable commands: - flutter pub get - flutter pub run build_runner build --delete-conflicting-outputs - - # Step 2: Lint the project - lint: - image: ghcr.io/cirruslabs/flutter:stable - commands: - - dart format --set-exit-if-changed . - depends_on: - - setup - - # Step 3: Static analysis - analyze: - image: ghcr.io/cirruslabs/flutter:stable - commands: - flutter analyze - depends_on: - - setup -- 2.52.0 From 6c3be735e92f924b89bbae9e2a10b065c0aadd31 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 19:30:01 +0100 Subject: [PATCH 4/8] ci: run pub get only once --- .woodpecker/pr.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.woodpecker/pr.yml b/.woodpecker/pr.yml index c5c38fe..486f9b8 100644 --- a/.woodpecker/pr.yml +++ b/.woodpecker/pr.yml @@ -12,11 +12,5 @@ steps: commands: - flutter pub get - dart format --set-exit-if-changed . - - # Step 2: Static analysis - analyze: - image: ghcr.io/cirruslabs/flutter:stable - commands: - - flutter pub get - flutter pub run build_runner build --delete-conflicting-outputs - flutter analyze -- 2.52.0 From f94631dbf6673a58e830f360360e52a2632fb5be Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 21:55:12 +0100 Subject: [PATCH 5/8] ci: updated pr workflow --- .woodpecker/pr.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.woodpecker/pr.yml b/.woodpecker/pr.yml index 486f9b8..5993356 100644 --- a/.woodpecker/pr.yml +++ b/.woodpecker/pr.yml @@ -6,10 +6,18 @@ when: event: pull_request steps: - # Step 1: Check formatting lint: - image: ghcr.io/cirruslabs/flutter:stable + image: debian:bookworm-slim commands: + - set -eux + + - apt-get update -qq && apt-get install -y --no-install-recommends bash curl git unzip xz-utils ca-certificates > /dev/null + - git config --global --add safe.directory '*' + + - curl -fsSL -o /tmp/flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.41.0-stable.tar.xz + - tar xf /tmp/flutter.tar.xz -C /opt && rm /tmp/flutter.tar.xz + - export PATH="/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:$PATH" + - flutter pub get - dart format --set-exit-if-changed . - flutter pub run build_runner build --delete-conflicting-outputs -- 2.52.0 From 3aaa9bec69813a4ef09caa68ed091256c15e66a8 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 22:06:01 +0100 Subject: [PATCH 6/8] ci: moved ci to a separate script --- .woodpecker/pr.yml | 14 +------------- .woodpecker/scripts/pr-lint.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 13 deletions(-) create mode 100755 .woodpecker/scripts/pr-lint.sh diff --git a/.woodpecker/pr.yml b/.woodpecker/pr.yml index 5993356..a673b70 100644 --- a/.woodpecker/pr.yml +++ b/.woodpecker/pr.yml @@ -9,16 +9,4 @@ steps: lint: image: debian:bookworm-slim commands: - - set -eux - - - apt-get update -qq && apt-get install -y --no-install-recommends bash curl git unzip xz-utils ca-certificates > /dev/null - - git config --global --add safe.directory '*' - - - curl -fsSL -o /tmp/flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.41.0-stable.tar.xz - - tar xf /tmp/flutter.tar.xz -C /opt && rm /tmp/flutter.tar.xz - - export PATH="/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:$PATH" - - - flutter pub get - - dart format --set-exit-if-changed . - - flutter pub run build_runner build --delete-conflicting-outputs - - flutter analyze + - bash .woodpecker/scripts/pr-lint.sh diff --git a/.woodpecker/scripts/pr-lint.sh b/.woodpecker/scripts/pr-lint.sh new file mode 100755 index 0000000..232045c --- /dev/null +++ b/.woodpecker/scripts/pr-lint.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eux + +apt-get update -qq && apt-get install -y --no-install-recommends bash curl git unzip xz-utils ca-certificates > /dev/null +git config --global --add safe.directory '*' + +curl -fsSL -o /tmp/flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.41.0-stable.tar.xz +tar xf /tmp/flutter.tar.xz -C /opt && rm /tmp/flutter.tar.xz +export PATH="/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:$PATH" + +flutter pub get +dart format --set-exit-if-changed . +flutter pub run build_runner build --delete-conflicting-outputs +flutter analyze -- 2.52.0 From 676ff547b8444b664d8e42a88b400fd57333675a Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 22:18:30 +0100 Subject: [PATCH 7/8] chore: analyze fixes --- lib/models/opds_feed.dart | 1 - lib/screens/browse/feed_screen.dart | 4 +- .../browse/library_browser_screen.dart | 4 +- lib/screens/library/library_screen.dart | 4 +- .../publication_detail_screen.dart | 7 +- lib/screens/reader/epub_reader_screen.dart | 11 +- lib/screens/reader/reader_screen.dart | 10 +- lib/screens/servers/edit_server_screen.dart | 2 +- lib/screens/servers/server_list_screen.dart | 4 +- .../settings/appearance_settings_screen.dart | 106 ++++++++---------- .../filter_quality_settings_screen.dart | 52 ++++----- .../precache_pages_settings_screen.dart | 52 ++++----- .../reading_mode_settings_screen.dart | 52 ++++----- lib/services/epub_download_service.dart | 10 +- lib/services/image_cache_service.dart | 5 +- lib/widgets/next_in_series_overlay.dart | 4 +- lib/widgets/publication_card.dart | 4 +- lib/widgets/recently_read_card.dart | 4 +- lib/widgets/server_card.dart | 10 +- pubspec.lock | 16 +-- 20 files changed, 175 insertions(+), 187 deletions(-) diff --git a/lib/models/opds_feed.dart b/lib/models/opds_feed.dart index 19e7a73..dc2377d 100644 --- a/lib/models/opds_feed.dart +++ b/lib/models/opds_feed.dart @@ -1,5 +1,4 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:worldhopper/models/enhanced_metadata.dart'; import 'package:worldhopper/models/opds_entry.dart'; import 'package:worldhopper/models/opds_link.dart'; diff --git a/lib/screens/browse/feed_screen.dart b/lib/screens/browse/feed_screen.dart index d77fa73..be09cf1 100644 --- a/lib/screens/browse/feed_screen.dart +++ b/lib/screens/browse/feed_screen.dart @@ -285,7 +285,7 @@ class _FeedScreenState extends ConsumerState { color: Theme.of(context) .colorScheme .secondary - .withOpacity(0.5), + .withValues(alpha: 0.5), ), const SizedBox(height: 24), Text( @@ -299,7 +299,7 @@ class _FeedScreenState extends ConsumerState { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ], diff --git a/lib/screens/browse/library_browser_screen.dart b/lib/screens/browse/library_browser_screen.dart index 53c8446..6a295ca 100644 --- a/lib/screens/browse/library_browser_screen.dart +++ b/lib/screens/browse/library_browser_screen.dart @@ -278,7 +278,7 @@ class _LibraryBrowserScreenState extends ConsumerState { color: Theme.of(context) .colorScheme .secondary - .withOpacity(0.5), + .withValues(alpha: 0.5), ), const SizedBox(height: 24), Text( @@ -292,7 +292,7 @@ class _LibraryBrowserScreenState extends ConsumerState { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ], diff --git a/lib/screens/library/library_screen.dart b/lib/screens/library/library_screen.dart index d3be167..42f43d4 100644 --- a/lib/screens/library/library_screen.dart +++ b/lib/screens/library/library_screen.dart @@ -122,7 +122,7 @@ class LibraryScreen extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), @@ -163,7 +163,7 @@ class LibraryScreen extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), textAlign: TextAlign.center, ), diff --git a/lib/screens/publication/publication_detail_screen.dart b/lib/screens/publication/publication_detail_screen.dart index 8d1d9a8..e4dce7f 100644 --- a/lib/screens/publication/publication_detail_screen.dart +++ b/lib/screens/publication/publication_detail_screen.dart @@ -68,7 +68,7 @@ class PublicationDetailScreen extends ConsumerWidget { false; return AppBar( - backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.75), + backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.75), title: Text(entry.title), actions: isOffline ? [ @@ -94,7 +94,7 @@ class PublicationDetailScreen extends ConsumerWidget { child: Icon( Icons.book, size: 100, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.3), ), ); } @@ -468,7 +468,4 @@ class PublicationDetailScreen extends ConsumerWidget { } } - String _stripHtml(String html) { - return html.replaceAll(RegExp(r'<[^>]*>'), '').trim(); - } } diff --git a/lib/screens/reader/epub_reader_screen.dart b/lib/screens/reader/epub_reader_screen.dart index 1eb7991..92b00c9 100644 --- a/lib/screens/reader/epub_reader_screen.dart +++ b/lib/screens/reader/epub_reader_screen.dart @@ -6,7 +6,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter_epub_viewer/flutter_epub_viewer.dart'; import 'package:go_router/go_router.dart'; -import 'package:worldhopper/config/constants.dart'; import 'package:worldhopper/helpers/snackbar_helper.dart'; import 'package:worldhopper/providers/eink_mode_provider.dart'; import 'package:worldhopper/models/enhanced_metadata.dart'; @@ -356,7 +355,7 @@ class _EpubReaderScreenState extends ConsumerState { child: Text( '${(_loadingProgress * 100).toInt()}%', style: TextStyle( - color: Colors.white.withOpacity(0.7), + color: Colors.white.withValues(alpha: 0.7), fontSize: 14, ), ), @@ -403,7 +402,7 @@ class _EpubReaderScreenState extends ConsumerState { return PopScope( canPop: true, - onPopInvoked: (bool didPop) { + onPopInvokedWithResult: (bool didPop, Object? result) { if (didPop) { if (_reachedEndThisSession) { _deleteCompletedProgress(); @@ -621,7 +620,7 @@ class _EpubReaderScreenState extends ConsumerState { /// Helper method to convert Flutter Color to CSS hex string String _colorToHex(Color color) { - return '#${color.value.toRadixString(16).substring(2).toUpperCase()}'; + return '#${color.toARGB32().toRadixString(16).substring(2).toUpperCase()}'; } /// Toggle app bar visibility @@ -687,8 +686,8 @@ class _EpubReaderScreenState extends ConsumerState { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black.withOpacity(0.7), - Colors.black.withOpacity(0.0), + Colors.black.withValues(alpha: 0.7), + Colors.black.withValues(alpha: 0.0), ], stops: const [0.0, 1.0], ), diff --git a/lib/screens/reader/reader_screen.dart b/lib/screens/reader/reader_screen.dart index 6519626..b70f743 100644 --- a/lib/screens/reader/reader_screen.dart +++ b/lib/screens/reader/reader_screen.dart @@ -127,7 +127,7 @@ class _ReaderScreenState extends ConsumerState { return PopScope( canPop: true, - onPopInvoked: (bool didPop) { + onPopInvokedWithResult: (bool didPop, Object? result) { if (didPop) { if (_reachedEndThisSession) { _deleteCompletedProgress(); @@ -508,7 +508,7 @@ class _ReaderScreenState extends ConsumerState { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black.withOpacity(0.7), + Colors.black.withValues(alpha: 0.7), Colors.transparent, ], ), @@ -555,7 +555,7 @@ class _ReaderScreenState extends ConsumerState { Text( widget.entry.authors.join(', '), style: TextStyle( - color: Colors.white.withOpacity(0.7), + color: Colors.white.withValues(alpha: 0.7), fontSize: 12, ), maxLines: 1, @@ -765,7 +765,7 @@ class _ReaderScreenState extends ConsumerState { begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ - Colors.black.withOpacity(0.7), + Colors.black.withValues(alpha: 0.7), Colors.transparent, ], ), @@ -805,7 +805,7 @@ class _ReaderScreenState extends ConsumerState { // Progress bar LinearProgressIndicator( value: progress, - backgroundColor: Colors.white.withOpacity(0.3), + backgroundColor: Colors.white.withValues(alpha: 0.3), valueColor: const AlwaysStoppedAnimation(Colors.white), minHeight: 4, ), diff --git a/lib/screens/servers/edit_server_screen.dart b/lib/screens/servers/edit_server_screen.dart index 9d8c8d1..3d4238f 100644 --- a/lib/screens/servers/edit_server_screen.dart +++ b/lib/screens/servers/edit_server_screen.dart @@ -252,7 +252,7 @@ class _EditServerScreenState extends ConsumerState { label, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: - Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), ), ), Text( diff --git a/lib/screens/servers/server_list_screen.dart b/lib/screens/servers/server_list_screen.dart index 61606f9..fe72b38 100644 --- a/lib/screens/servers/server_list_screen.dart +++ b/lib/screens/servers/server_list_screen.dart @@ -115,7 +115,7 @@ class ServerListScreen extends ConsumerWidget { Icon( Icons.cloud_off, size: 100, - color: Theme.of(context).colorScheme.secondary.withOpacity(0.5), + color: Theme.of(context).colorScheme.secondary.withValues(alpha: 0.5), ), const SizedBox(height: 24), Text( @@ -132,7 +132,7 @@ class ServerListScreen extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ), diff --git a/lib/screens/settings/appearance_settings_screen.dart b/lib/screens/settings/appearance_settings_screen.dart index f02ab9b..41789d0 100644 --- a/lib/screens/settings/appearance_settings_screen.dart +++ b/lib/screens/settings/appearance_settings_screen.dart @@ -20,72 +20,58 @@ class AppearanceSettingsScreen extends ConsumerWidget { _buildSectionHeader(context, 'Theme'), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - children: [ - ListTile( - leading: const Icon(Icons.light_mode), - title: const Text('Light'), - trailing: Radio( - value: ThemeMode.light, - groupValue: themeMode, - onChanged: (value) { - if (value != null) { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(value); - } + child: RadioGroup( + groupValue: themeMode, + onChanged: (value) { + if (value != null) { + ref + .read(themeNotifierProvider.notifier) + .setThemeMode(value); + } + }, + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.light_mode), + title: const Text('Light'), + trailing: const Radio( + value: ThemeMode.light, + ), + onTap: () { + ref + .read(themeNotifierProvider.notifier) + .setThemeMode(ThemeMode.light); }, ), - onTap: () { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(ThemeMode.light); - }, - ), - const Divider(height: 1), - ListTile( - leading: const Icon(Icons.dark_mode), - title: const Text('Dark'), - trailing: Radio( - value: ThemeMode.dark, - groupValue: themeMode, - onChanged: (value) { - if (value != null) { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(value); - } + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.dark_mode), + title: const Text('Dark'), + trailing: const Radio( + value: ThemeMode.dark, + ), + onTap: () { + ref + .read(themeNotifierProvider.notifier) + .setThemeMode(ThemeMode.dark); }, ), - onTap: () { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(ThemeMode.dark); - }, - ), - const Divider(height: 1), - ListTile( - leading: const Icon(Icons.brightness_auto), - title: const Text('System'), - subtitle: const Text('Follow device theme'), - trailing: Radio( - value: ThemeMode.system, - groupValue: themeMode, - onChanged: (value) { - if (value != null) { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(value); - } + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.brightness_auto), + title: const Text('System'), + subtitle: const Text('Follow device theme'), + trailing: const Radio( + value: ThemeMode.system, + ), + onTap: () { + ref + .read(themeNotifierProvider.notifier) + .setThemeMode(ThemeMode.system); }, ), - onTap: () { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(ThemeMode.system); - }, - ), - ], + ], + ), ), ), const SizedBox(height: 16), diff --git a/lib/screens/settings/filter_quality_settings_screen.dart b/lib/screens/settings/filter_quality_settings_screen.dart index 19f3b88..d77d0a5 100644 --- a/lib/screens/settings/filter_quality_settings_screen.dart +++ b/lib/screens/settings/filter_quality_settings_screen.dart @@ -20,34 +20,36 @@ class FilterQualitySettingsScreen extends ConsumerWidget { const SizedBox(height: 8), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - children: [ - for (final entry in _filterQualityOptions.entries) ...[ - if (entry.key != _filterQualityOptions.keys.first) - const Divider(height: 1), - ListTile( - leading: Icon(entry.value.icon), - title: Text(entry.value.label), - subtitle: Text(entry.value.description), - trailing: Radio( - value: entry.key, - groupValue: filterQuality, - onChanged: (value) { - if (value != null) { - ref - .read(filterQualityNotifierProvider.notifier) - .setFilterQuality(value); - } + child: RadioGroup( + groupValue: filterQuality, + onChanged: (value) { + if (value != null) { + ref + .read(filterQualityNotifierProvider.notifier) + .setFilterQuality(value); + } + }, + child: Column( + children: [ + for (final entry in _filterQualityOptions.entries) ...[ + if (entry.key != _filterQualityOptions.keys.first) + const Divider(height: 1), + ListTile( + leading: Icon(entry.value.icon), + title: Text(entry.value.label), + subtitle: Text(entry.value.description), + trailing: Radio( + value: entry.key, + ), + onTap: () { + ref + .read(filterQualityNotifierProvider.notifier) + .setFilterQuality(entry.key); }, ), - onTap: () { - ref - .read(filterQualityNotifierProvider.notifier) - .setFilterQuality(entry.key); - }, - ), + ], ], - ], + ), ), ), Padding( diff --git a/lib/screens/settings/precache_pages_settings_screen.dart b/lib/screens/settings/precache_pages_settings_screen.dart index 0be2f15..4588a94 100644 --- a/lib/screens/settings/precache_pages_settings_screen.dart +++ b/lib/screens/settings/precache_pages_settings_screen.dart @@ -20,34 +20,36 @@ class PrecachePagesSettingsScreen extends ConsumerWidget { const SizedBox(height: 8), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - children: [ - for (final entry in _precachePagesOptions.entries) ...[ - if (entry.key != _precachePagesOptions.keys.first) - const Divider(height: 1), - ListTile( - leading: Icon(entry.value.icon), - title: Text(entry.value.label), - subtitle: Text(entry.value.description), - trailing: Radio( - value: entry.key, - groupValue: precachePages, - onChanged: (value) { - if (value != null) { - ref - .read(precachePagesNotifierProvider.notifier) - .setPrecachePages(value); - } + child: RadioGroup( + groupValue: precachePages, + onChanged: (value) { + if (value != null) { + ref + .read(precachePagesNotifierProvider.notifier) + .setPrecachePages(value); + } + }, + child: Column( + children: [ + for (final entry in _precachePagesOptions.entries) ...[ + if (entry.key != _precachePagesOptions.keys.first) + const Divider(height: 1), + ListTile( + leading: Icon(entry.value.icon), + title: Text(entry.value.label), + subtitle: Text(entry.value.description), + trailing: Radio( + value: entry.key, + ), + onTap: () { + ref + .read(precachePagesNotifierProvider.notifier) + .setPrecachePages(entry.key); }, ), - onTap: () { - ref - .read(precachePagesNotifierProvider.notifier) - .setPrecachePages(entry.key); - }, - ), + ], ], - ], + ), ), ), Padding( diff --git a/lib/screens/settings/reading_mode_settings_screen.dart b/lib/screens/settings/reading_mode_settings_screen.dart index 2549d5a..534c274 100644 --- a/lib/screens/settings/reading_mode_settings_screen.dart +++ b/lib/screens/settings/reading_mode_settings_screen.dart @@ -20,34 +20,36 @@ class ReadingModeSettingsScreen extends ConsumerWidget { const SizedBox(height: 8), Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - children: [ - for (final entry in _readingModeOptions.entries) ...[ - if (entry.key != _readingModeOptions.keys.first) - const Divider(height: 1), - ListTile( - leading: Icon(entry.value.icon), - title: Text(entry.value.label), - subtitle: Text(entry.value.description), - trailing: Radio( - value: entry.key, - groupValue: readingMode, - onChanged: (value) { - if (value != null) { - ref - .read(readingModeNotifierProvider.notifier) - .setReadingMode(value); - } + child: RadioGroup( + groupValue: readingMode, + onChanged: (value) { + if (value != null) { + ref + .read(readingModeNotifierProvider.notifier) + .setReadingMode(value); + } + }, + child: Column( + children: [ + for (final entry in _readingModeOptions.entries) ...[ + if (entry.key != _readingModeOptions.keys.first) + const Divider(height: 1), + ListTile( + leading: Icon(entry.value.icon), + title: Text(entry.value.label), + subtitle: Text(entry.value.description), + trailing: Radio( + value: entry.key, + ), + onTap: () { + ref + .read(readingModeNotifierProvider.notifier) + .setReadingMode(entry.key); }, ), - onTap: () { - ref - .read(readingModeNotifierProvider.notifier) - .setReadingMode(entry.key); - }, - ), + ], ], - ], + ), ), ), Padding( diff --git a/lib/services/epub_download_service.dart b/lib/services/epub_download_service.dart index cd35a99..ef2ee00 100644 --- a/lib/services/epub_download_service.dart +++ b/lib/services/epub_download_service.dart @@ -30,7 +30,7 @@ class EpubDownloadService { } final url = acquisitionLink.href; - if (url == null || url.isEmpty) { + if (url.isEmpty) { throw Exception('Invalid acquisition link URL'); } @@ -76,7 +76,7 @@ class EpubDownloadService { if (acquisitionLink == null) return null; final url = acquisitionLink.href; - if (url == null || url.isEmpty) return null; + if (url.isEmpty) return null; final fileInfo = await _cacheManager.getFileFromCache(url); if (fileInfo != null && await fileInfo.file.exists()) { @@ -92,7 +92,7 @@ class EpubDownloadService { if (acquisitionLink == null) return; final url = acquisitionLink.href; - if (url == null || url.isEmpty) return; + if (url.isEmpty) return; await _cacheManager.removeFile(url); } @@ -103,7 +103,7 @@ class EpubDownloadService { if (acquisitionLink == null) return false; final url = acquisitionLink.href; - if (url == null || url.isEmpty) return false; + if (url.isEmpty) return false; final fileInfo = await _cacheManager.getFileFromCache(url); return fileInfo != null && await fileInfo.file.exists(); @@ -118,7 +118,7 @@ class EpubDownloadService { } final url = acquisitionLink.href; - if (url == null || url.isEmpty) { + if (url.isEmpty) { throw Exception('Invalid acquisition link URL'); } diff --git a/lib/services/image_cache_service.dart b/lib/services/image_cache_service.dart index b04b2b8..1012ff8 100644 --- a/lib/services/image_cache_service.dart +++ b/lib/services/image_cache_service.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; /// Service for caching publication cover images locally @@ -36,7 +37,7 @@ class ImageCacheService { return file.path; } catch (e) { - print('Error caching cover: $e'); + debugPrint('Error caching cover: $e'); return null; } } @@ -59,7 +60,7 @@ class ImageCacheService { return file.path; } catch (e) { - print('Error caching thumbnail: $e'); + debugPrint('Error caching thumbnail: $e'); return null; } } diff --git a/lib/widgets/next_in_series_overlay.dart b/lib/widgets/next_in_series_overlay.dart index f948b70..8348105 100644 --- a/lib/widgets/next_in_series_overlay.dart +++ b/lib/widgets/next_in_series_overlay.dart @@ -148,7 +148,7 @@ class _NextInSeriesOverlayState extends ConsumerState { Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: onColor.withOpacity(0.7), + color: onColor.withValues(alpha: 0.7), ), ), Text( @@ -173,7 +173,7 @@ class _NextInSeriesOverlayState extends ConsumerState { iconSize: 20, icon: Icon( Icons.close, - color: onColor.withOpacity(0.7), + color: onColor.withValues(alpha: 0.7), ), onPressed: () { setState(() { diff --git a/lib/widgets/publication_card.dart b/lib/widgets/publication_card.dart index 27312a6..6e69997 100644 --- a/lib/widgets/publication_card.dart +++ b/lib/widgets/publication_card.dart @@ -51,7 +51,7 @@ class PublicationCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -132,7 +132,7 @@ class PublicationCard extends StatelessWidget { child: Icon( entry.isNavigation ? Icons.folder : Icons.book, size: 48, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.3), ), ), ); diff --git a/lib/widgets/recently_read_card.dart b/lib/widgets/recently_read_card.dart index bfe5db5..82ab637 100644 --- a/lib/widgets/recently_read_card.dart +++ b/lib/widgets/recently_read_card.dart @@ -126,7 +126,7 @@ class RecentlyReadCard extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), ), ], @@ -183,7 +183,7 @@ class RecentlyReadCard extends ConsumerWidget { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.3), + color: Colors.black.withValues(alpha: 0.3), blurRadius: 4, offset: const Offset(0, 2), ), diff --git a/lib/widgets/server_card.dart b/lib/widgets/server_card.dart index abe2733..de284db 100644 --- a/lib/widgets/server_card.dart +++ b/lib/widgets/server_card.dart @@ -65,7 +65,7 @@ class ServerCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -123,7 +123,7 @@ class ServerCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), const SizedBox(width: 4), Text( @@ -132,7 +132,7 @@ class ServerCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), ), ], @@ -145,7 +145,7 @@ class ServerCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), const SizedBox(width: 4), Text( @@ -154,7 +154,7 @@ class ServerCard extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 9226459..cdf3b56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -604,18 +604,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -1049,10 +1049,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.8" timing: dependency: transitive description: -- 2.52.0 From ab92c71502914516c9a8fd41a641d2b904a07b62 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Thu, 12 Feb 2026 22:22:03 +0100 Subject: [PATCH 8/8] chore: dart format --- lib/screens/publication/publication_detail_screen.dart | 4 ++-- lib/screens/servers/edit_server_screen.dart | 6 ++++-- lib/screens/servers/server_list_screen.dart | 3 ++- lib/screens/settings/appearance_settings_screen.dart | 4 +--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/screens/publication/publication_detail_screen.dart b/lib/screens/publication/publication_detail_screen.dart index e4dce7f..47c335c 100644 --- a/lib/screens/publication/publication_detail_screen.dart +++ b/lib/screens/publication/publication_detail_screen.dart @@ -68,7 +68,8 @@ class PublicationDetailScreen extends ConsumerWidget { false; return AppBar( - backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.75), + backgroundColor: + Theme.of(context).colorScheme.surface.withValues(alpha: 0.75), title: Text(entry.title), actions: isOffline ? [ @@ -467,5 +468,4 @@ class PublicationDetailScreen extends ConsumerWidget { return _formatDate(dateTime); } } - } diff --git a/lib/screens/servers/edit_server_screen.dart b/lib/screens/servers/edit_server_screen.dart index 3d4238f..ea87654 100644 --- a/lib/screens/servers/edit_server_screen.dart +++ b/lib/screens/servers/edit_server_screen.dart @@ -251,8 +251,10 @@ class _EditServerScreenState extends ConsumerState { Text( label, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.6), ), ), Text( diff --git a/lib/screens/servers/server_list_screen.dart b/lib/screens/servers/server_list_screen.dart index fe72b38..f5c975f 100644 --- a/lib/screens/servers/server_list_screen.dart +++ b/lib/screens/servers/server_list_screen.dart @@ -115,7 +115,8 @@ class ServerListScreen extends ConsumerWidget { Icon( Icons.cloud_off, size: 100, - color: Theme.of(context).colorScheme.secondary.withValues(alpha: 0.5), + color: + Theme.of(context).colorScheme.secondary.withValues(alpha: 0.5), ), const SizedBox(height: 24), Text( diff --git a/lib/screens/settings/appearance_settings_screen.dart b/lib/screens/settings/appearance_settings_screen.dart index 41789d0..1150f10 100644 --- a/lib/screens/settings/appearance_settings_screen.dart +++ b/lib/screens/settings/appearance_settings_screen.dart @@ -24,9 +24,7 @@ class AppearanceSettingsScreen extends ConsumerWidget { groupValue: themeMode, onChanged: (value) { if (value != null) { - ref - .read(themeNotifierProvider.notifier) - .setThemeMode(value); + ref.read(themeNotifierProvider.notifier).setThemeMode(value); } }, child: Column( -- 2.52.0