Student and Staff App is a multifunctional platform catering to the German University in Cairo community, integrating essential features like anonymous confessions, academic queries, lost and found, location services, and user-controlled news/events, while tracking usability data for continual improvement.
·
Demo user journey video
·
Figma
·
UML
·
User Requirements
·
Report Bug
·
Be a Contributer
- Omar Sherif Ali Hassan [email protected] 49-3324
- Abdullah Ahmad Fouad [email protected] 49-2554
- David Samy [email protected] 49-15918
- Hussein Yasser [email protected] 49-18120
- Abdelrahman Fekri [email protected] 49-17814
The GUConnect is a Student and Staff App that is a dynamic platform tailored for the German University in Cairo community. Emphasizing user engagement and utility, the app requires GUC email verification for signup and includes an admin account. Its versatile features encompass Confessions for anonymous posts, academic inquiries with image uploads and ratings, Lost and Found, location services for university offices and outlets, essential phone number databases, and an editable News/Events section managed by approved users. The app's intuitive design strategically organizes functions while ensuring push notifications for updates. Admin control can be managed within the app. Crucially, the app collects usability data, tracking user interactions and behaviors to enhance functionality and user experience continually.
-
clone the repo
git clone
-
cd app
-
Install dependencies
flutter pub get
-
run the project
flutter run
- Splash screen(Loading Screen)
- User can login to the app with his university mail and password.
- User can preview his own profile with all posts he has shared in all the sections.
- Drawer menu
- Lost and Found Section
- User can call the person posting Lost and Found post (if the persons shared his number)
- User can report any post in any section or comment.
- Academic related questions section
- User can add a comment on any post in all the sections.
- Confessions section (Confessions can be posted anonymously or public) and users can mention each others.
- User can search for all the offices in the university and knows their location through the app.
- User can search for all the food outlets in the university and knows their location through the app.
- Using the direction button, the user can have the exact location of the office or his destination.
- User can search for Important phone numbers and call them directly through the app.
- Users can search for Important emails in the university.
- User can search for professors and Tas through the app.
-
User can view professor mail, office and avg. rating and he can rate a professor as well.
- Adding a new rating.
-
User can search for courses taught in the university.
- User can preview course page and rate a specific course.
- User can receive a notification on special events as someone likes/comments his post, someone mentions him in a post or an anouncment is posted in the app.
- User can edit his profile, changing his iamge, or password, or username and bio.
- Settings screen
- Admin can add important contacts
- Admin can add office/outlet
- Admin can add staff
- Admin can view pending reports
- Admin can approve/disapprove pending reports
- Admin can view/aprrove&reject pending post requests in club/news and events section
- User Engagement: To create an app that appeals to GUC students and staff, encouraging active participation and usage.
- Functionality and Utility: Providing diverse functionalities catering to academic, social, and emergency needs within the GUC community.
- Administrative Control: Efficient admin tools to add, edit, delete university staff and course information, manage, moderate, and approve user-generated content.
- Usability Enhancement: Gathering and analyzing user behavior data to enhance app usability and user experience over time.
- Enhancing Campus Life: Empowering students and staff by providing a central platform for various needs.
- Encouraging Participation: Fostering a sense of community and collaboration within the GUC.
- Improving User Experience: Continuously evolving and adapting the app based on user behavior insights for a more user-friendly experience.
-
Flutter: is Google's open-source UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, using the Dart programming language.
-
Firebase SDK: is a comprehensive platform by Google that offers tools and services for mobile and web app development, including authentication, Firestore (a NoSQL cloud database), cloud functions, and more, simplifying backend infrastructure management.
-
Firestore is Firebase's NoSQL cloud database that provides real-time data synchronization and querying capabilities, allowing developers to structure and manage app data efficiently across various devices.
-
Firebase Authentication (FireAuth): is a secure authentication system within Firebase SDK, enabling easy integration of user authentication methods like email/password, social logins, and phone number authentication into mobile and web applications.
-
Cloud Functions: is a serverless computing service that allows developers to execute backend code in response to events triggered by Firebase features or HTTP requests, enabling custom server-side logic without managing servers directly.
-
-
- Local Notifications: are alerts triggered and displayed directly on a user's device, enhancing user engagement by providing timely updates, reminders, or alerts within the app without requiring a server.
- Google Maps: is a mapping service that, when integrated into Flutter using google_maps_flutter package, offers interactive maps, markers, routing, and geolocation services, enabling users to explore locations and navigate within mobile applications.
The database will store user profiles, posts (confessions, academic queries, lost and found items, news, events), admin approvals, user behavior logs, and other necessary data to support the functionalities mentioned above.
- Description: Represents a user in the application.
- Schema:
- fullName: String
- userName: String
- phoneNumber: String (optional)
- image: String (optional)
- email: String
- password: String
- biography: String (optional)
- userType: String (admin, student, staff)
- user_id: String
- token: String (optional)
- Firestore Database:
- Collection: users
- Document fields: fullName, userName, phoneNumber, image, email, password, biography, userType, user_id, token
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a comment made by a user on a post.
- Schema:
- id: String
- content: String
- commenter: CustomUser
- createdAt: DateTime
- postType: int
- Roles in Firestore Database:
- Collection: comments
- Document fields: id, content, commenter, createdAt, postType
- only authorized users.
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a confession made by a user.
- Schema:
- isAnonymous: bool
- mentionedPeople: List (optional)
- comments: List
- likes: Set
- Roles in Firestore Database:
- Collection: confessions
- Document fields: isAnonymous, mentionedPeople, comments, likes
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents the ratings for a course or staff.
- Schema:
- id: String
- ratingSum: double
- ratingAverage: double
- ratingCount: int
- Roles in Firestore Database:
- Collection: ratings
- Document fields: id, ratingSum, ratingAverage, ratingCount
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a course in the application.
- Schema:
- courseCode: String
- courseName: String
- image: String (optional)
- ratings: List
- description: String
- Roles in Firestore Database:
- Collection: courses
- Document fields: courseCode, courseName, image, ratings, description
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents an important email contact for staff.
- Schema:
- title: String
- email: String
- Roles in Firestore Database:
- Collection: importantEmails
- Document fields: title, email
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents an important phone number contact.
- Schema:
- title: String
- phoneNumber: String
- Roles in Firestore Database:
- Collection: importantPhoneNumbers
- Document fields: title, phoneNumber
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a lost and found item.
- Schema:
- contact: String (optional)
- likes: Set
- comments: List
- Roles in Firestore Database:
- Collection: lostAndFound
- Document fields: contact, likes, comments
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents an office location.
- Schema:
- name: String
- latitude: double
- longitude: double
- location: String
- isOffice: bool
- Roles in Firestore Database:
- Collection: officeAndLocations
- Document fields: name, latitude, longitude, location, isOffice
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a post made by a user.
- Schema:
- content: String
- sender: CustomUser
- createdAt: DateTime
- id: String
- image: String
- likes: Set
- comments: List
- Roles in Firestore Database:
- Collection: posts
- Document fields: content, sender, createdAt, id, image, likes, comments
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a report made by a user.
- Schema:
- id: String
- reportedContentId: String
- reportedUser: CustomUser
- reportedContent: String
- reportType: String
- createdAt: DateTime
- image: String (optional)
- reason: String
- clarification: String (optional)
- Roles in Firestore Database:
- Collection: reports
- Document fields: id, reportedContentId, reportedUser, reportedContent, reportType, createdAt, image, reason, clarification
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a staff member in the application.
- Schema:
- fullName: String
- image: String (optional)
- email: String
- officeLocation: String (optional)
- staffType: String
- bio: String (optional)
- description: String
- speciality: String
- courses: List
- ratings: List
- Roles in Firestore Database:
- Collection: staffs
- Document fields: fullName, image, email, officeLocation, staffType, bio, description, speciality, courses, ratings
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents a user rating for a course or staff member.
- Schema:
- id: String
- userId: String
- rating: double
- comment: String (optional)
- createdAt: Timestamp
- updatedAt: Timestamp
- Roles in Firestore Database:
- Collection: userRatings
- Document fields: id, userId, rating, comment, createdAt, updatedAt
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents an academic question posted by a user.
- Schema:
- likes: Set
- comments: List
- Roles in Firestore Database:
- Collection: academicRelatedQuestions
- Document fields: likes, comments
- Restriction: Only authorized users can read and write to this collection.
- Description: Represents the screen time for usability tracking.
- Schema:
- screenName: String
- startTime: DateTime
- endTime: DateTime
- duration: double
- Description: Represents a user event for usability tracking.
- Schema:
- eventName: String
- timeStampe: DateTime
- Description: Represents the usability data for a user.
- Schema:
- user_email: String
- user_type: String
- events: List (optional)
- screenTimes: List (optional)
- Firestore Database:
- Collection: usabilityData
- Document fields: user_email, user_type, events, screenTimes
- Restriction: Only authorized users can read and write to this collection.
├── firebase_options.dart
├── main.dart
├── routes.dart
├── src
│ ├── dummy_data
│ │ ├── importantEmails.dart
│ │ ├── importantNumbers.dart
│ │ ├── OfficeItems.dart
│ │ ├── posts.dart
│ │ └── user.dart
│ ├── models
│ │ ├── AcademicQuestion.dart
│ │ ├── Comment.dart
│ │ ├── Confession.dart
│ │ ├── Course.dart
│ │ ├── ImportantEmail.dart
│ │ ├── ImportantPhoneNumber.dart
│ │ ├── LostAndFound.dart
│ │ ├── NewsEventClub.dart
│ │ ├── OfficeAndLocation.dart
│ │ ├── Post.dart
│ │ ├── Rating.dart
│ │ ├── Reports.dart
│ │ ├── Staff.dart
│ │ ├── Usability.dart
│ │ ├── User.dart
│ │ └── UserRating.dart
│ ├── providers
│ │ ├── AcademicQuestionProvider.dart
│ │ ├── CommentProvider.dart
│ │ ├── ConfessionProvider.dart
│ │ ├── CourseProvider.dart
│ │ ├── ImportantEmailProvider.dart
│ │ ├── ImportantPhoneNumberProvider.dart
│ │ ├── LikesProvider.dart
│ │ ├── LostAndFoundProvider.dart
│ │ ├── NewsEventClubProvider.dart
│ │ ├── OfficeLocationProvider.dart
│ │ ├── PostProvider.dart
│ │ ├── RatingProvider.dart
│ │ ├── ReportsProvider.dart
│ │ ├── StaffProvider.dart
│ │ ├── UsabilityProvider.dart
│ │ └── UserProvider.dart
│ ├── screens
│ │ ├── admin
│ │ │ ├── pending_reports.dart
│ │ │ ├── pendings_screen.dart
│ │ │ ├── report_content.dart
│ │ │ ├── request_post_screen.dart
│ │ │ ├── search_course.dart
│ │ │ ├── search_staff.dart
│ │ │ ├── set_course_screen.dart
│ │ │ ├── set_important_contacts_screen.dart
│ │ │ ├── set_office_screen.dart
│ │ │ └── set_staff_screen.dart
│ │ ├── authentication
│ │ │ ├── login.dart
│ │ │ └── register.dart
│ │ ├── common
│ │ │ ├── about.dart
│ │ │ ├── AcademicRelated
│ │ │ │ ├── academicRelated.dart
│ │ │ │ ├── addAcademicQuestion.dart
│ │ │ │ └── editAcademicPost.dart
│ │ │ ├── confessions
│ │ │ │ ├── addConfessions.dart
│ │ │ │ └── confessions.dart
│ │ │ ├── important_contacts.dart
│ │ │ ├── L&F
│ │ │ │ ├── addLostAndFoundPost.dart
│ │ │ │ ├── editLostAndFoundPost.dart
│ │ │ │ └── lostAndFound.dart
│ │ │ ├── newsEvents
│ │ │ │ ├── addPostClubs.dart
│ │ │ │ ├── clubsAndEvents.dart
│ │ │ │ └── editPostClubs.dart
│ │ │ ├── officesAndOutlets.dart
│ │ │ └── splash.dart
│ │ ├── course
│ │ │ └── course_profile.dart
│ │ ├── staff
│ │ │ └── profile.dart
│ │ └── user
│ │ ├── profile.dart
│ │ ├── profile_edit.dart
│ │ ├── profile_edit_form.dart
│ │ ├── search.dart
│ │ └── settings.dart
│ ├── services
│ │ └── notification_api.dart
│ ├── utils
│ │ ├── dates.dart
│ │ ├── titleCase.dart
│ │ └── uploadImageToStorage.dart
│ └── widgets
│ ├── app_bar.dart
│ ├── bottom_bar.dart
│ ├── cached_image.dart
│ ├── comment.dart
│ ├── comment_popup_menu.dart
│ ├── comments_modal.dart
│ ├── confession_widget.dart
│ ├── drawer.dart
│ ├── edit_comment.dart
│ ├── email_field.dart
│ ├── error_essage.dart
│ ├── input_field.dart
│ ├── likable_image.dart
│ ├── loader.dart
│ ├── mention_field.dart
│ ├── message_dialog.dart
│ ├── password_field.dart
│ ├── phone_field.dart
│ ├── popup_menue_button.dart
│ ├── post.dart
│ ├── post_widget.dart
│ ├── RatingBar.dart
│ ├── report_modal.dart
│ ├── status_indicator.dart
│ └── user_image_picker.dart
└── themes
├── colors.dart
├── sizes.dart
└── themes.dart
Usability Dataset Description:
The usability dataset captures essential user interactions and screen time data within our application, enabling a comprehensive analysis of user behavior and engagement. The dataset includes three main components:
-
User Information:
- User Email: This field serves as a unique identifier for users.
- User Type: Specifies the type of user, providing insights into different user categories or roles.
-
User Events:
- Event Name: Describes various user interactions, such as button clicks and scrolls.
- Timestamp: Records the exact date and time when the event occurred. This information is crucial for understanding the temporal aspects of user engagement.
-
Screen Time:
- Screen Name: Identifies the specific screen or page within the Flutter app.
- Start Time: Marks the beginning of the user's interaction with a particular screen.
- End Time: Indicates when the user navigates away from the screen.
- Duration: Measures the time spent on a given screen, providing insights into user engagement and preferences.
User Events encompass actions like button clicks and scrolls, offering valuable data on user interactions and preferences. This information is vital for assessing user engagement patterns and optimizing the user interface for enhanced usability.
- For user clicks and scrolls, a dedicated
logEvent
method is employed, capturing the user's email and the specific event type (e.g., button click/scroll) along with timestamp when the event happened. - This logging is strategically implemented on significant buttons throughout the app to capture user interactions.
- The application incorporates a
NotificationListener
wrapped around aListView
widget to capture scroll events initiated by the user.
- The
onNotification
callback is triggered when aUserScrollNotification
is received, allowing for the determination of the scroll direction. - If the direction is forward (upward), an event with the name 'Scroll_Up_Sreen_Name' is logged.
- If the direction is reverse (downward), an event with the name 'Scroll_Down_Sreen_Name' is logged.
logEvent
function is designed to be non-blocking to prevent slowing down the application's main thread.- This non-blocking approach allows the app to continue processing user interactions and UI updates without waiting for database operations to complete.
- The
logScreenTime
method is responsible for updating the usability dataset with screen time information. - It first queries the Firestore database to check if a document with the user's email exists.
- If the document doesn't exist, a new document is created with user details and an initial screen time entry.
- If the document exists, the screen time entry is added to the existing
screenTimes
array.
- Triggered when a new screen is pushed onto the navigator.
- Captures the start time when a screen is entered.
- Calculates and logs screen time for the previous screen(to handle case of back-to-back pushing to the navigation stack).
- Triggered when a screen is popped from the navigator.
- Captures the exit time when a screen is exited.
- Calculates and logs screen time for the popped screen.
- Captures the start time of the screen(route) that is currently at the top of navigation stack after popping.
- Triggered when a new screen replaces an existing screen.
- Captures the exit time when a screen is replaced.
- Calculates and logs screen time for the replaced screen.
- Captures the start time of the screen(route) that is currently at the top of navigation stack after replacing.
- The duration of screen time is calculated by taking the difference between the exit and enter times.
- If the duration is zero seconds, the logging is skipped to avoid recording insignificant screen times, possibly caused by rapid navigation changes or logging out as logout function pops out all the routes in the navigation stack.
- The screen name includes additional information for profile screens with visited user emails if it exists to distinguish whether the user surfing his own profile or others.
- The
UsabilityProvider
is integrated into the Flutter application as aNavigatorObserver
, ensuring that the defined methodologies for screen time logging are seamlessly executed throughout the app's navigation flow.
logScreenTime
function is designed to be non-blocking to prevent slowing down the application's main thread.- This non-blocking approach allows the app to continue processing user interactions and UI updates without waiting for database operations to complete.
This methodology ensures accurate and detailed tracking of user interactions and screen times, providing valuable data for usability analysis within the application. It's a common practice to avoid blocking the main thread by not awaiting certain asynchronous functions, especially if they are not critical to the immediate user experience.
---providers were unit tested using a fake firebase firestore below are sample cases and the others are in the test folder.
test('Login with valid credentials should succeed', () async {
// Mock Firebase Authentication if needed
final mockFirebaseAuth = MockFirebaseAuth();
// Define test user credentials
final email = '[email protected]';
final password = 'testpassword';
// Simulate successful login
bool userCredential = await userProvider.login(email, password);
// Login using your actual implementation
// Verify successful login
expect(userCredential, isNotNull);
});
test("create a post and make sure it is saved as mine and delete it",
() async {
final AcademicQuestionProvider provider =
AcademicQuestionProvider(instance);
final CustomUser user = CustomUser(
fullName: 'a b c',
userName: 'abc',
email: '[email protected]',
password: 'abcdef1',
);
AcademicQuestion q = AcademicQuestion(
content: 'academic question',
sender: user,
createdAt: DateTime.now(),
comments: [],
likes: {}
);
await provider.askQuestion(q);
expect(await provider.getQuestions(), isNotEmpty);
List<AcademicQuestion> myQuestions =
await provider.getMyQuestions('[email protected]');
expect(myQuestions, isNotEmpty);
await provider.deleteQuestion(q.id);
List<AcademicQuestion> l = await provider.getQuestions();
expect(l, isEmpty);
});
test("like and dislike all post types", () async {
final CustomUser user = CustomUser(
fullName: 'a b c',
userName: 'abc',
email: '[email protected]',
password: 'abcdef1',
);
Confession c = Confession(
isAnonymous: false,
content: 'confession',
sender: user,
createdAt: DateTime.now(),
comments: [],
likes: {});
NewsEventClub n = NewsEventClub(
reason: "reason",
content: "content",
sender: user,
createdAt: DateTime.now(),
likes: {},
comments: []);
AcademicQuestion aq = AcademicQuestion(
content: "content",
sender: user,
createdAt: DateTime.now(),
likes: {},
comments: []);
LostAndFound lf = LostAndFound(
contact: "contact",
content: "content",
createdAt: DateTime.now(),
image: "some image",
sender: user,
likes: {},
comments: []);
await confessionProvider.addConfession(c);
await necProvider.postContent(n);
await aqProvider.askQuestion(aq);
await lfProvider.postItem(lf);
await likesProvider.likePost(n.id, user.user_id, 0);
await likesProvider.likePost(lf.id, user.user_id, 1);
await likesProvider.likePost(aq.id, user.user_id, 2);
await likesProvider.likePost(c.id, user.user_id, 3);
LostAndFound res = (await lfProvider.getItems())[0];
AcademicQuestion res1 = (await aqProvider.getQuestions())[0];
Confession res2 = (await confessionProvider.getConfessions())[0];
NewsEventClub res3 = (await necProvider.getPosts())[0];
expect(res.likes.length, 0);
expect(res1.likes.length, 0);
expect(res2.likes.length, 0);
expect(res3.likes.length, 1);
await likesProvider.dislike(n.id, user.user_id, 0);
await likesProvider.dislike(lf.id, user.user_id, 1);
await likesProvider.dislike(aq.id, user.user_id, 2);
await likesProvider.dislike(c.id, user.user_id, 3);
res = (await lfProvider.getItems())[0];
res1 = (await aqProvider.getQuestions())[0];
res2 = (await confessionProvider.getConfessions())[0];
res3 = (await necProvider.getPosts())[0];
expect(res.likes.length, 0);
expect(res1.likes.length, 0);
expect(res2.likes.length, 0);
expect(res3.likes.length, 0);
});
test("report a confession and a comment and delete them", () async {
final ReportsProvider p = ReportsProvider(instance);
final CustomUser user = CustomUser(
fullName: 'a b c',
userName: 'abc',
email: '[email protected]',
password: 'abcdef1',
);
Confession c = Confession(
isAnonymous: false,
content: 'confession',
sender: user,
createdAt: DateTime.now(),
comments: [],
likes: {}
);
Report r = Report(
reportedContentId: c.id,
reportedUser: c.sender,
reportedContent: c.content,
reportType: "confession",
createdAt: DateTime.now(), reason: 'Other');
Comment c2 = Comment(
content: "comment",
commenter: user,
createdAt: DateTime.now(),
postType: 0);
expect(await p.getCommentReports(), isEmpty);
expect(await p.getConfessionReports(), isEmpty);
await p.reportContent(r);
expect(await p.getConfessionReports(), isNotEmpty);
Report r2 = Report(
reportedContentId: c2.id,
reportedUser: c2.commenter,
reportedContent: c2.content,
reportType: "comment",
createdAt: DateTime.now(), reason: 'Harrasment');
await p.reportContent(r2);
expect(await p.getCommentReports(), isNotEmpty);
await p.approveReport(r);
expect(await p.getConfessionReports(), isEmpty);
await p.approveReport(r2);
expect(await p.getCommentReports(), isEmpty);
await p.reportContent(r);
await p.reportContent(r2);
await p.disapproveReport(r);
expect(await p.getConfessionReports(), isEmpty);
await p.disapproveReport(r2);
expect(await p.getCommentReports(), isEmpty);
});
Users who will Use this Data should only use it for Practice and not for Commercial Purposes !