// ๐Ÿ›๏ธ ํ”„๋กœํ•„ ๊พธ๋ฏธ๊ธฐ ์ƒ์  ๊ธฐ๋Šฅ(์„œ๋ฒ„ ํ†ต์‹ /์ƒํƒœ) ๋‹ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ. // UI๋Š” lib/ui/profile_customize_screen.dart๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. import 'dart:convert'; 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; final String category; final String name; final int price; final Color primaryColor; final Color secondaryColor; final bool owned; final bool equipped; ShopTemplate({ required this.id, required this.category, required this.name, required this.price, required this.primaryColor, required this.secondaryColor, required this.owned, required this.equipped, }); factory ShopTemplate.fromJson(Map json) { return ShopTemplate( id: json['id'] as int, category: json['category'] as String, name: json['name'] as String, price: json['price'] as int, primaryColor: _parseColor(json['primaryColor'] as String), secondaryColor: _parseColor(json['secondaryColor'] as String), owned: json['owned'] == true, equipped: json['equipped'] == true, ); } } Color _parseColor(String hex) { return Color(int.parse(hex.substring(2), radix: 16)); } class ProfileShopController extends ChangeNotifier { bool _isLoading = false; bool get isLoading => _isLoading; int cashBalance = 0; List templates = []; void _safeNotify() { if (hasListeners) notifyListeners(); } Future loadCatalog(String studentId) async { _isLoading = true; _safeNotify(); try { final url = Uri.parse( '$baseUrl/api/shop/catalog?studentId=${Uri.encodeComponent(studentId)}', ); final response = await http.get(url); if (response.statusCode == 200) { final data = jsonDecode(response.body); cashBalance = (data['cashBalance'] as num?)?.toInt() ?? 0; templates = ((data['templates'] as List?) ?? []) .map((t) => ShopTemplate.fromJson(t as Map)) .toList(); } } catch (_) { // ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ์—ฌ๋„ ํ™”๋ฉด์€ ๋นˆ ์ƒํƒœ๋กœ๋ผ๋„ ๊ทธ๋ ค์ง€๊ฒŒ ์กฐ์šฉํžˆ ๋„˜์–ด๊ฐ„๋‹ค. } finally { _isLoading = false; _safeNotify(); } } Future<(bool success, String message)> purchase( String studentId, int templateId, ) async { try { final url = Uri.parse('$baseUrl/api/shop/purchase'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({'studentId': studentId, 'templateId': templateId}), ); final data = jsonDecode(response.body); final success = data['status'] == 'success'; if (success) { await loadCatalog(studentId); } return (success, '${data['message'] ?? (success ? '๊ตฌ๋งค ์™„๋ฃŒ!' : '๊ตฌ๋งค ์‹คํŒจ')}'); } catch (e) { return (false, '์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (๋„คํŠธ์›Œํฌ ์—๋Ÿฌ)'); } } Future<(bool success, String message)> equip( String studentId, int? templateId, ) async { try { final url = Uri.parse('$baseUrl/api/shop/equip'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({'studentId': studentId, 'templateId': templateId}), ); final data = jsonDecode(response.body); final success = data['status'] == 'success'; if (success) { await loadCatalog(studentId); } return (success, '${data['message'] ?? (success ? '์žฅ์ฐฉ ์™„๋ฃŒ' : '์žฅ์ฐฉ ์‹คํŒจ')}'); } catch (e) { return (false, '์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (๋„คํŠธ์›Œํฌ ์—๋Ÿฌ)'); } } /// ๐Ÿ  ๋ฉ”์ธ ๋Œ€์‹œ๋ณด๋“œ ํ”„๋กœํ•„ ์นด๋“œ ๋ Œ๋”๋ง ์ „์šฉ - ์ƒ์  ํ™”๋ฉด์„ ์—ด์ง€ ์•Š๊ณ ๋„ /// ํ˜„์žฌ ์žฅ์ฐฉ ์ค‘์ธ ํ…Œ๋งˆ ์ƒ‰์ƒ๋งŒ ๊ฐ€๋ณ๊ฒŒ ์กฐํšŒํ•œ๋‹ค. static Future fetchEquippedTheme(String studentId) async { try { final url = Uri.parse( '$baseUrl/api/shop/equipped?studentId=${Uri.encodeComponent(studentId)}', ); final response = await http.get(url); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['equipped'] == true) { return profileThemes[data['code'] as String?]; } } } catch (_) { // ์‹คํŒจํ•˜๋ฉด ๊ทธ๋ƒฅ ๊ธฐ๋ณธ ๋ชจ์Šต์œผ๋กœ ๋ณด์—ฌ์ค€๋‹ค. } return null; } }