Merge pull request #423 from Schnitzel5/feature/follow-system-theme

added follow system theme + screenshot in player view
This commit is contained in:
Moustapha Kodjo Amadou 2025-03-26 11:00:26 +01:00 committed by GitHub
commit 202c073859
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 601 additions and 215 deletions

View file

@ -89,6 +89,7 @@
"appearance_subtitle": "Thema, Datum- & Zeitformat",
"theme": "Thema",
"dark_mode": "Dunkler Modus",
"follow_system_theme": "System Hell/Dunkel-Modus folgen",
"on": "An",
"off": "Aus",
"pure_black_dark_mode": "Rein schwarzer Dunkler Modus",
@ -375,6 +376,7 @@
"save": "Speichern",
"picture_saved": "Bild gespeichert",
"cover_updated": "Cover aktualisiert",
"include_subtitles": "Mit Untertiteln speichern",
"blend_mode_default": "Standard",
"blend_mode_multiply": "Multiplizieren",
"blend_mode_screen": "Bildschirm",

View file

@ -89,6 +89,7 @@
"appearance_subtitle": "Theme, date & time format",
"theme": "Theme",
"dark_mode": "Dark mode",
"follow_system_theme": "Follow system theme",
"on": "On",
"off": "Off",
"pure_black_dark_mode": "Pure black dark mode",
@ -379,6 +380,7 @@
"save": "Save",
"picture_saved": "Picture saved",
"cover_updated": "Cover updated",
"include_subtitles": "Include subtitles",
"blend_mode_default": "Default",
"blend_mode_multiply": "Multiply",
"blend_mode_screen": "Screen",

View file

@ -6,6 +6,7 @@ import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
@ -100,6 +101,15 @@ class _MyAppState extends ConsumerState<MyApp> {
}
});
super.initState();
var dispatcher = SchedulerBinding.instance.platformDispatcher;
dispatcher.onPlatformBrightnessChanged = () {
var brightness = dispatcher.platformBrightness;
if (brightness == Brightness.light) {
ref.read(themeModeStateProvider.notifier).setLightTheme();
} else {
ref.read(themeModeStateProvider.notifier).setDarkTheme();
}
};
}
@override

View file

@ -51,6 +51,8 @@ class Settings {
bool? themeIsDark;
bool? followSystemTheme;
bool? incognitoMode;
List<ChapterPageurls>? chapterPageUrlsList;
@ -254,6 +256,7 @@ class Settings {
this.relativeTimesTamps = 2,
this.flexSchemeColorIndex = 2,
this.themeIsDark = false,
this.followSystemTheme = false,
this.incognitoMode = false,
this.chapterPageUrlsList,
this.showPagesNumber = true,
@ -472,6 +475,7 @@ class Settings {
.toList();
}
themeIsDark = json['themeIsDark'];
followSystemTheme = json['followSystemTheme'];
userAgent = json['userAgent'];
backupFrequency = json['backupFrequency'];
backupListOptions = json['backupListOptions']?.cast<int>();
@ -637,6 +641,7 @@ class Settings {
'sortLibraryAnime': sortLibraryAnime?.toJson(),
'sortLibraryManga': sortLibraryManga?.toJson(),
'themeIsDark': themeIsDark,
'followSystemTheme': followSystemTheme,
'userAgent': userAgent,
'backupFrequency': backupFrequency,
'backupListOptions': backupListOptions,

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as riv;
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/video.dart' as vid;
import 'package:mangayomi/modules/anime/providers/anime_player_controller_provider.dart';
import 'package:mangayomi/modules/anime/widgets/aniskip_countdown_btn.dart';
@ -19,9 +21,11 @@ import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
import 'package:mangayomi/modules/anime/widgets/subtitle_setting_widget.dart';
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/progress_center.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/aniskip.dart';
import 'package:mangayomi/services/get_video_list.dart';
import 'package:mangayomi/services/torrent_server.dart';
@ -30,8 +34,11 @@ import 'package:mangayomi/utils/language.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:share_plus/share_plus.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
bool _isDesktop = Platform.isMacOS || Platform.isLinux || Platform.isWindows;
@ -209,6 +216,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
bool _hasOpeningSkip = false;
bool _hasEndingSkip = false;
bool _initSubtitleAndAudio = true;
bool _includeSubtitles = false;
late StreamSubscription<Duration> _currentPositionSub = _player
.stream
@ -1174,6 +1182,16 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
}
},
),
btnToShowShareScreenshot(
widget.episode,
onChanged: (v) {
if (v) {
_player.play();
} else {
_player.pause();
}
},
),
// IconButton(
// onPressed: () {
// showDialog(
@ -1373,6 +1391,205 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
);
}
Widget btnToShowShareScreenshot(
Chapter episode, {
void Function(bool)? onChanged,
}) {
return IconButton(
onPressed: () async {
onChanged?.call(false);
Widget button(String label, IconData icon, Function() onPressed) =>
Expanded(
child: Padding(
padding: const EdgeInsets.all(15),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
elevation: 0,
shadowColor: Colors.transparent,
),
onPressed: onPressed,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(4),
child: Icon(icon),
),
Text(label),
],
),
),
),
);
final name =
"${episode.manga.value!.name} ${episode.name} - ${_currentPosition.value.toString()}"
.replaceAll(RegExp(r'[^a-zA-Z0-9 .()\-\s]'), '_');
await showModalBottomSheet(
context: context,
constraints: BoxConstraints(maxWidth: context.width(1)),
builder: (context) {
return SuperListView(
shrinkWrap: true,
children: [
Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
color: context.themeData.scaffoldBackgroundColor,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 7,
width: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: context.secondaryColor.withValues(
alpha: 0.4,
),
),
),
),
Row(
children: [
button(
context.l10n.set_as_cover,
Icons.image_outlined,
() async {
final imageBytes = await _player.screenshot(
format: "image/png",
includeLibassSubtitles: _includeSubtitles,
);
if (context.mounted) {
final res = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text(
context.l10n.use_this_as_cover_art,
),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(context.l10n.cancel),
),
const SizedBox(width: 15),
TextButton(
onPressed: () {
final manga =
episode.manga.value!;
isar.writeTxnSync(() {
isar.mangas.putSync(
manga
..customCoverImage =
imageBytes,
);
ref
.read(
synchingProvider(
syncId: 1,
).notifier,
)
.addChangedPart(
ActionType.updateItem,
manga.id,
manga.toJson(),
false,
);
});
if (context.mounted) {
Navigator.pop(context, "ok");
}
},
child: Text(context.l10n.ok),
),
],
),
],
);
},
);
if (res != null &&
res == "ok" &&
context.mounted) {
Navigator.pop(context);
botToast(
context.l10n.cover_updated,
second: 3,
);
}
}
},
),
button(
context.l10n.share,
Icons.share_outlined,
() async {
final imageBytes = await _player.screenshot(
format: "image/png",
includeLibassSubtitles: _includeSubtitles,
);
await Share.shareXFiles([
XFile.fromData(
imageBytes!,
name: name,
mimeType: 'image/png',
),
]);
},
),
button(
context.l10n.save,
Icons.save_outlined,
() async {
final imageBytes = await _player.screenshot(
format: "image/png",
includeLibassSubtitles: _includeSubtitles,
);
final dir =
await StorageProvider().getGalleryDirectory();
final file = File(p.join(dir!.path, "$name.png"));
file.writeAsBytesSync(imageBytes!);
if (context.mounted) {
botToast(context.l10n.picture_saved, second: 3);
}
},
),
],
),
SwitchListTile(
onChanged: (value) {
setState(() {
_includeSubtitles = value;
});
},
title: Text(context.l10n.include_subtitles),
value: _includeSubtitles,
),
],
),
),
],
);
},
);
onChanged?.call(true);
},
icon: Icon(Icons.adaptive.share),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(body: _videoPlayer(context));

View file

@ -6,7 +6,7 @@ part of 'download_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$downloadChapterHash() => r'08a7196ae7da5d980629ef80d04ab9b251006eaf';
String _$downloadChapterHash() => r'bf43fddf83fce382ff794c688288153477f9a3aa';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -452,7 +452,7 @@ class _RestoreKotatsuBackupProviderElement
}
String _$restoreTachiBkBackupHash() =>
r'8f45e43afa63618ee9b7c4bdd5cae50a2dc30b1a';
r'76021dbcf0d576b50379f19f17e6a3ee8434942c';
/// See also [restoreTachiBkBackup].
@ProviderFor(restoreTachiBkBackup)

View file

@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/appearance/widgets/follow_system_theme_button.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/date.dart';
@ -71,6 +72,7 @@ class AppearanceScreen extends ConsumerWidget {
),
),
const DarkModeButton(),
const FollowSystemThemeButton(),
const ThemeSelector(),
if (isDarkTheme)
Padding(

View file

@ -1,3 +1,6 @@
import 'dart:ui';
import 'package:flutter/widgets.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/flex_scheme_color_state_provider.dart';
@ -39,3 +42,27 @@ class ThemeModeState extends _$ThemeModeState {
);
}
}
@riverpod
class FollowSystemThemeState extends _$FollowSystemThemeState {
@override
bool build() {
return isar.settings.getSync(227)!.followSystemTheme ?? false;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
if (value) {
if (WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.light) {
ref.read(themeModeStateProvider.notifier).setLightTheme();
} else {
ref.read(themeModeStateProvider.notifier).setDarkTheme();
}
}
isar.writeTxnSync(
() => isar.settings.putSync(settings!..followSystemTheme = value),
);
}
}

View file

@ -22,5 +22,22 @@ final themeModeStateProvider =
);
typedef _$ThemeModeState = AutoDisposeNotifier<bool>;
String _$followSystemThemeStateHash() =>
r'0c65e1471807dddb2f536d336d1f44b29e9e3a5a';
/// See also [FollowSystemThemeState].
@ProviderFor(FollowSystemThemeState)
final followSystemThemeStateProvider =
AutoDisposeNotifierProvider<FollowSystemThemeState, bool>.internal(
FollowSystemThemeState.new,
name: r'followSystemThemeStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$followSystemThemeStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$FollowSystemThemeState = AutoDisposeNotifier<bool>;
// 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

View file

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart';
class FollowSystemThemeButton extends ConsumerStatefulWidget {
const FollowSystemThemeButton({super.key});
@override
ConsumerState<FollowSystemThemeButton> createState() => _FollowSystemThemeButtonState();
}
class _FollowSystemThemeButtonState extends ConsumerState<FollowSystemThemeButton> {
@override
Widget build(BuildContext context) {
bool isFollow = ref.watch(followSystemThemeStateProvider);
final l10n = l10nLocalizations(context);
return SwitchListTile(
onChanged: (value) {
ref.read(followSystemThemeStateProvider.notifier).set(value);
},
title: Text(l10n!.follow_system_theme),
subtitle: Text(
!isFollow ? l10n.off : l10n.on,
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
value: isFollow,
);
}
}