Cambios para pwa

This commit is contained in:
2025-03-20 15:18:54 +01:00
parent 6c062682f6
commit a6cace0fe3
137 changed files with 20185 additions and 277 deletions

View File

@ -1,4 +1,5 @@
import 'package:crcivan/BloC/contenedores_event.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:crcivan/bars/app_bar';
@ -9,7 +10,8 @@ import 'package:crcivan/pages/embalses';
import 'package:crcivan/pages/secciones';
import 'package:crcivan/widgets/background_widget.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:crcivan/pages/utils.dart'; // Importar la función global
import 'package:crcivan/pages/utils.dart';
import 'dart:html' as html;
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
@ -20,10 +22,229 @@ class MyHomePage extends StatefulWidget {
}
class _MyHomePageState extends State<MyHomePage> {
html.BeforeInstallPromptEvent? deferredPrompt;
bool isAppInstalled = false;
@override
void initState() {
super.initState();
verificarPrimerInicio();
if (kIsWeb) {
_detectInstallPrompt();
_checkIfAppInstalled();
} else {
verificarPrimerInicio();
}
}
void _checkIfAppInstalled() {
// Verificar si la app está ejecutándose en modo standalone (instalada)
bool isStandalone = html.window.matchMedia('(display-mode: standalone)').matches;
bool isIOSInstalled = false;
try {
// En iOS, navigator.standalone indica si se ejecuta como PWA
isIOSInstalled = html.window.navigator.userAgent.contains('iPhone') && html.window.navigator.userAgent.contains('Safari') && !html.window.navigator.userAgent.contains('CriOS');
} catch (e) {
// Ignorar errores si la propiedad no está disponible
}
setState(() {
isAppInstalled = isStandalone || isIOSInstalled;
});
// También escuchar cambios en el modo de visualización
html.window.addEventListener('appinstalled', (event) {
setState(() {
isAppInstalled = true;
deferredPrompt = null;
});
});
}
void _detectInstallPrompt() {
html.window.addEventListener('beforeinstallprompt', (event) {
// Prevenimos que el navegador muestre automáticamente el prompt
event.preventDefault();
// Guardamos el evento para usarlo más tarde
setState(() {
deferredPrompt = event as html.BeforeInstallPromptEvent;
});
print("Prompt de instalación capturado y listo para usar");
});
}
void _showInstallDialog() {
// Si el prompt está disponible, lo mostramos
try {
if (deferredPrompt != null) {
// Muestra el diálogo de instalación
deferredPrompt!.prompt();
// Esperamos la elección del usuario
deferredPrompt!.userChoice.then((choiceResult) {
// Limpiamos el prompt después de usarlo
setState(() {
deferredPrompt = null;
});
if (kDebugMode) {
if (choiceResult != null) {
print("Usuario respondió al prompt: ${choiceResult['outcome']}");
}
}
// Si el usuario instaló la app, actualizamos el estado
if (choiceResult != null && choiceResult['outcome'] == 'accepted') {
setState(() {
isAppInstalled = true;
});
}
// Configuramos para detectar un nuevo prompt
_detectInstallPrompt();
});
} else {
// Si no hay prompt disponible, mostramos instrucciones según el navegador
_showBrowserSpecificInstructions();
}
} catch (e) {
print("Error al mostrar el diálogo de instalación: $e");
_showBrowserSpecificInstructions();
}
}
void _showBrowserSpecificInstructions() {
final browserInfo = _detectBrowserAndDevice();
final String browser = browserInfo['browser'];
final bool isIOS = browserInfo['isIOS'];
final bool isAndroid = browserInfo['isAndroid'];
String title = "Instala CR-Civán en tu dispositivo";
String instructions = "";
List<Widget> contentWidgets = [];
// Instrucciones específicas según navegador y dispositivo
if (isIOS) {
if (browser == 'safari') {
instructions = "1. Toca el botón Compartir en la parte inferior de Safari (ícono de flecha hacia arriba)\n"
"2. Desplázate hacia abajo y selecciona 'Añadir a pantalla de inicio'\n"
"3. Confirma tocando 'Añadir'";
contentWidgets = [
Text("Para instalar esta aplicación en tu iPhone o iPad:"),
SizedBox(height: 16),
Text(instructions),
SizedBox(height: 16),
Text("Nota: La instalación de PWAs en iOS solo es compatible con Safari.",
style: TextStyle(fontStyle: FontStyle.italic)),
];
} else {
instructions = "Para instalar esta aplicación en iOS, debes abrir esta página en Safari.";
contentWidgets = [
Text(instructions),
SizedBox(height: 16),
Text("Las PWAs en iOS solo pueden instalarse desde Safari.",
style: TextStyle(fontStyle: FontStyle.italic)),
];
}
} else if (browser == 'chrome' || browser == 'edge') {
String browserName = (browser == 'chrome') ? "Chrome" : "Edge";
if (isAndroid) {
instructions = "1. Toca el botón de menú (⋮) en la esquina superior derecha\n"
"2. Selecciona 'Instalar aplicación' o 'Añadir a pantalla de inicio'\n"
"3. Confirma la instalación";
} else {
instructions = "1. Haz clic en el icono de instalación que aparece en la barra de direcciones (icono ▽)\n"
"2. O bien, haz clic en el botón de menú (⋮) y selecciona 'Instalar CR-Civán...'\n"
"3. Confirma la instalación en el diálogo que aparece";
}
contentWidgets = [
Text("Para instalar esta aplicación en $browserName:"),
SizedBox(height: 16),
Text(instructions),
];
} else if (browser == 'firefox') {
instructions = "1. Haz clic en el botón de menú (≡) en la esquina superior derecha\n"
"2. Selecciona 'Instalar aplicación' (si está disponible)\n"
"3. O añade manualmente esta página a tu pantalla de inicio";
contentWidgets = [
Text("Para instalar esta aplicación en Firefox:"),
SizedBox(height: 16),
Text(instructions),
SizedBox(height: 16),
Text("Nota: Firefox tiene soporte limitado para PWAs en algunos sistemas.",
style: TextStyle(fontStyle: FontStyle.italic)),
];
} else {
instructions = "1. Busca en el menú de tu navegador la opción 'Instalar aplicación' o 'Añadir a pantalla de inicio'\n"
"2. También puedes usar las opciones de marcadores/favoritos para acceder rápidamente";
contentWidgets = [
Text("Para instalar esta aplicación:"),
SizedBox(height: 16),
Text(instructions),
];
}
// Mostrar el diálogo con instrucciones
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: contentWidgets,
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("ENTENDIDO"),
),
],
),
);
}
Map<String, dynamic> _detectBrowserAndDevice() {
String userAgent = html.window.navigator.userAgent.toLowerCase();
String browser = 'unknown';
bool isIOS = false;
bool isAndroid = false;
// Detectar sistema operativo
if (userAgent.contains('iphone') || userAgent.contains('ipad') || userAgent.contains('ipod')) {
isIOS = true;
} else if (userAgent.contains('android')) {
isAndroid = true;
}
// Detectar navegador
if (userAgent.contains('chrome') && !userAgent.contains('edg')) {
browser = 'chrome';
} else if (userAgent.contains('firefox')) {
browser = 'firefox';
} else if (userAgent.contains('safari') && !userAgent.contains('chrome')) {
browser = 'safari';
} else if (userAgent.contains('edg')) {
browser = 'edge';
} else if (userAgent.contains('opr') || userAgent.contains('opera')) {
browser = 'opera';
}
return {
'browser': browser,
'isIOS': isIOS,
'isAndroid': isAndroid,
};
}
Future<void> verificarPrimerInicio() async {
@ -61,16 +282,13 @@ class _MyHomePageState extends State<MyHomePage> {
"4. Responsabilidad sobre los Datos:\n"
"Todos los datos presentados en esta aplicación provienen de fuentes externas y no han sido alterados ni modificados. "
"CR-Civán no asume ninguna responsabilidad sobre la veracidad o exactitud de los mismos.",
style: TextStyle(
fontSize: getResponsiveFontSize(
context, 16)), // Ajustar el tamaño del texto aquí
style: TextStyle(fontSize: getResponsiveFontSize(context, 16)),
),
),
actions: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Color.fromARGB(255, 255, 255, 255), // Color del botón
backgroundColor: Color.fromARGB(255, 255, 255, 255),
),
onPressed: () {
Navigator.of(context).pop();
@ -85,202 +303,208 @@ class _MyHomePageState extends State<MyHomePage> {
double getResponsiveFontSize(BuildContext context, double baseFontSize) {
double screenWidth = MediaQuery.of(context).size.width;
// Ajustar el tamaño del texto en función del ancho de la pantalla
if (screenWidth > 600) {
return baseFontSize * 1.5; // Aumentar el tamaño del texto en tablets
return baseFontSize * 1.5;
} else {
return baseFontSize; // Mantener el tamaño del texto en teléfonos
return baseFontSize;
}
}
@override
Widget build(BuildContext context) {
// Obtener dimensiones de pantalla
final screenSize = MediaQuery.of(context).size;
final screenHeight = screenSize.height;
// Hacer botones más pequeños con lógica adaptativa
final isVerySmallScreen = screenHeight < 600;
final isSmallScreen = screenHeight < 710;
// Ajustar tamaños según la pantalla
double buttonHeight = isVerySmallScreen ? 45 : (isSmallScreen ? 55 : 65);
double buttonFontSize = isVerySmallScreen ? 12 : (isSmallScreen ? 14 : 18);
double buttonBottomPadding = isVerySmallScreen ? 5.0 : (isSmallScreen ? 8.0 : 12.0);
return Scaffold(
appBar: const CustomAppBar(),
body: BackgroundWidget(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NoticiasPage()),
);
},
child: SizedBox(
width: 300.0,
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
// Ancho limitado para botones
double buttonWidth = constraints.maxWidth * 0.8;
if (buttonWidth > 300) buttonWidth = 300;
return Stack(
children: [
BackgroundWidget(
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 240, 35, 35),
borderRadius: BorderRadius.circular(7.0),
),
child: Text(
'Avisos',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: getResponsiveFontSize(context, 32.0),
fontWeight: FontWeight.bold,
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: CustomScrollView(
physics: ClampingScrollPhysics(),
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Botones de navegación con altura reducida
_buildNavButton(
'Avisos',
Color.fromARGB(255, 240, 35, 35),
() => Navigator.push(context, MaterialPageRoute(
builder: (context) => const NoticiasPage())),
buttonWidth, buttonHeight, buttonFontSize, buttonBottomPadding
),
_buildNavButton(
'Pregón',
Color.fromARGB(255, 230, 226, 0),
() => Navigator.push(context, MaterialPageRoute(
builder: (context) => const Pregon())),
buttonWidth, buttonHeight, buttonFontSize, buttonBottomPadding
),
_buildNavButton(
'Embalses',
Color.fromARGB(255, 6, 71, 169),
() => Navigator.push(context, MaterialPageRoute(
builder: (context) => const EmbalsesPage())),
buttonWidth, buttonHeight, buttonFontSize, buttonBottomPadding
),
_buildNavButton(
'Secciones',
Color.fromARGB(255, 97, 97, 97),
() => Navigator.push(context, MaterialPageRoute(
builder: (context) => const SeccionesPage())),
buttonWidth, buttonHeight, buttonFontSize, buttonBottomPadding
),
_buildNavButton(
'Tiempo',
Color.fromARGB(255, 255, 255, 255),
launchAemetURL,
buttonWidth, buttonHeight, buttonFontSize, buttonBottomPadding,
textColor: Color.fromARGB(255, 78, 169, 6)
),
SizedBox(height: isVerySmallScreen ? 5 : 10),
// Links y botones inferiores con tamaños reducidos
InkWell(
onTap: launchPrivacyPolicyURL,
child: Text(
'Lee nuestras políticas de privacidad',
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
fontSize: getResponsiveFontSize(context, 12),
),
),
),
SizedBox(height: isVerySmallScreen ? 5 : 8),
ElevatedButton(
onPressed: mostrarAvisoLegal,
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: isVerySmallScreen ? 4 : 8
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text(
'Aviso Legal',
style: TextStyle(
fontSize: getResponsiveFontSize(context, 12),
),
),
),
SizedBox(height: isVerySmallScreen ? 5 : 8),
// Botón de instalación con tamaño reducido - solo se muestra si es web y no está instalada
if (kIsWeb && !isAppInstalled)
ElevatedButton.icon(
icon: Icon(Icons.download_rounded, color: Colors.white, size: isVerySmallScreen ? 14 : 16),
label: Text(
"Instalar aplicación",
style: TextStyle(
color: Colors.white,
fontSize: isVerySmallScreen ? 12 : 14,
fontWeight: FontWeight.bold,
),
),
onPressed: _showInstallDialog,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: isVerySmallScreen ? 4 : 8
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
SizedBox(height: 8),
],
),
),
),
),
],
),
),
),
),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Pregon()),
);
},
child: SizedBox(
width: 300.0,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 230, 226, 0),
borderRadius: BorderRadius.circular(7.0),
),
child: Text(
'Pregón',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: getResponsiveFontSize(context, 32.0),
fontWeight: FontWeight.bold,
),
),
),
),
),
),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EmbalsesPage()),
);
},
child: SizedBox(
width: 300.0,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 6, 71, 169),
borderRadius: BorderRadius.circular(7.0),
),
child: Text(
'Embalses',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: getResponsiveFontSize(context, 32.0),
fontWeight: FontWeight.bold,
),
),
),
),
),
),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SeccionesPage()),
);
},
child: SizedBox(
width: 300.0,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 97, 97, 97),
borderRadius: BorderRadius.circular(7.0),
),
child: Text(
'Secciones',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: getResponsiveFontSize(context, 32.0),
fontWeight: FontWeight.bold,
),
),
),
),
),
),
InkWell(
onTap: () {
launchAemetURL();
},
child: SizedBox(
width: 300.0,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 255, 255),
borderRadius: BorderRadius.circular(7.0),
),
child: Text(
'Tiempo',
textAlign: TextAlign.center,
style: TextStyle(
color: Color.fromARGB(255, 78, 169, 6),
fontSize: getResponsiveFontSize(context, 32.0),
fontWeight: FontWeight.bold,
),
),
),
),
),
),
SizedBox(height: 20),
InkWell(
onTap: () {
launchPrivacyPolicyURL();
},
child: Text(
'Lee nuestras políticas de privacidad',
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
fontSize: getResponsiveFontSize(context, 16.0),
),
),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: mostrarAvisoLegal,
child: Text(
'Aviso Legal',
style: TextStyle(
fontSize: getResponsiveFontSize(context, 16.0),
),
),
),
],
),
],
);
},
),
),
bottomNavigationBar: const CustomBottomBar(),
);
}
// Widget para botones de navegación
Widget _buildNavButton(
String title, Color color, VoidCallback onTap,
double width, double height, double fontSize, double bottomPadding,
{Color textColor = Colors.white}) {
return Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: InkWell(
onTap: onTap,
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(7.0),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: Center(
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
color: textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
),
),
),
);
}
}