Play rejected version code 54 for still targeting 34. From API 35 Android forces edge-to-edge: content is drawn behind the system bars and it is up to each screen to keep clear of them. Seven screens had no SafeArea and their bottom action button would have ended up under the gesture bar, so FondoFarolero gains a respetarBarras flag that insets the content while the background still paints full bleed. The seventeen screens that already handle it are untouched. The navigation bar colour is ignored under edge-to-edge, so its icons are now forced light to stay visible over the dark background. Verified on the merged manifest: targetSdkVersion 35, and the release bundle builds. The visual result has not been checked on a device.
239 lines
7.4 KiB
Dart
239 lines
7.4 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/componentes_farolero.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: (didPop, result) => _guardarNotas(),
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(l10n.notesTitle),
|
|
actions: [
|
|
if (_autor != null)
|
|
IconButton(
|
|
icon: IconoFarolero(Icons.save),
|
|
onPressed: () async {
|
|
await _guardarNotas();
|
|
if (!context.mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(l10n.notesSaved)),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: FondoFarolero(
|
|
intenso: true,
|
|
respetarBarras: true,
|
|
child: _autor == null ? _buildSelector(context) : _buildNotas(context),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSelector(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const ArteGameplayFarolero.notas(height: 138),
|
|
const SizedBox(height: 10),
|
|
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: IconoFarolero(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: [
|
|
const ArteGameplayFarolero.notas(height: 120),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
if (widget.autoresControlados.length > 1)
|
|
IconButton(
|
|
icon: IconoFarolero(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: IconoFarolero(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: IconoFarolero(Icons.note, size: 20),
|
|
),
|
|
maxLines: 5,
|
|
minLines: 3,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|