Files
bowlingManager/mobile/lib/screens/subscription/subscription_details_screen.dart
2025-11-10 14:14:08 +09:00

396 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/subscription_model.dart';
import '../../services/subscription_service.dart';
class SubscriptionDetailsScreen extends StatefulWidget {
final SubscriptionPlan plan;
final bool isYearly;
const SubscriptionDetailsScreen({
super.key,
required this.plan,
required this.isYearly,
});
@override
State<SubscriptionDetailsScreen> createState() => _SubscriptionDetailsScreenState();
}
class _SubscriptionDetailsScreenState extends State<SubscriptionDetailsScreen> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
final price = widget.isYearly ? widget.plan.yearlyPrice : widget.plan.monthlyPrice;
final period = widget.isYearly ? '년' : '월';
return Scaffold(
appBar: AppBar(
title: Text('${widget.plan.name} 플랜'),
),
body: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 플랜 헤더
_buildPlanHeader(price, period),
const SizedBox(height: 24),
// 플랜 설명
Text(
widget.plan.description,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
// 플랜 혜택
const Text(
'플랜 혜택',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
...widget.plan.features.map((feature) => _buildFeatureItem(feature)),
const SizedBox(height: 24),
// 플랜 세부 정보
const Text(
'세부 정보',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_buildDetailItem('최대 회원 수', '${widget.plan.maxMembers}명'),
_buildDetailItem('최대 이벤트 수', '${widget.plan.maxEvents}개'),
_buildDetailItem(
'고급 통계 기능',
widget.plan.hasAdvancedStats ? '제공' : '미제공',
isAvailable: widget.plan.hasAdvancedStats,
),
_buildDetailItem(
'맞춤형 기능',
widget.plan.hasCustomization ? '제공' : '미제공',
isAvailable: widget.plan.hasCustomization,
),
_buildDetailItem(
'우선 지원',
widget.plan.hasPriority ? '제공' : '미제공',
isAvailable: widget.plan.hasPriority,
),
const SizedBox(height: 24),
// 결제 정보
const Text(
'결제 정보',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
_buildDetailItem(
'결제 주기',
widget.isYearly ? '연간' : '월간',
),
_buildDetailItem(
'결제 금액',
'₩${_formatPrice(price)}/${widget.isYearly ? '년' : '월'}',
),
if (widget.isYearly)
_buildDetailItem(
'월 환산 금액',
'₩${_formatPrice(widget.plan.yearlyPrice / 12)}/월',
),
_buildDetailItem(
'자동 갱신',
'활성화 (설정에서 변경 가능)',
),
// 하단 여백 (버튼 높이만큼)
const SizedBox(height: 80),
],
),
),
// 하단 구독 버튼
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: ElevatedButton(
onPressed: _isLoading ? null : () => _subscribe(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white)
: Text(
widget.plan.type == SubscriptionPlanType.free
? '무료 플랜 시작하기'
: '구독하기',
),
),
),
),
],
),
);
}
Widget _buildPlanHeader(double price, String period) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: _getPlanColor(),
width: 2,
),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Icon(
_getPlanIcon(),
color: _getPlanColor(),
size: 32,
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.plan.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (widget.plan.type == SubscriptionPlanType.premium)
Container(
margin: const EdgeInsets.only(top: 4),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.amber),
),
child: const Text(
'인기 플랜',
style: TextStyle(
color: Colors.amber,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'₩${_formatPrice(price)}',
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
'/$period',
style: const TextStyle(fontSize: 16),
),
),
],
),
if (widget.isYearly)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'월 ₩${_formatPrice(widget.plan.yearlyPrice / 12)} (20% 할인)',
style: TextStyle(
color: Colors.green[700],
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
Widget _buildFeatureItem(String feature) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.check_circle, color: Colors.green, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(feature, style: const TextStyle(fontSize: 16))),
],
),
);
}
Widget _buildDetailItem(String label, String value, {bool isAvailable = true}) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
label,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
Expanded(
flex: 3,
child: Text(
value,
style: TextStyle(
fontSize: 16,
color: isAvailable ? null : Colors.grey,
),
),
),
],
),
);
}
Color _getPlanColor() {
switch (widget.plan.type) {
case SubscriptionPlanType.free:
return Colors.grey;
case SubscriptionPlanType.basic:
return Colors.blue;
case SubscriptionPlanType.premium:
return Colors.amber;
case SubscriptionPlanType.enterprise:
return Colors.purple;
}
}
IconData _getPlanIcon() {
switch (widget.plan.type) {
case SubscriptionPlanType.free:
return Icons.star_border;
case SubscriptionPlanType.basic:
return Icons.star_half;
case SubscriptionPlanType.premium:
return Icons.star;
case SubscriptionPlanType.enterprise:
return Icons.workspace_premium;
}
}
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]},',
);
}
Future<void> _subscribe(BuildContext context) async {
final messenger = ScaffoldMessenger.of(context);
final navigator = Navigator.of(context);
setState(() {
_isLoading = true;
});
try {
final subscriptionService = Provider.of<SubscriptionService>(context, listen: false);
final success = await subscriptionService.createSubscription(
widget.plan.type,
widget.isYearly,
);
if (success && mounted) {
messenger.showSnackBar(
const SnackBar(content: Text('구독이 성공적으로 완료되었습니다')),
);
navigator.pop(); // 상세 화면 닫기
} else if (mounted) {
messenger.showSnackBar(
SnackBar(
content: Text(subscriptionService.error ?? '구독 처리 중 오류가 발생했습니다'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
if (mounted) {
messenger.showSnackBar(
SnackBar(
content: Text('오류: $e'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}