[ui] UI theme should match repertory blue #61
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				BlockStorage/repertory/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	BlockStorage/repertory/pipeline/head This commit looks good
				
			This commit is contained in:
		| @@ -1,4 +1,5 @@ | ||||
| autofocus | ||||
| autovalidatemode | ||||
| canvaskit | ||||
| cupertino | ||||
| cupertinoicons | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								web/repertory/assets/images/repertory.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/repertory/assets/images/repertory.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 368 B | 
| @@ -12,10 +12,20 @@ class AuthScreen extends StatefulWidget { | ||||
| } | ||||
|  | ||||
| class _AuthScreenState extends State<AuthScreen> { | ||||
|   bool _enabled = true; | ||||
|   final _formKey = GlobalKey<FormState>(); | ||||
|   final _passwordController = TextEditingController(); | ||||
|   final _userController = TextEditingController(); | ||||
|  | ||||
|   bool _enabled = true; | ||||
|   bool _obscure = true; | ||||
|  | ||||
|   @override | ||||
|   void dispose() { | ||||
|     _passwordController.dispose(); | ||||
|     _userController.dispose(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(context) { | ||||
|     final scheme = Theme.of(context).colorScheme; | ||||
| @@ -36,156 +46,221 @@ class _AuthScreenState extends State<AuthScreen> { | ||||
|       contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), | ||||
|     ); | ||||
|  | ||||
|     VoidCallback? createLoginHandler(Auth auth) { | ||||
|       if (!_enabled) return null; | ||||
|       return () async { | ||||
|         setState(() => _enabled = false); | ||||
|         await auth.authenticate(_userController.text, _passwordController.text); | ||||
|         setState(() => _enabled = true); | ||||
|       }; | ||||
|     Future<void> doLogin(Auth auth) async { | ||||
|       if (!_enabled) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (!_formKey.currentState!.validate()) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       setState(() => _enabled = false); | ||||
|       await auth.authenticate( | ||||
|         _userController.text.trim(), | ||||
|         _passwordController.text, | ||||
|       ); | ||||
|       setState(() => _enabled = true); | ||||
|  | ||||
|       // if (!context.mounted) return; | ||||
|       // | ||||
|       // // Only show the error if login failed | ||||
|       // if (!ok && !auth.authenticated) { | ||||
|       //   ScaffoldMessenger.of(context).showSnackBar( | ||||
|       //     const SnackBar( | ||||
|       //       content: Text('Invalid username or password'), | ||||
|       //       behavior: SnackBarBehavior.floating, | ||||
|       //     ), | ||||
|       //   ); | ||||
|       // } | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(title: Text(widget.title), scrolledUnderElevation: 0), | ||||
|       body: Container( | ||||
|         width: double.infinity, | ||||
|         height: double.infinity, | ||||
|         decoration: const BoxDecoration( | ||||
|           gradient: LinearGradient( | ||||
|             begin: Alignment.topLeft, | ||||
|             end: Alignment.bottomRight, | ||||
|             colors: [Color(0xFF1050A0), Color(0xFF202124)], | ||||
|       body: SafeArea( | ||||
|         child: Container( | ||||
|           width: double.infinity, | ||||
|           height: double.infinity, | ||||
|           decoration: BoxDecoration( | ||||
|             gradient: LinearGradient( | ||||
|               begin: Alignment.topLeft, | ||||
|               end: Alignment.bottomRight, | ||||
|               colors: [ | ||||
|                 Theme.of(context).colorScheme.primary, | ||||
|                 Theme.of(context).colorScheme.surface, | ||||
|               ], | ||||
|               stops: const [0.0, 1.0], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|         child: Consumer<Auth>( | ||||
|           builder: (context, auth, _) { | ||||
|             if (auth.authenticated) { | ||||
|               Future.delayed(Duration(milliseconds: 1), () { | ||||
|                 if (constants.navigatorKey.currentContext == null) { | ||||
|                   return; | ||||
|                 } | ||||
|           child: Consumer<Auth>( | ||||
|             builder: (context, auth, _) { | ||||
|               if (auth.authenticated) { | ||||
|                 Future.delayed(const Duration(milliseconds: 1), () { | ||||
|                   if (constants.navigatorKey.currentContext == null) { | ||||
|                     return; | ||||
|                   } | ||||
|  | ||||
|                 Navigator.of( | ||||
|                   constants.navigatorKey.currentContext!, | ||||
|                 ).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false); | ||||
|               }); | ||||
|                   Navigator.of( | ||||
|                     constants.navigatorKey.currentContext!, | ||||
|                   ).pushNamedAndRemoveUntil('/', (r) => false); | ||||
|                 }); | ||||
|  | ||||
|               return const SizedBox.shrink(); | ||||
|             } | ||||
|                 return const SizedBox.shrink(); | ||||
|               } | ||||
|  | ||||
|             return Center( | ||||
|               child: AnimatedScale( | ||||
|                 scale: 1.0, | ||||
|                 duration: const Duration(milliseconds: 250), | ||||
|                 curve: Curves.easeOutCubic, | ||||
|                 child: AnimatedOpacity( | ||||
|                   opacity: 1.0, | ||||
|               return Center( | ||||
|                 child: AnimatedScale( | ||||
|                   scale: 1.0, | ||||
|                   duration: const Duration(milliseconds: 250), | ||||
|                   child: ConstrainedBox( | ||||
|                     constraints: const BoxConstraints( | ||||
|                       maxWidth: 420, | ||||
|                       minWidth: 300, | ||||
|                     ), | ||||
|                     child: Card( | ||||
|                       elevation: 12, | ||||
|                       color: scheme.surface, | ||||
|                       shape: RoundedRectangleBorder( | ||||
|                         borderRadius: BorderRadius.circular(18), | ||||
|                   curve: Curves.easeOutCubic, | ||||
|                   child: AnimatedOpacity( | ||||
|                     opacity: 1.0, | ||||
|                     duration: const Duration(milliseconds: 250), | ||||
|                     child: ConstrainedBox( | ||||
|                       constraints: const BoxConstraints( | ||||
|                         maxWidth: 420, | ||||
|                         minWidth: 300, | ||||
|                       ), | ||||
|                       child: Padding( | ||||
|                         padding: const EdgeInsets.all(constants.padding), | ||||
|                         child: Column( | ||||
|                           mainAxisSize: MainAxisSize.min, | ||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                           children: [ | ||||
|                             Align( | ||||
|                               alignment: Alignment.center, | ||||
|                               child: CircleAvatar( | ||||
|                                 radius: 28, | ||||
|                                 backgroundColor: scheme.primary.withValues( | ||||
|                                   alpha: 0.18, | ||||
|                                 ), | ||||
|                                 child: Icon( | ||||
|                                   Icons.folder, | ||||
|                                   color: scheme.primary, | ||||
|                                   size: 28, | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ), | ||||
|                             const SizedBox(height: 14), | ||||
|                             Text( | ||||
|                               constants.appLogonTitle, | ||||
|                               textAlign: TextAlign.center, | ||||
|                               style: Theme.of(context).textTheme.headlineSmall | ||||
|                                   ?.copyWith(fontWeight: FontWeight.w600), | ||||
|                             ), | ||||
|                             const SizedBox(height: 6), | ||||
|                             Text( | ||||
|                               "Secure access to your mounts", | ||||
|                               textAlign: TextAlign.center, | ||||
|                               style: Theme.of(context).textTheme.bodyMedium | ||||
|                                   ?.copyWith( | ||||
|                                     color: scheme.onSurface.withValues( | ||||
|                                       alpha: 0.7, | ||||
|                       child: Card( | ||||
|                         elevation: 12, | ||||
|                         color: scheme.surface, | ||||
|                         shape: RoundedRectangleBorder( | ||||
|                           borderRadius: BorderRadius.circular(18), | ||||
|                         ), | ||||
|                         child: Padding( | ||||
|                           padding: const EdgeInsets.all(constants.padding), | ||||
|                           child: Form( | ||||
|                             key: _formKey, | ||||
|                             autovalidateMode: | ||||
|                                 AutovalidateMode.onUserInteraction, | ||||
|                             child: Column( | ||||
|                               mainAxisSize: MainAxisSize.min, | ||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                               children: [ | ||||
|                                 Align( | ||||
|                                   alignment: Alignment.center, | ||||
|                                   child: CircleAvatar( | ||||
|                                     radius: 28, | ||||
|                                     backgroundColor: scheme.primary.withValues( | ||||
|                                       alpha: 0.18, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                             ), | ||||
|                             const SizedBox(height: 20), | ||||
|                             TextField( | ||||
|                               autofocus: true, | ||||
|                               controller: _userController, | ||||
|                               textInputAction: TextInputAction.next, | ||||
|                               decoration: decoration('Username', Icons.person), | ||||
|                             ), | ||||
|                             const SizedBox(height: constants.padding), | ||||
|                             TextField( | ||||
|                               controller: _passwordController, | ||||
|                               obscureText: true, | ||||
|                               textInputAction: TextInputAction.go, | ||||
|                               onSubmitted: (_) { | ||||
|                                 final handler = createLoginHandler(auth); | ||||
|                                 handler?.call(); | ||||
|                               }, | ||||
|                               decoration: decoration('Password', Icons.lock), | ||||
|                             ), | ||||
|                             const SizedBox(height: constants.padding), | ||||
|                             SizedBox( | ||||
|                               height: 44, | ||||
|                               child: ElevatedButton( | ||||
|                                 onPressed: createLoginHandler(auth), | ||||
|                                 style: ElevatedButton.styleFrom( | ||||
|                                   shape: RoundedRectangleBorder( | ||||
|                                     borderRadius: BorderRadius.circular(12), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 child: _enabled | ||||
|                                     ? const Text('Login') | ||||
|                                     : const SizedBox( | ||||
|                                         height: 20, | ||||
|                                         width: 20, | ||||
|                                         child: CircularProgressIndicator( | ||||
|                                           strokeWidth: 2.4, | ||||
|                                     child: ClipRRect( | ||||
|                                       borderRadius: BorderRadius.circular(28), | ||||
|                                       child: Image.asset( | ||||
|                                         'assets/images/repertory.png', | ||||
|                                         width: 28, | ||||
|                                         height: 28, | ||||
|                                         fit: BoxFit.contain, | ||||
|                                         errorBuilder: (_, _, _) => Icon( | ||||
|                                           Icons.folder, | ||||
|                                           color: scheme.primary, | ||||
|                                           size: 28, | ||||
|                                         ), | ||||
|                                       ), | ||||
|                               ), | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 const SizedBox(height: 14), | ||||
|                                 Text( | ||||
|                                   constants.appLogonTitle, | ||||
|                                   textAlign: TextAlign.center, | ||||
|                                   style: Theme.of(context) | ||||
|                                       .textTheme | ||||
|                                       .headlineSmall | ||||
|                                       ?.copyWith(fontWeight: FontWeight.w600), | ||||
|                                 ), | ||||
|                                 const SizedBox(height: 6), | ||||
|                                 Text( | ||||
|                                   "Secure access to your mounts", | ||||
|                                   textAlign: TextAlign.center, | ||||
|                                   style: Theme.of(context).textTheme.bodyMedium | ||||
|                                       ?.copyWith( | ||||
|                                         color: scheme.onSurface.withValues( | ||||
|                                           alpha: 0.7, | ||||
|                                         ), | ||||
|                                       ), | ||||
|                                 ), | ||||
|                                 const SizedBox(height: 20), | ||||
|  | ||||
|                                 TextFormField( | ||||
|                                   autofocus: true, | ||||
|                                   controller: _userController, | ||||
|                                   textInputAction: TextInputAction.next, | ||||
|                                   decoration: decoration( | ||||
|                                     'Username', | ||||
|                                     Icons.person, | ||||
|                                   ), | ||||
|                                   validator: (v) => | ||||
|                                       (v == null || v.trim().isEmpty) | ||||
|                                       ? 'Enter your username' | ||||
|                                       : null, | ||||
|                                   onFieldSubmitted: (_) => | ||||
|                                       FocusScope.of(context).nextFocus(), | ||||
|                                 ), | ||||
|                                 const SizedBox(height: constants.padding), | ||||
|  | ||||
|                                 TextFormField( | ||||
|                                   controller: _passwordController, | ||||
|                                   obscureText: _obscure, | ||||
|                                   textInputAction: TextInputAction.go, | ||||
|                                   decoration: decoration('Password', Icons.lock) | ||||
|                                       .copyWith( | ||||
|                                         suffixIcon: IconButton( | ||||
|                                           tooltip: _obscure | ||||
|                                               ? 'Show password' | ||||
|                                               : 'Hide password', | ||||
|                                           icon: Icon( | ||||
|                                             _obscure | ||||
|                                                 ? Icons.visibility | ||||
|                                                 : Icons.visibility_off, | ||||
|                                           ), | ||||
|                                           onPressed: () => setState( | ||||
|                                             () => _obscure = !_obscure, | ||||
|                                           ), | ||||
|                                         ), | ||||
|                                       ), | ||||
|                                   validator: (v) => (v == null || v.isEmpty) | ||||
|                                       ? 'Enter your password' | ||||
|                                       : null, | ||||
|                                   onFieldSubmitted: (_) => doLogin(auth), | ||||
|                                 ), | ||||
|                                 const SizedBox(height: constants.padding), | ||||
|  | ||||
|                                 SizedBox( | ||||
|                                   height: 44, | ||||
|                                   child: ElevatedButton( | ||||
|                                     onPressed: _enabled | ||||
|                                         ? () => doLogin(auth) | ||||
|                                         : null, | ||||
|                                     style: ElevatedButton.styleFrom( | ||||
|                                       shape: RoundedRectangleBorder( | ||||
|                                         borderRadius: BorderRadius.circular(12), | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                     child: _enabled | ||||
|                                         ? const Text('Login') | ||||
|                                         : const SizedBox( | ||||
|                                             height: 20, | ||||
|                                             width: 20, | ||||
|                                             child: CircularProgressIndicator( | ||||
|                                               strokeWidth: 2.4, | ||||
|                                             ), | ||||
|                                           ), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           ], | ||||
|                           ), | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|           }, | ||||
|               ); | ||||
|             }, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void setState(VoidCallback fn) { | ||||
|     if (!mounted) return; | ||||
|     super.setState(fn); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -64,9 +64,8 @@ flutter: | ||||
|   uses-material-design: true | ||||
|  | ||||
|   # To add assets to your application, add an assets section, like this: | ||||
|   # assets: | ||||
|   #   - images/a_dot_burr.jpeg | ||||
|   #   - images/a_dot_ham.jpeg | ||||
|   assets: | ||||
|     - assets/images/repertory.png | ||||
|  | ||||
|   # An image asset can refer to one or more resolution-specific "variants", see | ||||
|   # https://flutter.dev/to/resolution-aware-images | ||||
|   | ||||
		Reference in New Issue
	
	Block a user