Luis Aparicio 1 тиждень тому
джерело
коміт
11f1907ce7
4 змінених файлів з 353 додано та 308 видалено
  1. 94
    90
      lib/custom_info_card.dart
  2. 42
    32
      lib/detail_page.dart
  3. 52
    41
      lib/favorites_page.dart
  4. 165
    145
      lib/main.dart

+ 94
- 90
lib/custom_info_card.dart Переглянути файл

@@ -3,10 +3,7 @@ import 'package:shared_preferences/shared_preferences.dart';
3 3
 import 'detail_page.dart';
4 4
 
5 5
 class CustomInfoCard extends StatefulWidget {
6
-  final String imagePath;
7
-  final String title;
8
-  final String subtitle;
9
-  final String texto;
6
+  final String imagePath, title, subtitle, texto, categoria;
10 7
   final bool isFavorite;
11 8
 
12 9
   const CustomInfoCard({
@@ -15,6 +12,7 @@ class CustomInfoCard extends StatefulWidget {
15 12
     required this.title,
16 13
     required this.subtitle,
17 14
     required this.texto,
15
+    required this.categoria,
18 16
     this.isFavorite = false,
19 17
   });
20 18
 
@@ -34,107 +32,113 @@ class _CustomInfoCardState extends State<CustomInfoCard> {
34 32
 
35 33
   Future<void> _loadFavorite() async {
36 34
     final prefs = await SharedPreferences.getInstance();
37
-    setState(() {
38
-      _isFavorite = prefs.getBool('fav_${widget.title}') ?? false;
39
-    });
35
+    if (mounted) setState(() => _isFavorite = prefs.getBool('fav_${widget.title}') ?? false);
40 36
   }
41 37
 
42 38
   Future<void> _toggleFavorite() async {
43
-    final prefs = await SharedPreferences.getInstance();
44
-    setState(() {
45
-      _isFavorite = !_isFavorite;
46
-      prefs.setBool('fav_${widget.title}', _isFavorite);
47
-    });
39
+    setState(() => _isFavorite = !_isFavorite);
40
+    (await SharedPreferences.getInstance()).setBool('fav_${widget.title}', _isFavorite);
48 41
   }
49 42
 
50 43
   @override
51
-  Widget build(BuildContext context) {
52
-    return GestureDetector(
53
-      onTap: () {
54
-        Navigator.push(
55
-          context,
56
-          MaterialPageRoute(
57
-            builder: (context) => DetailPage(
58
-              title: widget.title,
59
-              texto: widget.texto,
60
-            ),
61
-          ),
62
-        );
63
-      },
64
-      child: SizedBox(
65
-        width: double.infinity,
66
-        height: 130,
67
-        child: Stack(
68
-          children: [
69
-            Card(
70
-              margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
71
-              shape: RoundedRectangleBorder(
72
-                borderRadius: BorderRadius.circular(30),
44
+  Widget build(BuildContext context) => GestureDetector(
45
+    onTap: () => Navigator.push(
46
+      context,
47
+      MaterialPageRoute(
48
+        builder: (_) => DetailPage(
49
+          title: widget.title,
50
+          texto: widget.texto,
51
+          categoria: widget.categoria,
52
+        ),
53
+      ),
54
+    ),
55
+    child: SizedBox(
56
+      width: double.infinity,
57
+      height: 130,
58
+      child: Stack(children: [
59
+        Card(
60
+          margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
61
+          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
62
+          color: const Color.fromARGB(255, 254, 242, 221),
63
+          child: Padding(
64
+            padding: const EdgeInsets.only(right: 8),
65
+            child: Row(children: [
66
+              Container(
67
+                width: 90,
68
+                height: 90,
69
+                decoration: BoxDecoration(
70
+                  color: Colors.white,
71
+                  borderRadius: BorderRadius.circular(36),
72
+                ),
73
+                child: ClipRRect(
74
+                  borderRadius: BorderRadius.circular(36),
75
+                  child: Image.asset(
76
+                    widget.imagePath,
77
+                    fit: BoxFit.cover,
78
+                    width: 90,
79
+                    height: 90,
80
+                  ),
81
+                ),
73 82
               ),
74
-              color: const Color.fromARGB(255, 254, 242, 221),
75
-              child: Padding(
76
-                padding: const EdgeInsets.only(right: 8),
77
-                child: Row(
83
+              const SizedBox(width: 16),
84
+              Expanded(
85
+                child: Column(
86
+                  mainAxisAlignment: MainAxisAlignment.center,
87
+                  crossAxisAlignment: CrossAxisAlignment.start,
78 88
                   children: [
79
-                    Container(
80
-                      width: 90,
81
-                      height: 90,
82
-                      decoration: BoxDecoration(
83
-                        color: Colors.white,
84
-                        borderRadius: BorderRadius.circular(36),
85
-                      ),
86
-                      child: ClipRRect(
87
-                        borderRadius: BorderRadius.circular(36),
88
-                        child: Image.asset(
89
-                          widget.imagePath,
90
-                          fit: BoxFit.cover,
91
-                          width: 90,
92
-                          height: 90,
93
-                        ),
94
-                      ),
89
+                    Text(
90
+                      widget.title,
91
+                      style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
92
+                      maxLines: 1,
93
+                      overflow: TextOverflow.ellipsis,
95 94
                     ),
96
-                    const SizedBox(width: 16),
97
-                    Expanded(
98
-                      child: Column(
99
-                        mainAxisAlignment: MainAxisAlignment.center,
100
-                        crossAxisAlignment: CrossAxisAlignment.start,
101
-                        children: [
102
-                          Text(
103
-                            widget.title,
104
-                            style: const TextStyle(
105
-                              fontSize: 16,
106
-                              fontWeight: FontWeight.bold,
107
-                            ),
108
-                          ),
109
-                          const SizedBox(height: 4),
110
-                          Text(
111
-                            widget.subtitle,
112
-                            style: TextStyle(
113
-                              fontSize: 14,
114
-                              color: Colors.grey[600],
115
-                            ),
116
-                          ),
117
-                        ],
95
+                    const SizedBox(height: 4),
96
+                    Text(
97
+                      widget.subtitle,
98
+                      style: TextStyle(fontSize: 14, color: Colors.grey[600]),
99
+                      maxLines: 2,
100
+                      overflow: TextOverflow.ellipsis,
101
+                    ),
102
+                    const SizedBox(height: 4),
103
+                    Text(
104
+                      widget.categoria,
105
+                      style: TextStyle(
106
+                        fontSize: 12,
107
+                        color: _getCategoryColor(widget.categoria),
108
+                        fontWeight: FontWeight.bold,
118 109
                       ),
119 110
                     ),
120 111
                   ],
121 112
                 ),
122 113
               ),
114
+            ]),
115
+          ),
116
+        ),
117
+        Positioned(
118
+          top: 12,
119
+          right: 28,
120
+          child: IconButton(
121
+            icon: Icon(
122
+              _isFavorite ? Icons.favorite : Icons.favorite_border,
123
+              color: _isFavorite ? Colors.red : Colors.grey[400],
123 124
             ),
124
-            Positioned(
125
-              top: 12,
126
-              right: 28,
127
-              child: IconButton(
128
-                icon: Icon(
129
-                  _isFavorite ? Icons.favorite : Icons.favorite_border,
130
-                  color: _isFavorite ? Colors.red : Colors.grey[400],
131
-                ),
132
-                onPressed: _toggleFavorite,
133
-              ),
134
-            ),
135
-          ],
125
+            onPressed: _toggleFavorite,
126
+          ),
136 127
         ),
137
-      ),
138
-    );
128
+      ]),
129
+    ),
130
+  );
131
+
132
+  Color _getCategoryColor(String categoria) {
133
+    switch (categoria) {
134
+      case 'Actividades':
135
+        return Colors.blue;
136
+      case 'Noticias UPR':
137
+        return Colors.green;
138
+      case 'Fechas Importantes':
139
+        return Colors.purple;
140
+      default:
141
+        return Colors.grey;
142
+    }
139 143
   }
140 144
 }

+ 42
- 32
lib/detail_page.dart Переглянути файл

@@ -3,51 +3,61 @@ import 'package:flutter/material.dart';
3 3
 class DetailPage extends StatelessWidget {
4 4
   final String title;
5 5
   final String texto;
6
+  final String categoria;
6 7
 
7 8
   const DetailPage({
8 9
     super.key,
9 10
     required this.title,
10 11
     required this.texto,
12
+    required this.categoria,
11 13
   });
12 14
 
13 15
   @override
14
-  Widget build(BuildContext context) {
15
-    return Scaffold(
16
-      appBar: AppBar(
17
-        flexibleSpace: Padding(
18
-          padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + -20),
19
-          child: Image.asset(
20
-            'assets/header_image.png',
21
-            fit: BoxFit.cover,
22
-          ),
23
-        ),
24
-        toolbarHeight: MediaQuery.of(context).size.height * 0.19,
16
+  Widget build(BuildContext context) => Scaffold(
17
+    appBar: AppBar(
18
+      flexibleSpace: Padding(
19
+        padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
20
+        child: Image.asset('assets/header_image.png', fit: BoxFit.cover),
25 21
       ),
26
-      body: Container(
27
-        decoration: const BoxDecoration(
28
-          color: Color.fromARGB(255, 254, 242, 221),
29
-        ),
30
-        child: Padding(
31
-          padding: const EdgeInsets.all(16.0),
32
-          child: SingleChildScrollView( // Agregado para evitar overflow
33
-            child: Column(
34
-              crossAxisAlignment: CrossAxisAlignment.center,
35
-              children: [
36
-                Text(
37
-                  title,
38
-                  style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
22
+      toolbarHeight: MediaQuery.of(context).size.height * 0.19,
23
+      title: Text(
24
+        categoria,
25
+        style: const TextStyle(color: Colors.white, fontSize: 16),
26
+      ),
27
+    ),
28
+    body: Container(
29
+      decoration: const BoxDecoration(color: Color.fromARGB(255, 254, 242, 221)),
30
+      child: Padding(
31
+        padding: const EdgeInsets.all(16.0),
32
+        child: SingleChildScrollView(
33
+          child: Column(
34
+            crossAxisAlignment: CrossAxisAlignment.start,
35
+            children: [
36
+              Text(
37
+                title,
38
+                style: const TextStyle(
39
+                  fontSize: 24,
40
+                  fontWeight: FontWeight.bold,
41
+                  color: Colors.black87,
42
+                ),
43
+              ),
44
+              const SizedBox(height: 8),
45
+              Container(
46
+                padding: const EdgeInsets.all(12),
47
+                decoration: BoxDecoration(
48
+                  color: Colors.white,
49
+                  borderRadius: BorderRadius.circular(8),
39 50
                 ),
40
-                const SizedBox(height: 8),
41
-                Text(
51
+                child: Text(
42 52
                   texto,
43
-                  style: const TextStyle(fontSize: 18, color: Colors.black),
44
-                  textAlign: TextAlign.center,
53
+                  style: const TextStyle(fontSize: 16, height: 1.5),
54
+                  textAlign: TextAlign.justify,
45 55
                 ),
46
-              ],
47
-            ),
56
+              ),
57
+            ],
48 58
           ),
49 59
         ),
50 60
       ),
51
-    );
52
-  }
61
+    ),
62
+  );
53 63
 }

+ 52
- 41
lib/favorites_page.dart Переглянути файл

@@ -3,53 +3,64 @@ import 'custom_info_card.dart';
3 3
 
4 4
 class FavoritesPage extends StatelessWidget {
5 5
   final List<Map<String, dynamic>> favoriteCards;
6
+  final VoidCallback? onRefresh;
6 7
 
7
-  const FavoritesPage({super.key, required this.favoriteCards});
8
+  const FavoritesPage({super.key, required this.favoriteCards, this.onRefresh});
8 9
 
9 10
   @override
10
-  Widget build(BuildContext context) {
11
-    return Scaffold(
12
-      appBar: AppBar(
13
-        flexibleSpace: Padding(
14
-          padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
15
-          child: Image.asset(
16
-            'assets/header_image.png',
17
-            fit: BoxFit.cover,
18
-          ),
19
-        ),
20
-        toolbarHeight: MediaQuery.of(context).size.height * 0.19,
21
-        title: const Text('Mis Favoritos', style: TextStyle(color: Colors.white)),
11
+  Widget build(BuildContext context) => Scaffold(
12
+    appBar: AppBar(
13
+      flexibleSpace: Padding(
14
+        padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
15
+        child: Image.asset('assets/header_image.png', fit: BoxFit.cover),
22 16
       ),
23
-      body: Container(
24
-        decoration: const BoxDecoration(
25
-          gradient: LinearGradient(
26
-            colors: [Color.fromARGB(255, 254, 100, 91), Colors.orange],
27
-            begin: Alignment.topCenter,
28
-            end: Alignment.bottomCenter,
29
-          ),
17
+      toolbarHeight: MediaQuery.of(context).size.height * 0.19,
18
+      title: const Text('Mis Favoritos', style: TextStyle(color: Colors.white)),
19
+    ),
20
+    body: Container(
21
+      decoration: const BoxDecoration(
22
+        gradient: LinearGradient(
23
+          colors: [Color.fromARGB(255, 254, 100, 91), Colors.orange],
24
+          begin: Alignment.topCenter,
25
+          end: Alignment.bottomCenter,
30 26
         ),
31
-        child: favoriteCards.isEmpty
32
-            ? const Center(
33
-                child: Text(
34
-                  'No tienes favoritos aún',
35
-                  style: TextStyle(color: Colors.white, fontSize: 18),
36
-                ),
37
-              )
38
-            : ListView.builder(
27
+      ),
28
+      child: favoriteCards.isEmpty
29
+          ? _buildEmptyState()
30
+          : RefreshIndicator(
31
+              onRefresh: () async => onRefresh?.call(),
32
+              child: ListView.builder(
39 33
                 padding: const EdgeInsets.only(top: 16),
40 34
                 itemCount: favoriteCards.length,
41
-                itemBuilder: (context, index) {
42
-                  final card = favoriteCards[index];
43
-                  return CustomInfoCard(
44
-                    imagePath: "assets/icons/${card['image']}",
45
-                    title: card['title'],
46
-                    subtitle: card['subtitle'],
47
-                    texto: card['texto'],
48
-                    isFavorite: true,
49
-                  );
50
-                },
35
+                itemBuilder: (_, i) => CustomInfoCard(
36
+                  imagePath: "assets/icons/${favoriteCards[i]['image']}",
37
+                  title: favoriteCards[i]['title'],
38
+                  subtitle: favoriteCards[i]['subtitle'],
39
+                  texto: favoriteCards[i]['texto'],
40
+                  categoria: favoriteCards[i]['categoria'],
41
+                  isFavorite: true,
42
+                ),
51 43
               ),
52
-      ),
53
-    );
54
-  }
44
+            ),
45
+    ),
46
+  );
47
+
48
+  Widget _buildEmptyState() => Center(
49
+    child: Column(
50
+      mainAxisAlignment: MainAxisAlignment.center,
51
+      children: [
52
+        const Icon(Icons.favorite_border, size: 64, color: Colors.white54),
53
+        const SizedBox(height: 16),
54
+        const Text(
55
+          'No tienes favoritos aún',
56
+          style: TextStyle(color: Colors.white, fontSize: 18),
57
+        ),
58
+        const SizedBox(height: 8),
59
+        Text(
60
+          'Agrega favoritos desde las otras secciones',
61
+          style: TextStyle(color: Colors.white.withOpacity(0.8)),
62
+        ),
63
+      ],
64
+    ),
65
+  );
55 66
 }

+ 165
- 145
lib/main.dart Переглянути файл

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
4 4
 import 'package:http/http.dart' as http;
5 5
 import 'package:shared_preferences/shared_preferences.dart';
6 6
 import 'custom_info_card.dart';
7
-import 'favorites_page.dart'; // Nuevo import
7
+import 'favorites_page.dart';
8 8
 
9 9
 void main() => runApp(const MyApp());
10 10
 
@@ -15,20 +15,26 @@ class MyApp extends StatelessWidget {
15 15
   static const Color secondaryColor = Colors.orange;
16 16
 
17 17
   @override
18
-  Widget build(BuildContext context) {
19
-    return MaterialApp(
20
-      title: 'Custom Info Cards',
21
-      theme: ThemeData(
22
-        primaryColor: primaryColor,
23
-        appBarTheme: const AppBarTheme(
24
-          backgroundColor: primaryColor,
25
-          elevation: 0,
26
-        ),
18
+  Widget build(BuildContext context) => MaterialApp(
19
+    title: 'UPR App',
20
+    theme: ThemeData(
21
+      primaryColor: primaryColor,
22
+      appBarTheme: const AppBarTheme(
23
+        backgroundColor: primaryColor,
24
+        elevation: 0,
27 25
       ),
28
-      home: const HomePage(),
29
-      debugShowCheckedModeBanner: false,
30
-    );
31
-  }
26
+      bottomNavigationBarTheme: const BottomNavigationBarThemeData(
27
+        selectedItemColor: Colors.white,
28
+        unselectedItemColor: Colors.white70,
29
+        backgroundColor: primaryColor,
30
+        type: BottomNavigationBarType.fixed,
31
+        showSelectedLabels: true,
32
+        showUnselectedLabels: true,
33
+      ),
34
+    ),
35
+    home: const HomePage(),
36
+    debugShowCheckedModeBanner: false,
37
+  );
32 38
 }
33 39
 
34 40
 class HomePage extends StatefulWidget {
@@ -39,165 +45,179 @@ class HomePage extends StatefulWidget {
39 45
 }
40 46
 
41 47
 class _HomePageState extends State<HomePage> {
42
-  List<Map<String, dynamic>> infoCards = [];
48
+  List<Map<String, dynamic>> allCards = [];
49
+  List<Map<String, dynamic>> filteredCards = [];
43 50
   List<Map<String, dynamic>> favoriteCards = [];
44 51
   int _currentIndex = 0;
45
-  static const String googleSheetUrl = 'https://script.google.com/macros/s/AKfycbw1htmk7rlwLMOkOrpZ9ED-7ErWgMlYNtLKxoQ9QO-FopqTAhJuQkR7Gs1LxTJakbMT/exec';
52
+  bool _isLoading = false;
53
+  static const String _sheetUrl = 'https://script.google.com/macros/s/AKfycbw1htmk7rlwLMOkOrpZ9ED-7ErWgMlYNtLKxoQ9QO-FopqTAhJuQkR7Gs1LxTJakbMT/exec';
46 54
 
47 55
   @override
48 56
   void initState() {
49 57
     super.initState();
50
-    _loadInfoCards();
58
+    _loadData();
51 59
   }
52 60
 
53
-  Future<void> _loadInfoCards() async {
61
+  Future<void> _loadData() async {
54 62
     final prefs = await SharedPreferences.getInstance();
55
-    String? savedData = prefs.getString('infoCardsData');
56
-    
57 63
     setState(() {
58
-      infoCards = savedData != null 
59
-          ? List<Map<String, dynamic>>.from(json.decode(savedData))
60
-          : _defaultInfoCards();
64
+      allCards = prefs.getString('infoCardsData') != null 
65
+          ? List<Map<String, dynamic>>.from(json.decode(prefs.getString('infoCardsData')!))
66
+          : [_defaultCard()];
67
+      filteredCards = allCards;
61 68
     });
62
-    
63
-    await _fetchUpdatedData();
69
+    await _fetchData();
64 70
   }
65 71
 
66
-  Future<void> _loadFavorites() async {
67
-    final prefs = await SharedPreferences.getInstance();
68
-    final allKeys = prefs.getKeys().where((key) => key.startsWith('fav_')).toList();
69
-    
70
-    setState(() {
71
-      favoriteCards = infoCards.where((card) {
72
-        return allKeys.contains('fav_${card['title']}') && 
73
-               (prefs.getBool('fav_${card['title']}') ?? false);
74
-      }).toList();
75
-    });
76
-  }
77
-
78
-  List<Map<String, dynamic>> _defaultInfoCards() {
79
-    return [
80
-      {
81
-        'image': 'assets/icons/icono1.jpg',
82
-        'title': 'Tutorias de programación',
83
-        'subtitle': '8:00am a 11:30am En biblioteca Lázaro',
84
-        'texto': 'Detalles completos sobre tutorías...',
85
-      },
86
-    ];
87
-  }
88
-
89
-  Future<void> _fetchUpdatedData() async {
72
+  Future<void> _fetchData() async {
90 73
     try {
91
-      final response = await http.get(Uri.parse(googleSheetUrl));
74
+      setState(() => _isLoading = true);
75
+      final response = await http.get(Uri.parse(_sheetUrl)).timeout(const Duration(seconds: 10));
92 76
       if (response.statusCode == 200) {
93
-        final List<Map<String, dynamic>> newData = List<Map<String, dynamic>>.from(json.decode(response.body));
94
-        
95
-        if (json.encode(newData) != json.encode(infoCards)) {
96
-          setState(() => infoCards = newData);
97
-          final prefs = await SharedPreferences.getInstance();
98
-          await prefs.setString('infoCardsData', json.encode(newData));
77
+        final newData = List<Map<String, dynamic>>.from(json.decode(response.body));
78
+        if (json.encode(newData) != json.encode(allCards)) {
79
+          setState(() {
80
+            allCards = newData;
81
+            filteredCards = _filterCardsByCategory(_currentIndex);
82
+          });
83
+          (await SharedPreferences.getInstance()).setString('infoCardsData', response.body);
99 84
         }
100 85
       }
101 86
     } catch (e) {
102
-      if (kDebugMode) print('Error fetching data: $e');
87
+      if (kDebugMode) print('Fetch error: $e');
88
+    } finally {
89
+      if (mounted) setState(() => _isLoading = false);
103 90
     }
104 91
   }
105 92
 
93
+  Future<void> _loadFavorites() async {
94
+    final prefs = await SharedPreferences.getInstance();
95
+    final favKeys = prefs.getKeys().where((k) => k.startsWith('fav_') && (prefs.getBool(k) ?? false));
96
+    setState(() => favoriteCards = allCards.where((c) => favKeys.contains('fav_${c['title']}')).toList());
97
+  }
98
+
99
+  List<Map<String, dynamic>> _filterCardsByCategory(int index) {
100
+    switch (index) {
101
+      case 0: return allCards;
102
+      case 1: return allCards.where((card) => card['categoria'] == 'Actividades').toList();
103
+      case 2: return allCards.where((card) => card['categoria'] == 'Noticias UPR').toList();
104
+      case 3: return allCards.where((card) => card['categoria'] == 'Fechas Importantes').toList();
105
+      case 4: return favoriteCards;
106
+      default: return allCards;
107
+    }
108
+  }
109
+
110
+  Map<String, dynamic> _defaultCard() => {
111
+    'image': 'assets/icons/icono1.jpg',
112
+    'title': 'Tutorias de programación',
113
+    'subtitle': '8:00am a 11:30am En biblioteca Lázaro',
114
+    'texto': 'Detalles completos sobre tutorías...',
115
+    'categoria': 'Actividades',
116
+  };
117
+
106 118
   @override
107
-  Widget build(BuildContext context) {
108
-    return Scaffold(
109
-      appBar: _currentIndex == 0 
110
-          ? AppBar(
111
-              flexibleSpace: Padding(
112
-                padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
113
-                child: Image.asset(
114
-                  'assets/header_image.png',
115
-                  fit: BoxFit.cover,
116
-                ),
117
-              ),
118
-              toolbarHeight: MediaQuery.of(context).size.height * 0.19,
119
-            )
120
-          : null,
121
-      body: Container(
122
-        decoration: const BoxDecoration(
123
-          gradient: LinearGradient(
124
-            colors: [MyApp.primaryColor, MyApp.secondaryColor],
125
-            begin: Alignment.topCenter,
126
-            end: Alignment.bottomCenter,
127
-          ),
119
+  Widget build(BuildContext context) => Scaffold(
120
+    appBar: _currentIndex != 4 ? _buildAppBar(context) : null,
121
+    body: Container(
122
+      decoration: const BoxDecoration(
123
+        gradient: LinearGradient(
124
+          colors: [MyApp.primaryColor, MyApp.secondaryColor],
125
+          begin: Alignment.topCenter,
126
+          end: Alignment.bottomCenter,
128 127
         ),
129
-        child: _currentIndex == 0
130
-            ? RefreshIndicator(
131
-                onRefresh: _fetchUpdatedData,
132
-                child: _buildMainContent(),
133
-              )
134
-            : FavoritesPage(favoriteCards: favoriteCards),
135 128
       ),
136
-      bottomNavigationBar: BottomNavigationBar(
137
-        currentIndex: _currentIndex,
138
-        selectedItemColor: Colors.white,
139
-        unselectedItemColor: Colors.white70,
140
-        backgroundColor: MyApp.primaryColor,
141
-        items: const [
142
-          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Inicio'),
143
-          BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Favoritos'),
144
-        ],
145
-        onTap: (index) {
146
-          if (index == 1) _loadFavorites();
147
-          setState(() => _currentIndex = index);
148
-        },
149
-      ),
150
-    );
151
-  }
129
+      child: _currentIndex == 4 
130
+          ? FavoritesPage(favoriteCards: favoriteCards, onRefresh: _loadFavorites)
131
+          : _buildHome(),
132
+    ),
133
+    bottomNavigationBar: _buildBottomNavBar(),
134
+  );
152 135
 
153
-  Widget _buildMainContent() {
154
-    return SafeArea(
155
-      child: Column(
156
-        children: [
157
-          Expanded(
158
-            child: ListView.builder(
159
-              itemCount: infoCards.isEmpty ? 1 : infoCards.length,
160
-              itemBuilder: (context, index) {
161
-                if (infoCards.isEmpty) {
162
-                  return _buildErrorWidget();
163
-                }
164
-                final card = infoCards[index];
165
-                return CustomInfoCard(
166
-                  imagePath: "assets/icons/${card['image']}",
167
-                  title: card['title'],
168
-                  subtitle: card['subtitle'],
169
-                  texto: card['texto'],
170
-                );
171
-              },
172
-            ),
173
-          ),
174
-        ],
136
+  AppBar _buildAppBar(BuildContext context) => AppBar(
137
+    flexibleSpace: Padding(
138
+      padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
139
+      child: Image.asset('assets/header_image.png', fit: BoxFit.cover),
140
+    ),
141
+    toolbarHeight: MediaQuery.of(context).size.height * 0.19,
142
+  );
143
+
144
+  Widget _buildBottomNavBar() => BottomNavigationBar(
145
+    currentIndex: _currentIndex,
146
+    onTap: (index) {
147
+      setState(() {
148
+        _currentIndex = index;
149
+        filteredCards = _filterCardsByCategory(index);
150
+        if (index == 4) _loadFavorites();
151
+      });
152
+    },
153
+    items: const [
154
+      BottomNavigationBarItem(
155
+        icon: Icon(Icons.home_outlined),
156
+        activeIcon: Icon(Icons.home),
157
+        label: 'Inicio',
175 158
       ),
176
-    );
177
-  }
159
+      BottomNavigationBarItem(
160
+        icon: Icon(Icons.event_outlined),
161
+        activeIcon: Icon(Icons.event),
162
+        label: 'Actividades',
163
+      ),
164
+      BottomNavigationBarItem(
165
+        icon: Icon(Icons.article_outlined),
166
+        activeIcon: Icon(Icons.article),
167
+        label: 'Noticias',
168
+      ),
169
+      BottomNavigationBarItem(
170
+        icon: Icon(Icons.calendar_today_outlined),
171
+        activeIcon: Icon(Icons.calendar_today),
172
+        label: 'Fechas',
173
+      ),
174
+      BottomNavigationBarItem(
175
+        icon: Icon(Icons.favorite_outline),
176
+        activeIcon: Icon(Icons.favorite),
177
+        label: 'Favoritos',
178
+      ),
179
+    ],
180
+  );
178 181
 
179
-  Widget _buildErrorWidget() {
180
-    return Card(
181
-      margin: const EdgeInsets.all(16),
182
-      color: Colors.white,
183
-      child: Padding(
184
-        padding: const EdgeInsets.all(16),
185
-        child: Column(
186
-          children: [
187
-            const Icon(Icons.warning_amber_rounded, size: 48, color: Colors.orange),
188
-            const SizedBox(height: 16),
189
-            const Text('No se pudo cargar la información', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
190
-            const SizedBox(height: 8),
191
-            const Text('Desliza hacia abajo para intentar de nuevo', textAlign: TextAlign.center),
192
-            const SizedBox(height: 16),
193
-            ElevatedButton(
194
-              onPressed: _fetchUpdatedData,
195
-              style: ElevatedButton.styleFrom(backgroundColor: MyApp.primaryColor),
196
-              child: const Text('Reintentar'),
197
-            ),
198
-          ],
182
+  Widget _buildHome() => SafeArea(
183
+    child: Column(children: [
184
+      Expanded(child: _isLoading ? _buildLoader() : RefreshIndicator(
185
+        onRefresh: _fetchData,
186
+        child: filteredCards.isEmpty ? _buildEmptyState() : ListView.builder(
187
+          itemCount: filteredCards.length,
188
+          itemBuilder: (ctx, i) => CustomInfoCard(
189
+            imagePath: "assets/icons/${filteredCards[i]['image']}",
190
+            title: filteredCards[i]['title'],
191
+            subtitle: filteredCards[i]['subtitle'],
192
+            texto: filteredCards[i]['texto'],
193
+            categoria: filteredCards[i]['categoria'],
194
+          ),
199 195
         ),
196
+      )),
197
+    ]),
198
+  );
199
+
200
+  Widget _buildLoader() => const Center(child: CircularProgressIndicator(color: Colors.white));
201
+  
202
+  Widget _buildEmptyState() => Center(child: Column(
203
+    mainAxisSize: MainAxisSize.min,
204
+    children: [
205
+      const Icon(Icons.info_outline, size: 50, color: Colors.white),
206
+      const SizedBox(height: 16),
207
+      Text(
208
+        'No hay ${_getCategoryName(_currentIndex)} disponibles',
209
+        style: const TextStyle(color: Colors.white, fontSize: 18),
200 210
       ),
201
-    );
211
+    ],
212
+  ));
213
+
214
+  String _getCategoryName(int index) {
215
+    switch (index) {
216
+      case 0: return 'contenidos';
217
+      case 1: return 'actividades';
218
+      case 2: return 'noticias';
219
+      case 3: return 'fechas importantes';
220
+      default: return 'elementos';
221
+    }
202 222
   }
203 223
 }