511 lines
21 KiB
Dart
511 lines
21 KiB
Dart
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';
|
|
import 'package:crcivan/bars/bottom_bar';
|
|
import 'package:crcivan/pages/noticias';
|
|
import 'package:crcivan/pages/pregon';
|
|
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';
|
|
import 'dart:html' as html;
|
|
|
|
class MyHomePage extends StatefulWidget {
|
|
const MyHomePage({super.key, required this.title});
|
|
final String title;
|
|
|
|
@override
|
|
_MyHomePageState createState() => _MyHomePageState();
|
|
}
|
|
|
|
class _MyHomePageState extends State<MyHomePage> {
|
|
html.BeforeInstallPromptEvent? deferredPrompt;
|
|
bool isAppInstalled = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
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 {
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
bool yaMostrado = prefs.getBool('aviso_mostrado') ?? false;
|
|
|
|
if (!yaMostrado) {
|
|
Future.delayed(Duration.zero, () => mostrarAvisoLegal());
|
|
await prefs.setBool('aviso_mostrado', true);
|
|
}
|
|
}
|
|
|
|
void mostrarAvisoLegal() {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text("Aviso Legal"),
|
|
content: SingleChildScrollView(
|
|
child: Text(
|
|
"1. Relación con Entidades Oficiales:\n"
|
|
"Esta aplicación no está afiliada, respaldada ni oficialmente conectada con ninguna entidad gubernamental, institución o autoridad pública. "
|
|
"CR-Civán no representa a ninguna organización oficial ni gubernamental.\n\n"
|
|
"2. Datos de los Embalses:\n"
|
|
"Los datos sobre el estado de los embalses son proporcionados exclusivamente por la Confederación Hidrográfica del Ebro (CHE), "
|
|
"los cuales están a disposición de todo el mundo en (https://www.saihebro.com/homepage/estado-cuenca-ebro). "
|
|
"Los datos mostrados en esta aplicación no han sido alterados ni modificados; simplemente se presentan para mayor comodidad del usuario. "
|
|
"CR-Civán no representa ni tiene relación directa con la Confederación Hidrográfica del Ebro y, por lo tanto, no se hace responsable de la exactitud o veracidad de los datos proporcionados por la CHE.\n\n"
|
|
"3. Información Meteorológica:\n"
|
|
"La información meteorológica se proporciona a través de un enlace a la página oficial de la Agencia Estatal de Meteorología (AEMET), "
|
|
"disponible en (https://www.aemet.es). CR-Civán no se hace responsable de la exactitud de los datos proporcionados por AEMET, "
|
|
"ya que la aplicación solo redirige al usuario a su página oficial sin modificar ni representar la información de manera directa. "
|
|
"La información meteorológica se muestra en un navegador externo y no dentro de la propia aplicación, por lo que CR-Civán no se responsabiliza del contenido mostrado en dicho enlace.\n\n"
|
|
"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)),
|
|
),
|
|
),
|
|
actions: [
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Color.fromARGB(255, 255, 255, 255),
|
|
),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
child: Text("LO ENTIENDO"),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
double getResponsiveFontSize(BuildContext context, double baseFontSize) {
|
|
double screenWidth = MediaQuery.of(context).size.width;
|
|
if (screenWidth > 600) {
|
|
return baseFontSize * 1.5;
|
|
} else {
|
|
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: 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: 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),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|