diff --git a/assets/icons/.gitkeep b/assets/icons/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/icons/icon_1024.png b/assets/icons/icon_1024.png new file mode 100644 index 0000000..e92b1a8 Binary files /dev/null and b/assets/icons/icon_1024.png differ diff --git a/assets/icons/icon_128.png b/assets/icons/icon_128.png new file mode 100644 index 0000000..34c6e4b Binary files /dev/null and b/assets/icons/icon_128.png differ diff --git a/assets/icons/icon_16.png b/assets/icons/icon_16.png new file mode 100644 index 0000000..d432f99 Binary files /dev/null and b/assets/icons/icon_16.png differ diff --git a/assets/icons/icon_192.png b/assets/icons/icon_192.png new file mode 100644 index 0000000..e2e2f14 Binary files /dev/null and b/assets/icons/icon_192.png differ diff --git a/assets/icons/icon_256.png b/assets/icons/icon_256.png new file mode 100644 index 0000000..8163085 Binary files /dev/null and b/assets/icons/icon_256.png differ diff --git a/assets/icons/icon_32.png b/assets/icons/icon_32.png new file mode 100644 index 0000000..7b8c10c Binary files /dev/null and b/assets/icons/icon_32.png differ diff --git a/assets/icons/icon_48.png b/assets/icons/icon_48.png new file mode 100644 index 0000000..475fb3e Binary files /dev/null and b/assets/icons/icon_48.png differ diff --git a/assets/icons/icon_512.png b/assets/icons/icon_512.png new file mode 100644 index 0000000..f01e458 Binary files /dev/null and b/assets/icons/icon_512.png differ diff --git a/assets/icons/icon_64.png b/assets/icons/icon_64.png new file mode 100644 index 0000000..25f10c1 Binary files /dev/null and b/assets/icons/icon_64.png differ diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index a0bab72..8114345 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -24,16 +24,24 @@ import 'route_guard.dart'; part 'app_router.g.dart'; -@riverpod +class _RouterNotifier extends ChangeNotifier { + void notify() => notifyListeners(); +} + +@Riverpod(keepAlive: true) GoRouter appRouter(Ref ref) { - final authState = ref.watch(currentAuthStateProvider); + final notifier = _RouterNotifier(); + + ref.listen(currentAuthStateProvider, (_, __) => notifier.notify()); + ref.onDispose(notifier.dispose); return GoRouter( initialLocation: AppRoutes.login, debugLogDiagnostics: false, + refreshListenable: notifier, redirect: (context, state) => RouteGuard.redirect( state: state, - authState: authState, + authState: ref.read(currentAuthStateProvider), ), routes: [ GoRoute( diff --git a/lib/features/auth/presentation/login_screen.dart b/lib/features/auth/presentation/login_screen.dart index 1056392..9f43042 100644 --- a/lib/features/auth/presentation/login_screen.dart +++ b/lib/features/auth/presentation/login_screen.dart @@ -41,122 +41,142 @@ class _LoginScreenState extends ConsumerState { ref.listen(authStateProvider, (_, next) { next.whenOrNull( error: (e, _) => ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString()), backgroundColor: AppColors.error), + SnackBar( + content: Text(e.toString()), + backgroundColor: AppColors.error), ), ); }); return Scaffold( backgroundColor: AppColors.background, - // SafeArea + SingleChildScrollView prevents the form from being hidden - // behind the soft keyboard on short screens. body: SafeArea( - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height - - MediaQuery.of(context).padding.top - - MediaQuery.of(context).padding.bottom, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Center( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 32), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 400), - child: Card( + // LayoutBuilder + IntrinsicHeight keeps the card vertically centred + // on desktop while SingleChildScrollView prevents keyboard overflow + // on mobile/short screens. + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Column( + children: [ + Expanded( + child: Center( child: Padding( - padding: const EdgeInsets.all(40), - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( + padding: const EdgeInsets.symmetric( + horizontal: 24, vertical: 32), + child: ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: 400), + child: Card( + child: Padding( + padding: const EdgeInsets.all(40), + child: Form( + key: _formKey, child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.stretch, children: [ - SvgPicture.asset( - 'assets/images/bms_logo.svg', - height: 72, + Center( + child: Column( + children: [ + SvgPicture.asset( + 'assets/images/bms_logo.svg', + height: 72, + ), + const SizedBox(height: 16), + Text( + 'Business Management System', + style: AppTextStyles.bodySmall + .copyWith( + color: + AppColors.textSecondary, + ), + ), + ], + ), + ), + const SizedBox(height: 36), + TextFormField( + controller: _usernameController, + decoration: const InputDecoration( + labelText: 'Username', + prefixIcon: + Icon(Icons.person_outline), + ), + textInputAction: + TextInputAction.next, + autofocus: true, + validator: (v) => + (v == null || + v.trim().isEmpty) + ? 'Required' + : null, ), const SizedBox(height: 16), - Text( - 'Business Management System', - style: AppTextStyles.bodySmall.copyWith( - color: AppColors.textSecondary, + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + decoration: InputDecoration( + labelText: 'Password', + prefixIcon: + const Icon(Icons.lock_outline), + suffixIcon: IconButton( + icon: Icon(_obscurePassword + ? Icons.visibility_off + : Icons.visibility), + onPressed: () => setState(() => + _obscurePassword = + !_obscurePassword), + ), ), + textInputAction: + TextInputAction.done, + onFieldSubmitted: (_) => _submit(), + validator: (v) => + (v == null || v.isEmpty) + ? 'Required' + : null, + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: authAsync.isLoading + ? null + : _submit, + child: authAsync.isLoading + ? const SizedBox.square( + dimension: 20, + child: + CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white), + ) + : const Text('Sign In'), ), ], ), ), - const SizedBox(height: 36), - TextFormField( - controller: _usernameController, - decoration: const InputDecoration( - labelText: 'Username', - prefixIcon: Icon(Icons.person_outline), - ), - textInputAction: TextInputAction.next, - autofocus: true, - validator: (v) => - (v == null || v.trim().isEmpty) - ? 'Required' - : null, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - decoration: InputDecoration( - labelText: 'Password', - prefixIcon: const Icon(Icons.lock_outline), - suffixIcon: IconButton( - icon: Icon(_obscurePassword - ? Icons.visibility_off - : Icons.visibility), - onPressed: () => setState(() => - _obscurePassword = !_obscurePassword), - ), - ), - textInputAction: TextInputAction.done, - onFieldSubmitted: (_) => _submit(), - validator: (v) => - (v == null || v.isEmpty) ? 'Required' : null, - ), - const SizedBox(height: 32), - ElevatedButton( - onPressed: authAsync.isLoading ? null : _submit, - child: authAsync.isLoading - ? const SizedBox.square( - dimension: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white), - ) - : const Text('Sign In'), - ), - ], + ), ), ), ), ), ), - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Text( - '© ${DateTime.now().year} BMS. All rights reserved.', - style: AppTextStyles.bodySmall.copyWith( - color: AppColors.textDisabled, - fontSize: 11, + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Text( + '© ${DateTime.now().year} BMS. All rights reserved.', + style: AppTextStyles.bodySmall.copyWith( + color: AppColors.textDisabled, + fontSize: 11, + ), + ), ), - ), + ], ), - ], + ), ), ), ), diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 82b6f9d..e92b1a8 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 13b35eb..34c6e4b 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 0a3f5fa..d432f99 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bdb5722..8163085 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index f083318..7b8c10c 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 326c0e7..f01e458 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 2f1632c..25f10c1 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/web/favicon.png b/web/favicon.png index 8aaa46a..7b8c10c 100644 Binary files a/web/favicon.png and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..e2e2f14 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..f01e458 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d7..e2e2f14 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c566..f01e458 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..51b38f3 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ