Initial commit: monorepo Match Live TV.
Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
90
mobile/lib/providers/auth_provider.dart
Normal file
90
mobile/lib/providers/auth_provider.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../core/auth_storage.dart';
|
||||
import '../shared/models/user.dart';
|
||||
|
||||
class AuthState {
|
||||
const AuthState({
|
||||
this.user,
|
||||
this.accessToken,
|
||||
this.refreshToken,
|
||||
this.loading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
final User? user;
|
||||
final String? accessToken;
|
||||
final String? refreshToken;
|
||||
final bool loading;
|
||||
final String? error;
|
||||
|
||||
bool get isAuthenticated =>
|
||||
accessToken != null && accessToken!.isNotEmpty && user != null;
|
||||
|
||||
AuthState copyWith({
|
||||
User? user,
|
||||
String? accessToken,
|
||||
String? refreshToken,
|
||||
bool? loading,
|
||||
String? error,
|
||||
bool clearError = false,
|
||||
}) {
|
||||
return AuthState(
|
||||
user: user ?? this.user,
|
||||
accessToken: accessToken ?? this.accessToken,
|
||||
refreshToken: refreshToken ?? this.refreshToken,
|
||||
loading: loading ?? this.loading,
|
||||
error: clearError ? null : (error ?? this.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthNotifier extends StateNotifier<AuthState> {
|
||||
AuthNotifier() : super(const AuthState());
|
||||
|
||||
void setSession({
|
||||
required User user,
|
||||
required String accessToken,
|
||||
required String refreshToken,
|
||||
}) {
|
||||
state = AuthState(
|
||||
user: user,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
loading: false,
|
||||
);
|
||||
AuthStorage.save(
|
||||
user: user,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> restoreSession() async {
|
||||
final stored = await AuthStorage.load();
|
||||
if (stored == null) return;
|
||||
state = AuthState(
|
||||
user: stored.user,
|
||||
accessToken: stored.accessToken,
|
||||
refreshToken: stored.refreshToken,
|
||||
loading: false,
|
||||
);
|
||||
}
|
||||
|
||||
void setLoading(bool loading) {
|
||||
state = state.copyWith(loading: loading, clearError: true);
|
||||
}
|
||||
|
||||
void setError(String message) {
|
||||
state = state.copyWith(loading: false, error: message);
|
||||
}
|
||||
|
||||
void logout() {
|
||||
state = const AuthState();
|
||||
AuthStorage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>(
|
||||
(ref) => AuthNotifier(),
|
||||
);
|
||||
12
mobile/lib/providers/match_rules_provider.dart
Normal file
12
mobile/lib/providers/match_rules_provider.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../shared/scoring/match_scoring_rules.dart';
|
||||
import 'session_provider.dart';
|
||||
|
||||
final matchScoringRulesProvider = Provider<MatchScoringRules>((ref) {
|
||||
final match = ref.watch(sessionProvider).match;
|
||||
if (match == null) {
|
||||
return const MatchScoringRules(setsToWin: 3);
|
||||
}
|
||||
return MatchScoringRules.fromMatch(match);
|
||||
});
|
||||
77
mobile/lib/providers/score_provider.dart
Normal file
77
mobile/lib/providers/score_provider.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../shared/models/score_state.dart';
|
||||
|
||||
class ScoreNotifier extends StateNotifier<ScoreState> {
|
||||
ScoreNotifier() : super(const ScoreState());
|
||||
|
||||
void reset(ScoreState score) {
|
||||
state = score;
|
||||
}
|
||||
|
||||
void applyRemote(ScoreState score) {
|
||||
state = score;
|
||||
}
|
||||
|
||||
void incrementHome() {
|
||||
state = state.copyWith(homePoints: state.homePoints + 1);
|
||||
}
|
||||
|
||||
void incrementAway() {
|
||||
state = state.copyWith(awayPoints: state.awayPoints + 1);
|
||||
}
|
||||
|
||||
void decrementHome() {
|
||||
if (state.homePoints > 0) {
|
||||
state = state.copyWith(homePoints: state.homePoints - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void decrementAway() {
|
||||
if (state.awayPoints > 0) {
|
||||
state = state.copyWith(awayPoints: state.awayPoints - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void closeSet() {
|
||||
final homeWon = state.homePoints > state.awayPoints;
|
||||
final partials = List<SetPartial>.from(state.setPartials);
|
||||
if (state.homePoints > 0 || state.awayPoints > 0) {
|
||||
partials.add(SetPartial(
|
||||
set: state.currentSet,
|
||||
home: state.homePoints,
|
||||
away: state.awayPoints,
|
||||
));
|
||||
}
|
||||
state = state.copyWith(
|
||||
homeSets: homeWon ? state.homeSets + 1 : state.homeSets,
|
||||
awaySets: homeWon ? state.awaySets : state.awaySets + 1,
|
||||
homePoints: 0,
|
||||
awayPoints: 0,
|
||||
currentSet: state.currentSet + 1,
|
||||
setPartials: partials,
|
||||
timeoutHome: false,
|
||||
timeoutAway: false,
|
||||
);
|
||||
}
|
||||
|
||||
void timeoutHome() {
|
||||
state = state.copyWith(timeoutHome: true);
|
||||
}
|
||||
|
||||
void timeoutAway() {
|
||||
state = state.copyWith(timeoutAway: true);
|
||||
}
|
||||
|
||||
void setTimeoutHome(bool value) {
|
||||
state = state.copyWith(timeoutHome: value);
|
||||
}
|
||||
|
||||
void setTimeoutAway(bool value) {
|
||||
state = state.copyWith(timeoutAway: value);
|
||||
}
|
||||
}
|
||||
|
||||
final scoreProvider = StateNotifierProvider<ScoreNotifier, ScoreState>(
|
||||
(ref) => ScoreNotifier(),
|
||||
);
|
||||
165
mobile/lib/providers/session_provider.dart
Normal file
165
mobile/lib/providers/session_provider.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../shared/models/device_state.dart';
|
||||
import '../shared/models/match.dart';
|
||||
import '../shared/models/stream_session.dart';
|
||||
|
||||
enum DeviceRole { camera, controller }
|
||||
|
||||
class SessionState {
|
||||
const SessionState({
|
||||
this.session,
|
||||
this.match,
|
||||
this.connected = false,
|
||||
this.deviceRole = DeviceRole.controller,
|
||||
this.cameraDevice,
|
||||
this.elapsed = Duration.zero,
|
||||
this.viewerCount = 0,
|
||||
this.loading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
final StreamSession? session;
|
||||
final MatchModel? match;
|
||||
final bool connected;
|
||||
final DeviceRole deviceRole;
|
||||
final DeviceStateModel? cameraDevice;
|
||||
final Duration elapsed;
|
||||
final int viewerCount;
|
||||
final bool loading;
|
||||
final String? error;
|
||||
|
||||
SessionState copyWith({
|
||||
StreamSession? session,
|
||||
MatchModel? match,
|
||||
bool? connected,
|
||||
DeviceRole? deviceRole,
|
||||
DeviceStateModel? cameraDevice,
|
||||
Duration? elapsed,
|
||||
int? viewerCount,
|
||||
bool? loading,
|
||||
String? error,
|
||||
bool clearError = false,
|
||||
}) {
|
||||
return SessionState(
|
||||
session: session ?? this.session,
|
||||
match: match ?? this.match,
|
||||
connected: connected ?? this.connected,
|
||||
deviceRole: deviceRole ?? this.deviceRole,
|
||||
cameraDevice: cameraDevice ?? this.cameraDevice,
|
||||
elapsed: elapsed ?? this.elapsed,
|
||||
viewerCount: viewerCount ?? this.viewerCount,
|
||||
loading: loading ?? this.loading,
|
||||
error: clearError ? null : (error ?? this.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SessionNotifier extends StateNotifier<SessionState> {
|
||||
SessionNotifier() : super(const SessionState());
|
||||
|
||||
void setSession(StreamSession session, {MatchModel? match}) {
|
||||
state = state.copyWith(
|
||||
session: session,
|
||||
match: match,
|
||||
clearError: true,
|
||||
);
|
||||
}
|
||||
|
||||
void setMatch(MatchModel match) {
|
||||
state = state.copyWith(match: match);
|
||||
}
|
||||
|
||||
void setConnected(bool connected) {
|
||||
state = state.copyWith(connected: connected);
|
||||
}
|
||||
|
||||
void setDeviceRole(DeviceRole role) {
|
||||
state = state.copyWith(deviceRole: role);
|
||||
}
|
||||
|
||||
void updateDeviceState(DeviceStateModel device) {
|
||||
if (device.deviceRole == 'camera') {
|
||||
state = state.copyWith(cameraDevice: device);
|
||||
}
|
||||
if (device.status != null && state.session != null) {
|
||||
state = state.copyWith(
|
||||
session: StreamSession(
|
||||
id: state.session!.id,
|
||||
matchId: state.session!.matchId,
|
||||
status: device.status!,
|
||||
platform: state.session!.platform,
|
||||
rtmpIngestUrl: state.session!.rtmpIngestUrl,
|
||||
youtubeBroadcastId: state.session!.youtubeBroadcastId,
|
||||
privacyStatus: state.session!.privacyStatus,
|
||||
qualityPreset: state.session!.qualityPreset,
|
||||
targetBitrate: state.session!.targetBitrate,
|
||||
startedAt: state.session!.startedAt,
|
||||
disconnectionCount: state.session!.disconnectionCount,
|
||||
score: state.session!.score,
|
||||
devices: state.session!.devices,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setElapsed(Duration elapsed) {
|
||||
state = state.copyWith(elapsed: elapsed);
|
||||
}
|
||||
|
||||
void setViewerCount(int count) {
|
||||
state = state.copyWith(viewerCount: count);
|
||||
}
|
||||
|
||||
void applyCommand(String action) {
|
||||
final session = state.session;
|
||||
if (session == null) return;
|
||||
|
||||
String newStatus = session.status;
|
||||
switch (action) {
|
||||
case 'start_stream':
|
||||
newStatus = 'live';
|
||||
break;
|
||||
case 'pause_stream':
|
||||
newStatus = 'paused';
|
||||
break;
|
||||
case 'stop_stream':
|
||||
newStatus = 'ended';
|
||||
break;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
session: StreamSession(
|
||||
id: session.id,
|
||||
matchId: session.matchId,
|
||||
status: newStatus,
|
||||
platform: session.platform,
|
||||
rtmpIngestUrl: session.rtmpIngestUrl,
|
||||
youtubeBroadcastId: session.youtubeBroadcastId,
|
||||
privacyStatus: session.privacyStatus,
|
||||
qualityPreset: session.qualityPreset,
|
||||
targetBitrate: session.targetBitrate,
|
||||
startedAt: session.startedAt,
|
||||
disconnectionCount: session.disconnectionCount,
|
||||
score: session.score,
|
||||
devices: session.devices,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void setLoading(bool loading) {
|
||||
state = state.copyWith(loading: loading);
|
||||
}
|
||||
|
||||
void setError(String message) {
|
||||
state = state.copyWith(loading: false, error: message);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = const SessionState();
|
||||
}
|
||||
}
|
||||
|
||||
final sessionProvider = StateNotifierProvider<SessionNotifier, SessionState>(
|
||||
(ref) => SessionNotifier(),
|
||||
);
|
||||
Reference in New Issue
Block a user