feat: adopt material 3 theme and billing adjustments
This commit is contained in:
@@ -32,17 +32,9 @@ class AnimationControllerHelper {
|
||||
pulseController.duration = const Duration(milliseconds: 1500);
|
||||
pulseController.repeat(reverse: true);
|
||||
|
||||
// 웨이브 컨트롤러 초기화
|
||||
// 웨이브 컨트롤러 초기화: 반복으로 부드럽게 루프
|
||||
waveController.duration = const Duration(milliseconds: 8000);
|
||||
waveController.forward();
|
||||
|
||||
// 웨이브 애니메이션이 끝나면 다시 처음부터 부드럽게 시작하도록 설정
|
||||
waveController.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
waveController.reset();
|
||||
waveController.forward();
|
||||
}
|
||||
});
|
||||
waveController.repeat();
|
||||
}
|
||||
|
||||
/// 모든 애니메이션 컨트롤러를 재설정하는 메서드
|
||||
|
||||
103
lib/utils/billing_date_util.dart
Normal file
103
lib/utils/billing_date_util.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'business_day_util.dart';
|
||||
|
||||
/// 결제 주기 및 결제일 계산 유틸리티
|
||||
class BillingDateUtil {
|
||||
/// 결제 주기를 표준 키로 정규화합니다.
|
||||
/// 반환값 예: 'monthly' | 'quarterly' | 'half-yearly' | 'yearly' | 'weekly'
|
||||
static String normalizeCycle(String cycle) {
|
||||
final c = cycle.trim().toLowerCase();
|
||||
|
||||
// 영어 우선 매핑
|
||||
if (c.contains('monthly')) return 'monthly';
|
||||
if (c.contains('quarter')) return 'quarterly';
|
||||
if (c.contains('half') || c.contains('half-year')) return 'half-yearly';
|
||||
if (c.contains('year')) return 'yearly';
|
||||
if (c.contains('week')) return 'weekly';
|
||||
|
||||
// 한국어
|
||||
if (cycle.contains('매월') || cycle.contains('월간')) return 'monthly';
|
||||
if (cycle.contains('분기')) return 'quarterly';
|
||||
if (cycle.contains('반기')) return 'half-yearly';
|
||||
if (cycle.contains('매년') || cycle.contains('연간')) return 'yearly';
|
||||
if (cycle.contains('주간')) return 'weekly';
|
||||
|
||||
// 일본어
|
||||
if (cycle.contains('毎月')) return 'monthly';
|
||||
if (cycle.contains('四半期')) return 'quarterly';
|
||||
if (cycle.contains('半年')) return 'half-yearly';
|
||||
if (cycle.contains('年間')) return 'yearly';
|
||||
if (cycle.contains('週間')) return 'weekly';
|
||||
|
||||
// 중국어(간체/번체 공통 표현 대응)
|
||||
if (cycle.contains('每月')) return 'monthly';
|
||||
if (cycle.contains('每季度')) return 'quarterly';
|
||||
if (cycle.contains('每半年')) return 'half-yearly';
|
||||
if (cycle.contains('每年')) return 'yearly';
|
||||
if (cycle.contains('每周') || cycle.contains('每週')) return 'weekly';
|
||||
|
||||
// 기본값
|
||||
return 'monthly';
|
||||
}
|
||||
|
||||
/// 선택된 날짜가 오늘(또는 과거)이면, 결제 주기에 맞춰 다음 회차 날짜로 보정합니다.
|
||||
/// 이미 미래라면 해당 날짜를 그대로 반환합니다.
|
||||
static DateTime ensureFutureDate(DateTime selected, String cycle) {
|
||||
final normalized = normalizeCycle(cycle);
|
||||
final selectedDateOnly =
|
||||
DateTime(selected.year, selected.month, selected.day);
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
if (selectedDateOnly.isAfter(today)) return selectedDateOnly;
|
||||
|
||||
DateTime next = selectedDateOnly;
|
||||
switch (normalized) {
|
||||
case 'weekly':
|
||||
while (!next.isAfter(today)) {
|
||||
next = next.add(const Duration(days: 7));
|
||||
}
|
||||
break;
|
||||
case 'quarterly':
|
||||
while (!next.isAfter(today)) {
|
||||
next = _addMonthsClamped(next, 3);
|
||||
}
|
||||
break;
|
||||
case 'half-yearly':
|
||||
while (!next.isAfter(today)) {
|
||||
next = _addMonthsClamped(next, 6);
|
||||
}
|
||||
break;
|
||||
case 'yearly':
|
||||
while (!next.isAfter(today)) {
|
||||
next = _addYearsClamped(next, 1);
|
||||
}
|
||||
break;
|
||||
case 'monthly':
|
||||
default:
|
||||
while (!next.isAfter(today)) {
|
||||
next = _addMonthsClamped(next, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
/// month 단위 가산 시, 대상 월의 말일을 넘지 않도록 day를 클램프합니다.
|
||||
static DateTime _addMonthsClamped(DateTime base, int months) {
|
||||
final totalMonths = base.month - 1 + months;
|
||||
final year = base.year + totalMonths ~/ 12;
|
||||
final month = totalMonths % 12 + 1;
|
||||
final dim = BusinessDayUtil.daysInMonth(year, month);
|
||||
final day = base.day.clamp(1, dim);
|
||||
return DateTime(year, month, day);
|
||||
}
|
||||
|
||||
/// year 단위 가산 시, 대상 월의 말일을 넘지 않도록 day를 클램프합니다.
|
||||
static DateTime _addYearsClamped(DateTime base, int years) {
|
||||
final year = base.year + years;
|
||||
final dim = BusinessDayUtil.daysInMonth(year, base.month);
|
||||
final day = base.day.clamp(1, dim);
|
||||
return DateTime(year, base.month, day);
|
||||
}
|
||||
}
|
||||
39
lib/utils/business_day_util.dart
Normal file
39
lib/utils/business_day_util.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
/// 영업일 계산 유틸리티
|
||||
/// - 주말(토/일)과 일부 고정 공휴일을 제외하고 다음 영업일을 계산합니다.
|
||||
/// - 음력 기반 공휴일(설/추석 등)은 포함하지 않습니다. 필요 시 외부 소스 연동을 고려하세요.
|
||||
class BusinessDayUtil {
|
||||
static bool isWeekend(DateTime date) =>
|
||||
date.weekday == DateTime.saturday || date.weekday == DateTime.sunday;
|
||||
|
||||
/// 고정일 한국 공휴일(대체공휴일 미포함)
|
||||
static const List<String> _fixedHolidays = [
|
||||
'01-01', // 신정
|
||||
'03-01', // 삼일절
|
||||
'05-05', // 어린이날
|
||||
'06-06', // 현충일
|
||||
'08-15', // 광복절
|
||||
'10-03', // 개천절
|
||||
'10-09', // 한글날
|
||||
'12-25', // 성탄절
|
||||
];
|
||||
|
||||
static bool isFixedKoreanHoliday(DateTime date) {
|
||||
final key = '${_two(date.month)}-${_two(date.day)}';
|
||||
return _fixedHolidays.contains(key);
|
||||
}
|
||||
|
||||
static String _two(int n) => n.toString().padLeft(2, '0');
|
||||
|
||||
/// 입력 날짜가 주말/고정 공휴일이면 다음 영업일로 전진합니다.
|
||||
static DateTime nextBusinessDay(DateTime date) {
|
||||
var d = DateTime(date.year, date.month, date.day);
|
||||
while (isWeekend(d) || isFixedKoreanHoliday(d)) {
|
||||
d = d.add(const Duration(days: 1));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/// 대상 월의 말일을 반환합니다.
|
||||
static int daysInMonth(int year, int month) =>
|
||||
DateTime(year, month + 1, 0).day;
|
||||
}
|
||||
Reference in New Issue
Block a user