import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../models/subscription.dart'; import '../../models/payment_card_suggestion.dart'; import '../../providers/category_provider.dart'; import '../../providers/locale_provider.dart'; import '../../widgets/themed_text.dart'; import '../../widgets/common/buttons/primary_button.dart'; import '../../widgets/common/buttons/secondary_button.dart'; import '../../widgets/common/form_fields/base_text_field.dart'; import '../../widgets/common/form_fields/category_selector.dart'; import '../../widgets/common/snackbar/app_snackbar.dart'; import '../../widgets/payment_card/payment_card_selector.dart'; import '../../services/currency_util.dart'; import '../../utils/sms_scan/date_formatter.dart'; import '../../utils/sms_scan/category_icon_mapper.dart'; import '../../l10n/app_localizations.dart'; class SubscriptionCardWidget extends StatefulWidget { final Subscription subscription; final TextEditingController serviceNameController; final TextEditingController websiteUrlController; final String? selectedCategoryId; final Function(String?) onCategoryChanged; final String? selectedPaymentCardId; final Function(String?) onPaymentCardChanged; final Future Function()? onAddCard; final VoidCallback? onManageCards; final VoidCallback onAdd; final VoidCallback onSkip; final PaymentCardSuggestion? detectedCardSuggestion; final bool showDetectedCardShortcut; final Future Function(PaymentCardSuggestion suggestion)? onAddDetectedCard; final bool enableServiceNameEditing; final ValueChanged? onServiceNameChanged; const SubscriptionCardWidget({ super.key, required this.subscription, required this.serviceNameController, required this.websiteUrlController, this.selectedCategoryId, required this.onCategoryChanged, required this.selectedPaymentCardId, required this.onPaymentCardChanged, this.onAddCard, this.onManageCards, required this.onAdd, required this.onSkip, this.detectedCardSuggestion, this.showDetectedCardShortcut = false, this.onAddDetectedCard, this.enableServiceNameEditing = false, this.onServiceNameChanged, }); @override State createState() => _SubscriptionCardWidgetState(); } class _SubscriptionCardWidgetState extends State { @override void initState() { super.initState(); // URL 필드 자동 설정 if (widget.websiteUrlController.text.isEmpty && widget.subscription.websiteUrl != null) { widget.websiteUrlController.text = widget.subscription.websiteUrl!; } } void _handleCardTap() { // 구독 카드 클릭 시 처리 AppSnackBar.showInfo( context: context, message: AppLocalizations.of(context).featureComingSoon, icon: Icons.info_outline, ); } @override Widget build(BuildContext context) { final categoryProvider = Provider.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_hasRawSmsMessage) Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: _buildSmsPreviewCard(context), ), if (_hasRawSmsMessage) const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 구독 정보 카드 Card( elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0), side: BorderSide( color: Theme.of(context) .colorScheme .outline .withValues(alpha: 0.4), ), ), child: Column( children: [ // 클릭 가능한 정보 영역 Material( color: Colors.transparent, child: InkWell( onTap: _handleCardTap, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0), ), child: Padding( padding: const EdgeInsets.all(16.0), child: _buildInfoSection(categoryProvider), ), ), ), // 구분선 Container( height: 1, color: Theme.of(context) .colorScheme .onSurfaceVariant .withValues(alpha: 0.1), ), // 클릭 불가능한 액션 영역 Padding( padding: const EdgeInsets.all(16.0), child: _buildActionSection(categoryProvider), ), ], ), ), ], ), ), ], ); } bool get _hasRawSmsMessage { return widget.subscription.rawMessage != null && widget.subscription.rawMessage!.trim().isNotEmpty; } Widget _buildSmsPreviewCard(BuildContext context) { final theme = Theme.of(context); final loc = AppLocalizations.of(context); final rawMessage = widget.subscription.rawMessage?.trim() ?? ''; final lastDate = widget.subscription.lastPaymentDate; return Card( elevation: 0, color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.4), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.sms_outlined, color: theme.colorScheme.primary), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ThemedText( loc.latestSmsMessage, fontWeight: FontWeight.bold, ), if (lastDate != null) ThemedText( loc.smsDetectedDate(loc.formatDate(lastDate)), opacity: 0.7, fontSize: 13, ), ], ), ), ], ), const SizedBox(height: 12), Container( width: double.infinity, decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: theme.colorScheme.outline.withValues(alpha: 0.3), ), ), padding: const EdgeInsets.all(12), child: SelectableText( rawMessage, style: TextStyle( fontSize: 15, height: 1.4, color: theme.colorScheme.onSurface, ), ), ), ], ), ), ); } // 정보 섹션 (클릭 가능) Widget _buildInfoSection(CategoryProvider categoryProvider) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ThemedText( AppLocalizations.of(context).foundSubscription, fontSize: 18, fontWeight: FontWeight.bold, ), const SizedBox(height: 24), // 서비스명 ThemedText( AppLocalizations.of(context).serviceName, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 4), if (widget.enableServiceNameEditing) BaseTextField( controller: widget.serviceNameController, hintText: AppLocalizations.of(context).serviceNameRequired, onChanged: widget.onServiceNameChanged, textInputAction: TextInputAction.done, maxLines: 1, ) else ThemedText( widget.subscription.serviceName, fontSize: 22, fontWeight: FontWeight.bold, ), const SizedBox(height: 16), // 금액 및 결제 주기 Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ThemedText( AppLocalizations.of(context).monthlyCost, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 4), // 언어별 통화 표시 FutureBuilder( future: CurrencyUtil.formatAmountWithLocale( widget.subscription.monthlyCost, widget.subscription.currency, context.read().locale.languageCode, ), builder: (context, snapshot) { return ThemedText( snapshot.data ?? '-', fontSize: 18, fontWeight: FontWeight.bold, ); }, ), ], ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ThemedText( AppLocalizations.of(context).billingCycle, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 4), ThemedText( widget.subscription.billingCycle, fontSize: 16, fontWeight: FontWeight.w500, ), ], ), ), ], ), const SizedBox(height: 16), // 다음 결제일 ThemedText( AppLocalizations.of(context).nextBillingDateLabel, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 4), ThemedText( SmsDateFormatter.getNextBillingText( context, widget.subscription.nextBillingDate, widget.subscription.billingCycle, ), fontSize: 16, fontWeight: FontWeight.w500, ), ], ); } // 액션 섹션 (클릭 불가능) Widget _buildActionSection(CategoryProvider categoryProvider) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 카테고리 선택 ThemedText( AppLocalizations.of(context).category, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 8), CategorySelector( categories: categoryProvider.categories, selectedCategoryId: widget.selectedCategoryId ?? widget.subscription.category, onChanged: widget.onCategoryChanged, baseColor: _getCategoryColor(categoryProvider), ), const SizedBox(height: 24), // 결제수단 선택 ThemedText( AppLocalizations.of(context).paymentCard, fontWeight: FontWeight.w500, opacity: 0.7, ), const SizedBox(height: 8), PaymentCardSelector( selectedCardId: widget.selectedPaymentCardId, onChanged: widget.onPaymentCardChanged, onAddCard: widget.onAddCard, onManageCards: widget.onManageCards, ), if (widget.showDetectedCardShortcut && widget.detectedCardSuggestion != null) ...[ const SizedBox(height: 12), _DetectedCardSuggestionBanner( suggestion: widget.detectedCardSuggestion!, onAdd: widget.onAddDetectedCard, ), ], if (widget.selectedPaymentCardId == null) ...[ const SizedBox(height: 8), Text( AppLocalizations.of(context).paymentCardUnassignedWarning, style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], const SizedBox(height: 24), // 웹사이트 URL 입력 필드 BaseTextField( controller: widget.websiteUrlController, label: AppLocalizations.of(context).websiteUrlAuto, hintText: AppLocalizations.of(context).websiteUrlHint, prefixIcon: Icon( Icons.language, color: Theme.of(context).colorScheme.onSurfaceVariant, ), style: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), fillColor: Theme.of(context).colorScheme.surface, ), const SizedBox(height: 32), // 작업 버튼 Row( children: [ Expanded( child: SecondaryButton( text: AppLocalizations.of(context).skip, onPressed: widget.onSkip, height: 48, ), ), const SizedBox(width: 16), Expanded( child: PrimaryButton( text: AppLocalizations.of(context).add, onPressed: widget.onAdd, height: 48, ), ), ], ), ], ); } Color? _getCategoryColor(CategoryProvider categoryProvider) { final categoryId = widget.selectedCategoryId ?? widget.subscription.category; if (categoryId == null) return null; final category = categoryProvider.getCategoryById(categoryId); if (category == null) return null; return CategoryIconMapper.getCategoryColor(category); } } class _DetectedCardSuggestionBanner extends StatelessWidget { final PaymentCardSuggestion suggestion; final Future Function(PaymentCardSuggestion suggestion)? onAdd; const _DetectedCardSuggestionBanner({ required this.suggestion, this.onAdd, }); @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); final scheme = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: scheme.secondaryContainer, borderRadius: BorderRadius.circular(16), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: scheme.onSecondaryContainer.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon( Icons.auto_fix_high_rounded, color: scheme.onSecondaryContainer, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( loc.detectedPaymentCard, style: TextStyle( fontWeight: FontWeight.w600, color: scheme.onSecondaryContainer, ), ), const SizedBox(height: 4), Text( loc.detectedPaymentCardDescription( suggestion.issuerName, suggestion.last4 ?? '****', ), style: TextStyle( fontSize: 13, color: scheme.onSecondaryContainer.withValues(alpha: 0.9), ), ), ], ), ), const SizedBox(width: 12), ElevatedButton.icon( onPressed: onAdd == null ? null : () async { await onAdd!(suggestion); }, style: ElevatedButton.styleFrom( backgroundColor: scheme.onSecondaryContainer, foregroundColor: scheme.secondaryContainer, ), icon: const Icon(Icons.add_rounded, size: 16), label: Text(loc.addDetectedPaymentCard), ), ], ), ); } }