4 Commits

Author SHA1 Message Date
JiWoong Sul
37e797f6c1 chore: 버전 1.0.8+10 업데이트 2026-01-30 15:33:08 +09:00
JiWoong Sul
903906c880 fix(billing): 월별 비용 계산 시 연도 무시 버그 수정
- hasBillingInMonth()에서 targetYear를 실제로 사용하도록 수정
- 연간 구독 수정 시 잘못된 월에 비용이 포함되던 문제 해결
- 연도+월을 포함한 개월 차이 계산으로 정확한 결제 발생 여부 판단
2026-01-29 23:55:57 +09:00
JiWoong Sul
5de33992a2 fix(sms-scan): 에러 메시지를 토스트로 변경
- 구독 정보를 찾지 못한 경우 상단 붉은색 텍스트 대신 하단 토스트 메시지 출력
- ScanInitialWidget에서 errorMessage 파라미터 제거
- SmsScanController에서 AppSnackBar.showError() 사용
- 불필요한 _errorMessage 상태 변수 제거
2026-01-29 23:55:49 +09:00
JiWoong Sul
cc8bcc7b54 refactor(home): 구독 개수 표시에서 화살표 아이콘 제거
- 메인 화면의 구독 개수 표시 우측 ">" 아이콘 삭제
- 불필요한 UI 요소 정리
2026-01-29 23:55:39 +09:00
6 changed files with 39 additions and 55 deletions

View File

@@ -17,15 +17,13 @@ import '../providers/category_provider.dart';
import '../providers/payment_card_provider.dart'; import '../providers/payment_card_provider.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../utils/logger.dart'; import '../utils/logger.dart';
import '../widgets/common/snackbar/app_snackbar.dart';
class SmsScanController extends ChangeNotifier { class SmsScanController extends ChangeNotifier {
// 상태 관리 // 상태 관리
bool _isLoading = false; bool _isLoading = false;
bool get isLoading => _isLoading; bool get isLoading => _isLoading;
String? _errorMessage;
String? get errorMessage => _errorMessage;
List<Subscription> _scannedSubscriptions = []; List<Subscription> _scannedSubscriptions = [];
List<Subscription> get scannedSubscriptions => _scannedSubscriptions; List<Subscription> get scannedSubscriptions => _scannedSubscriptions;
PaymentCardSuggestion? _currentSuggestion; PaymentCardSuggestion? _currentSuggestion;
@@ -109,7 +107,6 @@ class SmsScanController extends ChangeNotifier {
Future<void> scanSms(BuildContext context) async { Future<void> scanSms(BuildContext context) async {
_isLoading = true; _isLoading = true;
_errorMessage = null;
_scannedSubscriptions = []; _scannedSubscriptions = [];
_currentIndex = 0; _currentIndex = 0;
notifyListeners(); notifyListeners();
@@ -137,9 +134,12 @@ class SmsScanController extends ChangeNotifier {
final req = await permission.Permission.sms.request(); final req = await permission.Permission.sms.request();
if (!ctx.mounted) return; if (!ctx.mounted) return;
if (!req.isGranted) { if (!req.isGranted) {
// 거부됨: 안내 후 종료 // 거부됨: 토스트 표시 후 종료
if (!ctx.mounted) return; if (!ctx.mounted) return;
_errorMessage = AppLocalizations.of(ctx).smsPermissionRequired; AppSnackBar.showError(
context: ctx,
message: AppLocalizations.of(ctx).smsPermissionRequired,
);
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
return; return;
@@ -162,7 +162,10 @@ class SmsScanController extends ChangeNotifier {
if (scanResults.isEmpty) { if (scanResults.isEmpty) {
Log.i('스캔된 구독이 없음'); Log.i('스캔된 구독이 없음');
_errorMessage = AppLocalizations.of(context).subscriptionNotFound; AppSnackBar.showError(
context: context,
message: AppLocalizations.of(context).subscriptionNotFound,
);
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
return; return;
@@ -184,7 +187,10 @@ class SmsScanController extends ChangeNotifier {
if (repeatSubscriptions.isEmpty) { if (repeatSubscriptions.isEmpty) {
Log.i('반복 결제된 구독이 없음'); Log.i('반복 결제된 구독이 없음');
_errorMessage = AppLocalizations.of(context).repeatSubscriptionNotFound; AppSnackBar.showError(
context: context,
message: AppLocalizations.of(context).repeatSubscriptionNotFound,
);
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
return; return;
@@ -223,8 +229,10 @@ class SmsScanController extends ChangeNotifier {
} catch (e) { } catch (e) {
Log.e('SMS 스캔 중 오류 발생', e); Log.e('SMS 스캔 중 오류 발생', e);
if (context.mounted) { if (context.mounted) {
_errorMessage = AppSnackBar.showError(
AppLocalizations.of(context).smsScanErrorWithMessage(e.toString()); context: context,
message: AppLocalizations.of(context).smsScanErrorWithMessage(e.toString()),
);
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
} }
@@ -343,7 +351,6 @@ class SmsScanController extends ChangeNotifier {
void resetState() { void resetState() {
_scannedSubscriptions = []; _scannedSubscriptions = [];
_currentIndex = 0; _currentIndex = 0;
_errorMessage = null;
_selectedPaymentCardId = null; _selectedPaymentCardId = null;
_currentSuggestion = null; _currentSuggestion = null;
_shouldSuggestCardCreation = false; _shouldSuggestCardCreation = false;

View File

@@ -58,7 +58,6 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
if (_controller.scannedSubscriptions.isEmpty) { if (_controller.scannedSubscriptions.isEmpty) {
return ScanInitialWidget( return ScanInitialWidget(
onScanPressed: () => _controller.startScan(context), onScanPressed: () => _controller.startScan(context),
errorMessage: _controller.errorMessage,
); );
} }
@@ -77,7 +76,6 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
}); });
return ScanInitialWidget( return ScanInitialWidget(
onScanPressed: () => _controller.startScan(context), onScanPressed: () => _controller.startScan(context),
errorMessage: _controller.errorMessage,
); );
} }

View File

@@ -214,20 +214,20 @@ class BillingCostUtil {
// 결제 주기에 따른 개월 수 // 결제 주기에 따른 개월 수
final cycleMonths = _getCycleMonths(normalizedCycle); final cycleMonths = _getCycleMonths(normalizedCycle);
// 결제 발생 월 계산 (nextBillingDate 기준으로 역산) // 연도+월을 포함한 개월 차이 계산
final billingMonth = nextBillingDate.month; // nextBillingDate와 target 월 사이의 차이가 cycleMonths의 배수인지 확인
final targetStart = DateTime(targetYear, targetMonth, 1);
final billingStart =
DateTime(nextBillingDate.year, nextBillingDate.month, 1);
// 대상 월이 결제 발생 월과 일치하는지 확인 final monthDiff = (billingStart.year - targetStart.year) * 12 +
// 예: 연간 결제(1월), targetMonth = 1 → true (billingStart.month - targetStart.month);
// 예: 연간 결제(1월), targetMonth = 2 → false
for (int i = 0; i < 12; i += cycleMonths) {
final checkMonth = ((billingMonth - 1 + i) % 12) + 1;
if (checkMonth == targetMonth) {
return true;
}
}
return false; // monthDiff가 cycleMonths의 배수이면 해당 월에 결제 발생
// 예: 연간 결제(2027-01), target=2026-01 → monthDiff=12, 12%12=0 → true (이전 결제)
// 예: 연간 결제(2027-01), target=2026-02 → monthDiff=11, 11%12≠0 → false
// 예: 연간 결제(2027-01), target=2027-01 → monthDiff=0, 0%12=0 → true
return monthDiff % cycleMonths == 0;
} }
/// 결제 주기별 개월 수 반환 /// 결제 주기별 개월 수 반환

View File

@@ -161,24 +161,14 @@ class _HomeContentState extends State<HomeContent> {
).animate(CurvedAnimation( ).animate(CurvedAnimation(
parent: widget.slideController, parent: widget.slideController,
curve: Curves.easeOutCubic)), curve: Curves.easeOutCubic)),
child: Row( child: Text(
children: [ AppLocalizations.of(context).subscriptionCount(
Text( subscriptionProvider.subscriptions.length),
AppLocalizations.of(context).subscriptionCount( style: TextStyle(
subscriptionProvider.subscriptions.length), fontSize: 14,
style: TextStyle( fontWeight: FontWeight.w600,
fontSize: 14, color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w600, ),
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 4),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).colorScheme.primary,
),
],
), ),
), ),
], ],

View File

@@ -7,12 +7,10 @@ import '../../l10n/app_localizations.dart';
class ScanInitialWidget extends StatelessWidget { class ScanInitialWidget extends StatelessWidget {
final VoidCallback onScanPressed; final VoidCallback onScanPressed;
final String? errorMessage;
const ScanInitialWidget({ const ScanInitialWidget({
super.key, super.key,
required this.onScanPressed, required this.onScanPressed,
this.errorMessage,
}); });
@override @override
@@ -24,15 +22,6 @@ class ScanInitialWidget extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(bottom: 24.0),
child: ThemedText(
errorMessage!,
color: Theme.of(context).colorScheme.error,
textAlign: TextAlign.center,
),
),
ThemedText( ThemedText(
AppLocalizations.of(context).findRepeatSubscriptions, AppLocalizations.of(context).findRepeatSubscriptions,
fontSize: 20, fontSize: 20,

View File

@@ -1,7 +1,7 @@
name: submanager name: submanager
description: A new Flutter project. description: A new Flutter project.
publish_to: 'none' publish_to: 'none'
version: 1.0.7+9 version: 1.0.8+10
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'