From 6f32659be09fc9468182e1fb640938406ad5e426 Mon Sep 17 00:00:00 2001 From: "Jose.Zamora" Date: Mon, 12 Feb 2024 11:38:32 -0500 Subject: [PATCH] implemented design --- .idea/libraries/Dart_Packages.xml | 248 +++++++++++ .idea/libraries/Flutter_Plugins.xml | 23 +- assets/images/calendar.png | Bin 0 -> 1471 bytes assets/images/location.png | Bin 0 -> 1179 bytes functions/attendees-counter.ts | 16 + lib/src/config/app_assets.dart | 2 + lib/src/config/app_colors.dart | 10 + lib/src/config/router.dart | 80 ++-- lib/src/data/events_repository.dart | 43 +- lib/src/models/event.dart | 29 +- lib/src/notifiers/event_notifier.dart | 46 +- lib/src/notifiers/events_notifier.dart | 9 +- lib/src/ui/screens/event_detail_screen.dart | 444 +++++++++++--------- lib/src/ui/screens/events_screen.dart | 39 +- lib/src/ui/widgets/event_widget.dart | 210 +++++---- pubspec.lock | 248 +++++++++++ pubspec.yaml | 2 + web/index.html | 3 + 18 files changed, 1047 insertions(+), 405 deletions(-) create mode 100644 assets/images/calendar.png create mode 100644 assets/images/location.png create mode 100644 functions/attendees-counter.ts create mode 100644 lib/src/config/app_colors.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index b933d14..787f29d 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -30,6 +30,27 @@ + + + + + + + + + + + + + + + + + + @@ -107,6 +128,20 @@ + + + + + + + + + + + + @@ -198,6 +233,13 @@ + + + + + + @@ -205,6 +247,13 @@ + + + + + + @@ -359,6 +408,13 @@ + + + + + + @@ -373,6 +429,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -380,6 +478,13 @@ + + + + + + @@ -394,6 +499,13 @@ + + + + + + @@ -408,6 +520,27 @@ + + + + + + + + + + + + + + + + + + @@ -429,6 +562,13 @@ + + + + + + @@ -450,6 +590,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -485,6 +688,20 @@ + + + + + + + + + + + + @@ -499,6 +716,9 @@ + + + @@ -510,6 +730,8 @@ + + @@ -523,6 +745,8 @@ + + @@ -542,23 +766,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index b55901a..f14b54b 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,17 +1,30 @@ - - + + + + - + + - + + - + + + + + + + + + + diff --git a/assets/images/calendar.png b/assets/images/calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..07ffea4028fa01e164d1b1a6820834150422f95a GIT binary patch literal 1471 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 literal 0 HcmV?d00001 diff --git a/functions/attendees-counter.ts b/functions/attendees-counter.ts new file mode 100644 index 0000000..51ccf6e --- /dev/null +++ b/functions/attendees-counter.ts @@ -0,0 +1,16 @@ +import * as functions from 'firebase-functions'; +import * as admin from 'firebase-admin'; + +admin.initializeApp(); + +export const updateAttendeesCount = functions.firestore + .document('event/{eventId}/attendees/{attendeeId}') + .onWrite(async (change, context) => { + const eventId = context.params.eventId; + const eventRef = admin.firestore().collection('event').doc(eventId); + + const attendeesSnapshot = await eventRef.collection('attendees').get(); + const attendeesCount = attendeesSnapshot.size; + + return eventRef.update({ attendees: attendeesCount }); + }); diff --git a/lib/src/config/app_assets.dart b/lib/src/config/app_assets.dart index 4072cf3..be7f15f 100644 --- a/lib/src/config/app_assets.dart +++ b/lib/src/config/app_assets.dart @@ -1,4 +1,6 @@ class AppAssets { static const _imagesPath = 'assets/images'; static const banner = '$_imagesPath/banner.jpeg'; + static const calendar = '$_imagesPath/calendar.png'; + static const location = '$_imagesPath/location.png'; } diff --git a/lib/src/config/app_colors.dart b/lib/src/config/app_colors.dart new file mode 100644 index 0000000..9c45a8d --- /dev/null +++ b/lib/src/config/app_colors.dart @@ -0,0 +1,10 @@ +import 'package:flutter/painting.dart'; + +class AppColors { + static const iconBackground = Color(0x195669FF); + static const primary = Color(0xFF5669FF); + static const primaryFont = Color(0xFF3F38DD); + static const flutterNavy = Color(0xFF042B59); + + static const cancel = Color(0xFF042B59); +} diff --git a/lib/src/config/router.dart b/lib/src/config/router.dart index 555101b..4f3e77c 100644 --- a/lib/src/config/router.dart +++ b/lib/src/config/router.dart @@ -1,5 +1,8 @@ +import 'package:flutter_community_ibague/src/models/event.dart'; import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; +import 'package:flutter_community_ibague/src/notifiers/event_notifier.dart'; import 'package:flutter_community_ibague/src/notifiers/events_notifier.dart'; +import 'package:flutter_community_ibague/src/ui/screens/event_detail_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/events_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/home_screen.dart'; import 'package:flutter_community_ibague/src/ui/screens/person_screen.dart'; @@ -10,43 +13,60 @@ class Routes { static const person = '/person'; static const notLogged = '/not_logged'; static const events = '/events'; + static const eventDetails = '/event-details'; } final GoRouter appRouter = GoRouter( initialLocation: Routes.events, routes: [ ShellRoute( - builder: (context, state, child) { - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (_) => EventsNotifier(), - ), - ChangeNotifierProvider( - create: (_) => AuthNotifier(), - ), - ], - child: HomeScreen( + builder: (context, state, child) { + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => EventsNotifier(), + ), + ChangeNotifierProvider( + create: (_) => AuthNotifier(), + ), + ], child: child, + ); + }, + routes: [ + GoRoute( + name: Routes.eventDetails, + path: Routes.eventDetails, + builder: (context, state) { + final event = state.extra as Event; + return ChangeNotifierProvider( + create: (_) => EventNotifier(event: event), + child: const EventDetailScreen()); + }, ), - ); - }, - routes: [ - GoRoute( - name: Routes.events, - path: Routes.events, - builder: (context, state) { - return const EventsScreen(); - }, - ), - GoRoute( - name: Routes.person, - path: Routes.person, - builder: (context, state) { - return const PersonScreen(); - }, - ), - ], - ), + ShellRoute( + builder: (context, state, child) { + return HomeScreen( + child: child, + ); + }, + routes: [ + GoRoute( + name: Routes.events, + path: Routes.events, + builder: (context, state) { + return const EventsScreen(); + }, + ), + GoRoute( + name: Routes.person, + path: Routes.person, + builder: (context, state) { + return const PersonScreen(); + }, + ), + ], + ), + ]) ], ); diff --git a/lib/src/data/events_repository.dart b/lib/src/data/events_repository.dart index 67197f4..dcd8b68 100644 --- a/lib/src/data/events_repository.dart +++ b/lib/src/data/events_repository.dart @@ -22,41 +22,26 @@ class EventsRepository { .toList()); } - Future attendeesCount(String eventId) async { - final result = await _firestore + Stream eventStream({required String id}) { + return _firestore .collection('events') - .doc(eventId) - .collection('attendees') - .count() - .get(); - return result.count ?? 0; + .doc(id) + .snapshots() + .map((doc) => Event.fromJson( + id: doc.id, + json: doc.data() ?? {}, + )); } Future attendToEvent(String eventId, String uid) async { - await _firestore - .collection('events') - .doc(eventId) - .collection('attendees') - .doc(uid) - .set({'attend': true}); + await _firestore.collection('events').doc(eventId).update({ + 'attendees': FieldValue.arrayUnion([uid]) + }); } Future notAttendToEvent(String eventId, String uid) async { - await _firestore - .collection('events') - .doc(eventId) - .collection('attendees') - .doc(uid) - .delete(); - } - - Stream attendStream(String eventId, String uid) { - return _firestore - .collection('events') - .doc(eventId) - .collection('attendees') - .doc(uid) - .snapshots() - .map((doc) => doc.exists); + await _firestore.collection('events').doc(eventId).update({ + 'attendees': FieldValue.arrayRemove([uid]) + }); } } diff --git a/lib/src/models/event.dart b/lib/src/models/event.dart index c8d9c12..4bc9848 100644 --- a/lib/src/models/event.dart +++ b/lib/src/models/event.dart @@ -6,9 +6,15 @@ class Event { final String description; final String banner; final DateTime dateTime; - final String location; + final String locationTitle; final String recommendations; - int attendees = 0; + final String locationDetails; + final GeoPoint location; + final String calendarUrl; + final String locationUrl; + final List attendees; + + int get attendeesCount => attendees.length; Event({ required this.id, @@ -18,6 +24,11 @@ class Event { required this.dateTime, required this.location, required this.recommendations, + required this.locationDetails, + required this.locationTitle, + required this.calendarUrl, + required this.locationUrl, + required this.attendees, }); factory Event.fromJson({ @@ -26,6 +37,13 @@ class Event { }) { final dateTimeTimeStamp = json['dateTime'] as Timestamp; + final recommendations = + (json['recommendations'] as String ?? '').replaceAll("\\n", "\n"); + + final attendees = (json['attendees'] as List? ?? []) + .map((uid) => '$uid') + .toList(); + return Event( id: id, title: json['title'] ?? '', @@ -33,7 +51,12 @@ class Event { banner: json['banner'] ?? '', dateTime: dateTimeTimeStamp.toDate(), location: json['location'] ?? '', - recommendations: json['recommendations'] ?? '', + locationTitle: json['location_title'] ?? '', + locationDetails: json['location_details'] ?? '', + locationUrl: json['location_url'] ?? '', + calendarUrl: json['calendar_url'] ?? '', + recommendations: recommendations, + attendees: attendees, ); } } diff --git a/lib/src/notifiers/event_notifier.dart b/lib/src/notifiers/event_notifier.dart index ff82108..3693210 100644 --- a/lib/src/notifiers/event_notifier.dart +++ b/lib/src/notifiers/event_notifier.dart @@ -1,25 +1,49 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_community_ibague/src/data/events_repository.dart'; +import 'package:flutter_community_ibague/src/models/event.dart'; class EventNotifier extends ChangeNotifier { final EventsRepository _eventsRepository; + final FirebaseAuth _auth; - final String eventId; + Event event; + bool get attending => event.attendees.any((uid) => uid == _user?.uid); + late final StreamSubscription _eventSubscription; + User? get _user => _auth.currentUser; EventNotifier({ - required this.eventId, + required this.event, EventsRepository? eventsRepository, - }) : _eventsRepository = eventsRepository ?? EventsRepository(); + FirebaseAuth? auth, + }) : _eventsRepository = eventsRepository ?? EventsRepository(), + _auth = auth ?? FirebaseAuth.instance { + print('listen to event'); + _eventSubscription = + _eventsRepository.eventStream(id: event.id).listen((event) { + print(event); + event = event; + notifyListeners(); + }); + } + + void attendEvent() { + if (_user != null) { + _eventsRepository.attendToEvent(event.id, _user!.uid); + } + } - void attendEvent( - String uid, - ) { - _eventsRepository.attendToEvent(eventId, uid); + void notAttendEvent() { + if (_user != null) { + _eventsRepository.notAttendToEvent(event.id, _user!.uid); + } } - void notAttendEvent( - String uid, - ) { - _eventsRepository.notAttendToEvent(eventId, uid); + @override + void dispose() { + _eventSubscription.cancel(); + super.dispose(); } } diff --git a/lib/src/notifiers/events_notifier.dart b/lib/src/notifiers/events_notifier.dart index 5a2b170..a5305b5 100644 --- a/lib/src/notifiers/events_notifier.dart +++ b/lib/src/notifiers/events_notifier.dart @@ -18,11 +18,14 @@ class EventsNotifier extends ChangeNotifier { void init() { _eventsSubscription = _eventsRepository.eventsStream().listen((events) async { - for (final event in events) { - event.attendees = await _eventsRepository.attendeesCount(event.id); - } this.events = events; notifyListeners(); }); } + + @override + void dispose() { + _eventsSubscription.cancel(); + super.dispose(); + } } diff --git a/lib/src/ui/screens/event_detail_screen.dart b/lib/src/ui/screens/event_detail_screen.dart index 4fd2cee..0fb21eb 100644 --- a/lib/src/ui/screens/event_detail_screen.dart +++ b/lib/src/ui/screens/event_detail_screen.dart @@ -1,17 +1,27 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_community_ibague/src/models/event.dart'; +import 'package:flutter_community_ibague/src/config/app_assets.dart'; +import 'package:flutter_community_ibague/src/config/app_colors.dart'; +import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; +import 'package:flutter_community_ibague/src/notifiers/event_notifier.dart'; import 'package:flutter_community_ibague/src/ui/widgets/event_widget.dart'; import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class EventDetailScreen extends StatelessWidget { - const EventDetailScreen({super.key, required this.event}); - - final Event event; + const EventDetailScreen({ + super.key, + }); @override Widget build(BuildContext context) { - final bigScreen = MediaQuery.sizeOf(context).width > 1000; + final notifier = context.watch(); + final bigScreen = MediaQuery.sizeOf(context).width > 800; final widthScreen = MediaQuery.sizeOf(context).width; + final event = notifier.event; + // Convertir la fecha al formato deseado DateTime fecha = DateTime.parse(event.dateTime.toString()); String fechaFormateada = DateFormat('d MMMM, y', 'es').format(fecha); @@ -20,206 +30,266 @@ class EventDetailScreen extends StatelessWidget { // Determinar el día de la semana String diaSemana = DateFormat('EEEE', 'es').format(fecha); return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 200.0, - floating: false, - pinned: true, - flexibleSpace: FlexibleSpaceBar( - title: const Text('Detalles del evento'), - centerTitle: false, - background: Image.network( - event.banner == 'url' - ? 'https://docs.flutter.dev/assets/images/dash/early-dash-sketches3.jpg' - : event.banner, - fit: BoxFit.cover, - ), - ), - ), - SliverToBoxAdapter( - child: Stack(alignment: Alignment.center, children: [ - Container( - width: widthScreen * 0.6, - padding: const EdgeInsets.all(10), - margin: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: Colors.black38, - blurRadius: 10.0, - offset: Offset(0.0, 5.0), - ) - ]), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const AvatarStack( - avatars: [ - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), + appBar: AppBar( + title: const Text( + 'Detalles del evento', + style: TextStyle(color: Colors.white), + ), + backgroundColor: AppColors.flutterNavy, + iconTheme: const IconThemeData(color: Colors.white), + scrolledUnderElevation: 0, + ), + body: Stack( + fit: StackFit.expand, + children: [ + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + constraints: BoxConstraints(maxHeight: bigScreen ? 300 : 200), + alignment: Alignment.bottomCenter, + color: AppColors.flutterNavy, + child: CachedNetworkImage( + imageUrl: event.banner, + ), + ), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 800), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(10), + margin: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black38, + blurRadius: 10.0, + offset: Offset(0.0, 5.0), + ) + ]), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const AvatarStack( + avatars: [Icon(Icons.people_outline_rounded)], + ), + const SizedBox(width: 5), + Text( + '+${event.attendeesCount} Asistirán', + style: const TextStyle( + color: AppColors.primaryFont, + fontWeight: FontWeight.bold, + ), + ), + ], ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + event.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 36, + ), + ), + _eventDetailItem( + iconAsset: AppAssets.calendar, + title: fechaFormateada, + subTitle: '$diaSemana, $horaFormateada', + isBigScreen: bigScreen, + onTap: () { + final Uri uri = Uri.parse(event.calendarUrl); + launchUrl(uri); + }), + _eventDetailItem( + iconAsset: AppAssets.location, + title: event.locationTitle, + subTitle: event.locationDetails, + isBigScreen: bigScreen, + onTap: () { + final Uri uri = Uri.parse(event.locationUrl); + launchUrl(uri); + }, + ), + Text( + 'Acerca del evento', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: bigScreen ? 22 : 16, + ), + ), + const SizedBox(height: 10), + Text( + event.description, + style: TextStyle(fontSize: bigScreen ? 20 : 14), + ), + const SizedBox(height: 10), + if (event.recommendations != '') + Text( + 'Recomendaciones', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: bigScreen ? 22 : 16, + ), + ), + const SizedBox(height: 10), + Text( + event.recommendations, + style: TextStyle(fontSize: bigScreen ? 20 : 14), + ), + ], ), - ], - ), - const SizedBox(width: 10), - Text( - '+${event.attendees} Asistirán', - style: const TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, ), - ), - ], - ), - ), - ]), + const SizedBox( + height: 70, + ), + ], + ), + ) + ], + ), ), - SliverToBoxAdapter( + Align( + alignment: Alignment.bottomCenter, child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - event.title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 36, - ), - ), - _eventDetailItem( - Icons.calendar_month_outlined, - fechaFormateada, - '$diaSemana, $horaFormateada', - bigScreen, - ), - _eventDetailItem( - Icons.location_pin, - event.location, - 'El Vergel, Ibagué', - bigScreen, - ), - Text( - 'Acerca del evento', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: bigScreen ? 22 : 16, - ), + padding: const EdgeInsets.only(bottom: 10), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + notifier.attending ? AppColors.cancel : AppColors.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), ), - const SizedBox(height: 10), - Text( - event.description, - style: TextStyle(fontSize: bigScreen ? 20 : 14), - ), - const SizedBox(height: 10), - if (event.recommendations != '') - Text( - 'Recomendaciones', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: bigScreen ? 22 : 16, - ), - ), - const SizedBox(height: 10), - Text( - event.recommendations, - style: TextStyle(fontSize: bigScreen ? 20 : 14), + padding: EdgeInsets.symmetric( + vertical: bigScreen ? 20 : 15, horizontal: 100), + ), + onPressed: () async { + final authNotifier = context.read(); + if (notifier.attending) { + notifier.notAttendEvent(); + return; + } + if (authNotifier.user == null) { + await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Inicia sesión'), + content: const Text( + 'Debes iniciar sesión para poder registrarte al evento', + style: TextStyle( + fontSize: 16, + )), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(ctx); + }, + child: const Text('Ok')) + ], + )); + final logged = await showModalBottomSheet( + context: context, + builder: (ctx) { + late final Widget child; + + child = SignInScreen( + actions: [ + AuthStateChangeAction( + (context, state) { + Navigator.pop(ctx, true); + }), + AuthStateChangeAction( + (context, state) { + Navigator.pop(ctx, true); + }), + ], + ); + + return Scaffold(body: child); + }) ?? + false; + + if (logged) { + notifier.attendEvent(); + } else {} + } + }, + child: Text( + notifier.attending ? 'Desistir' : 'Asistir', + style: TextStyle( + color: Colors.white, + fontSize: bigScreen ? 26 : 18, + fontWeight: FontWeight.w300, ), - ], + ), ), ), - ), + ) ], ), - bottomNavigationBar: Container( - height: 60, - width: 40, - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 15, - ), - margin: const EdgeInsets.all(10), - decoration: const BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.all( - Radius.circular(10), + ); + } +} + +Widget _eventDetailItem({ + required String iconAsset, + required String title, + required String subTitle, + required bool isBigScreen, + required VoidCallback onTap, +}) { + return InkWell( + onTap: onTap, + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + margin: const EdgeInsets.only( + right: 10, + top: 10, + bottom: 10, + ), + decoration: BoxDecoration( + color: AppColors.iconBackground, + borderRadius: BorderRadius.circular(12), + ), + child: Image.asset( + iconAsset, + height: 30, + width: 30, ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'ASISTIR', + title, style: TextStyle( - color: Colors.white, - fontSize: bigScreen ? 26 : 18, - fontWeight: FontWeight.w300, + fontWeight: FontWeight.bold, + fontSize: isBigScreen ? 22 : 16, + overflow: TextOverflow.ellipsis, ), - ) - ], - ), - ), - ); - } -} - -Widget _eventDetailItem( - IconData icon, - String date, - String time, - bool isBigcreen, -) { - return Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - margin: const EdgeInsets.only( - right: 10, - top: 10, - bottom: 10, - ), - decoration: BoxDecoration( - color: Colors.blue[100], - borderRadius: BorderRadius.circular(10), - ), - child: Icon(icon), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - date, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: isBigcreen ? 22 : 16, - overflow: TextOverflow.ellipsis, ), - ), - Text( - time, - style: TextStyle( - fontSize: isBigcreen ? 16 : 12, - overflow: TextOverflow.ellipsis, + Text( + subTitle, + style: TextStyle( + fontSize: isBigScreen ? 16 : 12, + overflow: TextOverflow.ellipsis, + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ); } diff --git a/lib/src/ui/screens/events_screen.dart b/lib/src/ui/screens/events_screen.dart index 6de7920..24de717 100644 --- a/lib/src/ui/screens/events_screen.dart +++ b/lib/src/ui/screens/events_screen.dart @@ -11,15 +11,22 @@ class EventsScreen extends StatelessWidget { Widget build(BuildContext context) { final state = context.watch(); final bigScreen = MediaQuery.sizeOf(context).width > 1000; + final widthScreen = MediaQuery.sizeOf(context).width; return ListView( children: [ // Image.asset(AppAssets.banner), - Text( - 'Próximos eventos', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: bigScreen ? 30 : 20, - fontWeight: FontWeight.bold, + Align( + alignment: Alignment.center, + child: SizedBox( + width: bigScreen ? widthScreen * 0.3 : widthScreen * 0.8, + child: Text( + 'Próximos eventos', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: bigScreen ? 30 : 20, + fontWeight: FontWeight.bold, + ), + ), ), ), const SizedBox( @@ -29,24 +36,8 @@ class EventsScreen extends StatelessWidget { ...state.events .map( (event) => ChangeNotifierProvider( - create: (_) => EventNotifier(eventId: event.id), - child: EventWidget(event: event), - ), - ) - .toList(), - ...state.events - .map( - (event) => ChangeNotifierProvider( - create: (_) => EventNotifier(eventId: event.id), - child: EventWidget(event: event), - ), - ) - .toList(), - ...state.events - .map( - (event) => ChangeNotifierProvider( - create: (_) => EventNotifier(eventId: event.id), - child: EventWidget(event: event), + create: (_) => EventNotifier(event: event), + child: EventWidget(), ), ) .toList(), diff --git a/lib/src/ui/widgets/event_widget.dart b/lib/src/ui/widgets/event_widget.dart index ebeadaa..176d40b 100644 --- a/lib/src/ui/widgets/event_widget.dart +++ b/lib/src/ui/widgets/event_widget.dart @@ -1,31 +1,25 @@ -import 'package:firebase_auth/firebase_auth.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_community_ibague/src/data/events_repository.dart'; -import 'package:flutter_community_ibague/src/models/event.dart'; -import 'package:flutter_community_ibague/src/notifiers/auth_notifier.dart'; +import 'package:flutter_community_ibague/src/config/app_colors.dart'; +import 'package:flutter_community_ibague/src/config/router.dart'; import 'package:flutter_community_ibague/src/notifiers/event_notifier.dart'; -import 'package:flutter_community_ibague/src/ui/screens/event_detail_screen.dart'; +import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; class EventWidget extends StatelessWidget { - const EventWidget({super.key, required this.event}); - - final Event event; + const EventWidget({ + super.key, + }); @override Widget build(BuildContext context) { final bigScreen = MediaQuery.sizeOf(context).width > 1000; final widthScreen = MediaQuery.sizeOf(context).width; + final notifier = context.watch(); + final event = notifier.event; return GestureDetector( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EventDetailScreen( - event: event, - ), - ), - ); + context.push(Routes.eventDetails, extra: event); }, child: Container( margin: const EdgeInsets.all(10), @@ -38,20 +32,13 @@ class EventWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - height: bigScreen ? 200 : 130, - decoration: BoxDecoration( + Center( + child: ClipRRect( borderRadius: BorderRadius.circular(10), - color: Colors.blue, - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage( - event.banner == 'url' - ? 'https://docs.flutter.dev/assets/images/dash/early-dash-sketches3.jpg' - : event.banner, - ), + child: CachedNetworkImage( + imageUrl: event.banner, + alignment: Alignment.center, ), - shape: BoxShape.rectangle, ), ), Padding( @@ -77,33 +64,33 @@ class EventWidget extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - const AvatarStack( - avatars: [ - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - CircleAvatar( - backgroundImage: - NetworkImage('https://via.placeholder.com/150'), - ), - ], - ), - const SizedBox(width: 10), + // const AvatarStack( + // avatars: [ + // CircleAvatar( + // backgroundImage: + // NetworkImage('https://via.placeholder.com/150'), + // ), + // CircleAvatar( + // backgroundImage: + // NetworkImage('https://via.placeholder.com/150'), + // ), + // CircleAvatar( + // backgroundImage: + // NetworkImage('https://via.placeholder.com/150'), + // ), + // CircleAvatar( + // backgroundImage: + // NetworkImage('https://via.placeholder.com/150'), + // ), + // ], + // ), + // const SizedBox(width: 10), Text( - '+${event.attendees} Asistirán', + '+${event.attendeesCount} Asistirán', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, - color: Colors.blue, + color: AppColors.primaryFont, ), ), ], @@ -118,7 +105,7 @@ class EventWidget extends StatelessWidget { color: Colors.black45, ), Text( - event.location, + event.locationTitle, style: const TextStyle( fontSize: 15, color: Colors.black45, @@ -126,62 +113,62 @@ class EventWidget extends StatelessWidget { ), ], ), - Consumer( - builder: (BuildContext context, AuthNotifier state, - Widget? child) { - final User? user = state.user; - if ((user?.displayName?.length ?? 0) < 10) { - return const Text( - 'Agrega tu nombre completo en tu perfil para asistir al evento', - style: TextStyle(color: Colors.red), - ); - } else { - return StreamBuilder( - stream: EventsRepository() - .attendStream(event.id, user!.uid), - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - return const CircularProgressIndicator(); - } else { - final attending = snapshot.data ?? false; - final eventNotifier = - context.read(); - final uid = user.uid; - if (attending) { - return ElevatedButton( - onPressed: () => - eventNotifier.notAttendEvent(uid), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.cancel), - Text('Cancelar asistencia'), - ], - ), - ); - } else { - return ElevatedButton( - onPressed: () => - eventNotifier.attendEvent(uid), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.check), - Text( - 'Confirmar asistencia', - ), - ], - ), - ); - } - } - }, - ); - } - }, - ), + // Consumer( + // builder: (BuildContext context, AuthNotifier state, + // Widget? child) { + // final User? user = state.user; + // if ((user?.displayName?.length ?? 0) < 10) { + // return const Text( + // 'Agrega tu nombre completo en tu perfil para asistir al evento', + // style: TextStyle(color: Colors.red), + // ); + // } else { + // return StreamBuilder( + // stream: EventsRepository() + // .attendStream(event.id, user!.uid), + // builder: (BuildContext context, + // AsyncSnapshot snapshot) { + // if (snapshot.connectionState == + // ConnectionState.done) { + // return const CircularProgressIndicator(); + // } else { + // final attending = snapshot.data ?? false; + // final eventNotifier = + // context.read(); + // final uid = user.uid; + // if (attending) { + // return ElevatedButton( + // onPressed: () => + // eventNotifier.notAttendEvent(uid), + // child: const Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon(Icons.cancel), + // Text('Cancelar asistencia'), + // ], + // ), + // ); + // } else { + // return ElevatedButton( + // onPressed: () => + // eventNotifier.attendEvent(uid), + // child: const Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon(Icons.check), + // Text( + // 'Confirmar asistencia', + // ), + // ], + // ), + // ); + // } + // } + // }, + // ); + // } + // }, + // ), ], ), ), @@ -215,11 +202,8 @@ class AvatarStack extends StatelessWidget { // Agregar los avatares al avatarList for (int i = 0; i < maxAvatars; i++) { - final avatar = Positioned( - left: i * 30.0, // Separación entre avatares - child: ClipOval( - child: avatars[i], - ), + final avatar = ClipOval( + child: avatars[i], ); avatarList.add(avatar); } diff --git a/pubspec.lock b/pubspec.lock index 6b74f27..cc5b9cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + url: "https://pub.dev" + source: hosted + version: "1.1.1" characters: dependency: transitive description: @@ -121,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" firebase_auth: dependency: "direct main" description: @@ -225,11 +265,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_lints: dependency: "direct dev" description: @@ -397,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -413,6 +477,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" petitparser: dependency: transitive description: @@ -421,6 +533,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -437,6 +557,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" sky_engine: dependency: transitive description: flutter @@ -450,6 +578,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" stack_trace: dependency: transitive description: @@ -474,6 +626,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -498,6 +658,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + url: "https://pub.dev" + source: hosted + version: "6.2.4" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + url: "https://pub.dev" + source: hosted + version: "6.2.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + url: "https://pub.dev" + source: hosted + version: "2.2.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + uuid: + dependency: transitive + description: + name: uuid + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + url: "https://pub.dev" + source: hosted + version: "4.3.3" vector_graphics: dependency: transitive description: @@ -538,6 +770,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + url: "https://pub.dev" + source: hosted + version: "5.2.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a18d881..fba1abf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. + cached_network_image: ^3.3.1 cloud_firestore: ^4.15.4 cupertino_icons: ^1.0.2 firebase_auth: ^4.17.4 @@ -45,6 +46,7 @@ dependencies: go_router: ^13.1.0 intl: ^0.18.1 provider: ^6.1.1 + url_launcher: ^6.2.4 dev_dependencies: diff --git a/web/index.html b/web/index.html index e94cd8e..7b3dccb 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,8 @@ +