[ui] UI theme should match repertory blue #61
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good

This commit is contained in:
2025-08-15 10:59:12 -05:00
parent fec755dc25
commit e9a5b7e9af
4 changed files with 208 additions and 133 deletions

View File

@@ -1,4 +1,5 @@
autofocus autofocus
autovalidatemode
canvaskit canvaskit
cupertino cupertino
cupertinoicons cupertinoicons

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -12,10 +12,20 @@ class AuthScreen extends StatefulWidget {
} }
class _AuthScreenState extends State<AuthScreen> { class _AuthScreenState extends State<AuthScreen> {
bool _enabled = true; final _formKey = GlobalKey<FormState>();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
final _userController = TextEditingController(); final _userController = TextEditingController();
bool _enabled = true;
bool _obscure = true;
@override
void dispose() {
_passwordController.dispose();
_userController.dispose();
super.dispose();
}
@override @override
Widget build(context) { Widget build(context) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
@@ -36,156 +46,221 @@ class _AuthScreenState extends State<AuthScreen> {
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
); );
VoidCallback? createLoginHandler(Auth auth) { Future<void> doLogin(Auth auth) async {
if (!_enabled) return null; if (!_enabled) {
return () async { return;
setState(() => _enabled = false); }
await auth.authenticate(_userController.text, _passwordController.text);
setState(() => _enabled = true); 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( return Scaffold(
appBar: AppBar(title: Text(widget.title), scrolledUnderElevation: 0), appBar: AppBar(title: Text(widget.title), scrolledUnderElevation: 0),
body: Container( body: SafeArea(
width: double.infinity, child: Container(
height: double.infinity, width: double.infinity,
decoration: const BoxDecoration( height: double.infinity,
gradient: LinearGradient( decoration: BoxDecoration(
begin: Alignment.topLeft, gradient: LinearGradient(
end: Alignment.bottomRight, begin: Alignment.topLeft,
colors: [Color(0xFF1050A0), Color(0xFF202124)], end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.surface,
],
stops: const [0.0, 1.0],
),
), ),
), child: Consumer<Auth>(
child: Consumer<Auth>( builder: (context, auth, _) {
builder: (context, auth, _) { if (auth.authenticated) {
if (auth.authenticated) { Future.delayed(const Duration(milliseconds: 1), () {
Future.delayed(Duration(milliseconds: 1), () { if (constants.navigatorKey.currentContext == null) {
if (constants.navigatorKey.currentContext == null) { return;
return; }
}
Navigator.of( Navigator.of(
constants.navigatorKey.currentContext!, constants.navigatorKey.currentContext!,
).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false); ).pushNamedAndRemoveUntil('/', (r) => false);
}); });
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Center( return Center(
child: AnimatedScale( child: AnimatedScale(
scale: 1.0, scale: 1.0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeOutCubic,
child: AnimatedOpacity(
opacity: 1.0,
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
child: ConstrainedBox( curve: Curves.easeOutCubic,
constraints: const BoxConstraints( child: AnimatedOpacity(
maxWidth: 420, opacity: 1.0,
minWidth: 300, duration: const Duration(milliseconds: 250),
), child: ConstrainedBox(
child: Card( constraints: const BoxConstraints(
elevation: 12, maxWidth: 420,
color: scheme.surface, minWidth: 300,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
), ),
child: Padding( child: Card(
padding: const EdgeInsets.all(constants.padding), elevation: 12,
child: Column( color: scheme.surface,
mainAxisSize: MainAxisSize.min, shape: RoundedRectangleBorder(
crossAxisAlignment: CrossAxisAlignment.stretch, borderRadius: BorderRadius.circular(18),
children: [ ),
Align( child: Padding(
alignment: Alignment.center, padding: const EdgeInsets.all(constants.padding),
child: CircleAvatar( child: Form(
radius: 28, key: _formKey,
backgroundColor: scheme.primary.withValues( autovalidateMode:
alpha: 0.18, AutovalidateMode.onUserInteraction,
), child: Column(
child: Icon( mainAxisSize: MainAxisSize.min,
Icons.folder, crossAxisAlignment: CrossAxisAlignment.stretch,
color: scheme.primary, children: [
size: 28, Align(
), alignment: Alignment.center,
), child: CircleAvatar(
), radius: 28,
const SizedBox(height: 14), backgroundColor: scheme.primary.withValues(
Text( alpha: 0.18,
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: ClipRRect(
), borderRadius: BorderRadius.circular(28),
const SizedBox(height: 20), child: Image.asset(
TextField( 'assets/images/repertory.png',
autofocus: true, width: 28,
controller: _userController, height: 28,
textInputAction: TextInputAction.next, fit: BoxFit.contain,
decoration: decoration('Username', Icons.person), errorBuilder: (_, _, _) => Icon(
), Icons.folder,
const SizedBox(height: constants.padding), color: scheme.primary,
TextField( size: 28,
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,
), ),
), ),
), ),
),
),
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);
}
} }

View File

@@ -64,9 +64,8 @@ flutter:
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: assets:
# - images/a_dot_burr.jpeg - assets/images/repertory.png
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images # https://flutter.dev/to/resolution-aware-images