MotorSignal-App/lib/features/settings/widgets/current_plan_card.dart

240 lines
9.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../core/theme/theme_extensions.dart';
import '../../../core/theme/app_typography.dart';
import '../../../services/billing/product_catalog.dart';
import '../../../services/entitlements/entitlement_providers.dart';
import '../../../services/entitlements/entitlement_state.dart';
import '../../entitlements/widgets/pro_lock_badge.dart';
/// Reusable canonical responsive card displaying the current plan and subscription management options.
class CurrentPlanCard extends ConsumerWidget {
const CurrentPlanCard({super.key});
Future<void> _openGooglePlaySubscriptions() async {
// Official Google Play subscription management deep link
final uri = Uri.parse(
'https://play.google.com/store/account/subscriptions?package=app.motorsignal.motorcycle&sku=${ProductCatalog.proMonthly}',
);
try {
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} catch (_) {}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final colors = context.motorColors;
final entitlement = ref.watch(entitlementProvider);
final isPro = entitlement.isPro;
final plan = entitlement.plan;
final status = entitlement.status;
String planTitle = 'MotorSignal Free';
String statusSubtitle = 'Basic telemetry & local recording active';
Color badgeColor = colors.outlineVariant;
String badgeText = 'FREE';
if (plan == MotorSignalPlan.proLifetime && isPro) {
planTitle = 'MotorSignal Pro';
statusSubtitle = 'Lifetime access • Permanent ownership';
badgeColor = colors.cyberCyan;
badgeText = 'LIFETIME';
} else if (plan == MotorSignalPlan.proMonthly) {
planTitle = 'MotorSignal Pro';
badgeColor = colors.cyberCyan;
badgeText = 'MONTHLY';
switch (status) {
case MotorSignalEntitlementStatus.proMonthlyActive:
statusSubtitle = entitlement.expiresAt != null
? 'Active • Renews ${entitlement.expiresAt!.toLocal().toString().split(' ')[0]}'
: 'Active subscription';
break;
case MotorSignalEntitlementStatus.proMonthlyGrace:
statusSubtitle =
'Payment grace period • Action needed in Google Play';
badgeColor = colors.warningAmber;
badgeText = 'GRACE PERIOD';
break;
case MotorSignalEntitlementStatus.proMonthlyAccountHold:
statusSubtitle =
'Account on hold • Please update billing in Google Play';
badgeColor = colors.raceRed;
badgeText = 'ON HOLD';
break;
case MotorSignalEntitlementStatus.proMonthlyPaused:
statusSubtitle = 'Subscription paused by rider in Google Play';
badgeColor = colors.warningAmber;
badgeText = 'PAUSED';
break;
case MotorSignalEntitlementStatus.proMonthlyExpired:
statusSubtitle = 'Subscription expired';
badgeColor = colors.outlineVariant;
badgeText = 'EXPIRED';
break;
case MotorSignalEntitlementStatus.syncPending:
statusSubtitle = 'Verification pending with Google Play...';
badgeColor = colors.warningAmber;
badgeText = 'VERIFYING';
break;
default:
statusSubtitle = 'Status: ${status.name}';
break;
}
} else if (status == MotorSignalEntitlementStatus.syncPending) {
statusSubtitle = 'Checking purchases with Google Play...';
badgeColor = colors.warningAmber;
badgeText = 'PENDING';
}
final isMonthlySub = plan == MotorSignalPlan.proMonthly;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isPro ? colors.cyberCyan.withValues(alpha: 0.6) : colors.outlineVariant,
width: 1.2,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
runSpacing: 6,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isPro ? Icons.verified : Icons.shield_outlined,
color: isPro ? colors.cyberCyan : colors.outline,
size: 18,
),
const SizedBox(width: 8),
Text(
planTitle,
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 13,
fontWeight: FontWeight.w700,
color: isPro ? colors.cyberCyan : Theme.of(context).colorScheme.onSurface,
letterSpacing: 0.2,
),
),
if (isPro) ...[const SizedBox(width: 6), const ProBadge()],
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2.5),
decoration: BoxDecoration(
color: badgeColor.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: badgeColor.withValues(alpha: 0.5),
width: 0.8,
),
),
child: Text(
badgeText,
style: TextStyle(
fontFamily: 'Inter',
fontSize: 9,
fontWeight: FontWeight.w700,
color: badgeColor,
letterSpacing: 0.5,
),
),
),
],
),
const SizedBox(height: 8),
Text(
statusSubtitle,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1.3,
),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
if (!isPro)
ElevatedButton(
key: const ValueKey('settings_upgrade_to_pro_button'),
style: ElevatedButton.styleFrom(
backgroundColor: colors.cyberCyan,
foregroundColor: Colors.black,
minimumSize: const Size(130, 44),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () => context.push('/settings/pro'),
child: const Text(
'Upgrade to Pro',
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
if (isMonthlySub)
OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
side: BorderSide(color: colors.outlineVariant),
minimumSize: const Size(140, 44),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: _openGooglePlaySubscriptions,
child: const Text(
'Manage Subscription',
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w600,
),
),
),
OutlinedButton(
key: const ValueKey('settings_pro_details_button'),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
side: BorderSide(color: colors.outlineVariant),
minimumSize: const Size(90, 44),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () => context.push('/settings/pro'),
child: Text(
isPro ? 'View Plans' : 'Details',
style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.w600),
),
),
],
),
],
),
);
}
}