diff --git a/lib/function/profile_shop_controller.dart b/lib/function/profile_shop_controller.dart index a830788..c4114e7 100644 --- a/lib/function/profile_shop_controller.dart +++ b/lib/function/profile_shop_controller.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show Color; import 'package:http/http.dart' as http; import '../config.dart'; +import '../theme/profile_themes.dart'; class ShopTemplate { final int id; @@ -125,9 +126,7 @@ class ProfileShopController extends ChangeNotifier { /// ๐Ÿ  ๋ฉ”์ธ ๋Œ€์‹œ๋ณด๋“œ ํ”„๋กœํ•„ ์นด๋“œ ๋ Œ๋”๋ง ์ „์šฉ - ์ƒ์  ํ™”๋ฉด์„ ์—ด์ง€ ์•Š๊ณ ๋„ /// ํ˜„์žฌ ์žฅ์ฐฉ ์ค‘์ธ ํ…Œ๋งˆ ์ƒ‰์ƒ๋งŒ ๊ฐ€๋ณ๊ฒŒ ์กฐํšŒํ•œ๋‹ค. - static Future<(Color? primary, Color? secondary)> fetchEquippedColors( - String studentId, - ) async { + static Future fetchEquippedTheme(String studentId) async { try { final url = Uri.parse( '$baseUrl/api/shop/equipped?studentId=${Uri.encodeComponent(studentId)}', @@ -136,15 +135,12 @@ class ProfileShopController extends ChangeNotifier { if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['equipped'] == true) { - return ( - _parseColor(data['primaryColor'] as String), - _parseColor(data['secondaryColor'] as String), - ); + return profileThemes[data['code'] as String?]; } } } catch (_) { // ์‹คํŒจํ•˜๋ฉด ๊ทธ๋ƒฅ ๊ธฐ๋ณธ ๋ชจ์Šต์œผ๋กœ ๋ณด์—ฌ์ค€๋‹ค. } - return (null, null); + return null; } } diff --git a/lib/theme/profile_themes.dart b/lib/theme/profile_themes.dart new file mode 100644 index 0000000..a9729b9 --- /dev/null +++ b/lib/theme/profile_themes.dart @@ -0,0 +1,98 @@ +// ๐ŸŽจ ํ”„๋กœํ•„ ๊พธ๋ฏธ๊ธฐ "ํ…œํ”Œ๋ฆฟ"์„ ์žฅ์ฐฉํ•˜๋ฉด ํ”„๋กœํ•„ ์นด๋“œ ์ƒ‰๋ฟ ์•„๋‹ˆ๋ผ ํ™”๋ฉด ์ „์ฒด์˜ +// ๋ฐฐ๊ฒฝ/ํƒ€์ผ/์•„์ด์ฝ˜ ์ƒ‰๊นŒ์ง€ ํ†ต์งธ๋กœ ๋ฐ”๋€Œ๋Š” "ํ…Œ๋งˆ" ๋ฐ์ดํ„ฐ. +// ํ…œํ”Œ๋ฆฟ์€ ์ƒ์ ์—์„œ ์‚ฌ๋Š” ์ƒ‰/์• ๋‹ˆ๋ฉ”์ด์…˜ ์ƒํ’ˆ, ํ…Œ๋งˆ๋Š” ๊ทธ ํ…œํ”Œ๋ฆฟ์ด ๋ชฐ๊ณ  ์˜ค๋Š” +// ํ™”๋ฉด ์ „์ฒด ๋””์ž์ธ(๋ฐฐ๊ฒฝ์ƒ‰/ํƒ€์ผ์ƒ‰/์•„์ด์ฝ˜์ƒ‰/๋ชจ์–‘)์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค - ์šฉ์–ด ๊ตฌ๋ถ„์€ +// ์‚ฌ์žฅ๋‹˜์ด ์ •ํ•ด์ค€ ๊ฒƒ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค. +// +// ํ…œํ”Œ๋ฆฟ ํ•˜๋‚˜ํ•˜๋‚˜์— ๋Œ€ํ•ด ์ง์ ‘ ๋””์ž์ธํ•œ ๊ณ ์ • ํŒ”๋ ˆํŠธ๋ผ์„œ, ์„œ๋ฒ„์—์„œ ๋‚ด๋ ค์ฃผ๋Š” +// code(์˜ˆ: "lavender")๋กœ ์ด ๋งต์„ ์ฐพ์•„ ์“ด๋‹ค. ํ…œํ”Œ๋ฆฟ์ด ๋Š˜์–ด๋‚˜๋ฉด ์—ฌ๊ธฐ๋„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. +import 'package:flutter/material.dart'; + +class ProfileTheme { + final Color primary; // ํ”„๋กœํ•„ ์นด๋“œ ๊ทธ๋ผ๋ฐ์ด์…˜ ์‹œ์ž‘์ƒ‰ (ํ…œํ”Œ๋ฆฟ ์ƒ‰) + final Color secondary; // ํ”„๋กœํ•„ ์นด๋“œ ๊ทธ๋ผ๋ฐ์ด์…˜ ๋์ƒ‰ (ํ…œํ”Œ๋ฆฟ ์ƒ‰) + final Color background; // ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ + final Color surface; // ํƒ€์ผ/์นด๋“œ ๋ฐฐ๊ฒฝ + final Color surfaceAlt; // ์•„์ด์ฝ˜ ๋™๊ทธ๋ผ๋ฏธ ๋“ฑ ๋ณด์กฐ ํ‘œ๋ฉด + final Color border; // ํƒ€์ผ ํ…Œ๋‘๋ฆฌ/๊ตฌ๋ถ„์„  + final Color accent; // ํƒ€์ผ ์•„์ด์ฝ˜ ๋“ฑ ๊ฐ•์กฐ์ƒ‰ + final Color onBackground; // ๋ฐฐ๊ฒฝ/ํƒ€์ผ ์œ„์— ์˜ฌ๋ผ๊ฐ€๋Š” ๊ธฐ๋ณธ ํ…์ŠคํŠธ ์ƒ‰ + final bool isDark; + + const ProfileTheme({ + required this.primary, + required this.secondary, + required this.background, + required this.surface, + required this.surfaceAlt, + required this.border, + required this.accent, + required this.onBackground, + this.isDark = false, + }); +} + +const Map profileThemes = { + 'lavender': ProfileTheme( + primary: Color(0xFFB39DDB), + secondary: Color(0xFF7E57C2), + background: Color(0xFFF5F0FA), + surface: Color(0xFFFFFFFF), + surfaceAlt: Color(0xFFEDE4F7), + border: Color(0xFFDCCCEF), + accent: Color(0xFF7E57C2), + onBackground: Color(0xFF1C1C1C), + ), + 'mint': ProfileTheme( + primary: Color(0xFF80CBC4), + secondary: Color(0xFF00897B), + background: Color(0xFFEAF6F4), + surface: Color(0xFFFFFFFF), + surfaceAlt: Color(0xFFDBF0EC), + border: Color(0xFFBFE3DC), + accent: Color(0xFF00897B), + onBackground: Color(0xFF1C1C1C), + ), + 'sunset': ProfileTheme( + primary: Color(0xFFFFB74D), + secondary: Color(0xFFE64A19), + background: Color(0xFFFFF3E6), + surface: Color(0xFFFFFFFF), + surfaceAlt: Color(0xFFFFE5CC), + border: Color(0xFFFFD1A6), + accent: Color(0xFFE64A19), + onBackground: Color(0xFF1C1C1C), + ), + 'ocean': ProfileTheme( + primary: Color(0xFF4FC3F7), + secondary: Color(0xFF01579B), + background: Color(0xFFEAF6FC), + surface: Color(0xFFFFFFFF), + surfaceAlt: Color(0xFFD9EEF9), + border: Color(0xFFBBDDF0), + accent: Color(0xFF01579B), + onBackground: Color(0xFF1C1C1C), + ), + 'aurora': ProfileTheme( + primary: Color(0xFF81C784), + secondary: Color(0xFF1565C0), + background: Color(0xFFEDF6EF), + surface: Color(0xFFFFFFFF), + surfaceAlt: Color(0xFFDCEEE0), + border: Color(0xFFC3E2CA), + accent: Color(0xFF1565C0), + onBackground: Color(0xFF1C1C1C), + ), + // ๐ŸŒŒ ์ตœ๊ณ ๊ฐ€ ํ…œํ”Œ๋ฆฟ - ๋‹ค๋ฅธ 5๊ฐœ์™€ ๋‹ฌ๋ฆฌ ํ†ต์งธ๋กœ ๋‹คํฌ ํ…Œ๋งˆ๋ผ์„œ ๋“ค์–ด๊ฐ€์ž๋งˆ์ž ํ™• ๋‹ค๋ฅด๊ฒŒ ๋А๊ปด์ง„๋‹ค. + 'galaxy': ProfileTheme( + primary: Color(0xFF9575CD), + secondary: Color(0xFF1A237E), + background: Color(0xFF14122B), + surface: Color(0xFF211F42), + surfaceAlt: Color(0xFF2C2A52), + border: Color(0xFF3A3768), + accent: Color(0xFFB39DDB), + onBackground: Color(0xFFF5F3FF), + isDark: true, + ), +}; diff --git a/lib/ui/main_dashboard.dart b/lib/ui/main_dashboard.dart index 8a2aa3c..8b1111e 100644 --- a/lib/ui/main_dashboard.dart +++ b/lib/ui/main_dashboard.dart @@ -12,6 +12,7 @@ import '../function/session_store.dart'; import '../function/student_dashboard_controller.dart'; import '../models/dashboard_tile_layout.dart'; import '../theme/app_palette.dart'; +import '../theme/profile_themes.dart'; import 'admin_dashboard.dart'; import 'app_notice.dart'; import 'board_home_screen.dart'; @@ -85,17 +86,17 @@ class _MainDashboardState extends State { } } - // ๐Ÿ›๏ธ ํ”„๋กœํ•„ ๊พธ๋ฏธ๊ธฐ ์ƒ์ ์—์„œ ์žฅ์ฐฉํ•œ ํ…Œ๋งˆ ์ƒ‰์ƒ (์—†์œผ๋ฉด ๊ธฐ๋ณธ ๋ชจ์Šต ๊ทธ๋Œ€๋กœ). - Color? _equippedPrimary; - Color? _equippedSecondary; + // ๐Ÿ›๏ธ ํ”„๋กœํ•„ ๊พธ๋ฏธ๊ธฐ ์ƒ์ ์—์„œ ์žฅ์ฐฉํ•œ ํ…œํ”Œ๋ฆฟ์ด ๋ชฐ๊ณ  ์˜ค๋Š” ํ…Œ๋งˆ (์—†์œผ๋ฉด ๊ธฐ๋ณธ ๋ชจ์Šต ๊ทธ๋Œ€๋กœ). + ProfileTheme? _equippedTheme; + + // ๐ŸŽจ ํƒ€์ผ ์•„์ด์ฝ˜ ๋“ฑ ๊ฐ•์กฐ์ƒ‰์— ์“ธ ์ƒ‰ - ํ…Œ๋งˆ๊ฐ€ ์—†์œผ๋ฉด ์›๋ž˜ ์“ฐ๋˜ AppPalette.ink. + Color get _tileAccent => _equippedTheme?.accent ?? AppPalette.ink; Future _loadEquippedTheme() async { - final (primary, secondary) = - await ProfileShopController.fetchEquippedColors(_displayId); + final theme = await ProfileShopController.fetchEquippedTheme(_displayId); if (!mounted) return; setState(() { - _equippedPrimary = primary; - _equippedSecondary = secondary; + _equippedTheme = theme; }); } @@ -312,7 +313,7 @@ class _MainDashboardState extends State { : Icons.lock_rounded, title: 'NFC ํƒœ๊ทธ', subtitle: widget.isDeviceMatched ? '์ถœ์„ ๋ฐ ํฐ ์ˆ˜๊ฑฐ ์™„๋ฃŒ' : '๋ณธ์ธ ์ธ์ฆ ๊ธฐ๊ธฐ ์ „์šฉ', - color: widget.isDeviceMatched ? AppPalette.ink : AppPalette.sage, + color: widget.isDeviceMatched ? _tileAccent : AppPalette.sage, onTap: widget.isDeviceMatched ? () => _openPocketCheckIn(context) : () => AppNotice.show( @@ -330,7 +331,7 @@ class _MainDashboardState extends State { icon: Icons.fastfood_rounded, title: '์‹ค์‹œ๊ฐ„ ํ•™๊ต ์ƒํ™ฉ', subtitle: '๊ธ‰์‹์‹ค ์ค„ & ๋งค์  ์žฌ๊ณ  ํ™•์ธ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => AppNotice.show( context, '์‹ค์‹œ๊ฐ„ ํ•™๊ต ์ƒํ™ฉ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.', @@ -344,7 +345,7 @@ class _MainDashboardState extends State { icon: Icons.tablet_mac_rounded, title: '์Šค๋งˆํŠธ๊ธฐ๊ธฐ ๋ฐ˜์ถœ', subtitle: 'ํŒจ๋“œ ์‚ฌ์šฉ ์‹ ์ฒญ (์‹œ๊ฐ„/๋ชฉ์ )', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => DeviceCheckoutRequestScreen( @@ -360,7 +361,7 @@ class _MainDashboardState extends State { icon: Icons.timer_rounded, title: '๋ฐฑํ’ˆํƒ€', subtitle: '๊ณต๋ถ€ ํƒ€์ด๋จธ | ํ•™๋…„๋ณ„ ๊ณต๋ถ€ ๋žญํ‚น', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => StudyTimerScreen( @@ -377,7 +378,7 @@ class _MainDashboardState extends State { icon: Icons.forum_rounded, title: '๋ฐฑํŒ', subtitle: '์ต๋ช… ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œํŒ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => BoardHomeScreen(studentId: _displayId), @@ -390,7 +391,7 @@ class _MainDashboardState extends State { icon: Icons.campaign_rounded, title: '์„ ์ƒ๋‹˜ ํ˜ธ์ถœ', subtitle: '๊ต๋ฌด์‹ค ์œ„์น˜ ํ™•์ธ ๋ฐ ํ˜ธ์ถœ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => TeacherCallScreen( @@ -406,7 +407,7 @@ class _MainDashboardState extends State { icon: Icons.people_alt_rounded, title: '์นœ๊ตฌ', subtitle: 'ํ•™๋ฒˆ์œผ๋กœ ์นœ๊ตฌ ์ถ”๊ฐ€', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => @@ -420,7 +421,7 @@ class _MainDashboardState extends State { icon: Icons.groups_rounded, title: '๊ทธ๋ฃน ์Šคํ„ฐ๋””', subtitle: '์นœ๊ตฌ๋ž‘ ๊ทธ๋ฃน ๋งŒ๋“ค์–ด ๊ฐ™์ด ๊ณต๋ถ€', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => GroupListScreen( @@ -437,7 +438,7 @@ class _MainDashboardState extends State { icon: Icons.calendar_month_rounded, title: '์ถœ์„ ๋‹ฌ๋ ฅ', subtitle: '์—ฐ์† ์ถœ์„ ๊ธฐ๋ก ํ™•์ธ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => StudyCalendarScreen(studentId: _displayId), @@ -453,7 +454,7 @@ class _MainDashboardState extends State { icon: Icons.assignment_turned_in_rounded, title: '์‹ค์‹œ๊ฐ„ ์ถœ์„ ํ™•์ธ', subtitle: 'ํ•™์ƒ ์ œ์ถœ ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ๋ง', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => const TeacherAttendancePage(), @@ -466,7 +467,7 @@ class _MainDashboardState extends State { icon: Icons.manage_accounts_rounded, title: 'ํ•™์ƒ ๊ณ„์ • ๊ด€๋ฆฌ', subtitle: '๊ณ„์ • ์ถ”๊ฐ€ ๋ฐ ๊ฐ•์ œ ๋ฆฌ์…‹', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => const TeacherStudentManagementPage(), @@ -479,7 +480,7 @@ class _MainDashboardState extends State { icon: Icons.tablet_mac_rounded, title: '์Šค๋งˆํŠธ๊ธฐ๊ธฐ ๋ฐ˜์ถœ ๋Œ€์žฅ', subtitle: 'ํŒจ๋“œ ๋ฐ˜์ถœ ์‹ ์ฒญ ์Šน์ธ/๊ฑฐ์ ˆ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => DeviceCheckoutLedgerPage( @@ -495,7 +496,7 @@ class _MainDashboardState extends State { icon: Icons.shield_outlined, title: '๋ฐฑํŒ ์‹ ๊ณ  ๊ฒ€ํ† ', subtitle: 'ํ•™์ƒ ๊ฒŒ์‹œํŒ ์‹ ๊ณ  ์ฒ˜๋ฆฌ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => BoardReportScreen(reviewerId: _displayId), @@ -508,7 +509,7 @@ class _MainDashboardState extends State { icon: Icons.event_available_rounded, title: '์ถœ์„ ๋‹ฌ๋ ฅ ๊ด€๋ฆฌ', subtitle: '๋‚ ์งœ๋ณ„ ์„ ๋ฌผ/์ด๋ฒคํŠธ ๋“ฑ๋ก', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => StudyCalendarScreen( @@ -524,7 +525,7 @@ class _MainDashboardState extends State { icon: Icons.my_location_rounded, title: '๋‚ด ์œ„์น˜ ์•Œ๋ฆฌ๊ธฐ', subtitle: 'ํ•™์ƒ ํ˜ธ์ถœ ํ™•์ธ ๋ฐ ์œ„์น˜ ๋“ฑ๋ก', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad( context, (context) => TeacherLocationScreen(teacherId: _displayId), @@ -540,7 +541,7 @@ class _MainDashboardState extends State { icon: Icons.terminal_rounded, title: '์„œ๋ฒ„ DB ์ œ์–ด', subtitle: '์‹œ์Šคํ…œ ์›๊ฒฉ ์ดˆ๊ธฐํ™”', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad(context, (context) => const AdminDashboard()), ), @@ -551,7 +552,7 @@ class _MainDashboardState extends State { icon: Icons.delete_sweep_rounded, title: '๊ณ„์ • ๊ฐ•์ œ ์‚ญ์ œ', subtitle: 'ํ•™์ƒ ๋ฐ ๊ต์‚ฌ DB ์‚ญ์ œ', - color: AppPalette.ink, + color: _tileAccent, onTap: () => _showDeleteUserDialog(context), ), )); @@ -561,7 +562,7 @@ class _MainDashboardState extends State { icon: Icons.edit_note_rounded, title: 'NFC ํƒœ๊ทธ ์“ฐ๊ธฐ', subtitle: '์ฃผ๋จธ๋‹ˆ ์Šคํ‹ฐ์ปค ์ดˆ๊ธฐ ์„ค์ •', - color: AppPalette.ink, + color: _tileAccent, onTap: () => pushLaunchpad(context, (context) => const NfcTagWriterScreen()), ), @@ -623,7 +624,7 @@ class _MainDashboardState extends State { final bool isEditing = _layout.isEditing; return Scaffold( - backgroundColor: AppPalette.mist, + backgroundColor: _equippedTheme?.background ?? AppPalette.mist, // ๐Ÿชถ ํฐ ์ƒ‰์ƒ ๋ฐฐ๋„ˆ ๋Œ€์‹  ์ž‘์€ ํ”„๋กœํ•„ ์นด๋“œ๋ฅผ ๋ณธ๋ฌธ์— ๋‘๋ฏ€๋กœ, ์•ฑ๋ฐ”๋Š” ">" ๋ฉ”๋‰ด // ๋ฒ„ํŠผ(๋˜๋Š” ํŽธ์ง‘ ์ค‘ "์™„๋ฃŒ" ๋ฒ„ํŠผ)๋งŒ ์˜ฌ๋ ค๋‘๋Š” ํˆฌ๋ช…ํ•œ ์–‡์€ ๋ฐ”๋กœ ๋‘”๋‹ค. appBar: AppBar( @@ -672,21 +673,21 @@ class _MainDashboardState extends State { vertical: 18, ), decoration: BoxDecoration( - color: _equippedPrimary == null ? theme.color : null, - gradient: _equippedPrimary == null + color: _equippedTheme == null ? theme.color : null, + gradient: _equippedTheme == null ? null : LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - _equippedPrimary!, - _equippedSecondary!, + _equippedTheme!.primary, + _equippedTheme!.secondary, ], ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: (_equippedPrimary ?? theme.color) + color: (_equippedTheme?.primary ?? theme.color) .withValues(alpha: 0.25), blurRadius: 16, offset: const Offset(0, 6), @@ -959,7 +960,7 @@ class _MainDashboardState extends State { width: 26, height: 26, decoration: BoxDecoration( - color: AppPalette.ink, + color: _tileAccent, borderRadius: BorderRadius.circular(8), border: Border.all( color: AppPalette.paper, @@ -1026,9 +1027,12 @@ class _MainDashboardState extends State { child: Ink( padding: EdgeInsets.all(cardPadding), decoration: BoxDecoration( - color: AppPalette.paper, + color: _equippedTheme?.surface ?? AppPalette.paper, borderRadius: BorderRadius.circular(cardRadius), - border: Border.all(color: AppPalette.sage, width: 1), + border: Border.all( + color: _equippedTheme?.border ?? AppPalette.sage, + width: 1, + ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1037,7 +1041,7 @@ class _MainDashboardState extends State { Container( padding: EdgeInsets.all(iconBoxPadding), decoration: BoxDecoration( - color: AppPalette.linen, + color: _equippedTheme?.surfaceAlt ?? AppPalette.linen, borderRadius: BorderRadius.circular(iconRadius), ), child: Icon(icon, color: color, size: iconSize), @@ -1056,7 +1060,9 @@ class _MainDashboardState extends State { style: TextStyle( fontSize: titleFontSize, fontWeight: FontWeight.bold, - color: AppPalette.ink, + color: + _equippedTheme?.onBackground ?? + AppPalette.ink, ), ), ), @@ -1081,7 +1087,8 @@ class _MainDashboardState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: subtitleFontSize, - color: AppPalette.ink.withValues(alpha: 0.55), + color: (_equippedTheme?.onBackground ?? AppPalette.ink) + .withValues(alpha: 0.55), height: 1.15, ), ),