diff --git a/lib/services/authentication_manager.dart b/lib/services/authentication_manager.dart index 81fcf9d..822dff7 100644 --- a/lib/services/authentication_manager.dart +++ b/lib/services/authentication_manager.dart @@ -32,11 +32,13 @@ class AuthenticationManager extends ChangeNotifier { } bool _isAuthenticated = false; + bool _isInitializing = true; String _apiToken = ''; UserInfo? _userInfo; BrandmeisterClient? _client; bool get isAuthenticated => _isAuthenticated; + bool get isInitializing => _isInitializing; String get apiToken => _apiToken; UserInfo? get userInfo => _userInfo; @@ -50,7 +52,6 @@ class AuthenticationManager extends ChangeNotifier { try { _userInfo = await _client!.whoami(); _isAuthenticated = true; - notifyListeners(); } catch (e) { // Token is invalid, clear it await logout(); @@ -58,6 +59,9 @@ class AuthenticationManager extends ChangeNotifier { } } catch (e) { debugPrint('Error loading token: $e'); + } finally { + _isInitializing = false; + notifyListeners(); } } diff --git a/lib/views/auth_view.dart b/lib/views/auth_view.dart new file mode 100644 index 0000000..bcd135d --- /dev/null +++ b/lib/views/auth_view.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../services/authentication_manager.dart'; +import '../services/brandmeister_client.dart'; + +class AuthView extends StatefulWidget { + const AuthView({super.key}); + + @override + State createState() => _AuthViewState(); +} + +class _AuthViewState extends State { + final TextEditingController _tokenController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + bool _isLoading = false; + String? _errorMessage; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + }); + } + + @override + void dispose() { + _tokenController.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + Future _verifyToken() async { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + final authManager = context.read(); + await authManager.verifyAndSaveToken(_tokenController.text); + } on BrandmeisterError catch (e) { + setState(() { + _errorMessage = e.message; + _isLoading = false; + }); + } catch (e) { + setState(() { + _errorMessage = 'Authentication failed: ${e.toString()}'; + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Icon( + Icons.radio, + size: 80, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(height: 24), + Text( + 'Sign In', + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Enter your BrandMeister API token', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 48), + TextField( + controller: _tokenController, + focusNode: _focusNode, + obscureText: true, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'API Token', + hintText: 'Enter your BrandMeister API token', + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.key), + errorText: _errorMessage, + ), + onSubmitted: (_) { + if (_tokenController.text.isNotEmpty && !_isLoading) { + _verifyToken(); + } + }, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _tokenController.text.isEmpty || _isLoading + ? null + : _verifyToken, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Sign In'), + ), + const SizedBox(height: 24), + TextButton.icon( + onPressed: () { + // Open BrandMeister website + }, + icon: const Icon(Icons.open_in_new), + label: const Text('Get API Token from BrandMeister'), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/content_view.dart b/lib/views/content_view.dart index 7064681..0958e31 100644 --- a/lib/views/content_view.dart +++ b/lib/views/content_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/authentication_manager.dart'; import 'welcome_view.dart'; +import 'auth_view.dart'; import 'main_view.dart'; class ContentView extends StatelessWidget { @@ -11,10 +12,16 @@ class ContentView extends StatelessWidget { Widget build(BuildContext context) { final authManager = context.watch(); + // Show splash screen while initializing + if (authManager.isInitializing) { + return const WelcomeView(); + } + + // Show main view if authenticated, otherwise show login if (authManager.isAuthenticated) { return const MainView(); } else { - return const WelcomeView(); + return const AuthView(); } } } diff --git a/lib/views/welcome_view.dart b/lib/views/welcome_view.dart index 280bb12..caeffc4 100644 --- a/lib/views/welcome_view.dart +++ b/lib/views/welcome_view.dart @@ -1,136 +1,37 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/authentication_manager.dart'; -import '../services/brandmeister_client.dart'; -class WelcomeView extends StatefulWidget { +class WelcomeView extends StatelessWidget { const WelcomeView({super.key}); - @override - State createState() => _WelcomeViewState(); -} - -class _WelcomeViewState extends State { - final TextEditingController _tokenController = TextEditingController(); - final FocusNode _focusNode = FocusNode(); - bool _isLoading = false; - String? _errorMessage; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _focusNode.requestFocus(); - }); - } - - @override - void dispose() { - _tokenController.dispose(); - _focusNode.dispose(); - super.dispose(); - } - - Future _verifyToken() async { - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - final authManager = context.read(); - await authManager.verifyAndSaveToken(_tokenController.text); - } on BrandmeisterError catch (e) { - setState(() { - _errorMessage = e.message; - _isLoading = false; - }); - } catch (e) { - setState(() { - _errorMessage = 'Authentication failed: ${e.toString()}'; - _isLoading = false; - }); - } - } - @override Widget build(BuildContext context) { return Scaffold( - body: SafeArea( - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Icon( - Icons.radio, - size: 80, - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(height: 24), - Text( - 'BM Manager', - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Manage your BrandMeister devices', - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.grey[600], - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 48), - TextField( - controller: _tokenController, - focusNode: _focusNode, - obscureText: true, - enabled: !_isLoading, - decoration: InputDecoration( - labelText: 'API Token', - hintText: 'Enter your BrandMeister API token', - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.key), - errorText: _errorMessage, - ), - onSubmitted: (_) { - if (_tokenController.text.isNotEmpty && !_isLoading) { - _verifyToken(); - } - }, - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _tokenController.text.isEmpty || _isLoading - ? null - : _verifyToken, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - ), - child: _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Text('Sign In'), - ), - const SizedBox(height: 24), - TextButton.icon( - onPressed: () { - // Open BrandMeister website - }, - icon: const Icon(Icons.open_in_new), - label: const Text('Get API Token from BrandMeister'), - ), - ], + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.radio, + size: 100, + color: Theme.of(context).colorScheme.primary, ), - ), + const SizedBox(height: 24), + Text( + 'BM Manager', + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Manage your BrandMeister devices', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey[600], + ), + ), + const SizedBox(height: 48), + const CircularProgressIndicator(), + ], ), ), );