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:
2026-05-26 17:45:37 +02:00
commit bba6df52c0
381 changed files with 20599 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import '../../app/theme.dart';
import '../../providers/session_provider.dart';
import '../../shared/models/score_state.dart';
import '../../shared/widgets/connected_badge.dart';
import '../../shared/widgets/destructive_cta.dart';
import 'controller_screen.dart';
/// Layout gamepad a 3 colonne per uso landscape.
class ControllerLandscape extends StatelessWidget {
const ControllerLandscape({
super.key,
required this.homeName,
required this.awayName,
required this.score,
required this.sessionState,
required this.onHomePoint,
required this.onAwayPoint,
required this.onHomeMinus,
required this.onAwayMinus,
required this.onCloseSet,
required this.onInterrupt,
required this.onCloseStream,
required this.pointsTarget,
});
final String homeName;
final String awayName;
final ScoreState score;
final SessionState sessionState;
final VoidCallback onHomePoint;
final VoidCallback onAwayPoint;
final VoidCallback onHomeMinus;
final VoidCallback onAwayMinus;
final VoidCallback onCloseSet;
final VoidCallback onInterrupt;
final Future<void> Function() onCloseStream;
final int pointsTarget;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SingleChildScrollView(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _TeamColumn(
teamLabel: 'CASA',
teamName: homeName,
sets: score.homeSets,
points: score.homePoints,
onPoint: onHomePoint,
onMinus: onHomeMinus,
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('REGIA', style: theme.textTheme.titleMedium),
const SizedBox(width: 12),
ConnectedBadge(connected: sessionState.connected),
],
),
const SizedBox(height: 8),
Text(
'SET ${score.currentSet}$pointsTarget pt',
style: theme.textTheme.headlineMedium?.copyWith(
color: AppTheme.accentYellow,
),
),
const SizedBox(height: 16),
YellowActionButton(label: 'CHIUDI SET', onPressed: onCloseSet),
const SizedBox(height: 16),
Text('ULTIME AZIONI', style: theme.textTheme.labelLarge),
const SizedBox(height: 8),
StreamMetricsFooter(
elapsed: sessionState.elapsed,
connected: sessionState.connected,
cameraDevice: sessionState.cameraDevice,
viewerCount: sessionState.viewerCount,
compact: true,
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: onInterrupt,
child: const Text('Interrompi'),
),
),
const SizedBox(width: 8),
Expanded(
child: DestructiveCta(
label: 'Chiudi',
compact: true,
onPressed: () => onCloseStream(),
),
),
],
),
],
),
),
),
Expanded(
child: _TeamColumn(
teamLabel: 'OSPITI',
teamName: awayName,
sets: score.awaySets,
points: score.awayPoints,
onPoint: onAwayPoint,
onMinus: onAwayMinus,
alignRight: true,
),
),
],
),
);
}
}
class _TeamColumn extends StatelessWidget {
const _TeamColumn({
required this.teamLabel,
required this.teamName,
required this.sets,
required this.points,
required this.onPoint,
required this.onMinus,
this.alignRight = false,
});
final String teamLabel;
final String teamName;
final int sets;
final int points;
final VoidCallback onPoint;
final VoidCallback onMinus;
final bool alignRight;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final crossAlign =
alignRight ? CrossAxisAlignment.end : CrossAxisAlignment.start;
return Column(
crossAxisAlignment: crossAlign,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(teamLabel, style: theme.textTheme.labelLarge),
Text(
teamName.toUpperCase(),
style: theme.textTheme.titleLarge,
textAlign: alignRight ? TextAlign.right : TextAlign.left,
),
const SizedBox(height: 8),
Text('SETS $sets', style: theme.textTheme.bodyMedium),
Text(
'$points',
style: theme.textTheme.displayLarge?.copyWith(
color: AppTheme.primaryRed,
fontSize: 56,
),
),
const SizedBox(height: 16),
ScoreTapButton(label: '+1', onTap: onPoint, large: true),
const SizedBox(height: 8),
OutlinedButton(
onPressed: points > 0 ? onMinus : null,
child: const Text('-1'),
),
],
);
}
}