Files
bowlingManager/mobile/lib/screens/subscription/subscription_screen.dart
T

486 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/subscription_model.dart';
import '../../services/subscription_service.dart';
import 'subscription_details_screen.dart';
import '../../widgets/dialog_actions.dart';
class SubscriptionScreen extends StatefulWidget {
const SubscriptionScreen({super.key});
@override
State<SubscriptionScreen> createState() => _SubscriptionScreenState();
}
class _SubscriptionScreenState extends State<SubscriptionScreen> {
bool _isYearly = true; // 기본값은 연간 구독
@override
void initState() {
super.initState();
// 화면 로드 시 구독 정보 조회
WidgetsBinding.instance.addPostFrameCallback((_) {
final subscriptionService = Provider.of<SubscriptionService>(
context,
listen: false,
);
subscriptionService.loadCurrentSubscription();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('구독 관리')),
body: Consumer<SubscriptionService>(
builder: (context, subscriptionService, child) {
if (subscriptionService.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (subscriptionService.error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('오류가 발생했습니다', style: TextStyle(color: Colors.red[700])),
const SizedBox(height: 8),
Text(subscriptionService.error!),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () =>
subscriptionService.loadCurrentSubscription(),
child: const Text('다시 시도'),
),
],
),
);
}
// 현재 구독 정보가 있는 경우
if (subscriptionService.hasSubscription) {
return _buildCurrentSubscription(subscriptionService);
}
// 구독 정보가 없는 경우 플랜 선택 화면
return _buildSubscriptionPlans(subscriptionService);
},
),
);
}
Widget _buildCurrentSubscription(SubscriptionService subscriptionService) {
final subscription = subscriptionService.currentSubscription!;
final plan = subscriptionService.availablePlans.firstWhere(
(plan) => plan.type == subscription.planType,
orElse: () => SubscriptionPlan.getDefaultPlans().first,
);
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 현재 구독 정보 카드
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: subscription.isActive ? Colors.green : Colors.grey,
width: 2,
),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.verified,
color: subscription.isActive
? Colors.green
: Colors.grey,
size: 28,
),
const SizedBox(width: 8),
Text(
'${plan.name} 플랜',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: subscription.isActive
? Colors.green.withValues(alpha: 0.1)
: Colors.grey.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: subscription.isActive
? Colors.green
: Colors.grey,
),
),
child: Text(
subscription.isActive ? '활성' : '만료됨',
style: TextStyle(
color: subscription.isActive
? Colors.green
: Colors.grey,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
_buildInfoRow('시작일', _formatDate(subscription.startDate)),
const SizedBox(height: 8),
_buildInfoRow('만료일', _formatDate(subscription.endDate)),
const SizedBox(height: 8),
_buildInfoRow(
'자동 갱신',
subscription.autoRenew ? '활성화' : '비활성화',
),
const SizedBox(height: 8),
_buildInfoRow('남은 기간', '${subscription.daysLeft}일'),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
const Text(
'플랜 혜택',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
...plan.features.map(
(feature) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(
Icons.check_circle,
color: Colors.green,
size: 16,
),
const SizedBox(width: 8),
Text(feature),
],
),
),
),
],
),
),
),
const SizedBox(height: 24),
// 구독 관리 버튼
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: subscription.isActive
? () => _showCancelDialog(subscriptionService)
: null,
icon: const Icon(Icons.cancel),
label: const Text('구독 취소'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade100,
foregroundColor: Colors.red.shade700,
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _toggleAutoRenew(subscriptionService),
icon: Icon(
subscription.autoRenew ? Icons.toggle_on : Icons.toggle_off,
),
label: Text(subscription.autoRenew ? '자동 갱신 끄기' : '자동 갱신 켜기'),
),
),
],
),
const SizedBox(height: 16),
// 플랜 변경 버튼
ElevatedButton.icon(
onPressed: () {
setState(() {
// 플랜 선택 화면으로 전환
subscriptionService.loadCurrentSubscription();
});
},
icon: const Icon(Icons.upgrade),
label: const Text('플랜 변경하기'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50),
),
),
],
),
);
}
Widget _buildSubscriptionPlans(SubscriptionService subscriptionService) {
final plans = subscriptionService.availablePlans;
return Column(
children: [
// 연간/월간 선택 토글
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('월간 구독'),
const SizedBox(width: 8),
Switch(
value: _isYearly,
onChanged: (value) {
setState(() {
_isYearly = value;
});
},
activeColor: Colors.blue,
),
const SizedBox(width: 8),
Row(
children: [
const Text('연간 구독'),
Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.green.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green),
),
child: const Text(
'20% 할인',
style: TextStyle(
color: Colors.green,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
// 플랜 목록
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: plans.length,
itemBuilder: (context, index) {
final plan = plans[index];
final price = _isYearly ? plan.yearlyPrice : plan.monthlyPrice;
final period = _isYearly ? '년' : '월';
return Card(
margin: const EdgeInsets.only(bottom: 16),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SubscriptionDetailsScreen(
plan: plan,
isYearly: _isYearly,
),
),
);
},
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
plan.name,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
if (plan.type == SubscriptionPlanType.premium)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.amber.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.amber),
),
child: const Text(
'인기',
style: TextStyle(
color: Colors.amber,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 8),
Text(plan.description),
const SizedBox(height: 16),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'₩${_formatPrice(price)}',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 4),
Text('/$period'),
const Spacer(),
const Icon(Icons.arrow_forward),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
...plan.features
.take(3)
.map(
(feature) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(
Icons.check_circle,
color: Colors.green,
size: 16,
),
const SizedBox(width: 8),
Text(feature),
],
),
),
),
if (plan.features.length > 3)
Text(
'외 ${plan.features.length - 3}개 혜택',
style: TextStyle(
color: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
),
),
),
);
},
),
),
],
);
}
Widget _buildInfoRow(String label, String value) {
return Row(
children: [
Text('$label: ', style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value),
],
);
}
String _formatDate(DateTime date) {
return '${date.year}${date.month}${date.day}일';
}
String _formatPrice(double price) {
if (price == 0) {
return '0';
}
// 천 단위 콤마 추가
final priceInt = price.toInt();
return priceInt.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]},',
);
}
void _showCancelDialog(SubscriptionService subscriptionService) {
final messenger = ScaffoldMessenger.of(context);
final navigator = Navigator.of(context);
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('구독 취소'),
content: const Text(
'정말로 구독을 취소하시겠습니까?\n'
'취소하면 현재 구독 기간이 끝날 때까지만 서비스를 이용할 수 있습니다.',
),
actions: DialogActions.confirm(
onCancel: () => Navigator.of(dialogContext).pop(),
onConfirm: () async {
navigator.pop();
final success = await subscriptionService.cancelSubscription();
if (success && mounted) {
messenger.showSnackBar(
const SnackBar(content: Text('구독이 취소되었습니다')),
);
}
},
destructive: true,
confirmText: '예, 취소합니다',
),
),
);
}
void _toggleAutoRenew(SubscriptionService subscriptionService) async {
final messenger = ScaffoldMessenger.of(context);
final success = await subscriptionService.toggleAutoRenew();
if (success && mounted) {
final isAutoRenew = subscriptionService.currentSubscription!.autoRenew;
messenger.showSnackBar(
SnackBar(
content: Text(isAutoRenew ? '자동 갱신이 활성화되었습니다' : '자동 갱신이 비활성화되었습니다'),
),
);
}
}
}