Skip to content

Commit

Permalink
Merge pull request #11 from BU-Spark/calendar
Browse files Browse the repository at this point in the history
Calendar Updates, UI Updates, etc
molly-yan authored Apr 16, 2024
2 parents 5ad57ab + 660f7eb commit e771238
Showing 32 changed files with 7,036 additions and 6,822 deletions.
Binary file modified bu_passport/assets/images/onboarding/onboarding1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bu_passport/assets/images/onboarding/onboarding2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bu_passport/assets/images/onboarding/onboarding3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bu_passport/assets/images/onboarding/onboarding4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bu_passport/assets/images/onboarding/onboarding5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/cloud_firestore
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/firebase_auth
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/firebase_core
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/firebase_storage
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/geolocator_apple
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/google_maps_flutter_ios
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/image_picker_ios
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/permission_handler_apple
2 changes: 1 addition & 1 deletion bu_passport/ios/.symlinks/plugins/url_launcher_ios
8 changes: 4 additions & 4 deletions bu_passport/ios/Flutter/Generated.xcconfig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/saisriram/development/flutter
FLUTTER_APPLICATION_PATH=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport
FLUTTER_ROOT=/Users/marcolam/flutter
FLUTTER_APPLICATION_PATH=/Users/marcolam/se-bu-passport-arts/bu_passport
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport/lib/main.dart
FLUTTER_TARGET=/Users/marcolam/se-bu-passport-arts/bu_passport/lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
@@ -12,4 +12,4 @@ DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport/.dart_tool/package_config.json
PACKAGE_CONFIG=/Users/marcolam/se-bu-passport-arts/bu_passport/.dart_tool/package_config.json
8 changes: 4 additions & 4 deletions bu_passport/ios/Flutter/flutter_export_environment.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/saisriram/development/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport"
export "FLUTTER_ROOT=/Users/marcolam/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/marcolam/se-bu-passport-arts/bu_passport"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport/lib/main.dart"
export "FLUTTER_TARGET=/Users/marcolam/se-bu-passport-arts/bu_passport/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC8wNDgxN2M5OWM5ZmQ0OTU2ZjI3NTA1MjA0ZjdlMzQ0MzM1ODEwYWVkLw=="
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/saisriram/Desktop/BUPassport/se-bu-passport-arts/bu_passport/.dart_tool/package_config.json"
export "PACKAGE_CONFIG=/Users/marcolam/se-bu-passport-arts/bu_passport/.dart_tool/package_config.json"
13,032 changes: 6,435 additions & 6,597 deletions bu_passport/ios/Pods/Pods.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions bu_passport/lib/auth/auth_gate.dart
Original file line number Diff line number Diff line change
@@ -7,16 +7,22 @@ class AuthGate extends StatelessWidget {
const AuthGate({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<User?>(
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const NavigationPage();
print(FirebaseAuth.instance.authStateChanges());

if (snapshot.connectionState == ConnectionState.waiting) {
// Show a loading indicator while waiting for the authentication state
return CircularProgressIndicator();
} else if (snapshot.hasData) {
// User is authenticated, navigate to the NavigationPage
return NavigationPage();
} else {
return const LoginPage();
// User is not authenticated, show the LoginPage
return LoginPage();
}
},
));
);
}
}
4 changes: 2 additions & 2 deletions bu_passport/lib/classes/categorized_events.dart
Original file line number Diff line number Diff line change
@@ -2,10 +2,10 @@ import "package:bu_passport/classes/event.dart";

class CategorizedEvents {
final List<Event> attendedEvents;
final List<Event> upcomingEvents;
final List<Event> userSavedEvents;

CategorizedEvents({
required this.attendedEvents,
required this.upcomingEvents,
required this.userSavedEvents,
});
}
7 changes: 2 additions & 5 deletions bu_passport/lib/classes/event.dart
Original file line number Diff line number Diff line change
@@ -9,9 +9,7 @@ class Event {
final String eventURL;
final DateTime eventStartTime;
final DateTime eventEndTime;

// final List<String> eventTags;
final List<String> registeredUsers;
final List<String> savedUsers;

Event({
required this.eventID,
@@ -22,7 +20,6 @@ class Event {
required this.eventEndTime,
required this.eventDescription,
required this.eventURL,
// required this.eventTags,
required this.registeredUsers,
required this.savedUsers,
});
}
8 changes: 4 additions & 4 deletions bu_passport/lib/classes/user.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import "package:bu_passport/classes/event.dart";

class Users {
final String firstName;
final String lastName;
@@ -9,8 +7,9 @@ class Users {
final String userSchool;
final String userUID;
final int userYear;
final int userPoints;
final List<String> userPreferences;
final List<String> userRegisteredEvents;
final Map<String, dynamic> userSavedEvents;

Users({
required this.firstName,
@@ -22,6 +21,7 @@ class Users {
required this.userUID,
required this.userYear,
required this.userPreferences,
required this.userRegisteredEvents,
required this.userSavedEvents,
required this.userPoints,
});
}
189 changes: 160 additions & 29 deletions bu_passport/lib/components/event_widget.dart
Original file line number Diff line number Diff line change
@@ -1,66 +1,197 @@
import 'package:bu_passport/classes/event.dart';
import 'package:bu_passport/pages/event_page.dart';
import 'package:bu_passport/services/firebase_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';

class EventWidget extends StatefulWidget {
final Event event;
const EventWidget({Key? key, required this.event}) : super(key: key);
final Function onUpdateEventPage;
const EventWidget(
{Key? key, required this.event, required this.onUpdateEventPage})
: super(key: key);

@override
_EventWidgetState createState() => _EventWidgetState();
}

class _EventWidgetState extends State<EventWidget> {
bool _isSaved = false;
bool _isCheckedIn = false;
String userUID = FirebaseAuth.instance.currentUser!.uid;

@override
void initState() {
checkIfUserSaved();
checkIfUserCheckedIn();
super.initState();
}

void checkIfUserSaved() async {
// Ensure there's a user logged in
if (userUID.isEmpty) {
print("User is not logged in.");
return;
}
bool isSaved =
await FirebaseService.hasUserSavedEvent(userUID, widget.event.eventID);
setState(() {
// changing save to saved
_isSaved = isSaved;
});
}

void checkIfUserCheckedIn() async {
// Ensure there's a user logged in
if (userUID.isEmpty) {
print("User is not logged in.");
return;
}
bool isCheckedIn = await FirebaseService.isUserCheckedInForEvent(
userUID, widget.event.eventID);
setState(() {
// changing save to saved
_isCheckedIn = isCheckedIn;
});
}

void updateEventPage() {
checkIfUserSaved();
checkIfUserCheckedIn();
}

@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;

double sizedBoxHeight = (MediaQuery.of(context).size.height * 0.02);
double edgeInsets = (MediaQuery.of(context).size.width * 0.02);
double widgetHeight = (MediaQuery.of(context).size.height * 0.25);

return GestureDetector(
onTap: () {
// Navigate to the event page
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EventPage(event: widget.event),
builder: (context) => EventPage(
event: widget.event,
onUpdateEventPage: widget.onUpdateEventPage),
),
);
},
child: Container(
padding: EdgeInsets.all(edgeInsets),
height: widgetHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
border: Border.all(color: Colors.grey),
image: DecorationImage(
image: AssetImage(widget.event.eventPhoto),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.3), BlendMode.multiply),
),
// put a black gradient
),
child: Stack(
children: [
Image.asset(
widget.event.eventPhoto,
width: double.infinity, // Use full width
fit: BoxFit.cover, // Cover the container with the image
),
// Texts overlaid on the image
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.event.eventTitle,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: Colors.white, // Text color on top of the image
bottom: sizedBoxHeight,
left: edgeInsets,
right: edgeInsets,
child: Container(
padding: EdgeInsets.all(edgeInsets),
decoration: BoxDecoration(
color: Colors.transparent, // Make background transparent
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start, // Align text left
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: screenWidth * 0.65,
child: Text(
widget.event.eventTitle,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w700,
color: Colors.white,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(height: sizedBoxHeight * 0.5),
Text(
DateFormat.yMMMd()
.format(widget.event.eventStartTime),
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
RichText(
text: TextSpan(
// text: '${widget.event.points}',
// It will be replaced with real value in future development
text: '30',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w800,
color: Colors.white,
),
children: <TextSpan>[
TextSpan(
text: ' pts',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
),
),
SizedBox(height: sizedBoxHeight),
Text(
'${widget.event.eventLocation}',
style: TextStyle(fontSize: 16.0, color: Colors.white),
),
],
SizedBox(height: sizedBoxHeight * 0.5),
],
),
),
),
// Positioned Heart Icon remains the same
Positioned(
top: sizedBoxHeight,
right: sizedBoxHeight,
child: GestureDetector(
onTap: () async {
_isSaved = await FirebaseService.hasUserSavedEvent(
userUID, widget.event.eventID);
if (_isSaved) {
FirebaseService.unsaveEvent(widget.event.eventID);
} else {
FirebaseService.saveEvent(widget.event.eventID);
}
setState(() {
_isSaved = !_isSaved; // Toggle saved status
});
},
child: Icon(
_isSaved ? Icons.favorite : Icons.favorite_border,
color: _isSaved ? Colors.red : Colors.grey,
),
),
),
],
5 changes: 5 additions & 0 deletions bu_passport/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:bu_passport/firebase_options.dart';
import 'package:bu_passport/pages/explore_page.dart';
import 'package:bu_passport/pages/login_page.dart';
import 'package:bu_passport/pages/profile_page.dart';
import 'package:bu_passport/pages/signup_page.dart';
import 'package:bu_passport/pages/onboarding_page.dart';

@@ -45,6 +47,9 @@ class MyApp extends StatelessWidget {
'/onboarding': (context) => const OnboardingPage(),
'/login': (context) => const LoginPage(),
'/signup': (context) => const SignUpPage(),
'/home': (context) => const AuthGate(),
'/explore_page': (context) => const ExplorePage(),
'/profile_page': (context) => const ProfilePage(),
},
);
}
137 changes: 108 additions & 29 deletions bu_passport/lib/pages/calendar_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import 'package:bu_passport/components/event_widget.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:bu_passport/services/firebase_service.dart';
import 'package:bu_passport/classes/event.dart';

class CalendarPage extends StatefulWidget {
const CalendarPage({Key? key}) : super(key: key);
@@ -12,54 +16,129 @@ class _CalendarPageState extends State<CalendarPage> {
late CalendarFormat _calendarFormat;
late DateTime _focusedDay;
late DateTime _selectedDay;
List<Event> _allEvents = []; // List to store events
// List to store events for the selected day
late Future<List<Event>> _allEventsFuture;

@override
void initState() {
super.initState();
_calendarFormat = CalendarFormat.month;
_focusedDay = DateTime.now();
_selectedDay = DateTime.now();
_fetchEvents(); // Initialize the future to fetch events
_allEventsFuture = FirebaseService.fetchEvents();
}

Future<void> _fetchEvents() async {
List<Event> allEvents = await FirebaseService.fetchEvents();
setState(() {
_allEvents = allEvents;
});
}

void updateEventPage() {}

@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;

double defaultPadding = screenWidth * 0.02;
double itemVerticalMargin = screenHeight * 0.005;
double itemHorizontalMargin = screenWidth * 0.02;

double sizedBoxHeight = (MediaQuery.of(context).size.height * 0.05);

return Scaffold(
appBar: AppBar(
title: Text('Calendar'),
),
body: Column(
children: [
TableCalendar(
calendarFormat: _calendarFormat,
focusedDay: _focusedDay,
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
startingDayOfWeek: StartingDayOfWeek.sunday,
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
),
SizedBox(height: sizedBoxHeight),
Text(
'Selected Day: $_selectedDay',
style: TextStyle(fontSize: 20),
),
],
body: FutureBuilder<List<Event>>(
future: _allEventsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(
child: Text('Error: ${snapshot.error}'),
);
} else {
List<Event>? events = snapshot.data;
if (events == null || events.isEmpty) {
return Center(
child: Text('No events found.'),
);
}
List<Event> selectedEvents =
FirebaseService.fetchEventsForDay(_selectedDay, events);
print('Selected events: $selectedEvents');
return Column(
children: [
TableCalendar(
calendarFormat: _calendarFormat,
focusedDay: _focusedDay,
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
startingDayOfWeek: StartingDayOfWeek.sunday,
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
// Provide events to the calendar
eventLoader: (day) {
return _allEvents
.where((event) =>
event.eventStartTime.year == day.year &&
event.eventStartTime.month == day.month &&
event.eventStartTime.day == day.day)
.toList();
},
),
SizedBox(height: sizedBoxHeight),
Text(
'${DateFormat('EEEE, MMMM d, y').format(_selectedDay)}',
style: TextStyle(fontSize: 20),
),
Expanded(
child: selectedEvents.isEmpty
? Center(
child: Text('No events for today.',
style: TextStyle(fontSize: 20)))
: Padding(
padding: EdgeInsets.all(defaultPadding),
child: ListView.builder(
itemCount: selectedEvents.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.symmetric(
vertical: itemVerticalMargin,
horizontal: itemHorizontalMargin,
),
child: EventWidget(
event: selectedEvents[index],
onUpdateEventPage: updateEventPage),
);
},
),
),
),
],
);
}
},
),
);
}
159 changes: 111 additions & 48 deletions bu_passport/lib/pages/event_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:bu_passport/services/geocoding_service.dart';
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:bu_passport/classes/event.dart';
import 'package:bu_passport/services/firebase_service.dart';
@@ -11,7 +12,10 @@ import 'package:url_launcher/url_launcher.dart';
class EventPage extends StatefulWidget {
final Event event;

const EventPage({Key? key, required this.event}) : super(key: key);
const EventPage(
{Key? key, required this.event, required this.onUpdateEventPage})
: super(key: key);
final Function onUpdateEventPage;

@override
_EventPageState createState() => _EventPageState();
@@ -20,38 +24,47 @@ class EventPage extends StatefulWidget {
class _EventPageState extends State<EventPage> {
GeocodingService geocodingService = GeocodingService();

bool _isInterested =
false; // Track whether the user is interested in the event
bool _isSaved = false; // Track whether the user is interested in the event
bool _isCheckedIn = false; // To track if the user has checked in

// Checks if user is registered -- if so, the button will reflect that
// Checks if user saved event -- if so, the button will reflect that
@override
void initState() {
super.initState();
checkIfUserIsRegistered();
checkIfUserSaved();
checkIfUserIsCheckedIn();
}

void checkIfUserIsRegistered() async {
void checkIfUserSaved() async {
String userUID = FirebaseAuth.instance.currentUser?.uid ?? "";
// Ensure there's a user logged in
if (userUID.isEmpty) {
print("User is not logged in.");
return;
}
bool isRegistered = await FirebaseService.isUserRegisteredForEvent(
bool isSaved =
await FirebaseService.hasUserSavedEvent(userUID, widget.event.eventID);
setState(() {
// changing save to saved
_isSaved = isSaved;
});
}

void checkIfUserIsCheckedIn() async {
String userUID = FirebaseAuth.instance.currentUser?.uid ?? "";
// Ensure there's a user logged in
if (userUID.isEmpty) {
print("User is not logged in.");
return;
}
bool isCheckedIn = await FirebaseService.isUserCheckedInForEvent(
userUID, widget.event.eventID);
setState(() {
// changing registered to Interested
_isInterested = isRegistered;
_isCheckedIn = isCheckedIn;
});
}

bool isEventToday(DateTime eventDateTimestamp) {
// DateTime? eventDateTimeStart = convertEventStartTime(eventDateTimeStartStr);
// Ensuring that it is EST

// DateTime eventDateTimeStart = eventDateTimestamp.toDate();

final eventDateTimeLocal = tz.TZDateTime.from(eventDateTimestamp, tz.local);
final nowLocal = tz.TZDateTime.now(tz.local);
return nowLocal.year == eventDateTimeLocal.year &&
@@ -117,9 +130,7 @@ class _EventPageState extends State<EventPage> {
double edgeInsets = (MediaQuery.of(context).size.width * 0.02);

return Scaffold(
appBar: AppBar(
title: Text(widget.event.eventTitle),
),
appBar: AppBar(),
body: ListView(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -130,7 +141,7 @@ class _EventPageState extends State<EventPage> {
height: screenHeight * 0.4, // Adjust the height as needed
),
Padding(
padding: EdgeInsets.all(edgeInsets),
padding: EdgeInsets.all(edgeInsets * 2.5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -142,19 +153,50 @@ class _EventPageState extends State<EventPage> {
),
),
SizedBox(height: sizedBoxHeight),
Text(
'Location: ${widget.event.eventLocation}',
style: TextStyle(fontSize: 16),
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.0, color: Colors.black),
children: [
WidgetSpan(
child: Icon(Icons.location_on),
),
TextSpan(
text: " ${widget.event.eventLocation}",
),
],
),
),
SizedBox(height: sizedBoxHeight),
Text(
'Start Time: ${DateFormat('h:mm a, EEEE, MMMM d, y').format(widget.event.eventStartTime)}',
style: TextStyle(fontSize: 16),
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.0, color: Colors.black),
children: [
TextSpan(
text: 'Start: ',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: DateFormat('h:mm a, EEEE, MMMM d, y')
.format(widget.event.eventStartTime),
),
],
),
),
SizedBox(height: sizedBoxHeight),
Text(
'End Time: ${DateFormat('h:mm a, EEEE, MMMM d, y').format(widget.event.eventEndTime)}',
style: TextStyle(fontSize: 16),
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.0, color: Colors.black),
children: [
TextSpan(
text: 'End: ',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: DateFormat('h:mm a, EEEE, MMMM d, y')
.format(widget.event.eventEndTime),
),
],
),
),
SizedBox(height: sizedBoxHeight),
GestureDetector(
@@ -166,27 +208,46 @@ class _EventPageState extends State<EventPage> {
throw 'Could not launch $url';
}
},
child: Text(
'Event URL: ${widget.event.eventURL}',
style: TextStyle(fontSize: 16, color: Colors.blue),
child: RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.0, color: Colors.black),
children: [
WidgetSpan(
child: Icon(Icons.link),
),
TextSpan(
text: " ${widget.event.eventURL}",
style: TextStyle(color: Colors.blue),
),
],
),
),
),

SizedBox(height: sizedBoxHeight),
Text(
'Description: ${widget.event.eventDescription}',
style: TextStyle(fontSize: 16),
RichText(
text: TextSpan(
style: TextStyle(fontSize: 16.0, color: Colors.black),
children: [
TextSpan(
text: 'Description: \n',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: widget.event.eventDescription,
),
],
),
),
// Add more event details as needed
],
),
),
Padding(
padding: EdgeInsets.all(edgeInsets),
child: Column(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: (!_isInterested ||
onPressed: (!_isSaved ||
!isEventToday(widget.event.eventStartTime) ||
_isCheckedIn)
? null
@@ -196,6 +257,9 @@ class _EventPageState extends State<EventPage> {
if (success) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Checked in successfully!")));
FirebaseService.checkInUserForEvent(
widget.event.eventID);
widget.onUpdateEventPage();
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
@@ -206,29 +270,28 @@ class _EventPageState extends State<EventPage> {
style: ElevatedButton.styleFrom(
backgroundColor: _isCheckedIn
? Colors.grey
: (_isInterested ? Colors.blue : Colors.grey),
: (_isSaved ? Colors.red : Colors.grey),
),
),
SizedBox(height: sizedBoxHeight), // Optional spacing
SizedBox(width: sizedBoxHeight * 3), // Optional spacing
ElevatedButton(
onPressed: () async {
String userUID =
FirebaseAuth.instance.currentUser?.uid ?? "";
String eventId = widget.event.eventID;
bool isRegistered =
await FirebaseService.isUserRegisteredForEvent(
userUID, eventId);
if (isRegistered) {
FirebaseService.unregisterFromEvent(userUID, eventId);
bool isSaved = await FirebaseService.hasUserSavedEvent(
userUID, eventId);
if (isSaved) {
FirebaseService.unsaveEvent(eventId);
} else {
FirebaseService.registerForEvent(userUID, eventId);
FirebaseService.saveEvent(eventId);
}
setState(() {
_isInterested =
!_isInterested; // Toggle registration status
_isSaved = !_isSaved; // Toggle saved status
});
widget.onUpdateEventPage();
},
child: Text(_isInterested ? 'Not Interested' : 'Interested'),
child: Text(_isSaved ? 'Unsave' : 'Save'),
),
],
),
45 changes: 28 additions & 17 deletions bu_passport/lib/pages/explore_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// home page welcoming user with sign out button

import 'package:bu_passport/classes/event.dart';
import 'package:bu_passport/components/event_widget.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
@@ -27,27 +25,28 @@ class _HomePageState extends State<ExplorePage> {
fetchEventsFuture = FirebaseService.fetchEvents();
}

void updateEventPage() {
setState(() {
fetchEventsFuture = FirebaseService.fetchEvents();
});
}

@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;

double sizedBoxHeight = (MediaQuery.of(context).size.height * 0.05);
double edgeInsets = (MediaQuery.of(context).size.width * 0.02);

return Scaffold(
appBar: AppBar(
title: Text('Events'),
),
body: Center(
child: ListView(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: sizedBoxHeight),
Text(
'Events',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: sizedBoxHeight),
// Search bar
TextField(
onChanged: (value) {
@@ -73,12 +72,24 @@ class _HomePageState extends State<ExplorePage> {
if (events != null && events.isNotEmpty) {
List<Event> filteredEvents =
FirebaseService.filterEvents(events, _searchQuery);
return ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
children: filteredEvents.map((event) {
return EventWidget(event: event);
}).toList(),
return Padding(
padding: EdgeInsets.all(edgeInsets * 1.5),
child: ListView.separated(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: filteredEvents.length,
separatorBuilder: (BuildContext context, int index) {
// Add vertical space between items
return SizedBox(height: sizedBoxHeight * 0.4);
},
itemBuilder: (BuildContext context, int index) {
// Return your EventWidget
return EventWidget(
event: filteredEvents[index],
onUpdateEventPage: updateEventPage,
);
},
),
);
} else {
return Text('No events found');
8 changes: 3 additions & 5 deletions bu_passport/lib/pages/login_page.dart
Original file line number Diff line number Diff line change
@@ -58,17 +58,15 @@ class _LoginPageState extends State<LoginPage> {
setState(() {
_errorMessage = null;
});

// Implement your sign-in logic here
String email = _emailController.text;
String password = _passwordController.text;
try {
// Call your authentication method with email and password
UserCredential userCredential =
await FirebaseAuth.instance.signInWithEmailAndPassword(
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
// Navigate to home page
Navigator.pushNamed(context, '/home');
} catch (e) {
// Handle login errors
print('Login error: $e');
55 changes: 39 additions & 16 deletions bu_passport/lib/pages/profile_page.dart
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ class ProfilePage extends StatefulWidget {
class _ProfilePageState extends State<ProfilePage>
with SingleTickerProviderStateMixin {
List<Event> attendedEvents = [];
List<Event> upcomingEvents = [];
List<Event> userSavedEvents = [];
bool isLoading = true;

final TextEditingController _firstNameController = TextEditingController();
@@ -75,13 +75,17 @@ class _ProfilePageState extends State<ProfilePage>
await FirebaseService.fetchAndCategorizeEvents();
setState(() {
attendedEvents = categorizedEvents.attendedEvents;
upcomingEvents = categorizedEvents.upcomingEvents;
userSavedEvents = categorizedEvents.userSavedEvents;
});
} catch (e) {
print("Error fetching events: $e");
}
}

void updateEventPage() {
fetchAndDisplayEvents(); // Refresh the events when returning from EventPage
}

@override
void dispose() {
_firstNameController.dispose();
@@ -138,15 +142,30 @@ class _ProfilePageState extends State<ProfilePage>
}
}

Widget _buildEventsList(List<Event> events) {
return ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
return EventWidget(
event: event); // Use your EventWidget to display each event
},
);
Widget _buildEventsList(List<Event> events, String message) {
double screenHeight = MediaQuery.of(context).size.height;
double screenWidth = MediaQuery.of(context).size.width;
double verticalMargin = screenHeight * 0.01; // 1% of screen height
double horizontalMargin = screenWidth * 0.035; // 2% of screen width

print(events);

if (events.isEmpty) {
return Center(child: Text(message, style: TextStyle(fontSize: 20)));
} else {
return ListView.builder(
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
return Card(
margin: EdgeInsets.symmetric(
vertical: verticalMargin, horizontal: horizontalMargin),
child: EventWidget(
event: event, onUpdateEventPage: updateEventPage));
// Use your EventWidget to display each event
},
);
}
}

@override
@@ -161,6 +180,7 @@ class _ProfilePageState extends State<ProfilePage>

return Scaffold(
appBar: AppBar(
leading: Container(),
actions: <Widget>[
IconButton(
icon: Icon(_isEditing ? Icons.check : Icons.edit),
@@ -180,8 +200,7 @@ class _ProfilePageState extends State<ProfilePage>
icon: Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut();
Navigator.of(context).pushReplacementNamed(
'/login'); // Replace with your login route
Navigator.of(context).pushReplacementNamed('/login');
},
),
],
@@ -207,6 +226,7 @@ class _ProfilePageState extends State<ProfilePage>
var userData = snapshot.data!.data() as Map<String, dynamic>;
String fullName =
'${userData['firstName'] ?? 'Not set'} ${userData['lastName'] ?? ''}';
int userPoints = userData['points'] ?? 0;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
@@ -245,6 +265,7 @@ class _ProfilePageState extends State<ProfilePage>
),
] else ...[
Text(fullName, style: TextStyle(fontSize: 20)),
Text(userPoints.toString() + ' points'),
],
],
);
@@ -260,15 +281,17 @@ class _ProfilePageState extends State<ProfilePage>
appBar: TabBar(
controller: _tabController,
tabs: [
Tab(text: 'Saved'),
Tab(text: 'Attended'),
Tab(text: 'Upcoming'),
],
),
body: TabBarView(
controller: _tabController,
children: [
_buildEventsList(attendedEvents), // Attended events list
_buildEventsList(upcomingEvents), // Upcoming events list
_buildEventsList(userSavedEvents,
"No saved events."), // Saved events list
_buildEventsList(attendedEvents,
"No attended events."), // Attended events list
],
),
),
6 changes: 3 additions & 3 deletions bu_passport/lib/pages/signup_page.dart
Original file line number Diff line number Diff line change
@@ -112,14 +112,14 @@ class _SignUpPageState extends State<SignUpPage> {
'userSchool': school,
'userYear': year,
'userBUID': buID,
'userRegisteredEvents': [],
'userPreferences': [],
'userPoints': 0,
'userSavedEvents': Map<String, bool>(),
};
await db
.collection('users')
.doc(userCredential.user!.uid)
.set(user);
Navigator.pushNamed(context, '/home');
Navigator.pushNamed(context, '/onboarding');
} catch (e) {
print(e);
}
138 changes: 95 additions & 43 deletions bu_passport/lib/services/firebase_service.dart
Original file line number Diff line number Diff line change
@@ -23,8 +23,7 @@ class FirebaseService {
eventStartTime: (eventData['eventStartTime'] as Timestamp?)!.toDate(),
eventEndTime: (eventData['eventEndTime'] as Timestamp?)!.toDate(),
eventDescription: eventData['eventDescription'] ?? '',
registeredUsers:
List<String>.from(eventData['registeredUsers'] ?? []),
savedUsers: List<String>.from(eventData['savedUsers'] ?? []),
);

eventList.add(event);
@@ -36,6 +35,14 @@ class FirebaseService {
}
}

static List<Event> fetchEventsForDay(DateTime date, List<Event> events) {
return events.where((event) {
return event.eventStartTime.day == date.day &&
event.eventStartTime.month == date.month &&
event.eventStartTime.year == date.year;
}).toList();
}

static List<Event> filterEvents(List<Event> events, String query) {
if (query.isEmpty) {
return events;
@@ -63,9 +70,10 @@ class FirebaseService {
userSchool: userData['userSchool'],
userUID: userData['userUID'],
userYear: userData['userYear'],
userPoints: userData['userPoints'],
userPreferences: List<String>.from(userData['userPreferences'] ?? []),
userRegisteredEvents:
List<String>.from(userData['userRegisteredEvents'] ?? []),
userSavedEvents:
Map<String, dynamic>.from(userData['userSavedEvents'] ?? {}),
);
return user;
}
@@ -75,49 +83,57 @@ class FirebaseService {
return null;
}

static Future<void> registerForEvent(String userUID, String eventId) async {
static Future<void> saveEvent(String eventId) async {
final db = FirebaseFirestore.instance;
final userUID = FirebaseAuth.instance.currentUser?.uid;
final userDoc = db.collection('users').doc(userUID);

try {
// Atomically add the new event ID to the user's registered events list
// Atomically add the new event ID to the user's saved events list
// Atomically add the new event ID to the user's saved events map with value `false`
await userDoc.update({
'userRegisteredEvents': FieldValue.arrayUnion([eventId]),
'userSavedEvents.$eventId': false,
});
print("Event registration successful");
await db.collection('events').doc(eventId).update({
'savedUsers': FieldValue.arrayUnion([userUID]),
});
print("Event saved successfully");
} catch (error) {
print("Failed to register for event: $error");
print("Failed to save event: $error");
}
}

static Future<void> unregisterFromEvent(
String userUID, String eventId) async {
static Future<void> unsaveEvent(String eventId) async {
final db = FirebaseFirestore.instance;
final userUID = FirebaseAuth.instance.currentUser?.uid;
final userDoc = db.collection('users').doc(userUID);

try {
// Atomically remove the event ID from the user's registered events list
// Atomically remove the event ID from the user's saved events list
await userDoc.update({
'userRegisteredEvents': FieldValue.arrayRemove([eventId]),
'userSavedEvents.$eventId': FieldValue.delete(),
});
await db.collection('events').doc(eventId).update({
'savedUsers': FieldValue.arrayRemove([userUID]),
});
print("Event unregistration successful");
print("Event unsaving successful");
} catch (error) {
print("Failed to unregister from event: $error");
print("Failed to unsave event: $error");
}
}

static Future<bool> isUserRegisteredForEvent(
String userUID, String eventId) async {
static Future<bool> hasUserSavedEvent(String userUID, String eventId) async {
final db = FirebaseFirestore.instance;
DocumentSnapshot userDocSnapshot =
await db.collection('users').doc(userUID).get();

if (userDocSnapshot.exists) {
final userData = userDocSnapshot.data() as Map<String, dynamic>;
List<dynamic> registeredEvents = userData['userRegisteredEvents'] ?? [];
print(userData['userSavedEvents']);
Map<String, dynamic> savedEvents = userData['userSavedEvents'] ?? [];

// Check if the eventId exists in the list
return registeredEvents.contains(eventId);
return savedEvents.containsKey(eventId);
}
return false;
}
@@ -136,31 +152,39 @@ class FirebaseService {
}

final userData = userDoc.data();
final List<dynamic> registeredEventIds =
userData?['userRegisteredEvents'] ?? [];

List<Event> fetchedEvents = (await Future.wait(registeredEventIds.map(
(eventId) => FirebaseService.fetchEventById(eventId as String))))
.whereType<Event>()
.toList(); // Ensure only non-null Events are kept
Map<String, dynamic> savedEvents = userData!['userSavedEvents'] ?? [];

final now = DateTime.now();
final DateTime today = DateTime(now.year, now.month, now.day);

final List<Event> attendedEvents = [];
final List<Event> upcomingEvents = [];

for (Event event in fetchedEvents) {
if (event.eventStartTime.isBefore(now)) {
// Event has already occurred (attended)
attendedEvents.add(event);
} else {
// Event is upcoming
upcomingEvents.add(event);
final List<Event> userSavedEvents = [];

await Future.forEach(savedEvents.entries,
(MapEntry<String, dynamic> entry) async {
String eventId = entry.key;
bool isCheckedIn = entry.value;

Event? event = await fetchEventById(eventId);
print(event?.eventTitle);
if (event != null) {
DateTime startOfDayEvent = DateTime(event.eventStartTime.year,
event.eventStartTime.month, event.eventStartTime.day);
if ((startOfDayEvent.isBefore(now) ||
startOfDayEvent.isAtSameMomentAs(today)) &&
isCheckedIn) {
// Event has already occurred (attended)
attendedEvents.add(event);
} else {
// Event is upcoming
userSavedEvents.add(event);
}
}
}
});

return CategorizedEvents(
attendedEvents: attendedEvents, upcomingEvents: upcomingEvents);
attendedEvents: attendedEvents, userSavedEvents: userSavedEvents);
}

static Future<Event?> fetchEventById(String eventId) async {
@@ -178,18 +202,46 @@ class FirebaseService {
eventEndTime: (eventData['eventEndTime'] as Timestamp?)!.toDate(),
eventURL: eventData['eventURL'] ?? '',
eventDescription: eventData['eventDescription'] ?? '',
registeredUsers: List<String>.from(eventData['registeredUsers'] ?? []),
savedUsers: List<String>.from(eventData['savedUsers'] ?? []),
);
return event;
}
throw Exception("Event not found");
}

static Future<List<String>> fetchUserRegisteredEventIds(String userId) async {
var userDoc =
await FirebaseFirestore.instance.collection('users').doc(userId).get();
List<String> registeredEventIds =
List<String>.from(userDoc.data()?['registeredEvents'] ?? []);
return registeredEventIds;
static void checkInUserForEvent(String eventID) {
final userUID = FirebaseAuth.instance.currentUser?.uid;
if (userUID == null) {
throw Exception("User is not logged in");
}

final db = FirebaseFirestore.instance;
final userDoc = db.collection('users').doc(userUID);

try {
// Atomically add the new event ID to the user's saved events list
userDoc.update({
'userSavedEvents.$eventID': true,
});
print("Event check-in successful");
} catch (error) {
print("Failed to check-in for event: $error");
}
}

static Future<bool> isUserCheckedInForEvent(
String userUID, String eventId) async {
final db = FirebaseFirestore.instance;
DocumentSnapshot userDocSnapshot =
await db.collection('users').doc(userUID).get();

if (userDocSnapshot.exists) {
final userData = userDocSnapshot.data() as Map<String, dynamic>;
Map<String, dynamic> savedEvents = userData['userSavedEvents'] ?? [];

// Check if the eventId exists in the list
return savedEvents.containsKey(eventId) && savedEvents[eventId];
}
return false;
}
}
3 changes: 2 additions & 1 deletion bu_passport/scripts/eventscraper.py
Original file line number Diff line number Diff line change
@@ -149,7 +149,8 @@
'eventDescription': eventDescription if eventDescription else None, # Assign None if description doesn't exist
'eventID': eventID if eventID else None, # Assign None if event ID doesn't exist
'eventPhoto': eventPhoto if eventPhoto else None, # Assign None if photo doesn't exist
'registeredUsers': [],
'eventPoints': 30,
'savedUsers': [],
}

db.collection('events').document(eventID).set(event_data)

0 comments on commit e771238

Please sign in to comment.