6e5e423ab4
No se puede marcar “vista” sin revelar la palabra antes. Se puede volver a ver la palabra durante debate/votación/resultado. Notas online privadas por partida y jugador. Tests añadidos para notas scoped. Ajusté roomId en el payload de inicio para que las notas no se mezclen entre partidas.
230 lines
7.0 KiB
Dart
230 lines
7.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:farolero/l10n/generated/app_localizations.dart';
|
|
import 'package:farolero/modelos/inicio_partida_multijugador.dart';
|
|
import 'package:farolero/modelos/jugador.dart';
|
|
import 'package:farolero/servicios/servicio_notas.dart';
|
|
import 'package:farolero/tema/tema_app.dart';
|
|
|
|
class PantallaNotasOnline extends StatefulWidget {
|
|
final String partidaId;
|
|
final List<Jugador> jugadores;
|
|
final List<JugadorInicioPartida> autoresControlados;
|
|
|
|
const PantallaNotasOnline({
|
|
super.key,
|
|
required this.partidaId,
|
|
required this.jugadores,
|
|
required this.autoresControlados,
|
|
});
|
|
|
|
@override
|
|
State<PantallaNotasOnline> createState() => _PantallaNotasOnlineState();
|
|
}
|
|
|
|
class _PantallaNotasOnlineState extends State<PantallaNotasOnline> {
|
|
JugadorInicioPartida? _autor;
|
|
final Map<String, TextEditingController> _controladores = {};
|
|
final TextEditingController _notaLibreController = TextEditingController();
|
|
bool _cargando = false;
|
|
|
|
List<Jugador> get _jugadoresActivos =>
|
|
widget.jugadores.where((jugador) => !jugador.eliminado).toList();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
for (final jugador in _jugadoresActivos) {
|
|
_controladores[jugador.id] = TextEditingController();
|
|
}
|
|
if (widget.autoresControlados.length == 1) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) _seleccionarAutor(widget.autoresControlados.first);
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
for (final controller in _controladores.values) {
|
|
controller.dispose();
|
|
}
|
|
_notaLibreController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _seleccionarAutor(JugadorInicioPartida autor) async {
|
|
setState(() {
|
|
_autor = autor;
|
|
_cargando = true;
|
|
for (final controller in _controladores.values) {
|
|
controller.clear();
|
|
}
|
|
_notaLibreController.clear();
|
|
});
|
|
|
|
final datos = await ServicioNotas.cargarNotasPartida(
|
|
partidaId: widget.partidaId,
|
|
autorJugadorId: autor.jugadorId,
|
|
);
|
|
if (!mounted) return;
|
|
final notas = datos['notas'] as Map<String, String>;
|
|
for (final entry in notas.entries) {
|
|
_controladores[entry.key]?.text = entry.value;
|
|
}
|
|
_notaLibreController.text = datos['notaLibre'] as String;
|
|
setState(() => _cargando = false);
|
|
}
|
|
|
|
Future<void> _guardarNotas() async {
|
|
final autor = _autor;
|
|
if (autor == null) return;
|
|
final notas = <String, String>{};
|
|
for (final entry in _controladores.entries) {
|
|
final texto = entry.value.text.trim();
|
|
if (texto.isNotEmpty) notas[entry.key] = texto;
|
|
}
|
|
await ServicioNotas.guardarNotasPartida(
|
|
partidaId: widget.partidaId,
|
|
autorJugadorId: autor.jugadorId,
|
|
notasPorJugador: notas,
|
|
notaLibre: _notaLibreController.text,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
|
return PopScope(
|
|
canPop: true,
|
|
onPopInvokedWithResult: (_, __) => _guardarNotas(),
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(l10n.notesTitle),
|
|
actions: [
|
|
if (_autor != null)
|
|
IconButton(
|
|
icon: const Icon(Icons.save),
|
|
onPressed: () async {
|
|
await _guardarNotas();
|
|
if (!context.mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(l10n.notesSaved)),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: _autor == null ? _buildSelector(context) : _buildNotas(context),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSelector(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
AppLocalizations.of(context)!.whoAreYou,
|
|
style: Theme.of(context).textTheme.headlineSmall,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: ListView(
|
|
children: widget.autoresControlados
|
|
.map(
|
|
(autor) => Card(
|
|
child: ListTile(
|
|
leading: const Icon(Icons.edit_note),
|
|
title: Text(autor.nombre),
|
|
onTap: () => _seleccionarAutor(autor),
|
|
),
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNotas(BuildContext context) {
|
|
if (_cargando) return const Center(child: CircularProgressIndicator());
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final autor = _autor!;
|
|
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
if (widget.autoresControlados.length > 1)
|
|
IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () async {
|
|
await _guardarNotas();
|
|
if (!mounted) return;
|
|
setState(() => _autor = null);
|
|
},
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
l10n.notesOf(autor.nombre),
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
l10n.notesAboutPlayers,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
color: TemaApp.colorTextoSecundario,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
..._jugadoresActivos
|
|
.where((jugador) => jugador.id != autor.jugadorId)
|
|
.map(
|
|
(jugador) => Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: TextField(
|
|
controller: _controladores[jugador.id],
|
|
decoration: InputDecoration(
|
|
labelText: jugador.nombre,
|
|
prefixIcon: const Icon(Icons.person, size: 20),
|
|
hintText: l10n.playerNoteHint,
|
|
),
|
|
maxLines: 2,
|
|
minLines: 1,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
l10n.freeNote,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
color: TemaApp.colorTextoSecundario,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: _notaLibreController,
|
|
decoration: InputDecoration(
|
|
hintText: l10n.freeNoteHint,
|
|
prefixIcon: const Icon(Icons.note, size: 20),
|
|
),
|
|
maxLines: 5,
|
|
minLines: 3,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|