Merge pull request #526 from Schnitzel5/feature/discord-rpc

added discord rpc
This commit is contained in:
Moustapha Kodjo Amadou 2025-07-21 09:33:55 +01:00 committed by GitHub
commit 204c4f698c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1500 additions and 354 deletions

View file

@ -52,7 +52,7 @@ jobs:
uses: seanmiddleditch/gha-setup-ninja@master
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: Setup Android keystore
run: |
@ -105,7 +105,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: flutter pub get
run: flutter pub get
@ -125,7 +125,7 @@ jobs:
env:
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
flutter build ios --release --no-codesign
flutter build ios --release --no-codesign --verbose
mkdir -p build/ios/iphoneos/Payload
ln -s ../Runner.app build/ios/iphoneos/Payload/Runner.app
./zsign -f -k ./certificate.p12 -p "$P12_PASSWORD" -m ./profile.mobileprovision ./build/ios/iphoneos/Payload/Runner.app
@ -161,7 +161,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: flutter pub get
run: flutter pub get
@ -171,7 +171,7 @@ jobs:
cd macos
pod update
cd ..
flutter build macos --release
flutter build macos --release --verbose
brew install create-dmg
create-dmg --volname Mangayomi-${{ github.ref_name }}-macos --window-pos 200 120 --window-size 800 450 --icon-size 100 --app-drop-link 600 185 Mangayomi-${{ github.ref_name }}-macos.dmg build/macos/Build/Products/Release/Mangayomi.app
@ -204,7 +204,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: flutter pub get
run: flutter pub get
@ -271,13 +271,13 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: flutter pub get
run: flutter pub get
- name: build linux
run: flutter build linux --release
run: flutter build linux --release --verbose
- name: Zip
uses: thedoctor0/zip-release@master
@ -402,7 +402,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen' --locked
run: cargo install 'flutter_rust_bridge_codegen'
- name: flutter pub get
run: flutter pub get

View file

@ -463,5 +463,10 @@
"track_library_not_logged": "Login to the corresponding tracker to use this feature!",
"track_library_switch": "Switch to another tracker",
"go_back": "Go back",
"merge_library_nav_mobile": "Merge library navigation on mobile"
}
"merge_library_nav_mobile": "Merge library navigation on mobile",
"enable_discord_rpc": "Enable Discord RPC",
"hide_discord_rpc_incognito": "Hide Discord RPC while in Incognito",
"rpc_show_reading_watching_progress": "Show current chapter in Discord (requires a restart)",
"rpc_show_title": "Show current title in Discord",
"rpc_show_cover_image": "Show current cover image in Discord"
}

View file

@ -2854,6 +2854,36 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Merge library navigation on mobile'**
String get merge_library_nav_mobile;
/// No description provided for @enable_discord_rpc.
///
/// In en, this message translates to:
/// **'Enable Discord RPC'**
String get enable_discord_rpc;
/// No description provided for @hide_discord_rpc_incognito.
///
/// In en, this message translates to:
/// **'Hide Discord RPC while in Incognito'**
String get hide_discord_rpc_incognito;
/// No description provided for @rpc_show_reading_watching_progress.
///
/// In en, this message translates to:
/// **'Show current chapter in Discord (requires a restart)'**
String get rpc_show_reading_watching_progress;
/// No description provided for @rpc_show_title.
///
/// In en, this message translates to:
/// **'Show current title in Discord'**
String get rpc_show_title;
/// No description provided for @rpc_show_cover_image.
///
/// In en, this message translates to:
/// **'Show current cover image in Discord'**
String get rpc_show_cover_image;
}
class _AppLocalizationsDelegate

View file

@ -1468,4 +1468,21 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1481,4 +1481,21 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1469,4 +1469,21 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1486,6 +1486,23 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).

View file

@ -1487,4 +1487,21 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1475,4 +1475,21 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1484,4 +1484,21 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1483,6 +1483,23 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}
/// The translations for Portuguese, as used in Brazil (`pt_BR`).

View file

@ -1485,4 +1485,21 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1469,4 +1469,21 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1475,4 +1475,21 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -1440,4 +1440,21 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get merge_library_nav_mobile => 'Merge library navigation on mobile';
@override
String get enable_discord_rpc => 'Enable Discord RPC';
@override
String get hide_discord_rpc_incognito =>
'Hide Discord RPC while in Incognito';
@override
String get rpc_show_reading_watching_progress =>
'Show current chapter in Discord (requires a restart)';
@override
String get rpc_show_title => 'Show current title in Discord';
@override
String get rpc_show_cover_image => 'Show current cover image in Discord';
}

View file

@ -26,6 +26,7 @@ import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_
import 'package:mangayomi/l10n/generated/app_localizations.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/src/rust/frb_generated.dart';
import 'package:mangayomi/utils/discord_rpc.dart';
import 'package:mangayomi/utils/url_protocol/api.dart';
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart';
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
@ -35,6 +36,7 @@ import 'package:window_manager/window_manager.dart';
import 'package:path/path.dart' as p;
late Isar isar;
late DiscordRPC discordRpc;
WebViewEnvironment? webViewEnvironment;
void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
@ -61,6 +63,8 @@ void main(List<String> args) async {
isar = await StorageProvider().initDB(null, inspector: kDebugMode);
await Hive.initFlutter();
Hive.registerAdapter(TrackSearchAdapter());
discordRpc = DiscordRPC(applicationId: "1395040506677039157");
await discordRpc.initialize();
runApp(const ProviderScope(child: MyApp()));
unawaited(_postLaunchInit()); // Defer non-essential async operations
@ -129,6 +133,7 @@ class _MyAppState extends ConsumerState<MyApp> {
@override
void dispose() {
_linkSubscription?.cancel();
discordRpc.destroy();
super.dispose();
}

View file

@ -246,6 +246,16 @@ class Settings {
bool? mergeLibraryNavMobile;
bool? enableDiscordRpc;
bool? hideDiscordRpcInIncognito;
bool? rpcShowReadingWatchingProgress;
bool? rpcShowTitle;
bool? rpcShowCoverImage;
Settings({
this.id = 227,
this.displayType = DisplayType.compactGrid,
@ -355,6 +365,11 @@ class Settings {
this.novelExtensionsRepo,
this.lastTrackerLibraryLocation,
this.mergeLibraryNavMobile = false,
this.enableDiscordRpc = true,
this.hideDiscordRpcInIncognito = true,
this.rpcShowReadingWatchingProgress = true,
this.rpcShowTitle = true,
this.rpcShowCoverImage = true,
});
Settings.fromJson(Map<String, dynamic> json) {
@ -570,6 +585,11 @@ class Settings {
}
lastTrackerLibraryLocation = json['lastTrackerLibraryLocation'];
mergeLibraryNavMobile = json['mergeLibraryNavMobile'];
enableDiscordRpc = json['enableDiscordRpc'];
hideDiscordRpcInIncognito = json['hideDiscordRpcInIncognito'];
rpcShowReadingWatchingProgress = json['rpcShowReadingWatchingProgress'];
rpcShowTitle = json['rpcShowTitle'];
rpcShowCoverImage = json['rpcShowCoverImage'];
}
Map<String, dynamic> toJson() => {
@ -702,6 +722,11 @@ class Settings {
'novelExtensionsRepo': novelExtensionsRepo?.map((e) => e.toJson()).toList(),
'lastTrackerLibraryLocation': lastTrackerLibraryLocation,
'mergeLibraryNavMobile': mergeLibraryNavMobile,
'enableDiscordRpc': enableDiscordRpc,
'hideDiscordRpcInIncognito': hideDiscordRpcInIncognito,
'rpcShowReadingWatchingProgress': rpcShowReadingWatchingProgress,
'rpcShowTitle': rpcShowTitle,
'rpcShowCoverImage': rpcShowCoverImage,
};
}

File diff suppressed because it is too large Load diff

View file

@ -213,6 +213,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
bool _hasEndingSkip = false;
bool _initSubtitleAndAudio = true;
bool _includeSubtitles = false;
int lastRpcTimestampUpdate = DateTime.now().millisecondsSinceEpoch;
late final StreamSubscription<Duration> _currentPositionSub;
@ -221,6 +222,10 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
.duration
.listen((duration) {
_currentTotalDuration.value = duration;
discordRpc.startChapterTimestamp(
_currentPosition.value.inMilliseconds,
duration.inMilliseconds,
);
});
bool get hasNextEpisode => _streamController.getEpisodeIndex().$1 != 0;
@ -305,6 +310,14 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
if (_skipPhase.value != newPhase) _skipPhase.value = newPhase;
}
void _updateRpcTimestamp() {
final now = DateTime.now().millisecondsSinceEpoch;
if (lastRpcTimestampUpdate + 10000 < now) {
discordRpc.updateChapterTimestamp(_currentPosition.value.inMilliseconds);
lastRpcTimestampUpdate = now;
}
}
@override
void initState() {
super.initState();
@ -338,6 +351,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
_setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider));
if (ref.read(enableAniSkipStateProvider)) _initAniSkip();
});
discordRpc.showChapterDetails(ref, widget.episode);
_currentPosition.addListener(_updateRpcTimestamp);
WidgetsBinding.instance.addObserver(this);
}
@ -404,6 +419,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
@override
void dispose() {
_currentPosition.removeListener(_updateRpcTimestamp);
WidgetsBinding.instance.removeObserver(this);
_setCurrentPosition(true);
_player.dispose();
@ -422,6 +438,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
_setLandscapeMode(false);
}
_skipPhase.dispose();
discordRpc.showIdleText();
discordRpc.showOriginalTimestamp();
_currentPosition.dispose();
super.dispose();
}
@ -1113,24 +1131,6 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
),
),
),
Flexible(
fit: FlexFit.tight,
child: ValueListenableBuilder<bool>(
valueListenable: _isDoubleSpeed,
builder: (context, snapshot, _) {
return Text.rich(
TextSpan(
children: snapshot
? [
WidgetSpan(child: Icon(Icons.fast_forward)),
TextSpan(text: " 2X"),
]
: [],
),
);
},
),
),
Row(
children: [
btnToShowChapterListDialog(
@ -1272,6 +1272,46 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
height: context.height(1),
resumeUponEnteringForegroundMode: true,
),
Stack(
alignment: AlignmentDirectional.center,
children: [
Positioned(
top: 30,
child: ValueListenableBuilder<bool>(
valueListenable: _isDoubleSpeed,
builder: (context, snapshot, _) {
return Text.rich(
textAlign: TextAlign.center,
TextSpan(
style: TextStyle(
background: Paint()
..color = Theme.of(context).scaffoldBackgroundColor
..strokeWidth = 30.0
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke,
),
children: snapshot
? [
TextSpan(
text: " 2X ",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(Icons.fast_forward),
),
]
: [],
),
);
},
),
),
],
),
if (enableAniSkip && (_hasOpeningSkip || _hasEndingSkip))
Positioned(
right: 0,

View file

@ -101,6 +101,7 @@ class CustomSeekBarState extends State<CustomSeekBar> {
milliseconds: value.toInt() - position.inMilliseconds,
),
);
widget.player.seek(Duration(milliseconds: value.toInt()));
if (mounted) {
setState(() {
tempPosition = Duration(milliseconds: value.toInt());

View file

@ -96,6 +96,8 @@ class _MainScreenState extends ConsumerState<MainScreen> {
_initializeProviders();
}
});
discordRpc.connect(ref);
}
void _initializeTimers() {
@ -158,6 +160,7 @@ class _MainScreenState extends ConsumerState<MainScreen> {
void dispose() {
_backupTimer?.cancel();
_syncTimer?.cancel();
discordRpc.disconnect();
super.dispose();
}

View file

@ -163,6 +163,7 @@ class _MangaChapterPageGalleryState
overlays: SystemUiOverlay.values,
);
}
discordRpc.showIdleText();
super.dispose();
}
@ -221,6 +222,7 @@ class _MangaChapterPageGalleryState
_animation.addListener(() => _photoViewController.scale = _animation.value);
_itemPositionsListener.itemPositions.addListener(_readProgressListener);
_initCurrentIndex();
discordRpc.showChapterDetails(ref, chapter);
WidgetsBinding.instance.addObserver(this);
}

View file

@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
import 'package:mangayomi/l10n/generated/app_localizations.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class GeneralScreen extends ConsumerWidget {
const GeneralScreen({super.key});
@ -12,70 +10,63 @@ class GeneralScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = l10nLocalizations(context);
final l10nLocale = ref.watch(l10nLocaleStateProvider);
final enableDiscordRpc = ref.watch(enableDiscordRpcStateProvider);
final hideDiscordRpcInIncognito = ref.watch(
hideDiscordRpcInIncognitoStateProvider,
);
final rpcShowReadingWatchingProgress = ref.watch(
rpcShowReadingWatchingProgressStateProvider,
);
final rpcShowTitleState = ref.watch(rpcShowTitleStateProvider);
final rpcShowCoverImage = ref.watch(rpcShowCoverImageStateProvider);
return Scaffold(
appBar: AppBar(title: Text(l10n!.general)),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
onTap: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(l10n.app_language),
content: SizedBox(
width: context.width(0.8),
child: SuperListView.builder(
shrinkWrap: true,
itemCount: AppLocalizations.supportedLocales.length,
itemBuilder: (context, index) {
final locale =
AppLocalizations.supportedLocales[index];
return RadioListTile(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: locale,
groupValue: l10nLocale,
onChanged: (value) {
ref
.read(l10nLocaleStateProvider.notifier)
.setLocale(locale);
Navigator.pop(context);
},
title: Text(
completeLanguageName(locale.toLanguageTag()),
),
);
},
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () async {
Navigator.pop(context);
},
child: Text(
l10n.cancel,
style: TextStyle(color: context.primaryColor),
),
),
],
),
],
);
},
);
SwitchListTile(
value: enableDiscordRpc,
title: Text(l10n.enable_discord_rpc),
onChanged: (value) {
ref.read(enableDiscordRpcStateProvider.notifier).set(value);
if (value) {
discordRpc.connect(ref);
} else {
discordRpc.disconnect();
}
},
),
SwitchListTile(
value: hideDiscordRpcInIncognito,
title: Text(l10n.hide_discord_rpc_incognito),
onChanged: (value) {
ref
.read(hideDiscordRpcInIncognitoStateProvider.notifier)
.set(value);
},
),
SwitchListTile(
value: rpcShowReadingWatchingProgress,
title: Text(l10n.rpc_show_reading_watching_progress),
onChanged: (value) {
ref
.read(rpcShowReadingWatchingProgressStateProvider.notifier)
.set(value);
},
),
SwitchListTile(
value: rpcShowTitleState,
title: Text(l10n.rpc_show_title),
onChanged: (value) {
ref.read(rpcShowTitleStateProvider.notifier).set(value);
},
),
SwitchListTile(
value: rpcShowCoverImage,
title: Text(l10n.rpc_show_cover_image),
onChanged: (value) {
ref.read(rpcShowCoverImageStateProvider.notifier).set(value);
},
title: Text(l10n.app_language),
subtitle: Text(
completeLanguageName(l10nLocale.toLanguageTag()),
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
],
),

View file

@ -0,0 +1,87 @@
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'general_state_provider.g.dart';
@riverpod
class EnableDiscordRpcState extends _$EnableDiscordRpcState {
@override
bool build() {
return isar.settings.getSync(227)!.enableDiscordRpc ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..enableDiscordRpc = value),
);
}
}
@riverpod
class HideDiscordRpcInIncognitoState extends _$HideDiscordRpcInIncognitoState {
@override
bool build() {
return isar.settings.getSync(227)!.hideDiscordRpcInIncognito ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..hideDiscordRpcInIncognito = value),
);
}
}
@riverpod
class RpcShowReadingWatchingProgressState
extends _$RpcShowReadingWatchingProgressState {
@override
bool build() {
return isar.settings.getSync(227)!.rpcShowReadingWatchingProgress ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings!..rpcShowReadingWatchingProgress = value,
),
);
}
}
@riverpod
class RpcShowTitleState extends _$RpcShowTitleState {
@override
bool build() {
return isar.settings.getSync(227)!.rpcShowTitle ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..rpcShowTitle = value),
);
}
}
@riverpod
class RpcShowCoverImageState extends _$RpcShowCoverImageState {
@override
bool build() {
return isar.settings.getSync(227)!.rpcShowCoverImage ?? true;
}
void set(bool value) {
final settings = isar.settings.getSync(227);
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(settings!..rpcShowCoverImage = value),
);
}
}

View file

@ -0,0 +1,94 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'general_state_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$enableDiscordRpcStateHash() =>
r'bc134787a33f362087befd5de85692812659faa0';
/// See also [EnableDiscordRpcState].
@ProviderFor(EnableDiscordRpcState)
final enableDiscordRpcStateProvider =
AutoDisposeNotifierProvider<EnableDiscordRpcState, bool>.internal(
EnableDiscordRpcState.new,
name: r'enableDiscordRpcStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$enableDiscordRpcStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$EnableDiscordRpcState = AutoDisposeNotifier<bool>;
String _$hideDiscordRpcInIncognitoStateHash() =>
r'8e45104c34b1255b7a3d4a6f182bf43be9e2b93d';
/// See also [HideDiscordRpcInIncognitoState].
@ProviderFor(HideDiscordRpcInIncognitoState)
final hideDiscordRpcInIncognitoStateProvider =
AutoDisposeNotifierProvider<HideDiscordRpcInIncognitoState, bool>.internal(
HideDiscordRpcInIncognitoState.new,
name: r'hideDiscordRpcInIncognitoStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$hideDiscordRpcInIncognitoStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HideDiscordRpcInIncognitoState = AutoDisposeNotifier<bool>;
String _$rpcShowReadingWatchingProgressStateHash() =>
r'b4000c10234ce9070b44b535e9d10d16e6b7c0ec';
/// See also [RpcShowReadingWatchingProgressState].
@ProviderFor(RpcShowReadingWatchingProgressState)
final rpcShowReadingWatchingProgressStateProvider = AutoDisposeNotifierProvider<
RpcShowReadingWatchingProgressState, bool>.internal(
RpcShowReadingWatchingProgressState.new,
name: r'rpcShowReadingWatchingProgressStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$rpcShowReadingWatchingProgressStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$RpcShowReadingWatchingProgressState = AutoDisposeNotifier<bool>;
String _$rpcShowTitleStateHash() => r'9d7005af8968f61872fcc37979f5c54b745ffe65';
/// See also [RpcShowTitleState].
@ProviderFor(RpcShowTitleState)
final rpcShowTitleStateProvider =
AutoDisposeNotifierProvider<RpcShowTitleState, bool>.internal(
RpcShowTitleState.new,
name: r'rpcShowTitleStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$rpcShowTitleStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$RpcShowTitleState = AutoDisposeNotifier<bool>;
String _$rpcShowCoverImageStateHash() =>
r'6ce1c61be44c0a6315fbb51a45ac6e47c3e49b33';
/// See also [RpcShowCoverImageState].
@ProviderFor(RpcShowCoverImageState)
final rpcShowCoverImageStateProvider =
AutoDisposeNotifierProvider<RpcShowCoverImageState, bool>.internal(
RpcShowCoverImageState.new,
name: r'rpcShowCoverImageStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$rpcShowCoverImageStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$RpcShowCoverImageState = 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

@ -14,6 +14,11 @@ class SettingsScreen extends StatelessWidget {
body: SingleChildScrollView(
child: Column(
children: [
ListTileWidget(
title: l10n.general,
icon: Icons.settings,
onTap: () => context.push('/general'),
),
ListTileWidget(
title: l10n.appearance,
subtitle: l10n.appearance_subtitle,

View file

@ -1,7 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart';
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -12,12 +14,17 @@ class IncognitoModeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final l10n = l10nLocalizations(context);
final incognitoMode = ref.watch(incognitoModeStateProvider);
final hideDiscordRpcInIncognito = ref.watch(
hideDiscordRpcInIncognitoStateProvider,
);
return ListTileWidget(
onTap: () {
if (incognitoMode == true) {
ref.read(incognitoModeStateProvider.notifier).setIncognitoMode(false);
if (hideDiscordRpcInIncognito) discordRpc.connect(ref);
} else {
ref.read(incognitoModeStateProvider.notifier).setIncognitoMode(true);
if (hideDiscordRpcInIncognito) discordRpc.disconnect();
}
},
icon: CupertinoIcons.eyeglasses,
@ -27,6 +34,13 @@ class IncognitoModeWidget extends ConsumerWidget {
value: incognitoMode,
onChanged: (value) {
ref.read(incognitoModeStateProvider.notifier).setIncognitoMode(value);
if (hideDiscordRpcInIncognito) {
if (value) {
discordRpc.disconnect();
} else {
discordRpc.connect(ref);
}
}
},
),
);

View file

@ -95,6 +95,7 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
overlays: SystemUiOverlay.values,
);
}
discordRpc.showIdleText();
super.dispose();
}
@ -113,6 +114,7 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
fontSize = initFontSize;
});
});
discordRpc.showChapterDetails(ref, chapter);
}
late bool _isBookmarked = _readerController.getChapterBookmarked();

248
lib/utils/discord_rpc.dart Normal file
View file

@ -0,0 +1,248 @@
import 'package:flutter_discord_rpc_fork/flutter_discord_rpc.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/modules/more/providers/incognito_mode_state_provider.dart';
import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart';
class DiscordRPC {
/// Id of the Discord Application
final String applicationId;
/// Whether it should reconnect to the discord client
final bool autoRetry;
/// Seconds between each attempt to reconnect
final int retryDelayInSeconds;
/// Start timestamp in millis
final int startAt = DateTime.timestamp().millisecondsSinceEpoch;
/// Start timestamp in millis for the current chapter/episode
int chapterStartAt = 0;
/// End timestamp in millis for the current chapter/episode
int chapterEndAt = 0;
/// Temp var
late bool rpcShowReadingWatchingProgress;
/// Instance of the current RPC activity
final RpcActivity activity = RpcActivity(
assets: const RPCAssets(largeImage: "app-icon", largeText: "Mangayomi"),
buttons: [
const RPCButton(
label: "Get Mangayomi",
url: "https://github.com/kodjodevf/mangayomi",
),
const RPCButton(
label: "Join us",
url: "https://discord.com/invite/EjfBuYahsP",
),
],
details: "Idle",
state: "-----",
timestamps: RPCTimestamps(
start: DateTime.timestamp().millisecondsSinceEpoch,
),
activityType: ActivityType.watching,
);
DiscordRPC({
required this.applicationId,
this.autoRetry = true,
this.retryDelayInSeconds = 10,
});
Future<void> initialize() async {
await FlutterDiscordRPC.initialize(applicationId);
}
Future<void> connect(WidgetRef ref) async {
final enableDiscordRpc = ref.read(enableDiscordRpcStateProvider);
final incognitoMode = ref.read(incognitoModeStateProvider);
final hideDiscordRpcInIncognito = ref.read(
hideDiscordRpcInIncognitoStateProvider,
);
rpcShowReadingWatchingProgress = ref.read(
rpcShowReadingWatchingProgressStateProvider,
);
if (enableDiscordRpc && (!hideDiscordRpcInIncognito || !incognitoMode)) {
await FlutterDiscordRPC.instance.connect(
autoRetry: autoRetry,
retryDelay: Duration(seconds: retryDelayInSeconds),
);
await Future.delayed(Duration(seconds: 3));
showIdleText();
}
}
Future<void> showIdleText() async {
await updateActivity(
details: "Idle",
state: "-----",
assets: const RPCAssets(largeImage: "app-icon", largeText: "Mangayomi"),
);
}
Future<void> showChapterDetails(WidgetRef ref, Chapter chapter) async {
final status = chapter.manga.value!.itemType == ItemType.anime
? "Watching"
: "Reading";
final title = chapter.manga.value!.name;
final chapterTitle = chapter.name;
final rpcShowTitle = ref.read(rpcShowTitleStateProvider);
final rpcShowCoverImage = ref.read(rpcShowCoverImageStateProvider);
await updateActivity(
details: rpcShowTitle ? "$status $title" : "Idle",
state: rpcShowTitle && rpcShowReadingWatchingProgress
? chapterTitle
: "-----",
assets: rpcShowCoverImage
? RPCAssets(
largeImage: chapter.manga.value!.imageUrl,
largeText: rpcShowTitle ? chapter.manga.value!.name : "-----",
smallImage: "app-icon",
smallText: "Mangayomi",
)
: const RPCAssets(largeImage: "app-icon", largeText: "Mangayomi"),
);
}
Future<void> showLargeImage() async {
await updateActivity(
assets: const RPCAssets(largeImage: "app-icon", largeText: "Mangayomi"),
);
}
Future<void> showSmallImage(String largeImage, String largeText) async {
await updateActivity(
assets: RPCAssets(
largeImage: largeImage,
largeText: largeText,
smallImage: "app-icon",
smallText: "Mangayomi",
),
);
}
Future<void> showOriginalTimestamp() async {
await updateActivity(timestamps: RPCTimestamps(start: startAt));
}
Future<void> startChapterTimestamp(
int offsetInMillis,
int durationInMillis,
) async {
if (!rpcShowReadingWatchingProgress) {
return;
}
chapterStartAt = DateTime.timestamp().millisecondsSinceEpoch;
chapterEndAt =
DateTime.timestamp().millisecondsSinceEpoch + durationInMillis;
await updateActivity(
timestamps: RPCTimestamps(
start: chapterStartAt,
end: chapterEndAt - offsetInMillis,
),
);
}
Future<void> updateChapterTimestamp(int newOffsetInMillis) async {
if (!rpcShowReadingWatchingProgress) {
return;
}
await updateActivity(
timestamps: RPCTimestamps(
start: chapterStartAt,
end: chapterEndAt - newOffsetInMillis,
),
);
}
Future<void> updateActivity({
String? state,
String? details,
RPCTimestamps? timestamps,
RPCParty? party,
RPCAssets? assets,
RPCSecrets? secrets,
List<RPCButton>? buttons,
ActivityType? activityType,
}) async {
if (!FlutterDiscordRPC.instance.isConnected) {
return;
}
if (state != null) {
activity.state = state;
}
if (details != null) {
activity.details = details;
}
if (timestamps != null) {
activity.timestamps = timestamps;
}
if (party != null) {
activity.party = party;
}
if (assets != null) {
activity.assets = assets;
}
if (secrets != null) {
activity.secrets = secrets;
}
if (buttons != null) {
activity.buttons = buttons;
}
if (activityType != null) {
activity.activityType = activityType;
}
await FlutterDiscordRPC.instance.setActivity(
activity: activity.toRPCActivity(),
);
}
Future<void> disconnect() async {
await FlutterDiscordRPC.instance.disconnect();
}
Future<void> destroy() async {
await FlutterDiscordRPC.instance.disconnect();
await FlutterDiscordRPC.instance.dispose();
}
}
class RpcActivity {
String? state;
String? details;
RPCTimestamps? timestamps;
RPCParty? party;
RPCAssets? assets;
RPCSecrets? secrets;
List<RPCButton>? buttons;
ActivityType? activityType;
RpcActivity({
this.state,
this.details,
this.timestamps,
this.party,
this.assets,
this.secrets,
this.buttons,
this.activityType,
});
RPCActivity toRPCActivity() {
return RPCActivity(
state: state,
details: details,
timestamps: timestamps,
party: party,
assets: assets,
secrets: secrets,
buttons: buttons,
activityType: activityType,
);
}
}

View file

@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc_fork
rust_lib_mangayomi
)

View file

@ -549,6 +549,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.4.1"
flutter_discord_rpc_fork:
dependency: "direct main"
description:
name: flutter_discord_rpc_fork
sha256: a32b3d5871af591ec73b65feb2818f3e3d4e05d46b9903c6f117b03865a86282
url: "https://pub.dev"
source: hosted
version: "1.0.4"
flutter_inappwebview:
dependency: "direct main"
description:

View file

@ -94,6 +94,10 @@ dependencies:
d4rt: 0.0.9
hive: ^2.2.3
hive_flutter: ^1.1.0
flutter_discord_rpc_fork:
git:
url: https://github.com/Schnitzel5/flutter-discord-rpc.git
ref: main
dependency_overrides:
ffi: ^2.1.3

View file

@ -21,6 +21,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc_fork
rust_lib_mangayomi
)