Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
188 lines
5.9 KiB
Dart
188 lines
5.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../../app/theme.dart';
|
|
import '../../providers/session_provider.dart';
|
|
import '../../shared/models/score_state.dart';
|
|
import '../../shared/widgets/destructive_cta.dart';
|
|
import '../../shared/widgets/screen_insets.dart';
|
|
import 'controller_screen.dart';
|
|
|
|
class ControllerPortrait extends StatelessWidget {
|
|
const ControllerPortrait({
|
|
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);
|
|
final match = sessionState.match;
|
|
|
|
return SingleChildScrollView(
|
|
padding: ScreenInsets.contentPadding(context, horizontal: 16, vertical: 8),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
if (match != null)
|
|
Text(
|
|
'${match.sport.toUpperCase()} · ${match.category ?? ''} · ${match.phase ?? ''} · SET ${score.currentSet}',
|
|
style: theme.textTheme.bodyMedium,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
_Scoreboard(
|
|
homeName: homeName,
|
|
awayName: awayName,
|
|
score: score,
|
|
pointsTarget: pointsTarget,
|
|
onHomePoint: onHomePoint,
|
|
onAwayPoint: onAwayPoint,
|
|
onHomeMinus: onHomeMinus,
|
|
onAwayMinus: onAwayMinus,
|
|
),
|
|
const SizedBox(height: 12),
|
|
YellowActionButton(label: 'CHIUDI SET', onPressed: onCloseSet),
|
|
const SizedBox(height: 16),
|
|
StreamMetricsFooter(
|
|
elapsed: sessionState.elapsed,
|
|
connected: sessionState.connected,
|
|
cameraDevice: sessionState.cameraDevice,
|
|
viewerCount: sessionState.viewerCount,
|
|
),
|
|
const SizedBox(height: 16),
|
|
OutlinedButton(
|
|
onPressed: onInterrupt,
|
|
child: const Text('Interrompi (riprendibile)'),
|
|
),
|
|
const SizedBox(height: 8),
|
|
DestructiveCta(
|
|
label: 'Chiudi diretta',
|
|
onPressed: () => onCloseStream(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Scoreboard extends StatelessWidget {
|
|
const _Scoreboard({
|
|
required this.homeName,
|
|
required this.awayName,
|
|
required this.score,
|
|
required this.pointsTarget,
|
|
required this.onHomePoint,
|
|
required this.onAwayPoint,
|
|
required this.onHomeMinus,
|
|
required this.onAwayMinus,
|
|
});
|
|
|
|
final String homeName;
|
|
final String awayName;
|
|
final ScoreState score;
|
|
final int pointsTarget;
|
|
final VoidCallback onHomePoint;
|
|
final VoidCallback onAwayPoint;
|
|
final VoidCallback onHomeMinus;
|
|
final VoidCallback onAwayMinus;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(homeName.toUpperCase(), style: theme.textTheme.labelLarge),
|
|
Text(
|
|
'SETS ${score.homeSets}',
|
|
style: theme.textTheme.bodyMedium,
|
|
),
|
|
Text(
|
|
'${score.homePoints}',
|
|
style: theme.textTheme.displayMedium?.copyWith(
|
|
color: AppTheme.primaryRed,
|
|
),
|
|
),
|
|
Text('→ $pointsTarget pt', style: theme.textTheme.bodySmall),
|
|
const SizedBox(height: 8),
|
|
ScoreTapButton(label: '+1', onTap: onHomePoint),
|
|
const SizedBox(height: 6),
|
|
OutlinedButton(
|
|
onPressed: score.homePoints > 0 ? onHomeMinus : null,
|
|
child: const Text('-1'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
'-',
|
|
style: theme.textTheme.headlineLarge?.copyWith(
|
|
color: AppTheme.textSecondary,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(awayName.toUpperCase(), style: theme.textTheme.labelLarge),
|
|
Text(
|
|
'SETS ${score.awaySets}',
|
|
style: theme.textTheme.bodyMedium,
|
|
),
|
|
Text(
|
|
'${score.awayPoints}',
|
|
style: theme.textTheme.displayMedium?.copyWith(
|
|
color: AppTheme.primaryRed,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
ScoreTapButton(label: '+1', onTap: onAwayPoint),
|
|
const SizedBox(height: 6),
|
|
OutlinedButton(
|
|
onPressed: score.awayPoints > 0 ? onAwayMinus : null,
|
|
child: const Text('-1'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|