import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import '../../i18n/strings.g.dart'; import '../../models/trackers/device_code.dart'; import '../../providers/trackers_provider.dart'; import '../../services/trackers/anilist/anilist_tracker.dart'; import '../../services/trackers/mal/mal_tracker.dart'; import '../../services/trackers/oauth_proxy_client.dart'; import '../../services/trackers/simkl/simkl_tracker.dart'; import '../../services/trackers/tracker_constants.dart'; import '../../services/settings_service.dart'; import '../../utils/dialogs.dart'; import '../../widgets/device_code_dialog.dart'; import '../../widgets/oauth_proxy_dialog.dart'; import '../../widgets/settings_page.dart'; import 'tracker_account_settings_body.dart'; import 'tracker_connect_launcher.dart'; Future startMalConnection(BuildContext context) { final account = context.read(); final name = t.trackers.services.mal; return launchTrackerConnect( context, isBusyOrConnected: account.isConnecting(TrackerService.mal) || account.isMalConnected, serviceName: name, connect: (cb) => account.connectMal(onCodeReady: cb), onCancel: account.cancelConnect, buildDialog: (p, cancel) => OAuthProxyDialog(start: p, serviceName: name, onCancel: cancel), urlFor: (p) => p.url, ); } Future startAnilistConnection(BuildContext context) { final account = context.read(); final name = t.trackers.services.anilist; return launchTrackerConnect( context, isBusyOrConnected: account.isConnecting(TrackerService.anilist) || account.isAnilistConnected, serviceName: name, connect: (cb) => account.connectAnilist(onCodeReady: cb), onCancel: account.cancelConnect, buildDialog: (p, cancel) => OAuthProxyDialog(start: p, serviceName: name, onCancel: cancel), urlFor: (p) => p.url, ); } Future startSimklConnection(BuildContext context) { final account = context.read(); final name = t.trackers.services.simkl; return launchTrackerConnect( context, isBusyOrConnected: account.isConnecting(TrackerService.simkl) || account.isSimklConnected, serviceName: name, connect: (cb) => account.connectSimkl(onCodeReady: cb), onCancel: account.cancelConnect, buildDialog: (p, cancel) => DeviceCodeDialog(code: p, serviceName: name, onCancel: cancel), urlFor: (p) => p.verificationUrlComplete ?? p.verificationUrl, ); } /// Per-service wiring for [TrackerSettingsScreen]. Keeps tracker-specific /// method names out of the shared screen body. class TrackerConfig { final TrackerService service; final String displayName; final bool Function(TrackersProvider) isConnected; final String? Function(TrackersProvider) username; final Pref scrobblePref; final Future Function(bool) onScrobbleChanged; final Future Function(TrackersProvider) disconnect; const TrackerConfig({ required this.service, required this.displayName, required this.isConnected, required this.username, required this.scrobblePref, required this.onScrobbleChanged, required this.disconnect, }); static TrackerConfig mal() => TrackerConfig( service: TrackerService.mal, displayName: t.trackers.services.mal, isConnected: (a) => a.isMalConnected, username: (a) => a.malUsername, scrobblePref: SettingsService.enableMalScrobble, onScrobbleChanged: MalTracker.instance.setEnabled, disconnect: (a) => a.disconnectMal(), ); static TrackerConfig anilist() => TrackerConfig( service: TrackerService.anilist, displayName: t.trackers.services.anilist, isConnected: (a) => a.isAnilistConnected, username: (a) => a.anilistUsername, scrobblePref: SettingsService.enableAnilistScrobble, onScrobbleChanged: AnilistTracker.instance.setEnabled, disconnect: (a) => a.disconnectAnilist(), ); static TrackerConfig simkl() => TrackerConfig( service: TrackerService.simkl, displayName: t.trackers.services.simkl, isConnected: (a) => a.isSimklConnected, username: (a) => a.simklUsername, scrobblePref: SettingsService.enableSimklScrobble, onScrobbleChanged: SimklTracker.instance.setEnabled, disconnect: (a) => a.disconnectSimkl(), ); } /// Shared settings screen for MAL, AniList, and Simkl. Only reachable while /// connected — if the session drops (refresh failure, back-nav race) we pop /// back to the hub. class TrackerSettingsScreen extends StatelessWidget { final TrackerConfig config; const TrackerSettingsScreen({super.key, required this.config}); Future _disconnect(BuildContext context, TrackersProvider account) async { final confirmed = await showConfirmDialog( context, title: t.trackers.disconnectConfirm(service: config.displayName), message: t.trackers.disconnectConfirmBody(service: config.displayName), confirmText: t.common.disconnect, isDestructive: true, ); if (!confirmed) return; await config.disconnect(account); } @override Widget build(BuildContext context) { final title = Text(config.displayName); return Consumer( builder: (context, account, _) { if (!config.isConnected(account)) { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) Navigator.of(context).pop(); }); return SettingsPage.slivers( title: title, slivers: const [SliverFillRemaining(child: SizedBox.shrink())], ); } final username = config.username(account); return TrackerAccountSettingsBody( title: title, accountTitle: username != null ? t.trackers.connectedAs(username: username) : config.displayName, service: config.service, toggles: [ TrackerSettingsToggle( pref: config.scrobblePref, icon: Symbols.auto_timer, title: t.trackers.scrobble, subtitle: t.trackers.scrobbleDescription, onAfterWrite: config.onScrobbleChanged, ), ], onDisconnect: () => _disconnect(context, account), ); }, ); } }