205 lines
6.2 KiB
Dart
205 lines
6.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// 배지(Chip) 스타일 매핑과 헬퍼
|
|
class BadgeStyles {
|
|
// 색상 팔레트
|
|
static const Color primary = Color(0xFF1565C0); // Blue 700
|
|
static const Color success = Color(0xFF2E7D32); // Green 800
|
|
static const Color warning = Color(0xFFEF6C00); // Orange 800
|
|
static const Color danger = Color(0xFFC62828); // Red 800
|
|
|
|
// 배경 톤 (100 계열 느낌)
|
|
static final Color primaryBg = Colors.blue.shade100;
|
|
static final Color successBg = Colors.green.shade100;
|
|
static final Color dangerBg = Colors.red.shade100;
|
|
static final Color neutralBg = Colors.grey.shade200;
|
|
|
|
// 회원 유형 컬러/아이콘 매핑
|
|
static Chip memberType(String type) {
|
|
late final Color bg;
|
|
late final IconData icon;
|
|
switch (type) {
|
|
case '정회원':
|
|
bg = primaryBg;
|
|
icon = Icons.person;
|
|
break;
|
|
case '준회원':
|
|
bg = Colors.indigo.shade100;
|
|
icon = Icons.person_outline;
|
|
break;
|
|
case '게스트':
|
|
bg = neutralBg;
|
|
icon = Icons.person_add_alt;
|
|
break;
|
|
default:
|
|
bg = neutralBg;
|
|
icon = Icons.person_outline;
|
|
}
|
|
return _chip(type, bg, icon);
|
|
}
|
|
|
|
// 게스트용 초소형 원형 배지 (이니셜 'G')
|
|
static Widget guestSmall({double size = 18, Color? bg, Color? fg}) {
|
|
final Color bgColor = bg ?? neutralBg;
|
|
final Color fgColor = fg ?? Colors.black87;
|
|
return Container(
|
|
width: size,
|
|
height: size,
|
|
decoration: BoxDecoration(
|
|
color: bgColor,
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
'G',
|
|
style: TextStyle(
|
|
fontSize: size * 0.62,
|
|
fontWeight: FontWeight.w700,
|
|
color: fgColor,
|
|
height: 1.0,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 참가 상태 컬러/아이콘 매핑
|
|
static Chip participantStatus(String status) {
|
|
late final Color bg;
|
|
late final IconData icon;
|
|
// status는 한국어 라벨 또는 API 값(pending/confirmed/canceled/attended)로 들어올 수 있음
|
|
final s = status.toLowerCase();
|
|
String label;
|
|
if (s == 'pending' || s == '참가예정' || s == '등록됨' || s == '대기') {
|
|
bg = neutralBg;
|
|
icon = Icons.how_to_reg;
|
|
label = '예정';
|
|
} else if (s == 'confirmed' || s == '참가확정' || s == '확인됨') {
|
|
bg = successBg;
|
|
icon = Icons.check_circle;
|
|
label = '확정';
|
|
} else if (s == 'canceled' || s == '취소' || s == '취소됨') {
|
|
bg = dangerBg;
|
|
icon = Icons.cancel;
|
|
label = '취소';
|
|
} else if (s == 'attended' || s == '참석' || s == '참석함') {
|
|
bg = primaryBg;
|
|
icon = Icons.event_available;
|
|
label = '참석';
|
|
} else {
|
|
bg = neutralBg;
|
|
icon = Icons.help_outline;
|
|
label = status;
|
|
}
|
|
return _chip(label, bg, icon);
|
|
}
|
|
|
|
// 회원 상태 컬러/아이콘 매핑 (라벨 기준: 활성/비활성/정지/삭제됨)
|
|
static Chip memberStatus(String label) {
|
|
late final Color bg;
|
|
late final IconData icon;
|
|
switch (label) {
|
|
case '활성':
|
|
bg = successBg;
|
|
icon = Icons.check_circle;
|
|
break;
|
|
case '비활성':
|
|
bg = neutralBg;
|
|
icon = Icons.pause_circle_filled;
|
|
break;
|
|
case '정지':
|
|
bg = dangerBg;
|
|
icon = Icons.block;
|
|
break;
|
|
case '삭제됨':
|
|
bg = Colors.grey.shade300;
|
|
icon = Icons.delete_forever;
|
|
break;
|
|
default:
|
|
bg = neutralBg;
|
|
icon = Icons.person;
|
|
}
|
|
return _chip(label, bg, icon);
|
|
}
|
|
|
|
// 결제 상태 컬러/아이콘 매핑
|
|
static Chip payment(bool isPaid) {
|
|
return _chip(
|
|
isPaid ? '납부' : '미납',
|
|
isPaid ? successBg : dangerBg,
|
|
isPaid ? Icons.check_circle : Icons.cancel,
|
|
);
|
|
}
|
|
|
|
// 이벤트 상태 컬러/아이콘 매핑
|
|
static Chip eventStatus(String status) {
|
|
late final Color bg;
|
|
late final IconData icon;
|
|
// DB 원시값(영문) 또는 한국어 라벨을 모두 지원하도록 정규화
|
|
final s = status.toLowerCase().trim();
|
|
String label = status; // 표시용 라벨 (한국어)
|
|
if (s == 'active' || s == 'enabled' || s == '활성') {
|
|
label = '활성';
|
|
bg = successBg;
|
|
icon = Icons.check_circle;
|
|
} else if (s == 'pending' ||
|
|
s == 'draft' ||
|
|
s == 'scheduled' ||
|
|
s == 'waiting' ||
|
|
s == '대기') {
|
|
label = '대기';
|
|
bg = Colors.amber.shade100;
|
|
icon = Icons.hourglass_top;
|
|
} else if (s == 'canceled' || s == 'cancelled' || s == '취소') {
|
|
label = '취소';
|
|
bg = dangerBg;
|
|
icon = Icons.cancel;
|
|
} else if (s == 'completed' ||
|
|
s == 'done' ||
|
|
s == 'finished' ||
|
|
s == '완료') {
|
|
label = '완료';
|
|
bg = Colors.grey.shade300;
|
|
icon = Icons.done_all;
|
|
} else {
|
|
// 알 수 없는 상태는 중립 처리
|
|
label = status;
|
|
bg = neutralBg;
|
|
icon = Icons.event;
|
|
}
|
|
return _chip(label, bg, icon);
|
|
}
|
|
|
|
static Chip _chip(String text, Color bg, IconData icon) {
|
|
// 배경 대비에 따라 텍스트/아이콘 색상을 자동 설정 (흰/검정 중 더 높은 대비 선택)
|
|
double lumBg = bg.computeLuminance();
|
|
// 대비비 계산 함수 (WCAG 근사)
|
|
double contrast(double lum1, double lum2) {
|
|
final double l1 = lum1 > lum2 ? lum1 : lum2;
|
|
final double l2 = lum1 > lum2 ? lum2 : lum1;
|
|
return (l1 + 0.05) / (l2 + 0.05);
|
|
}
|
|
|
|
final double contrastWithWhite = contrast(
|
|
lumBg,
|
|
Colors.white.computeLuminance(),
|
|
);
|
|
final double contrastWithBlack = contrast(
|
|
lumBg,
|
|
Colors.black.computeLuminance(),
|
|
);
|
|
final Color fg = contrastWithWhite >= contrastWithBlack
|
|
? Colors.white
|
|
: Colors.black;
|
|
return Chip(
|
|
avatar: Icon(icon, size: 14, color: fg),
|
|
label: Text(text, style: TextStyle(color: fg, fontSize: 11)),
|
|
backgroundColor: bg,
|
|
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 0),
|
|
labelPadding: const EdgeInsets.only(left: -2, right: 5),
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
visualDensity: const VisualDensity(horizontal: -3, vertical: -3),
|
|
);
|
|
}
|
|
}
|