1
0
forked from baron/baron-sso

대시보드 다크 모드/테마 토글 적용

This commit is contained in:
2026-04-08 16:11:40 +09:00
parent 3d7d4767bf
commit dce418d0b9
4 changed files with 649 additions and 537 deletions

View File

@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import '../../../core/constants/error_whitelist.dart'; import '../../../core/constants/error_whitelist.dart';
import '../../../core/i18n/locale_utils.dart'; import '../../../core/i18n/locale_utils.dart';
import '../../../core/services/auth_proxy_service.dart'; import '../../../core/services/auth_proxy_service.dart';
import '../../../core/widgets/theme_toggle_button.dart';
import 'package:userfront/i18n.dart'; import 'package:userfront/i18n.dart';
class ErrorScreen extends StatelessWidget { class ErrorScreen extends StatelessWidget {
@@ -22,6 +23,7 @@ class ErrorScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isProd = isProdOverride ?? AuthProxyService.isProdEnv; final isProd = isProdOverride ?? AuthProxyService.isProdEnv;
final normalizedCode = (errorCode ?? '').trim(); final normalizedCode = (errorCode ?? '').trim();
final hasCode = normalizedCode.isNotEmpty; final hasCode = normalizedCode.isNotEmpty;
@@ -62,7 +64,7 @@ class ErrorScreen extends StatelessWidget {
: tr('msg.userfront.error.detail_request'))); : tr('msg.userfront.error.detail_request')));
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF7F8FA), backgroundColor: colorScheme.surfaceContainerLowest,
body: Center( body: Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560), constraints: const BoxConstraints(maxWidth: 560),
@@ -71,7 +73,7 @@ class ErrorScreen extends StatelessWidget {
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: Color(0xFFE5E7EB)), side: BorderSide(color: colorScheme.outlineVariant),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(28, 28, 28, 24), padding: const EdgeInsets.fromLTRB(28, 28, 28, 24),
@@ -79,18 +81,25 @@ class ErrorScreen extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Row(
children: [
Expanded(
child: Text(
title, title,
style: theme.textTheme.titleLarge?.copyWith( style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: const Color(0xFF111827), color: colorScheme.onSurface,
), ),
), ),
),
const ThemeToggleButton(compact: true),
],
),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
detail, detail,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: const Color(0xFF4B5563), color: colorScheme.onSurfaceVariant,
height: 1.5, height: 1.5,
), ),
), ),
@@ -98,7 +107,7 @@ class ErrorScreen extends StatelessWidget {
Text( Text(
tr('msg.userfront.error.type', params: {'type': errorType}), tr('msg.userfront.error.type', params: {'type': errorType}),
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: const Color(0xFF6B7280), color: colorScheme.onSurfaceVariant,
), ),
), ),
if (errorId != null && errorId!.isNotEmpty) ...[ if (errorId != null && errorId!.isNotEmpty) ...[
@@ -106,7 +115,7 @@ class ErrorScreen extends StatelessWidget {
Text( Text(
tr('msg.userfront.error.id', params: {'id': errorId!}), tr('msg.userfront.error.id', params: {'id': errorId!}),
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: const Color(0xFF6B7280), color: colorScheme.onSurfaceVariant,
), ),
), ),
], ],
@@ -118,8 +127,8 @@ class ErrorScreen extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: () => context.go('/login'), onPressed: () => context.go('/login'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF111827), backgroundColor: colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: colorScheme.onPrimary,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
@@ -134,12 +143,12 @@ class ErrorScreen extends StatelessWidget {
onPressed: () => onPressed: () =>
context.go(buildLocalizedHomePath(Uri.base)), context.go(buildLocalizedHomePath(Uri.base)),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF111827), foregroundColor: colorScheme.onSurface,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 12, vertical: 12,
), ),
side: const BorderSide(color: Color(0xFFCBD5F5)), side: BorderSide(color: colorScheme.outline),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),

View File

@@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:userfront/i18n.dart'; import 'package:userfront/i18n.dart';
import '../../../core/widgets/language_selector.dart'; import '../../../core/widgets/language_selector.dart';
import '../../../core/widgets/theme_toggle_button.dart';
import '../../../core/services/web_auth_integration.dart'; import '../../../core/services/web_auth_integration.dart';
import '../../../core/services/auth_proxy_service.dart'; import '../../../core/services/auth_proxy_service.dart';
import '../../../core/services/auth_token_store.dart'; import '../../../core/services/auth_token_store.dart';
@@ -1385,6 +1386,10 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final mutedColor = colorScheme.onSurfaceVariant;
if (_verificationOnly && _verificationApproved) { if (_verificationOnly && _verificationApproved) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -1393,12 +1398,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () => context.go(buildLocalizedHomePath(Uri.base)), onPressed: () => context.go(buildLocalizedHomePath(Uri.base)),
), ),
actions: const [ThemeToggleButton(compact: true)],
), ),
body: _buildVerificationResultView(), body: _buildVerificationResultView(),
); );
} }
return Scaffold( return Scaffold(
backgroundColor: colorScheme.surfaceContainerLowest,
body: LayoutBuilder( body: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return SingleChildScrollView( return SingleChildScrollView(
@@ -1407,6 +1414,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
child: Center( child: Center(
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
padding: const EdgeInsets.all(24),
child: Card(
child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -1414,8 +1424,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
children: [ children: [
Text( Text(
tr('ui.userfront.app_title'), tr('ui.userfront.app_title'),
style: const TextStyle( style: theme.textTheme.headlineMedium?.copyWith(
fontSize: 32,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -1430,7 +1439,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFFFF3CD), color: const Color(0xFFFFF3CD),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFFC107)), border: Border.all(
color: const Color(0xFFFFC107),
),
), ),
child: Row( child: Row(
children: [ children: [
@@ -1484,12 +1495,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
tr( tr(
'ui.userfront.login.field.login_id', 'ui.userfront.login.field.login_id',
), ),
border: const OutlineInputBorder(),
prefixIcon: const Icon( prefixIcon: const Icon(
Icons.person_outline, Icons.person_outline,
), ),
), ),
onSubmitted: (_) => _handlePasswordLogin(), onSubmitted: (_) =>
_handlePasswordLogin(),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
@@ -1503,12 +1514,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
labelText: tr( labelText: tr(
'ui.userfront.login.field.password', 'ui.userfront.login.field.password',
), ),
border: const OutlineInputBorder(),
prefixIcon: const Icon( prefixIcon: const Icon(
Icons.lock_outline, Icons.lock_outline,
), ),
), ),
onSubmitted: (_) => _handlePasswordLogin(), onSubmitted: (_) =>
_handlePasswordLogin(),
), ),
if (_isPasswordCapsLockOn) ...[ if (_isPasswordCapsLockOn) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -1540,10 +1551,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
), ),
onPressed: _handlePasswordLogin, onPressed: _handlePasswordLogin,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(50), minimumSize: const Size.fromHeight(
50,
),
), ),
child: Text( child: Text(
tr('ui.userfront.login.action.submit'), tr(
'ui.userfront.login.action.submit',
),
), ),
), ),
], ],
@@ -1562,18 +1577,20 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
'ui.userfront.login.field.login_id', 'ui.userfront.login.field.login_id',
), ),
hintText: '', hintText: '',
border: const OutlineInputBorder(),
prefixIcon: const Icon( prefixIcon: const Icon(
Icons.person_outline, Icons.person_outline,
), ),
), ),
onSubmitted: (_) => _handleLinkLogin(), onSubmitted: (_) =>
_handleLinkLogin(),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
FilledButton( FilledButton(
onPressed: _handleLinkLogin, onPressed: _handleLinkLogin,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(50), minimumSize: const Size.fromHeight(
50,
),
), ),
child: Text( child: Text(
tr('ui.userfront.login.link.send'), tr('ui.userfront.login.link.send'),
@@ -1582,8 +1599,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
const SizedBox(height: 24), const SizedBox(height: 24),
Text( Text(
tr('msg.userfront.login.link.helper'), tr('msg.userfront.login.link.helper'),
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 12, fontSize: 12,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -1592,10 +1609,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (_linkPendingRef != null) ...[ if (_linkPendingRef != null) ...[
if (_linkExpired) ...[ if (_linkExpired) ...[
Text( Text(
tr('msg.userfront.login.link_timeout'), tr(
'msg.userfront.login.link_timeout',
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 12, fontSize: 12,
), ),
), ),
@@ -1605,19 +1624,20 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
setState(_resetLinkLoginState); setState(_resetLinkLoginState);
}, },
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight( minimumSize:
45, const Size.fromHeight(45),
), ),
child: Text(
tr('ui.common.refresh'),
), ),
child: Text(tr('ui.common.refresh')),
), ),
] else ...[ ] else ...[
Text( Text(
tr( tr(
'msg.userfront.login.link.short_code_help', 'msg.userfront.login.link.short_code_help',
), ),
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 12, fontSize: 12,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -1631,16 +1651,15 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
controller: controller:
_shortCodePrefixController, _shortCodePrefixController,
textCapitalization: textCapitalization:
TextCapitalization.characters, TextCapitalization
.characters,
decoration: InputDecoration( decoration: InputDecoration(
labelText: tr( labelText: tr(
'ui.userfront.login.short_code.prefix', 'ui.userfront.login.short_code.prefix',
), ),
border:
const OutlineInputBorder(),
hintText: 'AB', hintText: 'AB',
hintStyle: const TextStyle( hintStyle: TextStyle(
color: Colors.grey, color: mutedColor,
), ),
), ),
maxLength: 2, maxLength: 2,
@@ -1658,11 +1677,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
labelText: tr( labelText: tr(
'ui.userfront.login.short_code.digits', 'ui.userfront.login.short_code.digits',
), ),
border:
const OutlineInputBorder(),
hintText: '345678', hintText: '345678',
hintStyle: const TextStyle( hintStyle: TextStyle(
color: Colors.grey, color: mutedColor,
), ),
suffixText: suffixText:
_linkExpireSeconds > 0 _linkExpireSeconds > 0
@@ -1685,11 +1702,13 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
FilledButton( FilledButton(
onPressed: () { onPressed: () {
final prefix = final prefix =
_shortCodePrefixController.text _shortCodePrefixController
.text
.trim() .trim()
.toUpperCase(); .toUpperCase();
final digits = final digits =
_shortCodeDigitsController.text _shortCodeDigitsController
.text
.trim(); .trim();
if (prefix.length != 2 || if (prefix.length != 2 ||
digits.length != 6) { digits.length != 6) {
@@ -1703,9 +1722,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_verifyShortCode(prefix + digits); _verifyShortCode(prefix + digits);
}, },
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight( minimumSize:
45, const Size.fromHeight(45),
),
), ),
child: Text( child: Text(
tr( tr(
@@ -1780,7 +1798,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
} }
final loginId = final loginId =
_lastLinkLoginId ?? _lastLinkLoginId ??
_linkIdController.text.trim(); _linkIdController.text
.trim();
if (loginId.isEmpty) { if (loginId.isEmpty) {
_showError( _showError(
tr( tr(
@@ -1823,10 +1842,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
Column( Column(
children: [ children: [
Text( Text(
tr('msg.userfront.login.qr_expired'), tr(
'msg.userfront.login.qr_expired',
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 12, fontSize: 12,
), ),
), ),
@@ -1834,11 +1855,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
FilledButton( FilledButton(
onPressed: _startQrFlow, onPressed: _startQrFlow,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight( minimumSize:
45, const Size.fromHeight(45),
), ),
child: Text(
tr('ui.common.refresh'),
), ),
child: Text(tr('ui.common.refresh')),
), ),
], ],
) )
@@ -1851,16 +1873,16 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: Colors.grey.shade300, color: colorScheme.outline,
),
borderRadius: BorderRadius.circular(
12,
), ),
borderRadius:
BorderRadius.circular(12),
), ),
child: QrImageView( child: QrImageView(
data: _qrImageBase64!, data: _qrImageBase64!,
version: QrVersions.auto, version: QrVersions.auto,
size: 200.0, size: 200.0,
backgroundColor: Colors.white,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -1887,24 +1909,30 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
tr('msg.userfront.login.qr.scan_hint'), tr(
'msg.userfront.login.qr.scan_hint',
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 12, fontSize: 12,
), ),
), ),
TextButton( TextButton(
onPressed: _startQrFlow, onPressed: _startQrFlow,
child: Text( child: Text(
tr('ui.userfront.login.qr.refresh'), tr(
'ui.userfront.login.qr.refresh',
),
), ),
), ),
], ],
) )
else else
Text( Text(
tr('msg.userfront.login.qr.load_failed'), tr(
'msg.userfront.login.qr.load_failed',
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -1916,7 +1944,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
Column( Column(
children: [ children: [
TextButton( TextButton(
onPressed: () => context.push('/forgot-password'), onPressed: () =>
context.push('/forgot-password'),
child: Text( child: Text(
tr('ui.userfront.login.forgot_password'), tr('ui.userfront.login.forgot_password'),
), ),
@@ -1926,29 +1955,35 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
children: [ children: [
Text( Text(
tr('msg.userfront.login.no_account'), tr('msg.userfront.login.no_account'),
style: const TextStyle( style: TextStyle(
color: Colors.grey, color: mutedColor,
fontSize: 14, fontSize: 14,
), ),
), ),
TextButton( TextButton(
onPressed: () => context.push('/signup'), onPressed: () => context.push('/signup'),
child: Text(tr('ui.userfront.login.signup')), child: Text(
tr('ui.userfront.login.signup'),
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
const Align( const Wrap(
alignment: Alignment.center, alignment: WrapAlignment.center,
child: LanguageSelector(), spacing: 10,
runSpacing: 10,
children: [ThemeToggleButton(), LanguageSelector()],
), ),
], ],
), ),
), ),
), ),
), ),
),
),
); );
}, },
), ),

View File

@@ -18,6 +18,7 @@ import '../../../../core/services/auth_token_store.dart';
import '../../../../core/services/http_client.dart'; import '../../../../core/services/http_client.dart';
import '../../../../core/i18n/locale_utils.dart'; import '../../../../core/i18n/locale_utils.dart';
import '../../../../core/widgets/language_selector.dart'; import '../../../../core/widgets/language_selector.dart';
import '../../../../core/widgets/theme_toggle_button.dart';
import '../../../../core/ui/layout_breakpoints.dart'; import '../../../../core/ui/layout_breakpoints.dart';
import '../../../../core/ui/toast_service.dart'; import '../../../../core/ui/toast_service.dart';
import '../../profile/domain/notifiers/profile_notifier.dart'; import '../../profile/domain/notifiers/profile_notifier.dart';
@@ -33,10 +34,6 @@ class DashboardScreen extends ConsumerStatefulWidget {
} }
class _DashboardScreenState extends ConsumerState<DashboardScreen> { class _DashboardScreenState extends ConsumerState<DashboardScreen> {
static const _ink = Color(0xFF1A1F2C);
static const _surface = Colors.white;
static const _border = Color(0xFFE5E7EB);
static const _subtle = Color(0xFFF7F8FA);
static const double _dashboardCardSpacing = 12; static const double _dashboardCardSpacing = 12;
static const double _dashboardCardMaxWidth = 228; static const double _dashboardCardMaxWidth = 228;
static const double _activityDialogMaxWidth = 360; static const double _activityDialogMaxWidth = 360;
@@ -66,8 +63,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
bool _showAllActivities = false; bool _showAllActivities = false;
bool _showActiveSessionsOnly = false; bool _showActiveSessionsOnly = false;
bool _isDesktopSideMenuOpen = true;
final Set<String> _revokedClientIds = {}; final Set<String> _revokedClientIds = {};
Color get _ink => Theme.of(context).colorScheme.onSurface;
Color get _surface => Theme.of(context).colorScheme.surface;
Color get _border => Theme.of(context).colorScheme.outlineVariant;
Color get _subtle => Theme.of(context).colorScheme.surfaceContainerLowest;
String _renderTranslatedText( String _renderTranslatedText(
String key, { String key, {
String? fallback, String? fallback,
@@ -275,7 +278,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [ children: [
Text( Text(
item.appName, item.appName,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -346,7 +349,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon( Icon(
Icons.shield_outlined, Icons.shield_outlined,
size: 14, size: 14,
color: _ink, color: _ink,
@@ -354,7 +357,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
scope, scope,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _ink, color: _ink,
@@ -414,7 +417,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [ children: [
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -519,7 +522,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
), ),
const Padding( const Padding(
padding: EdgeInsets.only(bottom: 16), padding: EdgeInsets.only(bottom: 16),
child: LanguageSelector(compact: true), child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ThemeToggleButton(),
SizedBox(height: 8),
LanguageSelector(compact: true),
],
),
), ),
], ],
), ),
@@ -942,14 +952,35 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
return Scaffold( return Scaffold(
backgroundColor: _subtle, backgroundColor: _subtle,
appBar: AppBar( appBar: AppBar(
leading: isWide
? IconButton(
icon: Icon(
_isDesktopSideMenuOpen ? Icons.menu_open : Icons.menu,
),
tooltip: _isDesktopSideMenuOpen
? tr('ui.common.collapse')
: '펼치기',
onPressed: () {
setState(() {
_isDesktopSideMenuOpen = !_isDesktopSideMenuOpen;
});
},
)
: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
tooltip: MaterialLocalizations.of(
context,
).openAppDrawerTooltip,
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: Text( title: Text(
tr('ui.userfront.app_title'), tr('ui.userfront.app_title'),
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
elevation: 0,
backgroundColor: _surface,
foregroundColor: Colors.black,
actions: [ actions: [
const ThemeToggleButton(compact: true),
IconButton( IconButton(
icon: const Icon(Icons.person_outline), icon: const Icon(Icons.person_outline),
tooltip: tr('ui.userfront.nav.profile'), tooltip: tr('ui.userfront.nav.profile'),
@@ -972,7 +1003,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
: Drawer(child: _buildSideMenu(context, closeOnTap: true)), : Drawer(child: _buildSideMenu(context, closeOnTap: true)),
body: Row( body: Row(
children: [ children: [
if (isWide) if (isWide && _isDesktopSideMenuOpen)
SizedBox( SizedBox(
width: 240, width: 240,
child: _buildSideMenu(context, closeOnTap: false), child: _buildSideMenu(context, closeOnTap: false),
@@ -1065,7 +1096,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
fallback: 'Hello, {{name}}.', fallback: 'Hello, {{name}}.',
values: {'name': userName}, values: {'name': userName},
), ),
style: const TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: _ink, color: _ink,
@@ -1117,7 +1148,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [ children: [
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -1271,7 +1302,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: _ink, color: _ink,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -1496,7 +1527,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Expanded( Expanded(
child: Text( child: Text(
item.appName, item.appName,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _ink, color: _ink,
@@ -1530,7 +1561,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
item.lastAuthAt, item.lastAuthAt,
style: const TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _ink, color: _ink,
@@ -1544,7 +1575,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
onPressed: () => _showRpDetails(item), onPressed: () => _showRpDetails(item),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: _ink, foregroundColor: _ink,
side: const BorderSide(color: _border), side: BorderSide(color: _border),
padding: const EdgeInsets.symmetric(vertical: 7), padding: const EdgeInsets.symmetric(vertical: 7),
), ),
child: Text( child: Text(
@@ -1745,7 +1776,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [ children: [
Text( Text(
tr('ui.userfront.audit.filter.title'), tr('ui.userfront.audit.filter.title'),
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -1765,7 +1796,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [ children: [
Text( Text(
tr('ui.userfront.audit.filter.toggle_label'), tr('ui.userfront.audit.filter.toggle_label'),
style: const TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _ink, color: _ink,
@@ -2224,7 +2255,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Expanded( Expanded(
child: _buildAppCell( child: _buildAppCell(
log, log,
style: const TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: _ink, color: _ink,
), ),

View File

@@ -9,6 +9,7 @@ import '../../../../core/services/logout_service.dart';
import '../../../../core/ui/layout_breakpoints.dart'; import '../../../../core/ui/layout_breakpoints.dart';
import '../../../../core/ui/toast_service.dart'; import '../../../../core/ui/toast_service.dart';
import '../../../../core/widgets/language_selector.dart'; import '../../../../core/widgets/language_selector.dart';
import '../../../../core/widgets/theme_toggle_button.dart';
import '../../data/models/user_profile_model.dart'; import '../../data/models/user_profile_model.dart';
import '../../domain/notifiers/profile_notifier.dart'; import '../../domain/notifiers/profile_notifier.dart';
@@ -20,10 +21,6 @@ class ProfilePage extends ConsumerStatefulWidget {
} }
class _ProfilePageState extends ConsumerState<ProfilePage> { class _ProfilePageState extends ConsumerState<ProfilePage> {
static const _ink = Color(0xFF1A1F2C);
static const _surface = Colors.white;
static const _border = Color(0xFFE5E7EB);
static const _subtle = Color(0xFFF7F8FA);
static final _log = Logger('ProfilePage'); static final _log = Logger('ProfilePage');
UserProfile? _cachedProfile; UserProfile? _cachedProfile;
@@ -54,9 +51,15 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
bool _showCurrentPassword = false; bool _showCurrentPassword = false;
bool _showNewPassword = false; bool _showNewPassword = false;
bool _showConfirmPassword = false; bool _showConfirmPassword = false;
bool _isDesktopSideMenuOpen = true;
Map<String, dynamic>? _passwordPolicy; Map<String, dynamic>? _passwordPolicy;
bool _isPasswordPolicyLoading = false; bool _isPasswordPolicyLoading = false;
Color get _ink => Theme.of(context).colorScheme.onSurface;
Color get _surface => Theme.of(context).colorScheme.surface;
Color get _border => Theme.of(context).colorScheme.outlineVariant;
Color get _subtle => Theme.of(context).colorScheme.surfaceContainerLowest;
String _renderTranslatedText( String _renderTranslatedText(
String key, { String key, {
String? fallback, String? fallback,
@@ -615,7 +618,14 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
), ),
const Padding( const Padding(
padding: EdgeInsets.only(bottom: 16), padding: EdgeInsets.only(bottom: 16),
child: LanguageSelector(compact: true), child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ThemeToggleButton(),
SizedBox(height: 8),
LanguageSelector(compact: true),
],
),
), ),
], ],
); );
@@ -627,7 +637,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
children: [ children: [
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -654,7 +664,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: _ink, color: _ink,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -705,7 +715,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
fallback: 'Hello, {{name}}.', fallback: 'Hello, {{name}}.',
values: {'name': name}, values: {'name': name},
), ),
style: const TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _ink, color: _ink,
@@ -996,12 +1006,17 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
tr('msg.userfront.profile.password.subtitle'), tr('msg.userfront.profile.password.subtitle'),
style: const TextStyle(color: Color(0xFF6B7280)), style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
_buildPasswordPolicyDescription(), _buildPasswordPolicyDescription(),
style: const TextStyle(color: Color(0xFF6B7280), fontSize: 12), style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 12,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
@@ -1231,14 +1246,35 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
return Scaffold( return Scaffold(
backgroundColor: _subtle, backgroundColor: _subtle,
appBar: AppBar( appBar: AppBar(
leading: isWide
? IconButton(
icon: Icon(
_isDesktopSideMenuOpen ? Icons.menu_open : Icons.menu,
),
tooltip: _isDesktopSideMenuOpen
? tr('ui.common.collapse')
: '펼치기',
onPressed: () {
setState(() {
_isDesktopSideMenuOpen = !_isDesktopSideMenuOpen;
});
},
)
: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
tooltip: MaterialLocalizations.of(
context,
).openAppDrawerTooltip,
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: Text( title: Text(
tr('ui.userfront.app_title'), tr('ui.userfront.app_title'),
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
elevation: 0,
backgroundColor: _surface,
foregroundColor: Colors.black,
actions: [ actions: [
const ThemeToggleButton(compact: true),
IconButton( IconButton(
icon: const Icon(Icons.home_outlined), icon: const Icon(Icons.home_outlined),
tooltip: tr('ui.userfront.nav.dashboard'), tooltip: tr('ui.userfront.nav.dashboard'),
@@ -1259,7 +1295,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
drawer: isWide ? null : Drawer(child: _buildSideMenu(context)), drawer: isWide ? null : Drawer(child: _buildSideMenu(context)),
body: Row( body: Row(
children: [ children: [
if (isWide) SizedBox(width: 240, child: _buildSideMenu(context)), if (isWide && _isDesktopSideMenuOpen)
SizedBox(width: 240, child: _buildSideMenu(context)),
Expanded(child: _buildContent(profile, isUpdating)), Expanded(child: _buildContent(profile, isUpdating)),
], ],
), ),