헤더∙로그인 UI 정비하고 벤더 오류 메시지 안정화

- 로그인 화면 상단에 브랜드 아이콘과 안내 문구를 추가하고 카드/푸터 레이아웃을 정리

- 네비게이션 레일/앱바 구성 재배치, 테마 토글 상단 이동, 그라데이션/브랜드 아이콘 스타일 조정

- ScaffoldMessenger 의존성 문제를 피하도록 벤더 컨트롤러 에러 스낵바 호출 시점을 프레임 이후로 이연

- IMPLEMENTATION_TASKS.md에 이번 변경 내역을 요약하여 진행 상황을 문서화
This commit is contained in:
JiWoong Sul
2025-09-30 15:00:23 +09:00
parent 47c87dc118
commit 5578bf443f
4 changed files with 305 additions and 118 deletions

View File

@@ -29,18 +29,27 @@ class AppShell extends StatelessWidget {
if (manager.can(page.path, PermissionAction.view)) page,
];
final pages = filteredPages.isEmpty ? allAppPages : filteredPages;
final themeController = ThemeControllerScope.of(context);
final appBar = _GradientAppBar(
title: const _BrandTitle(),
actions: [
_ThemeMenuButton(
mode: themeController.mode,
onChanged: themeController.update,
),
const SizedBox(width: 8),
IconButton(
tooltip: '로그아웃',
icon: const Icon(lucide.LucideIcons.logOut),
onPressed: () => context.go(loginRoutePath),
),
const SizedBox(width: 8),
],
);
if (isWide) {
return Scaffold(
appBar: AppBar(
title: const Text('Superport v2'),
actions: [
IconButton(
tooltip: '로그아웃',
icon: const Icon(lucide.LucideIcons.logOut),
onPressed: () => context.go(loginRoutePath),
),
],
),
appBar: appBar,
body: Row(
children: [
_NavigationRail(currentLocation: currentLocation, pages: pages),
@@ -52,16 +61,7 @@ class AppShell extends StatelessWidget {
}
return Scaffold(
appBar: AppBar(
title: const Text('Superport v2'),
actions: [
IconButton(
tooltip: '로그아웃',
icon: const Icon(lucide.LucideIcons.logOut),
onPressed: () => context.go(loginRoutePath),
),
],
),
appBar: appBar,
drawer: Drawer(
child: SafeArea(
child: _NavigationList(
@@ -92,18 +92,14 @@ class _NavigationRail extends StatelessWidget {
final selectedIndex = _selectedIndex(currentLocation, pages);
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final themeController = ThemeControllerScope.of(context);
final currentThemeMode = themeController.mode;
return Container(
width: 104,
width: 220,
decoration: BoxDecoration(
border: Border(right: BorderSide(color: colorScheme.outlineVariant)),
),
child: Column(
children: [
const SizedBox(height: 24),
const FlutterLogo(size: 48),
const SizedBox(height: 24),
Expanded(
child: ListView.builder(
@@ -134,11 +130,10 @@ class _NavigationRail extends StatelessWidget {
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 8,
vertical: 10,
horizontal: 12,
),
child: Column(
mainAxisSize: MainAxisSize.min,
child: Row(
children: [
Icon(
page.icon,
@@ -147,13 +142,14 @@ class _NavigationRail extends StatelessWidget {
? colorScheme.primary
: colorScheme.onSurfaceVariant,
),
const SizedBox(height: 6),
Text(
page.label,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: textStyle,
const SizedBox(width: 12),
Expanded(
child: Text(
page.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textStyle,
),
),
],
),
@@ -164,13 +160,6 @@ class _NavigationRail extends StatelessWidget {
},
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 16),
child: _ThemeMenuButton(
mode: currentThemeMode,
onChanged: themeController.update,
),
),
],
),
);
@@ -228,6 +217,82 @@ class _NavigationList extends StatelessWidget {
}
}
class _BrandTitle extends StatelessWidget {
const _BrandTitle();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.primary,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Icon(
lucide.LucideIcons.ship,
size: 28,
color: colorScheme.onPrimary,
),
),
),
const SizedBox(width: 12),
Text(
'Superport v2',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
);
}
}
class _GradientAppBar extends StatelessWidget implements PreferredSizeWidget {
const _GradientAppBar({required this.title, required this.actions});
final Widget title;
final List<Widget> actions;
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AppBar(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
automaticallyImplyLeading: false,
titleSpacing: 16,
toolbarHeight: kToolbarHeight,
title: title,
actions: actions,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
colorScheme.primary.withValues(alpha: 0),
colorScheme.primary.withValues(alpha: 0.2),
],
stops: const [0, 1],
),
),
),
);
}
}
class _ThemeMenuButton extends StatelessWidget {
const _ThemeMenuButton({required this.mode, required this.onChanged});