Billing upgrade/downgrade, inviti in app e diretta YouTube da mobile.
Upgrade Stripe con addebito immediato; downgrade programmato a fine periodo. UX billing più sobria con box info; icone piani. API inviti e OAuth YouTube per staff trasmissione; deep link join; scelta partita programmata o nuova. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
77
mobile/lib/core/deep_link_listener.dart
Normal file
77
mobile/lib/core/deep_link_listener.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../features/matches/team_providers.dart';
|
||||
import 'invite_storage.dart';
|
||||
|
||||
/// Gestisce link invito (join) e ritorno OAuth YouTube dall'app.
|
||||
class DeepLinkListener extends ConsumerStatefulWidget {
|
||||
const DeepLinkListener({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
ConsumerState<DeepLinkListener> createState() => _DeepLinkListenerState();
|
||||
}
|
||||
|
||||
class _DeepLinkListenerState extends ConsumerState<DeepLinkListener> {
|
||||
final _appLinks = AppLinks();
|
||||
StreamSubscription<Uri>? _sub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_handleInitialLink();
|
||||
_sub = _appLinks.uriLinkStream.listen(_onUri, onError: (_) {});
|
||||
}
|
||||
|
||||
Future<void> _handleInitialLink() async {
|
||||
final uri = await _appLinks.getInitialLink();
|
||||
if (uri != null) await _onUri(uri);
|
||||
}
|
||||
|
||||
Future<void> _onUri(Uri uri) async {
|
||||
final inviteToken = _extractInviteToken(uri);
|
||||
if (inviteToken != null) {
|
||||
await InviteStorage.saveToken(inviteToken);
|
||||
if (!mounted) return;
|
||||
context.go('/login?invite=$inviteToken');
|
||||
return;
|
||||
}
|
||||
|
||||
if (uri.scheme == 'matchlivetv' && uri.host == 'youtube-connected') {
|
||||
ref.invalidate(teamsProvider);
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Canale YouTube collegato. Puoi selezionare YouTube Live.'),
|
||||
duration: Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String? _extractInviteToken(Uri uri) {
|
||||
if (uri.scheme == 'matchlivetv' && uri.host == 'join') {
|
||||
final segment = uri.pathSegments.isNotEmpty ? uri.pathSegments.first : uri.path.replaceFirst('/', '');
|
||||
return segment.isEmpty ? null : segment;
|
||||
}
|
||||
if (uri.pathSegments.length >= 2 && uri.pathSegments.first == 'join') {
|
||||
return uri.pathSegments[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
20
mobile/lib/core/invite_storage.dart
Normal file
20
mobile/lib/core/invite_storage.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class InviteStorage {
|
||||
static const _keyToken = 'pending_invite_token';
|
||||
|
||||
static Future<void> saveToken(String token) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_keyToken, token);
|
||||
}
|
||||
|
||||
static Future<String?> readToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(_keyToken);
|
||||
}
|
||||
|
||||
static Future<void> clear() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_keyToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user