diff --git a/lib/ui/main_dashboard.dart b/lib/ui/main_dashboard.dart index 8b1111e..5a036f9 100644 --- a/lib/ui/main_dashboard.dart +++ b/lib/ui/main_dashboard.dart @@ -81,7 +81,7 @@ class _MainDashboardState extends State { if (kTileEditingEnabled) { _layout.load(_tileSpecs().map((t) => t.id).toList()); } - if (_isStudent || _isAdmin) { + if (_isStudent || _isTeacherOrAbove) { _loadEquippedTheme(); } } @@ -763,7 +763,9 @@ class _MainDashboardState extends State { ), ); // 상점에서 테마를 바꾸고 돌아왔을 수 있으니 다시 불러온다. - if (_isStudent || _isAdmin) _loadEquippedTheme(); + if (_isStudent || _isTeacherOrAbove) { + _loadEquippedTheme(); + } }, child: const Padding( padding: EdgeInsets.all(8), diff --git a/lib/ui/profile_customize_screen.dart b/lib/ui/profile_customize_screen.dart index 3e36322..92ca4bd 100644 --- a/lib/ui/profile_customize_screen.dart +++ b/lib/ui/profile_customize_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/profile_shop_controller.dart'; import '../theme/app_palette.dart'; +import '../theme/profile_themes.dart'; import 'app_notice.dart'; import 'title_pill.dart'; @@ -31,10 +32,15 @@ class _ProfileCustomizeScreenState extends State { // null = 아직 직접 고르지 않음(장착된 테마를 그대로 보여줌), -1 = "기본 모습"을 명시적으로 미리보는 중. int? _previewTemplateId; + // 🎨 지금 실제로 장착 중인 테마 - 화면 배경/상점 타일 자체를 이걸로 물들인다. + // (미리보기는 큰 타일 하나만 바뀌는 것과 별개로, 이건 "진짜 내 테마"를 화면 전체에 반영) + ProfileTheme? _equippedTheme; + @override void initState() { super.initState(); _controller.loadCatalog(widget.studentId); + _loadEquippedTheme(); } @override @@ -43,6 +49,14 @@ class _ProfileCustomizeScreenState extends State { super.dispose(); } + Future _loadEquippedTheme() async { + final theme = await ProfileShopController.fetchEquippedTheme( + widget.studentId, + ); + if (!mounted) return; + setState(() => _equippedTheme = theme); + } + /// 지금 큰 타일에 보여줄 테마를 고른다. 직접 미리보기를 누른 게 있으면 그걸, /// 없으면 실제 장착 중인 테마를, 그마저도 없으면 null(기본 모습)을 돌려준다. ShopTemplate? _resolvePreviewTemplate() { @@ -78,6 +92,7 @@ class _ProfileCustomizeScreenState extends State { widget.studentId, templateId, ); + if (success) await _loadEquippedTheme(); if (!mounted) return; AppNotice.show( context, @@ -89,7 +104,7 @@ class _ProfileCustomizeScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppPalette.mist, + backgroundColor: _equippedTheme?.background ?? AppPalette.mist, appBar: AppBar( backgroundColor: Colors.transparent, foregroundColor: AppPalette.ink, @@ -133,7 +148,7 @@ class _ProfileCustomizeScreenState extends State { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), decoration: BoxDecoration( - color: AppPalette.ink, + color: _equippedTheme?.secondary ?? AppPalette.ink, borderRadius: BorderRadius.circular(18), ), child: Row( @@ -231,6 +246,7 @@ class _ProfileCustomizeScreenState extends State { _DefaultTemplateTile( equipped: !hasEquippedTemplate, previewing: previewId == null, + theme: _equippedTheme, onEquip: () => _handleEquip(null), onPreview: () => setState(() => _previewTemplateId = -1), ), @@ -239,6 +255,7 @@ class _ProfileCustomizeScreenState extends State { template: template, canAfford: _controller.cashBalance >= template.price, previewing: previewId == template.id, + theme: _equippedTheme, onPurchase: () => _handlePurchase(template), onEquip: () => _handleEquip(template.id), onPreview: () => setState(() => _previewTemplateId = template.id), @@ -346,25 +363,28 @@ class _CategoryTile extends StatelessWidget { class _DefaultTemplateTile extends StatelessWidget { final bool equipped; final bool previewing; + final ProfileTheme? theme; final VoidCallback onEquip; final VoidCallback onPreview; const _DefaultTemplateTile({ required this.equipped, required this.previewing, + required this.theme, required this.onEquip, required this.onPreview, }); @override Widget build(BuildContext context) { + final onBackground = theme?.onBackground ?? AppPalette.ink; final borderColor = equipped ? AppPalette.approved - : (previewing ? AppPalette.ink : AppPalette.sage); + : (previewing ? onBackground : (theme?.border ?? AppPalette.sage)); final borderWidth = equipped || previewing ? 2.0 : 1.0; return Material( - color: AppPalette.paper, + color: theme?.surface ?? AppPalette.paper, borderRadius: BorderRadius.circular(18), child: InkWell( borderRadius: BorderRadius.circular(18), @@ -390,7 +410,7 @@ class _DefaultTemplateTile extends StatelessWidget { const SizedBox(height: 10), Row( children: [ - const Expanded( + Expanded( child: Text( '기본 모습', maxLines: 1, @@ -398,16 +418,17 @@ class _DefaultTemplateTile extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 13, + color: onBackground, ), ), ), if (previewing && !equipped) - const Text( + Text( '미리보기', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, - color: AppPalette.ink, + color: onBackground, ), ), ], @@ -426,6 +447,12 @@ class _DefaultTemplateTile extends StatelessWidget { ) : OutlinedButton( onPressed: onEquip, + style: OutlinedButton.styleFrom( + foregroundColor: theme?.accent, + side: theme == null + ? null + : BorderSide(color: theme!.accent), + ), child: const Text('장착하기'), ), ), @@ -441,6 +468,7 @@ class _TemplateTile extends StatelessWidget { final ShopTemplate template; final bool canAfford; final bool previewing; + final ProfileTheme? theme; final VoidCallback onPurchase; final VoidCallback onEquip; final VoidCallback onPreview; @@ -449,6 +477,7 @@ class _TemplateTile extends StatelessWidget { required this.template, required this.canAfford, required this.previewing, + required this.theme, required this.onPurchase, required this.onEquip, required this.onPreview, @@ -456,13 +485,14 @@ class _TemplateTile extends StatelessWidget { @override Widget build(BuildContext context) { + final onBackground = theme?.onBackground ?? AppPalette.ink; final borderColor = template.equipped ? AppPalette.approved - : (previewing ? AppPalette.ink : AppPalette.sage); + : (previewing ? onBackground : (theme?.border ?? AppPalette.sage)); final borderWidth = template.equipped || previewing ? 2.0 : 1.0; return Material( - color: AppPalette.paper, + color: theme?.surface ?? AppPalette.paper, borderRadius: BorderRadius.circular(18), child: InkWell( borderRadius: BorderRadius.circular(18), @@ -497,19 +527,20 @@ class _TemplateTile extends StatelessWidget { template.name, maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.bold, fontSize: 13, + color: onBackground, ), ), ), if (previewing && !template.equipped) - const Text( + Text( '미리보기', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, - color: AppPalette.ink, + color: onBackground, ), ), ], @@ -517,7 +548,10 @@ class _TemplateTile extends StatelessWidget { const SizedBox(height: 2), Text( '${template.price} 캐시', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), + style: TextStyle( + fontSize: 12, + color: onBackground.withValues(alpha: 0.6), + ), ), const SizedBox(height: 8), if (template.equipped) @@ -537,6 +571,12 @@ class _TemplateTile extends StatelessWidget { width: double.infinity, child: OutlinedButton( onPressed: onEquip, + style: OutlinedButton.styleFrom( + foregroundColor: theme?.accent, + side: theme == null + ? null + : BorderSide(color: theme!.accent), + ), child: const Text('장착하기'), ), ) @@ -546,6 +586,12 @@ class _TemplateTile extends StatelessWidget { Expanded( child: OutlinedButton( onPressed: onPreview, + style: OutlinedButton.styleFrom( + foregroundColor: theme?.accent, + side: theme == null + ? null + : BorderSide(color: theme!.accent), + ), child: const Text('체험하기'), ), ), @@ -554,7 +600,7 @@ class _TemplateTile extends StatelessWidget { child: ElevatedButton( onPressed: canAfford ? onPurchase : null, style: ElevatedButton.styleFrom( - backgroundColor: AppPalette.ink, + backgroundColor: theme?.secondary ?? AppPalette.ink, foregroundColor: Colors.white, disabledBackgroundColor: AppPalette.sage, ),