From 3ad3876474b349b478f4dc7579e6b0f100720488 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:31:36 +0200 Subject: [PATCH] feat: auto close login browser --- lib/screens/auth_screen.dart | 38 +++++++++++++++++++++++++++-- lib/services/plex_auth_service.dart | 8 +++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index c0586102..adc2053c 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -15,6 +15,7 @@ class _AuthScreenState extends State { bool _isAuthenticating = false; String? _errorMessage; late PlexAuthService _authService; + bool _shouldCancelPolling = false; @override void initState() { @@ -30,6 +31,7 @@ class _AuthScreenState extends State { setState(() { _isAuthenticating = true; _errorMessage = null; + _shouldCancelPolling = false; }); try { @@ -49,8 +51,16 @@ class _AuthScreenState extends State { throw Exception('Could not launch auth URL'); } - // Poll for authentication - final token = await _authService.pollPinUntilClaimed(pinId); + // Poll for authentication with cancellation support + final token = await _authService.pollPinUntilClaimed( + pinId, + shouldCancel: () => _shouldCancelPolling, + ); + + // If polling was cancelled, don't show error + if (_shouldCancelPolling) { + return; + } if (token == null) { setState(() { @@ -60,6 +70,13 @@ class _AuthScreenState extends State { return; } + // Auto-close the in-app browser on mobile (no-op on desktop) + try { + await closeInAppWebView(); + } catch (e) { + // Ignore errors - browser might already be closed or on desktop + } + // Store the token final storage = await StorageService.getInstance(); await storage.savePlexToken(token); @@ -84,6 +101,15 @@ class _AuthScreenState extends State { } } + void _retryAuthentication() { + setState(() { + _shouldCancelPolling = true; + _isAuthenticating = false; + }); + // Start new authentication after a brief delay to ensure cleanup + Future.delayed(const Duration(milliseconds: 100), _startAuthentication); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -117,6 +143,14 @@ class _AuthScreenState extends State { textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), + const SizedBox(height: 24), + OutlinedButton( + onPressed: _retryAuthentication, + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), + ), + child: const Text('Retry'), + ), ] else ...[ ElevatedButton( onPressed: _startAuthentication, diff --git a/lib/services/plex_auth_service.dart b/lib/services/plex_auth_service.dart index ca3f65f8..3c45a0a8 100644 --- a/lib/services/plex_auth_service.dart +++ b/lib/services/plex_auth_service.dart @@ -109,11 +109,17 @@ class PlexAuthService { /// Poll the PIN until it's claimed or timeout Future pollPinUntilClaimed( int pinId, { - Duration timeout = const Duration(minutes: 5), + Duration timeout = const Duration(minutes: 2), + bool Function()? shouldCancel, }) async { final endTime = DateTime.now().add(timeout); while (DateTime.now().isBefore(endTime)) { + // Check if polling should be cancelled + if (shouldCancel != null && shouldCancel()) { + return null; + } + final token = await checkPin(pinId); if (token != null) { return token;