Aucune description

main.dart 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import 'dart:convert';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:http/http.dart' as http;
  5. import 'package:shared_preferences/shared_preferences.dart';
  6. import 'custom_info_card.dart';
  7. import 'favorites_page.dart'; // Nuevo import
  8. void main() => runApp(const MyApp());
  9. class MyApp extends StatelessWidget {
  10. const MyApp({super.key});
  11. static const Color primaryColor = Color.fromARGB(255, 254, 100, 91);
  12. static const Color secondaryColor = Colors.orange;
  13. @override
  14. Widget build(BuildContext context) {
  15. return MaterialApp(
  16. title: 'Custom Info Cards',
  17. theme: ThemeData(
  18. primaryColor: primaryColor,
  19. appBarTheme: const AppBarTheme(
  20. backgroundColor: primaryColor,
  21. elevation: 0,
  22. ),
  23. ),
  24. home: const HomePage(),
  25. debugShowCheckedModeBanner: false,
  26. );
  27. }
  28. }
  29. class HomePage extends StatefulWidget {
  30. const HomePage({super.key});
  31. @override
  32. State<HomePage> createState() => _HomePageState();
  33. }
  34. class _HomePageState extends State<HomePage> {
  35. List<Map<String, dynamic>> infoCards = [];
  36. List<Map<String, dynamic>> favoriteCards = [];
  37. int _currentIndex = 0;
  38. static const String googleSheetUrl = 'https://script.google.com/macros/s/AKfycbw1htmk7rlwLMOkOrpZ9ED-7ErWgMlYNtLKxoQ9QO-FopqTAhJuQkR7Gs1LxTJakbMT/exec';
  39. @override
  40. void initState() {
  41. super.initState();
  42. _loadInfoCards();
  43. }
  44. Future<void> _loadInfoCards() async {
  45. final prefs = await SharedPreferences.getInstance();
  46. String? savedData = prefs.getString('infoCardsData');
  47. setState(() {
  48. infoCards = savedData != null
  49. ? List<Map<String, dynamic>>.from(json.decode(savedData))
  50. : _defaultInfoCards();
  51. });
  52. await _fetchUpdatedData();
  53. }
  54. Future<void> _loadFavorites() async {
  55. final prefs = await SharedPreferences.getInstance();
  56. final allKeys = prefs.getKeys().where((key) => key.startsWith('fav_')).toList();
  57. setState(() {
  58. favoriteCards = infoCards.where((card) {
  59. return allKeys.contains('fav_${card['title']}') &&
  60. (prefs.getBool('fav_${card['title']}') ?? false);
  61. }).toList();
  62. });
  63. }
  64. List<Map<String, dynamic>> _defaultInfoCards() {
  65. return [
  66. {
  67. 'image': 'assets/icons/icono1.jpg',
  68. 'title': 'Tutorias de programación',
  69. 'subtitle': '8:00am a 11:30am En biblioteca Lázaro',
  70. 'texto': 'Detalles completos sobre tutorías...',
  71. },
  72. ];
  73. }
  74. Future<void> _fetchUpdatedData() async {
  75. try {
  76. final response = await http.get(Uri.parse(googleSheetUrl));
  77. if (response.statusCode == 200) {
  78. final List<Map<String, dynamic>> newData = List<Map<String, dynamic>>.from(json.decode(response.body));
  79. if (json.encode(newData) != json.encode(infoCards)) {
  80. setState(() => infoCards = newData);
  81. final prefs = await SharedPreferences.getInstance();
  82. await prefs.setString('infoCardsData', json.encode(newData));
  83. }
  84. }
  85. } catch (e) {
  86. if (kDebugMode) print('Error fetching data: $e');
  87. }
  88. }
  89. @override
  90. Widget build(BuildContext context) {
  91. return Scaffold(
  92. appBar: _currentIndex == 0
  93. ? AppBar(
  94. flexibleSpace: Padding(
  95. padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
  96. child: Image.asset(
  97. 'assets/header_image.png',
  98. fit: BoxFit.cover,
  99. ),
  100. ),
  101. toolbarHeight: MediaQuery.of(context).size.height * 0.19,
  102. )
  103. : null,
  104. body: Container(
  105. decoration: const BoxDecoration(
  106. gradient: LinearGradient(
  107. colors: [MyApp.primaryColor, MyApp.secondaryColor],
  108. begin: Alignment.topCenter,
  109. end: Alignment.bottomCenter,
  110. ),
  111. ),
  112. child: _currentIndex == 0
  113. ? RefreshIndicator(
  114. onRefresh: _fetchUpdatedData,
  115. child: _buildMainContent(),
  116. )
  117. : FavoritesPage(favoriteCards: favoriteCards),
  118. ),
  119. bottomNavigationBar: BottomNavigationBar(
  120. currentIndex: _currentIndex,
  121. selectedItemColor: Colors.white,
  122. unselectedItemColor: Colors.white70,
  123. backgroundColor: MyApp.primaryColor,
  124. items: const [
  125. BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Inicio'),
  126. BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Favoritos'),
  127. ],
  128. onTap: (index) {
  129. if (index == 1) _loadFavorites();
  130. setState(() => _currentIndex = index);
  131. },
  132. ),
  133. );
  134. }
  135. Widget _buildMainContent() {
  136. return SafeArea(
  137. child: Column(
  138. children: [
  139. Expanded(
  140. child: ListView.builder(
  141. itemCount: infoCards.isEmpty ? 1 : infoCards.length,
  142. itemBuilder: (context, index) {
  143. if (infoCards.isEmpty) {
  144. return _buildErrorWidget();
  145. }
  146. final card = infoCards[index];
  147. return CustomInfoCard(
  148. imagePath: "assets/icons/${card['image']}",
  149. title: card['title'],
  150. subtitle: card['subtitle'],
  151. texto: card['texto'],
  152. );
  153. },
  154. ),
  155. ),
  156. ],
  157. ),
  158. );
  159. }
  160. Widget _buildErrorWidget() {
  161. return Card(
  162. margin: const EdgeInsets.all(16),
  163. color: Colors.white,
  164. child: Padding(
  165. padding: const EdgeInsets.all(16),
  166. child: Column(
  167. children: [
  168. const Icon(Icons.warning_amber_rounded, size: 48, color: Colors.orange),
  169. const SizedBox(height: 16),
  170. const Text('No se pudo cargar la información', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
  171. const SizedBox(height: 8),
  172. const Text('Desliza hacia abajo para intentar de nuevo', textAlign: TextAlign.center),
  173. const SizedBox(height: 16),
  174. ElevatedButton(
  175. onPressed: _fetchUpdatedData,
  176. style: ElevatedButton.styleFrom(backgroundColor: MyApp.primaryColor),
  177. child: const Text('Reintentar'),
  178. ),
  179. ],
  180. ),
  181. ),
  182. );
  183. }
  184. }