헤더∙로그인 UI 정비하고 벤더 오류 메시지 안정화
- 로그인 화면 상단에 브랜드 아이콘과 안내 문구를 추가하고 카드/푸터 레이아웃을 정리 - 네비게이션 레일/앱바 구성 재배치, 테마 토글 상단 이동, 그라데이션/브랜드 아이콘 스타일 조정 - ScaffoldMessenger 의존성 문제를 피하도록 벤더 컨트롤러 에러 스낵바 호출 시점을 프레임 이후로 이연 - IMPLEMENTATION_TASKS.md에 이번 변경 내역을 요약하여 진행 상황을 문서화
This commit is contained in:
@@ -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});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user