diff --git a/lib/pages/reader/epub_reader/epub_reader_controls.dart b/lib/pages/reader/epub_reader/epub_reader_controls.dart index a6312980..6f1f216d 100644 --- a/lib/pages/reader/epub_reader/epub_reader_controls.dart +++ b/lib/pages/reader/epub_reader/epub_reader_controls.dart @@ -6,6 +6,7 @@ import 'package:kover/utils/constants/kover_icons.dart'; import 'package:kover/utils/layout_constants.dart'; import 'package:kover/widgets/settings/boolean_option.dart'; import 'package:kover/widgets/settings/choice_option.dart'; +import 'package:kover/widgets/settings/dim_option.dart'; import 'package:kover/widgets/settings/numeric_option.dart'; import 'package:kover/widgets/util/async_value.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; @@ -91,6 +92,7 @@ class EpubReaderSettingsBottomSheet extends ConsumerWidget { .read(provider.notifier) .setMarginSize(newValue), ), + const DimOption(), NumericOption( title: 'Line Height', diff --git a/lib/pages/reader/image_reader/image_reader_controls.dart b/lib/pages/reader/image_reader/image_reader_controls.dart index 857d9532..7e1eac04 100644 --- a/lib/pages/reader/image_reader/image_reader_controls.dart +++ b/lib/pages/reader/image_reader/image_reader_controls.dart @@ -7,6 +7,7 @@ import 'package:kover/utils/constants/kover_icons.dart'; import 'package:kover/utils/layout_constants.dart'; import 'package:kover/widgets/settings/boolean_option.dart'; import 'package:kover/widgets/settings/choice_option.dart'; +import 'package:kover/widgets/settings/dim_option.dart'; import 'package:kover/widgets/settings/numeric_option.dart'; import 'package:kover/widgets/util/async_value.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; @@ -150,6 +151,7 @@ class ImageReaderSettingsBottomSheet extends ConsumerWidget { .read(provider.notifier) .setVerticalReaderPadding(newValue), ), + const DimOption(), NumericOption( title: 'Vertical Gap', icon: LucideIcons.unfoldVertical, diff --git a/lib/pages/reader/overlay/reader_overlay.dart b/lib/pages/reader/overlay/reader_overlay.dart index 75403ec3..58da208d 100644 --- a/lib/pages/reader/overlay/reader_overlay.dart +++ b/lib/pages/reader/overlay/reader_overlay.dart @@ -10,6 +10,7 @@ import 'package:kover/riverpod/providers/reader//reader.dart'; import 'package:kover/riverpod/providers/reader/epub_reader.dart'; import 'package:kover/riverpod/providers/reader/reader_navigation.dart'; import 'package:kover/riverpod/providers/router.dart'; +import 'package:kover/riverpod/providers/settings/reader_dim_settings.dart'; import 'package:kover/utils/layout_constants.dart'; import 'package:kover/utils/logging.dart'; import 'package:kover/widgets/util/async_value.dart'; @@ -95,6 +96,13 @@ class ReaderOverlay extends HookConsumerWidget { ), ); + final dimLevel = ref + .watch(readerDimSettingsProvider) + .maybeWhen( + data: (state) => state.dimLevel, + orElse: () => 0.0, + ); + ref.listen( readerNavigationProvider( seriesId: seriesId, @@ -141,12 +149,21 @@ class ReaderOverlay extends HookConsumerWidget { }, child: Stack( children: [ - Positioned.fill( - child: Column( - mainAxisSize: .min, - children: [ - Expanded(child: child), - if (showProgressBar && state.series.format == .epub) + Positioned.fill(child: child), + if (dimLevel > 0) + Positioned.fill( + child: IgnorePointer( + child: ColoredBox( + color: Color.fromRGBO(0, 0, 0, dimLevel), + ), + ), + ), + if (showProgressBar && state.series.format == .epub) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: SubpageProgress( seriesId: seriesId, chapterId: chapterId, @@ -154,8 +171,14 @@ class ReaderOverlay extends HookConsumerWidget { .animate( target: uiVisible.value ? 0.0 : 1.0, ) - .fadeIn(duration: 200.ms) - else if (showProgressBar) + .fadeIn(duration: 200.ms), + ) + else if (showProgressBar) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: ReaderProgress( seriesId: seriesId, chapterId: chapterId, @@ -164,9 +187,7 @@ class ReaderOverlay extends HookConsumerWidget { target: uiVisible.value ? 0.0 : 1.0, ) .fadeIn(duration: 200.ms), - ], ), - ), Positioned.fill( child: Row( children: [ diff --git a/lib/pages/reader/pdf_reader/pdf_reader_controls.dart b/lib/pages/reader/pdf_reader/pdf_reader_controls.dart index 08b53895..9e6d3e42 100644 --- a/lib/pages/reader/pdf_reader/pdf_reader_controls.dart +++ b/lib/pages/reader/pdf_reader/pdf_reader_controls.dart @@ -6,6 +6,7 @@ import 'package:kover/utils/constants/kover_icons.dart'; import 'package:kover/utils/layout_constants.dart'; import 'package:kover/widgets/settings/boolean_option.dart'; import 'package:kover/widgets/settings/choice_option.dart'; +import 'package:kover/widgets/settings/dim_option.dart'; import 'package:kover/widgets/util/async_value.dart'; class PdfReaderSettingsBottomSheet extends ConsumerWidget { @@ -110,6 +111,7 @@ class PdfReaderSettingsBottomSheet extends ConsumerWidget { .setShowProgressBar(newValue); }, ), + const DimOption(), ], ), ), diff --git a/lib/riverpod/providers/settings/reader_dim_settings.dart b/lib/riverpod/providers/settings/reader_dim_settings.dart new file mode 100644 index 00000000..f314d77b --- /dev/null +++ b/lib/riverpod/providers/settings/reader_dim_settings.dart @@ -0,0 +1,71 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/experimental/persist.dart'; +import 'package:kover/riverpod/repository/storage_repository.dart'; +import 'package:kover/utils/logging.dart'; +import 'package:riverpod_annotation/experimental/json_persist.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'reader_dim_settings.freezed.dart'; +part 'reader_dim_settings.g.dart'; + +sealed class ReaderDimSettingsLimits { + static const double dimMin = 0.0; + static const double dimMax = 0.9; + static const double dimStep = 5.0; +} + +@freezed +sealed class ReaderDimSettingsState with _$ReaderDimSettingsState { + const ReaderDimSettingsState._(); + const factory ReaderDimSettingsState({ + @Default(0.0) double dimLevel, + }) = _ReaderDimSettingsState; + + factory ReaderDimSettingsState.fromJson(Map json) => + _$ReaderDimSettingsStateFromJson(json); +} + +@riverpod +@JsonPersist() +class ReaderDimSettings extends _$ReaderDimSettings { + @override + Future build() async { + await persist( + ref.watch(storageProvider.future), + options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever), + ).future; + return state.value ?? const ReaderDimSettingsState(); + } + + Future adjustDimLevel(double delta) async { + final current = await future; + state = AsyncData( + current.copyWith( + dimLevel: (current.dimLevel + delta).clamp( + ReaderDimSettingsLimits.dimMin, + ReaderDimSettingsLimits.dimMax, + ), + ), + ); + log.info( + 'adjust dim level', + attributes: {'value': .double(state.value!.dimLevel)}, + ); + } + + Future setDimLevel(double level) async { + final current = await future; + state = AsyncData( + current.copyWith( + dimLevel: level.clamp( + ReaderDimSettingsLimits.dimMin, + ReaderDimSettingsLimits.dimMax, + ), + ), + ); + log.info( + 'set dim level', + attributes: {'value': .double(level)}, + ); + } +} diff --git a/lib/widgets/settings/dim_option.dart b/lib/widgets/settings/dim_option.dart new file mode 100644 index 00000000..ccd4e0dc --- /dev/null +++ b/lib/widgets/settings/dim_option.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:kover/riverpod/providers/settings/reader_dim_settings.dart'; +import 'package:kover/widgets/settings/numeric_option.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; + +class DimOption extends ConsumerWidget { + const DimOption({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final dimLevel = ref + .watch(readerDimSettingsProvider) + .maybeWhen( + data: (state) => state.dimLevel, + orElse: () => 0.0, + ); + + return NumericOption( + title: 'Screen Dimming', + icon: LucideIcons.sunMedium, + value: dimLevel * 100, + min: 0, + max: 90, + step: ReaderDimSettingsLimits.dimStep, + decimalPlaces: 0, + onChanged: (newValue) => ref + .read(readerDimSettingsProvider.notifier) + .setDimLevel(newValue / 100), + ); + } +}