Matthias Nott
2026-04-01 98e5695f9c77c594a103e9e81128798d41bae46a
lib/screens/settings_screen.dart
....@@ -5,6 +5,7 @@
55 import '../models/server_config.dart';
66 import '../providers/providers.dart';
77 import '../services/mqtt_service.dart' show ConnectionStatus;
8
+import '../services/purchase_service.dart';
89 import '../services/wol_service.dart';
910 import '../theme/app_theme.dart';
1011 import '../widgets/status_dot.dart';
....@@ -25,10 +26,12 @@
2526 late final TextEditingController _macController;
2627 late final TextEditingController _mqttTokenController;
2728 bool _isWaking = false;
29
+ bool _iapLoading = false;
2830
2931 @override
3032 void initState() {
3133 super.initState();
34
+ PurchaseService.instance.addListener(_onPurchaseChanged);
3235 final config = ref.read(serverConfigProvider);
3336 _localHostController =
3437 TextEditingController(text: config?.localHost ?? '');
....@@ -46,6 +49,7 @@
4649
4750 @override
4851 void dispose() {
52
+ PurchaseService.instance.removeListener(_onPurchaseChanged);
4953 _localHostController.dispose();
5054 _vpnHostController.dispose();
5155 _remoteHostController.dispose();
....@@ -53,6 +57,45 @@
5357 _macController.dispose();
5458 _mqttTokenController.dispose();
5559 super.dispose();
60
+ }
61
+
62
+ void _onPurchaseChanged() {
63
+ if (!mounted) return;
64
+ final isPro = PurchaseService.instance.isPro;
65
+ ref.read(isProProvider.notifier).state = isPro;
66
+ setState(() => _iapLoading = PurchaseService.instance.isLoading);
67
+ final error = PurchaseService.instance.errorMessage;
68
+ if (error != null && mounted) {
69
+ ScaffoldMessenger.of(context).showSnackBar(
70
+ SnackBar(content: Text(error)),
71
+ );
72
+ }
73
+ if (isPro && mounted) {
74
+ ScaffoldMessenger.of(context).showSnackBar(
75
+ const SnackBar(
76
+ content: Text('PAILot Pro activated. Enjoy unlimited sessions!'),
77
+ duration: Duration(seconds: 3),
78
+ ),
79
+ );
80
+ }
81
+ }
82
+
83
+ Future<void> _handleUpgrade() async {
84
+ setState(() => _iapLoading = true);
85
+ await PurchaseService.instance.purchaseFullAccess();
86
+ }
87
+
88
+ Future<void> _handleRestore() async {
89
+ setState(() => _iapLoading = true);
90
+ await PurchaseService.instance.restorePurchases();
91
+ if (mounted) {
92
+ ScaffoldMessenger.of(context).showSnackBar(
93
+ const SnackBar(
94
+ content: Text('Checking for previous purchases...'),
95
+ duration: Duration(seconds: 2),
96
+ ),
97
+ );
98
+ }
5699 }
57100
58101 Future<void> _save() async {
....@@ -328,6 +371,89 @@
328371 icon: const Icon(Icons.shield_outlined),
329372 label: const Text('Reset Server Trust'),
330373 ),
374
+ const SizedBox(height: 24),
375
+
376
+ // --- PAILot Pro ---
377
+ const Divider(),
378
+ const SizedBox(height: 8),
379
+ Row(
380
+ children: [
381
+ const Icon(Icons.star, size: 18),
382
+ const SizedBox(width: 8),
383
+ Text(
384
+ 'PAILot Pro',
385
+ style: Theme.of(context).textTheme.titleMedium,
386
+ ),
387
+ const Spacer(),
388
+ Consumer(
389
+ builder: (ctx, ref, _) {
390
+ final isPro = ref.watch(isProProvider);
391
+ return Chip(
392
+ label: Text(
393
+ isPro ? 'Active' : 'Free Tier',
394
+ style: TextStyle(
395
+ fontSize: 11,
396
+ color: isPro ? Colors.white : null,
397
+ ),
398
+ ),
399
+ backgroundColor: isPro ? AppColors.accent : null,
400
+ padding: EdgeInsets.zero,
401
+ visualDensity: VisualDensity.compact,
402
+ );
403
+ },
404
+ ),
405
+ ],
406
+ ),
407
+ const SizedBox(height: 4),
408
+ Text(
409
+ 'Free: 2 sessions, messages expire after 15 min\n'
410
+ 'Pro: unlimited sessions, messages persist forever',
411
+ style: Theme.of(context)
412
+ .textTheme
413
+ .bodySmall
414
+ ?.copyWith(color: Colors.grey.shade500),
415
+ ),
416
+ const SizedBox(height: 12),
417
+ Consumer(
418
+ builder: (ctx, ref, _) {
419
+ final isPro = ref.watch(isProProvider);
420
+ if (isPro) {
421
+ return const Center(
422
+ child: Text(
423
+ 'Full access active',
424
+ style: TextStyle(color: AppColors.accent),
425
+ ),
426
+ );
427
+ }
428
+ return Column(
429
+ crossAxisAlignment: CrossAxisAlignment.stretch,
430
+ children: [
431
+ ElevatedButton.icon(
432
+ onPressed: _iapLoading ? null : _handleUpgrade,
433
+ icon: _iapLoading
434
+ ? const SizedBox(
435
+ width: 16,
436
+ height: 16,
437
+ child:
438
+ CircularProgressIndicator(strokeWidth: 2),
439
+ )
440
+ : const Icon(Icons.upgrade),
441
+ label: const Text('Upgrade to Pro — \$4.99'),
442
+ style: ElevatedButton.styleFrom(
443
+ backgroundColor: AppColors.accent,
444
+ foregroundColor: Colors.white,
445
+ ),
446
+ ),
447
+ const SizedBox(height: 8),
448
+ OutlinedButton.icon(
449
+ onPressed: _iapLoading ? null : _handleRestore,
450
+ icon: const Icon(Icons.restore),
451
+ label: const Text('Restore Purchase'),
452
+ ),
453
+ ],
454
+ );
455
+ },
456
+ ),
331457 const SizedBox(height: 12),
332458 ],
333459 ),