fix(sms-permission): re-request on denial and guide permanent denial to app settings
Summary: Improve SMS permission UX so users can request again after denial and are guided to app settings when permanently denied.\nChanges: handle Permission.sms status in controllers, show settings dialog for permanently denied, use kIsWeb guard, context-safety across async.\nValidation: scripts/check.sh passed (analyze/tests OK).\nRisk & Rollback: low; scoped to permission request flow. Revert two controllers if issues.
This commit is contained in:
@@ -8,6 +8,9 @@ import '../services/sms_service.dart';
|
||||
import '../services/subscription_url_matcher.dart';
|
||||
import '../widgets/common/snackbar/app_snackbar.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../utils/billing_date_util.dart';
|
||||
import '../utils/business_day_util.dart';
|
||||
import 'package:permission_handler/permission_handler.dart' as permission;
|
||||
|
||||
/// AddSubscriptionScreen의 비즈니스 로직을 관리하는 Controller
|
||||
class AddSubscriptionController {
|
||||
@@ -104,6 +107,26 @@ class AddSubscriptionController {
|
||||
scrollOffset = scrollController.offset;
|
||||
});
|
||||
|
||||
// 언어별 기본 통화 설정
|
||||
try {
|
||||
final lang = Localizations.localeOf(context).languageCode;
|
||||
switch (lang) {
|
||||
case 'ko':
|
||||
currency = 'KRW';
|
||||
break;
|
||||
case 'ja':
|
||||
currency = 'JPY';
|
||||
break;
|
||||
case 'zh':
|
||||
currency = 'CNY';
|
||||
break;
|
||||
default:
|
||||
currency = 'USD';
|
||||
}
|
||||
} catch (_) {
|
||||
// Localizations가 아직 준비되지 않은 경우 기본값 유지
|
||||
}
|
||||
|
||||
// 애니메이션 시작
|
||||
animationController!.forward();
|
||||
}
|
||||
@@ -284,25 +307,55 @@ class AddSubscriptionController {
|
||||
setState(() => isLoading = true);
|
||||
|
||||
try {
|
||||
final ctx = context;
|
||||
if (!await SMSService.hasSMSPermission()) {
|
||||
final granted = await SMSService.requestSMSPermission();
|
||||
if (!ctx.mounted) return;
|
||||
if (!granted) {
|
||||
if (context.mounted) {
|
||||
AppSnackBar.showError(
|
||||
context: context,
|
||||
message: AppLocalizations.of(context).smsPermissionRequired,
|
||||
);
|
||||
if (ctx.mounted) {
|
||||
// 영구 거부 여부 확인 후 설정 화면 안내
|
||||
final status = await permission.Permission.sms.status;
|
||||
if (!ctx.mounted) return;
|
||||
if (status.isPermanentlyDenied) {
|
||||
await showDialog(
|
||||
context: ctx,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(ctx).smsPermissionRequired),
|
||||
content:
|
||||
Text(AppLocalizations.of(ctx).permanentlyDeniedMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: Text(AppLocalizations.of(ctx).cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await permission.openAppSettings();
|
||||
if (ctx.mounted) Navigator.of(ctx).pop();
|
||||
},
|
||||
child: Text(AppLocalizations.of(ctx).openSettings),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
AppSnackBar.showError(
|
||||
context: ctx,
|
||||
message: AppLocalizations.of(ctx).smsPermissionRequired,
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final subscriptions = await SMSService.scanSubscriptions();
|
||||
if (!ctx.mounted) return;
|
||||
if (subscriptions.isEmpty) {
|
||||
if (context.mounted) {
|
||||
if (ctx.mounted) {
|
||||
AppSnackBar.showWarning(
|
||||
context: context,
|
||||
message: AppLocalizations.of(context).noSubscriptionSmsFound,
|
||||
context: ctx,
|
||||
message: AppLocalizations.of(ctx).noSubscriptionSmsFound,
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -434,12 +487,22 @@ class AddSubscriptionController {
|
||||
double.tryParse(eventPriceController.text.replaceAll(',', ''));
|
||||
}
|
||||
|
||||
// 선택일이 오늘(또는 과거)이면 결제 주기에 맞춰 다음 회차로 보정하여 저장 + 영업일 이월
|
||||
final originalDateOnly = DateTime(
|
||||
nextBillingDate!.year,
|
||||
nextBillingDate!.month,
|
||||
nextBillingDate!.day,
|
||||
);
|
||||
var adjustedNext =
|
||||
BillingDateUtil.ensureFutureDate(originalDateOnly, billingCycle);
|
||||
adjustedNext = BusinessDayUtil.nextBusinessDay(adjustedNext);
|
||||
|
||||
await Provider.of<SubscriptionProvider>(context, listen: false)
|
||||
.addSubscription(
|
||||
serviceName: serviceNameController.text.trim(),
|
||||
monthlyCost: monthlyCost,
|
||||
billingCycle: billingCycle,
|
||||
nextBillingDate: nextBillingDate!,
|
||||
nextBillingDate: adjustedNext,
|
||||
websiteUrl: websiteUrlController.text.trim(),
|
||||
categoryId: selectedCategoryId,
|
||||
currency: currency,
|
||||
@@ -449,6 +512,16 @@ class AddSubscriptionController {
|
||||
eventPrice: eventPrice,
|
||||
);
|
||||
|
||||
// 자동 보정이 발생했으면 안내
|
||||
if (adjustedNext.isAfter(originalDateOnly)) {
|
||||
if (context.mounted) {
|
||||
AppSnackBar.showInfo(
|
||||
context: context,
|
||||
message: '다음 결제 예정일로 저장됨',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true); // 성공 여부 반환
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user