From e9f834c2676a9bea950379fad7c841c520ee0f28 Mon Sep 17 00:00:00 2001 From: "EPAZZ\\estpp" Date: Sun, 3 Aug 2025 12:23:47 -0500 Subject: [PATCH] added activities and conference type, get list dynamic, todo: year, month params and fix ui for list whit images. --- lib/main.dart | 15 +- lib/models/conferences_model.dart | 98 -------- lib/models/item_model.dart | 216 ++++++++++++++++++ lib/navigation_bar.dart | 51 +++++ lib/pages/conferences.dart | 88 ------- ...etail_conference.dart => detail_item.dart} | 41 ++-- lib/pages/home.dart | 15 ++ lib/pages/items_list.dart | 62 +++++ lib/providers/directus_service.dart | 4 +- lib/providers/wordpress_service.dart | 35 +++ pubspec.lock | 8 + pubspec.yaml | 1 + 12 files changed, 424 insertions(+), 210 deletions(-) delete mode 100644 lib/models/conferences_model.dart create mode 100644 lib/models/item_model.dart create mode 100644 lib/navigation_bar.dart delete mode 100644 lib/pages/conferences.dart rename lib/pages/{detail_conference.dart => detail_item.dart} (61%) create mode 100644 lib/pages/home.dart create mode 100644 lib/pages/items_list.dart create mode 100644 lib/providers/wordpress_service.dart diff --git a/lib/main.dart b/lib/main.dart index c799653..6a1461c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,19 +1,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:lgcc/pages/conferences.dart'; +import 'package:lgcc/pages/home.dart'; +import 'package:lgcc/navigation_bar.dart'; +// import 'package:lgcc/pages/items_list.dart'; Future main() async { await dotenv.load(fileName: ".env"); runApp(MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { @override Widget build(BuildContext context) { - return const MaterialApp( - home: ConferencesPage(), + return MaterialApp( + home: MainNavigationBar(), ); } } diff --git a/lib/models/conferences_model.dart b/lib/models/conferences_model.dart deleted file mode 100644 index a386374..0000000 --- a/lib/models/conferences_model.dart +++ /dev/null @@ -1,98 +0,0 @@ -class TranslationModel { - final int id; - final String language; - final String title; - final String? markdown; - - TranslationModel({ - required this.id, - required this.language, - required this.title, - this.markdown, - }); - - Map toJson() { - return { - 'id': id, - 'language': language, - 'title': title, - 'markdown': markdown, - }; - } - factory TranslationModel.fromJson(Map json) { - return TranslationModel( - id: json['id'] ?? 0, - language: json['language'] ?? '', - title: json['title'] ?? '', - markdown: json['markdown'] ?? '', - ); - } -} - -class ConferencesModel { - - final int id; - final String title; - final DateTime date; - int activity; - int duration; - String place; - String city; - String state; - String country; - // List type; - String thumbnail; - List translations; - - ConferencesModel({ - required this.id, - required this.title, - required this.date, - this.activity = 0, - this.duration = 0, - this.place = '', - this.city = '', - this.state = '', - this.country = '', - // this.type = const [], - this.thumbnail = '', - this.translations = const [], - }); - - Map toJson() { - return { - 'id': id, - 'title': title, - 'date': date.toIso8601String(), - 'activity': activity, - 'duration': duration, - 'place': place, - 'city': city, - 'state': state, - 'country': country, - // 'type': type, - 'thumbnail': thumbnail.toString(), - 'translations': translations.toString() , - }; - } - - factory ConferencesModel.fromJson(Map json) { - return ConferencesModel( - id: json['id'] ?? 0, - title: json['title'] ?? '', - date: json['date'] != null ? DateTime.parse(json['date'].toString()) : DateTime.now(), - activity: json['activity'] ?? 0, - duration: json['duration'] ?? 0, - place: json['place'] ?? '', - city: json['city'] ?? '', - state: json['state'] ?? '', - country: json['country'] ?? '', - // type: List.from(json['type'] ?? []), - thumbnail: json['thumbnail'] ?? '', - translations: (json['translations'] as List?) - ?.map((item) => TranslationModel.fromJson(item as Map)) - .toList() ?? [], - ); - - } -} diff --git a/lib/models/item_model.dart b/lib/models/item_model.dart new file mode 100644 index 0000000..aaee234 --- /dev/null +++ b/lib/models/item_model.dart @@ -0,0 +1,216 @@ +class TranslationModel { + final int id; + final String locale; + + TranslationModel({required this.id, required this.locale}); + + Map toJson() { + return {'id': id, 'language': locale}; + } + + factory TranslationModel.fromJson(Map json) { + return TranslationModel(id: json['id'] ?? 0, locale: json['locale'] ?? ''); + } +} + +class Files { + final String? youtube; + final String? video; + final String? audio; + final String? booklet; + final String? simple; + + Files({this.youtube, this.video, this.audio, this.booklet, this.simple}); + + Map toJson() { + return { + 'youtube': youtube, + 'video': video, + 'audio': audio, + 'booklet': booklet, + 'simple': simple, + }; + } + + factory Files.fromJson(Map json) { + return Files( + youtube: json['youtube'] as String?, + video: json['video'] as String?, + audio: json['audio'] as String?, + booklet: json['booklet'] as String?, + simple: json['simple'] as String?, + ); + } +} + +class DurationField { + final int duration; + + DurationField({required this.duration}); + + factory DurationField.fromJson(Map json) { + // convert duration from string to int + final dynamic rawDuration = json['duration']; + final int parsedDuration = rawDuration is String + ? int.tryParse(rawDuration) ?? 0 + : rawDuration is int + ? rawDuration + : 0; + return DurationField(duration: parsedDuration); + } +} + +class ItemModel { + final String id; + final String title; + final DateTime date; + final String type; + final String? slug; + bool rm; + String biblestudy; + bool draft; + int timestamp; + String activity; + DurationField duration; + String place; + String city; + String state; + String country; + // List type; + String thumbnail; + List translations; + Files files; + + ItemModel({ + required this.id, + required this.title, + required this.date, + this.type = '', + this.slug, + this.rm = false, + this.biblestudy = '', + this.draft = false, + this.timestamp = 0, + this.activity = '', + required this.duration, + this.place = '', + this.city = '', + this.state = '', + this.country = '', + // this.type = const [], + this.thumbnail = '', + this.translations = const [], + Files? files, + }) : files = files ?? Files(); + + Map toJson() { + return { + 'id': id, + 'title': title, + 'date': date.toIso8601String(), + 'type': type, + 'slug': slug, + 'rm': rm, + 'biblestudy': biblestudy, + 'draft': draft, + 'timestamp': timestamp, + 'activity': activity, + 'duration': duration, + 'place': place, + 'city': city, + 'state': state, + 'country': country, + 'thumbnail': thumbnail.toString(), + 'translations': translations.toString(), + 'files': files.toString(), + }; + } + + factory ItemModel.fromJson(Map json) { + return ItemModel( + id: json['id'] ?? '', + title: json['title'] ?? '', + date: + json['date'] != null + ? DateTime.parse(json['date'].toString()) + : DateTime.now(), + type: json['type'] ?? '', + slug: json['slug'], + rm: json['rm'] ?? false, + biblestudy: json['biblestudy'] ?? '', + draft: json['draft'] ?? false, + timestamp: json['timestamp'] ?? 0, + activity: json['activity'] ?? '', + duration: DurationField.fromJson({'duration': json['duration']}), + place: json['place'] ?? '', + city: json['city'] ?? '', + state: json['state'] ?? '', + country: json['country'] ?? '', + // type: List.from(json['type'] ?? []), + thumbnail: json['thumbnail'] ?? '', + translations: + (json['translations'] as List?) + ?.map( + (item) => + TranslationModel.fromJson(item as Map), + ) + .toList() ?? + [], + files: + json['files'] != null + ? (json['files'] is Map + ? Files.fromJson(json['files'] as Map) + : Files()) + : Files(), + ); + } +} + +class ResultModel { + final List years; + + ResultModel({required this.years}); + + factory ResultModel.fromJson(Map json) { + return ResultModel( + years: (json['years'] as List) + .map((e) => YearModel.fromJson(e)) + .toList(), + ); + } +} + +class YearModel { + final int year; + final int count; + final List months; + + YearModel({required this.year, required this.count, required this.months}); + + factory YearModel.fromJson(Map json) { + return YearModel( + year: json['year'], + count: json['count'], + months: (json['months'] as List) + .map((e) => MonthModel.fromJson(e)) + .toList(), + ); + } +} + +class MonthModel { + final String name; + final String month; + final int count; + + MonthModel({required this.name, required this.month, required this.count}); + + factory MonthModel.fromJson(Map json) { + return MonthModel( + name: json['name'], + month: json['month'], + count: int.parse(json['count']), + ); + } +} + diff --git a/lib/navigation_bar.dart b/lib/navigation_bar.dart new file mode 100644 index 0000000..f511ca4 --- /dev/null +++ b/lib/navigation_bar.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:lgcc/pages/home.dart'; +import 'package:lgcc/pages/items_list.dart'; + +class MainNavigationBar extends StatefulWidget { + @override _MainNavigationBarState createState() => _MainNavigationBarState(); +} + +class _MainNavigationBarState extends State { + int _currentIndex =0; + + final List _pages = [ + HomePage(), + ItemsList(type: 'conferencias'), + ItemsList(type: 'actividades'), + ]; + + void _onTabTapped(int index) { + setState(() { + _currentIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: IndexedStack( + index: _currentIndex, + children: _pages, + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _currentIndex, + onTap: _onTabTapped, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Inicio', + ), + BottomNavigationBarItem( + icon: Icon(Icons.list), + label: 'Conferencias', + ), + BottomNavigationBarItem( + icon: Icon(Icons.list), + label: 'Publicaciones', + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/conferences.dart b/lib/pages/conferences.dart deleted file mode 100644 index b7c1eeb..0000000 --- a/lib/pages/conferences.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; -import 'package:lgcc/models/conferences_model.dart'; -import 'package:lgcc/providers/directus_service.dart'; -import 'package:lgcc/pages/detail_conference.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; - -class ConferencesPage extends StatelessWidget { - final dynamic confereceDetail; - - const ConferencesPage({super.key, this.confereceDetail}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Conferencias'), - centerTitle: true, - backgroundColor: Color(0xFFE5EBFD), - ), - body: _buildConferenceListView(), - ); - } -} - -Widget _buildConferenceListView() { - final DirectusService _directusService = DirectusService(); - - return FutureBuilder( - future: Future.wait([_directusService.getConferences()]), - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - print('Error: ${snapshot.error}'); - return Center(child: Text('Error: ${snapshot.error}')); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return const Center(child: Text('No conferences found.')); - } else { - List conferences = List.from( - snapshot.data![0], - ); - return ListView.builder( - itemCount: conferences.length, - itemBuilder: (context, index) { - final conference = conferences[index]; - String urlImage; - if (conference.thumbnail.isEmpty) { - urlImage = - 'https://ik.imagekit.io/lgccc/tr:w-1920,f-auto/youtube_thumbnail_46396.png'; - } else { - urlImage = - 'https://directus.carpa.com/assets/${conference.thumbnail}?access_token=${dotenv.env['DIRECTUS_API_TOKEN']}'; - } - return InkWell( - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DetailConference(conference: conference), - ), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - Image.network( - urlImage, - fit: BoxFit.cover, - width: double.infinity, - height: 200, - ), - const SizedBox(height: 8), - Text(conference.title, style: const TextStyle(fontSize: 18)), - ], - ), - ), - ), - ), - ); - }, - ); - } - }, - ); -} diff --git a/lib/pages/detail_conference.dart b/lib/pages/detail_item.dart similarity index 61% rename from lib/pages/detail_conference.dart rename to lib/pages/detail_item.dart index 40404c5..c232b98 100644 --- a/lib/pages/detail_conference.dart +++ b/lib/pages/detail_item.dart @@ -1,31 +1,36 @@ import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:lgcc/models/conferences_model.dart'; +import 'package:lgcc/models/item_model.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:intl/intl.dart'; -class DetailConference extends StatelessWidget { - final ConferencesModel conference; - const DetailConference({super.key, required this.conference}); +class DetailItem extends StatefulWidget { + final ItemModel items; + const DetailItem({super.key, required this.items}); + @override + State createState() => _DetailItemState(); +} + +class _DetailItemState extends State { @override Widget build(BuildContext context) { // Check if the conference has a thumbnail, if not use a default image - String urlImage; - if (conference.thumbnail.isEmpty) { - urlImage = - 'https://ik.imagekit.io/lgccc/tr:w-1920,f-auto/youtube_thumbnail_46396.png'; - } else { - urlImage = - 'https://directus.carpa.com/assets/${conference.thumbnail}?access_token=${dotenv.env['DIRECTUS_API_TOKEN']}'; - } + // String urlImage; + // if (widget.items.thumbnail.isEmpty) { + // urlImage = + // 'https://ik.imagekit.io/lgccc/tr:w-1920,f-auto/youtube_thumbnail_46396.png'; + // } else { + // urlImage = + // 'https://actividadeswp.carpa.com/v5/uploads/?path=${widget.items.thumbnail}'; + // } - final formattedDate = DateFormat.yMMMMEEEEd().format(conference.date); + final formattedDate = DateFormat.yMMMMEEEEd().format(widget.items.date); return Scaffold( appBar: AppBar( - title: const Text('Conferencias'), + title: Text(widget.items.title), centerTitle: true, backgroundColor: Color(0xFFE5EBFD), ), @@ -38,10 +43,10 @@ class DetailConference extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network(urlImage), + // Image.network(urlImage), const SizedBox(height: 8), Text( - conference.title, + widget.items.title, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, @@ -53,11 +58,11 @@ class DetailConference extends StatelessWidget { style: const TextStyle(fontSize: 16, color: Colors.black), ), Text( - '${conference.place}, ${conference.city}, ${conference.state}, ${conference.country}', + '${widget.items.place}, ${widget.items.city}, ${widget.items.state}, ${widget.items.country}', style: const TextStyle(fontSize: 16, color: Colors.black), ), Text( - 'Actividad: ${conference.activity}', + 'Actividad: ${widget.items.activity}', style: const TextStyle(fontSize: 16, color: Colors.black), ), ], diff --git a/lib/pages/home.dart b/lib/pages/home.dart new file mode 100644 index 0000000..3c1c217 --- /dev/null +++ b/lib/pages/home.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold(); + } +} diff --git a/lib/pages/items_list.dart b/lib/pages/items_list.dart new file mode 100644 index 0000000..dad54cf --- /dev/null +++ b/lib/pages/items_list.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:lgcc/models/item_model.dart'; +import 'package:lgcc/providers/wordpress_service.dart'; +import 'package:lgcc/pages/detail_item.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class ItemsList extends StatelessWidget { + final dynamic itemDetail; + final String type; + + const ItemsList({Key? key, required this.type, this.itemDetail}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(type), + centerTitle: true, + backgroundColor: Color(0xFFE5EBFD), + ), + body: _buildItemListView(context), + ); + } + + Widget _buildItemListView(BuildContext context) { + final WordpressService wordpressService = WordpressService(); + return FutureBuilder>( + future: wordpressService.fetchItems(type), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center(child: Text('No hay items disponibles.')); + } else { + final List items = snapshot.data!.cast(); + + return ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return ListTile( + title: Text(item.title), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailItem(items: item), + ), + ); + }, + ); + }, + ); + } + }, + ); + } +} diff --git a/lib/providers/directus_service.dart b/lib/providers/directus_service.dart index 86fb7ec..109b1ef 100644 --- a/lib/providers/directus_service.dart +++ b/lib/providers/directus_service.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:lgcc/models/conferences_model.dart'; +import 'package:lgcc/models/item_model.dart'; class DirectusService { @@ -17,7 +17,7 @@ Future> getConferences() async { }()); if (response.statusCode == 200) { final data = jsonDecode(response.body)['data']; - return data.map((item) => ConferencesModel.fromJson(item)).toList(); + return data.map((item) => ItemModel.fromJson(item)).toList(); } else { throw Exception('Failed to load conferences'); } diff --git a/lib/providers/wordpress_service.dart b/lib/providers/wordpress_service.dart new file mode 100644 index 0000000..359998d --- /dev/null +++ b/lib/providers/wordpress_service.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:lgcc/models/item_model.dart'; + +class WordpressService { + + String getBaseUrlType(String type) { + return 'https://${type}wp.carpa.com/v5/items/?cache=false'; + } + + Future> fetchItems(String type) async { + final baseUrl = getBaseUrlType(type); + final response = await http.get(Uri.parse('$baseUrl&f=list')); + print('Fetching items from: $response'); + if (response.statusCode == 200) { + final List jsonData = json.decode(response.body); + return jsonData.map((item) => ItemModel.fromJson(item)).toList(); + } else { + throw Exception('Failed to load items'); + } + } + + Future> fetchYearsParam(String type ) async { + final baseUrl = getBaseUrlType(type); + final response = await http.get(Uri.parse(baseUrl + '&years')); + print('Fetching years from: $baseUrl'); + if (response.statusCode == 200) { + final List jsonData = json.decode(response.body); + return jsonData.map((item) => ResultModel.fromJson(item)).toList(); + } else { + throw Exception('Failed to load years'); + } + + } +} diff --git a/pubspec.lock b/pubspec.lock index 947fd84..584a5cb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -131,6 +131,14 @@ packages: description: flutter source: sdk version: "0.0.0" + google_nav_bar: + dependency: "direct main" + description: + name: google_nav_bar + sha256: bb12dd21514ee1b041ab3127673e2fd85e693337df308f7f2b75cd1e8e92eaf4 + url: "https://pub.dev" + source: hosted + version: "5.0.7" html: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7af1a2f..65534db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: flutter_dotenv: ^5.2.1 flutter_html: ^3.0.0 flutter_markdown: ^0.7.7+1 + google_nav_bar: ^5.0.7 http: ^1.4.0 intl: ^0.20.2 path: ^1.9.1