239 lines
9.1 KiB
Dart
239 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 '../../../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: 20,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
planTitle.toUpperCase(),
|
|
style: TextStyle(
|
|
fontFamily: 'Sora',
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w800,
|
|
color: isPro ? colors.cyberCyan : Theme.of(context).colorScheme.onSurface,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
if (isPro) ...[const SizedBox(width: 8), const ProBadge()],
|
|
],
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
|
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.w900,
|
|
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: 10,
|
|
runSpacing: 10,
|
|
children: [
|
|
if (!isPro)
|
|
ElevatedButton(
|
|
key: const ValueKey('settings_upgrade_to_pro_button'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colors.cyberCyan,
|
|
foregroundColor: Colors.black,
|
|
minimumSize: const Size(140, 48),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
onPressed: () => context.push('/settings/pro'),
|
|
child: const Text(
|
|
'UPGRADE TO PRO',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w800,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
),
|
|
if (isMonthlySub)
|
|
OutlinedButton(
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
|
side: BorderSide(color: colors.outlineVariant),
|
|
minimumSize: const Size(160, 48),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
onPressed: _openGooglePlaySubscriptions,
|
|
child: const Text(
|
|
'MANAGE SUBSCRIPTION',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
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(100, 48),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
onPressed: () => context.push('/settings/pro'),
|
|
child: Text(
|
|
isPro ? 'VIEW PLANS' : 'DETAILS',
|
|
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|