Files
school-attendance/lib/function/profile_shop_controller.dart
sihoo 726d2dc632 커스텀창 - "체험하기"를 누르면 화면 전체 테마가 즉시 바뀌도록 수정
기존엔 미리보기가 큰 타일 하나만 바꾸고, 배경/카드 등 화면 전체는 실제
장착된 테마를 따로 불러와 보여줬다. 이제는 _resolvePreviewTemplate()
하나로 "지금 보여줄 테마"를 통일해서, 체험하기로 고른 템플릿이 배경색/
보유 캐시 바/카드 전부에 즉시 반영된다. "기본 모습"을 고르면 화면 전체가
원래 모습으로 돌아간다.

ShopTemplate에 code 필드를 추가해서 profileThemes 맵을 바로 찾아 쓸 수
있게 했다.
2026-10-01 06:29:40 +00:00

150 lines
4.6 KiB
Dart

// 🛍️ 프로필 꾸미기 상점 기능(서버 통신/상태) 담당 컨트롤러.
// 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 code;
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.code,
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<String, dynamic> json) {
return ShopTemplate(
id: json['id'] as int,
code: json['code'] as String,
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<ShopTemplate> templates = [];
void _safeNotify() {
if (hasListeners) notifyListeners();
}
Future<void> 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<String, dynamic>))
.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<ProfileTheme?> 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;
}
}