6 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
JiWoong Sul
9a950ee6c7 chore: 버전 1.0.7+9 업데이트 2026-01-21 17:01:32 +09:00
JiWoong Sul
88569a57bf chore: 스플래시 화면 저작권 텍스트에 cclabs 추가 2026-01-21 17:01:22 +09:00
7 changed files with 40 additions and 56 deletions

View File

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

View File

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

View File

@@ -384,7 +384,7 @@ class _SplashScreenState extends State<SplashScreen>
child: FadeTransition(
opacity: _fadeAnimation,
child: Text(
'© 2025 NatureBridgeAI. All rights reserved.',
'© 2025 NatureBridgeAI @ cclabs. All rights reserved.',
style: TextStyle(
fontSize: 12,
color: Theme.of(context)

View File

@@ -214,20 +214,20 @@ class BillingCostUtil {
// 결제 주기에 따른 개월 수
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);
// 대상 월이 결제 발생 월과 일치하는지 확인
// 예: 연간 결제(1월), targetMonth = 1 → true
// 예: 연간 결제(1월), targetMonth = 2 → false
for (int i = 0; i < 12; i += cycleMonths) {
final checkMonth = ((billingMonth - 1 + i) % 12) + 1;
if (checkMonth == targetMonth) {
return true;
}
}
final monthDiff = (billingStart.year - targetStart.year) * 12 +
(billingStart.month - targetStart.month);
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,9 +161,7 @@ class _HomeContentState extends State<HomeContent> {
).animate(CurvedAnimation(
parent: widget.slideController,
curve: Curves.easeOutCubic)),
child: Row(
children: [
Text(
child: Text(
AppLocalizations.of(context).subscriptionCount(
subscriptionProvider.subscriptions.length),
style: TextStyle(
@@ -172,14 +170,6 @@ class _HomeContentState extends State<HomeContent> {
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 {
final VoidCallback onScanPressed;
final String? errorMessage;
const ScanInitialWidget({
super.key,
required this.onScanPressed,
this.errorMessage,
});
@override
@@ -24,15 +22,6 @@ class ScanInitialWidget extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
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(
AppLocalizations.of(context).findRepeatSubscriptions,
fontSize: 20,

View File

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