From 39429b09f1383b6f12321ead5864543619644aa3 Mon Sep 17 00:00:00 2001 From: jaredStef Date: Thu, 6 Apr 2023 04:23:02 -0400 Subject: [PATCH 1/2] Add New Group Creation UI and New Group Loading --- project/lib/home.dart | 92 +++++- project/lib/main.dart | 9 +- project/lib/new_group_creation.dart | 287 ++++++++++++++++++ project/lib/new_social_onboarding.dart | 2 +- project/lib/signin.dart | 2 +- project/lib/theme.dart | 17 +- .../ephemeral/Flutter-Generated.xcconfig | 4 +- .../ephemeral/flutter_export_environment.sh | 4 +- project/pubspec.lock | 8 + project/pubspec.yaml | 1 + 10 files changed, 407 insertions(+), 19 deletions(-) create mode 100644 project/lib/new_group_creation.dart mode change 100644 => 100755 project/macos/Flutter/ephemeral/flutter_export_environment.sh diff --git a/project/lib/home.dart b/project/lib/home.dart index 72ef752a..7110919d 100644 --- a/project/lib/home.dart +++ b/project/lib/home.dart @@ -1,14 +1,34 @@ +import 'dart:collection'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:groupmeet/new_group_creation.dart'; import 'package:groupmeet/theme.dart'; import 'code_reception.dart'; import 'group_creation.dart'; import 'settings.dart'; -class HomeScreen extends StatelessWidget { - const HomeScreen({super.key, required String title}); +class Group { + Color color; + String emoji; + String name; + + Group(this.color, this.emoji, this.name); +} + +class HomeScreen extends StatefulWidget { + @override + _HomeScreen createState() => _HomeScreen(); +} + +class _HomeScreen extends State { - get title => null; + List displayedGroups = []; + + bool observing = false; // TODO: Cross-Platform working QR Screen void showQR(context) { @@ -24,7 +44,7 @@ class HomeScreen extends StatelessWidget { Navigator.of(context).push( platformPageRoute( context: context, - builder: (context) => const GroupCreation(title: "Group Creation")), + builder: (context) => NewGroupCreation()), ); } @@ -41,8 +61,62 @@ class HomeScreen extends StatelessWidget { print("Tapped group $group"); } + void observeGroups() { + if (observing) { + return; + } + + String? userID = FirebaseAuth.instance.currentUser?.uid; + + if (userID == null) { + // TODO: Alert saying not logged in yet? + print("Not logged in"); + } + + FirebaseDatabase.instance.ref("users/${userID!}/groupIds").onValue.listen((event) async { + if (event.snapshot.value == null) { + return; + } + + print(event.snapshot.value); + + Iterable groups = (event.snapshot.value as Map).keys; + + List newGroups = []; + + for (Object? groupID in groups) { + String groupIDCasted = groupID as String; + final groupInfo = await FirebaseDatabase.instance.ref("groups/$groupIDCasted/").get(); + + if (!groupInfo.exists) { + continue; + } + print(groupInfo.value); + + Map vals = groupInfo.value as Map; + + int color = vals['color'] as int; + String emoji = vals['emoji'] as String; + String name = vals['name'] as String; + + print(color); + print(emoji); + print(name); + + newGroups.add(Group(Color(color), emoji, name)); + } + + setState(() => displayedGroups = newGroups); + }); + + observing = true; + } + @override Widget build(BuildContext context) { + + observeGroups(); + double screenWidth = MediaQuery.of(context).size.width; double screenHeight = MediaQuery.of(context).size.height; @@ -60,7 +134,7 @@ class HomeScreen extends StatelessWidget { top: 48 + MediaQuery.of(context).viewPadding.top + 8 + 8), crossAxisCount: 2, mainAxisSpacing: 0, - children: List.generate(3, (index) { + children: List.generate(displayedGroups.length, (index) { return GestureDetector( onTap: () => selectedGroup(index), child: Column( @@ -70,12 +144,12 @@ class HomeScreen extends StatelessWidget { alignment: Alignment.center, children: [ ColorFiltered( - colorFilter: const ColorFilter.mode( - roundPurple, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + displayedGroups[index].color, BlendMode.srcIn), child: Image.asset("images/GroupRound.png", width: 120, height: 120, isAntiAlias: true), ), - PlatformText("🖥️️", + PlatformText(displayedGroups[index].emoji, style: const TextStyle( fontSize: 40, fontWeight: FontWeight.bold, @@ -85,7 +159,7 @@ class HomeScreen extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), child: PlatformText( - "STEM Baddies", + displayedGroups[index].name, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14), textAlign: TextAlign.center, diff --git a/project/lib/main.dart b/project/lib/main.dart index d0cf4ee1..95e3d219 100644 --- a/project/lib/main.dart +++ b/project/lib/main.dart @@ -8,6 +8,7 @@ import 'package:groupmeet/theme.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'home.dart'; +import 'new_group_creation.dart'; // Initialize the app and run it. Future main() async { @@ -64,7 +65,7 @@ class MyApp extends StatelessWidget { User? user = FirebaseAuth.instance.currentUser; if (user != null) { - firstStop = const HomeScreen(title: 'Home'); + firstStop = HomeScreen(); } else { firstStop = Explainer(pageNo: 0); } @@ -76,7 +77,11 @@ class MyApp extends StatelessWidget { material: (context, platform) => MaterialAppData(theme: materialTheme, color: roundPurple), cupertino: (context, platform) => - CupertinoAppData(theme: cupertinoTheme, color: roundPurple), + CupertinoAppData(theme: cupertinoTheme, color: roundPurple, localizationsDelegates: [ + DefaultCupertinoLocalizations.delegate, + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ]), home: firstStop, //Explainer(pageNo: 0), title: "Round", color: roundPurple); diff --git a/project/lib/new_group_creation.dart b/project/lib/new_group_creation.dart new file mode 100644 index 00000000..88530f07 --- /dev/null +++ b/project/lib/new_group_creation.dart @@ -0,0 +1,287 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_material_color_picker/flutter_material_color_picker.dart'; +import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:groupmeet/new_calendar_selection.dart'; +import 'package:groupmeet/theme.dart'; + +import 'signin.dart'; + +class NewGroupCreation extends StatefulWidget { + @override + _NewGroupCreationState createState() => _NewGroupCreationState(); +} + +class _NewGroupCreationState extends State { + + _NewGroupCreationState() { + selectedColor = createMaterialColor(roundPurple); + } + + MaterialColor createMaterialColor(Color color) { + List strengths = [.05]; + Map swatch = {}; + final int r = color.red, g = color.green, b = color.blue; + + for (int i = 1; i < 10; i++) { + strengths.add(0.1 * i); + } + for (var strength in strengths) { + final double ds = 0.5 - strength; + swatch[(strength * 1000).round()] = Color.fromRGBO( + r + ((ds < 0 ? r : (255 - r)) * ds).round(), + g + ((ds < 0 ? g : (255 - g)) * ds).round(), + b + ((ds < 0 ? b : (255 - b)) * ds).round(), + 1, + ); + } + return MaterialColor(color.value, swatch); + } + + MaterialColor? selectedColor; + MaterialColor? tempColor; + + String name = ""; + String emoji = ""; + + late DatabaseReference ref; + String? uid; + + void changedName(String string) { + name = string; + } + + void changedEmoji(String string) { + emoji = string; + } + + Future buttonPress(BuildContext context) async { + if(name.trim().isEmpty || emoji.trim().characters.length != 1) { + PlatformAlertDialog error = PlatformAlertDialog( + title: PlatformText("Whoops!"), + content: PlatformText( + 'Please enter a group name and 1 character emoji. You may optionally select a non-default color'), + actions: [ + PlatformTextButton( + child: PlatformText("Ok", + selectionColor: roundPurple, + style: const TextStyle(color: roundPurple)), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ); + + showPlatformDialog( + context: context, + builder: (context) { + return error; + }, + ); + return; + } + + // TODO: Create Group in Firebase and Dismiss + // TODO: Load User Groups to home screen :) + // TODO: Then Accept Screen, Viewing, and Settings + + String? userID = FirebaseAuth.instance.currentUser?.uid; + + if (userID == null) { + + PlatformAlertDialog error = PlatformAlertDialog( + title: PlatformText("Whoops!"), + content: PlatformText( + 'You are not logged in. Log back in or check your internet connection'), + actions: [ + PlatformTextButton( + child: PlatformText("Ok", + selectionColor: roundPurple, + style: const TextStyle(color: roundPurple)), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ); + + showPlatformDialog( + context: context, + builder: (context) { + return error; + }, + ); + return; + } + + final DatabaseReference groupRef = + FirebaseDatabase.instance.ref().child('groups').push(); + + await groupRef.set({ + 'name': name.trim(), + 'emoji': emoji.trim(), + 'admin': userID, + 'members': {userID: true}, + 'color': selectedColor!.shade500.value + }); + + await FirebaseDatabase.instance + .ref("users/$userID/groupIds/${groupRef.key}").set(true); + + Navigator.of(context).pop(); + } + + void signIn(BuildContext context) { + Navigator.of(context).push( + platformPageRoute(context: context, builder: (context) => NewSignIn())); + } + + void _openDialog(String title, Widget content) { + showDialog( + context: context, + builder: (_) { + return AlertDialog( + title: PlatformText(title), + content: content, + actions: [ + TextButton( + child: PlatformText('Cancel', selectionColor: roundPurple), + onPressed: Navigator.of(context).pop, + ), + TextButton( + child: PlatformText('Submit', selectionColor: roundPurple), + onPressed: () { + Navigator.of(context).pop(); + setState(() => selectedColor = tempColor); + }, + ), + ], + ); + }, + ); + } + + void colorTapped() { + _openDialog( + "Color Picker", + MaterialColorPicker( + selectedColor: selectedColor, + colors: [ + createMaterialColor(roundPurple), + createMaterialColor(roundWhite), + createMaterialColor(roundRed), + createMaterialColor(roundOrange), + createMaterialColor(roundYellow), + createMaterialColor(roundBlue), + createMaterialColor(roundPink), + createMaterialColor(roundLightPurple), + createMaterialColor(roundGreen), + createMaterialColor(roundSilver), + createMaterialColor(roundForest), + createMaterialColor(roundTeal) + ], + allowShades: false, + onMainColorChange: (color) => setState(() => tempColor = color as MaterialColor?), + + ) + ); + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + double screenHeight = MediaQuery.of(context).size.height; + CircleColor colorCircle = CircleColor(color: selectedColor!.shade500, circleSize: 32,); + + return PlatformScaffold( + appBar: PlatformAppBar(title: PlatformText("New Circle")), + body: Center( + child: Column( + children: [ + SizedBox( + width: screenWidth, + height: + MediaQuery.of(context).viewPadding.top + 0.08 * screenHeight), + Image.asset( + "images/AddPhoto.png", + height: 160, + width: screenWidth, + isAntiAlias: true, + ), + SizedBox(width: screenWidth, height: 8), + PlatformText("Get Around", + style: const TextStyle(fontSize: 36, fontWeight: FontWeight.w600), + textAlign: TextAlign.center), + SizedBox(width: screenWidth, height: 32), + + SizedBox( + width: screenWidth * (3 / 4), + child: PlatformTextField( + hintText: "Group Name", + autofocus: true, + cursorColor: roundPurple, + onChanged: (p0) => changedName(p0), + material: (_, __) => MaterialTextFieldData( + decoration: const InputDecoration( + focusColor: roundPurple, hoverColor: roundPurple)), + )), + SizedBox( + width: screenWidth, + height: 16, + ), + Row( + children: [ + SizedBox(width: screenWidth / 8), + SizedBox( + width: screenWidth * (3 / 8), + child: PlatformTextField( + hintText: "Emoji", + keyboardType: TextInputType.text, + cursorColor: roundPurple, + onChanged: (p0) => changedEmoji(p0), + )), + SizedBox(width: screenWidth / 16,), + PlatformText("Color", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + SizedBox(width: screenWidth / 16,), + GestureDetector( + child: colorCircle, + onTap: () => colorTapped(), + ) + ], + ), + + Expanded( + child: Align( + alignment: FractionalOffset.bottomCenter, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 64, + width: 64, + child: PlatformIconButton( + icon: Image.asset( + "images/OnboardingNext.png", + height: 64, + width: 64, + isAntiAlias: true, + ), + padding: EdgeInsets.zero, + onPressed: () => buttonPress(context), + )), + SizedBox(width: screenWidth, height: 16), + PlatformText("© 2023 Round Corp\nFrom Philly with Love 🤍", + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 10)), + SizedBox( + width: screenWidth, + height: 32, + ) + ], + ), + ), + ), + ], + ))); + } +} diff --git a/project/lib/new_social_onboarding.dart b/project/lib/new_social_onboarding.dart index 06e20d3d..827091fc 100644 --- a/project/lib/new_social_onboarding.dart +++ b/project/lib/new_social_onboarding.dart @@ -53,7 +53,7 @@ class NewSocialOnboarding extends StatelessWidget { Navigator.of(context).push(platformPageRoute( context: context, - builder: (context) => const HomeScreen(title: "Home"))); + builder: (context) => HomeScreen())); } late DatabaseReference databaseReference; diff --git a/project/lib/signin.dart b/project/lib/signin.dart index 08ed8548..57ec7333 100644 --- a/project/lib/signin.dart +++ b/project/lib/signin.dart @@ -39,7 +39,7 @@ class NewSignIn extends StatelessWidget { await FirebaseAuth.instance .signInWithEmailAndPassword(email: email, password: password); Navigator.of(context).push(platformPageRoute( - context: context, builder: (context) => const HomeScreen(title: 'Home',))); + context: context, builder: (context) => HomeScreen())); } catch (e) { String errorMessage = 'An error occurred, please try again later.'; diff --git a/project/lib/theme.dart b/project/lib/theme.dart index 646919ed..592df755 100644 --- a/project/lib/theme.dart +++ b/project/lib/theme.dart @@ -1,5 +1,18 @@ import 'package:flutter/material.dart'; -const roundWhite = Color(0xFFEBEBEB); +const roundPurple = Color(0xFF513BDB); + const roundBlack = Color(0xFF020300); -const roundPurple = Color(0xFF513BDB); \ No newline at end of file +const roundWhite = Color(0xFFEBEBEB); +const roundRed = Color(0xFFAF1B3F); +const roundOrange = Color(0xFFF79824); +const roundYellow = Color(0xFFFDCA40); +const roundBlue = Color(0xFF33A1FD); +const roundPink = Color(0xFFFECEE9); +const roundLightPurple = Color(0xFFEB9FEF); +const roundGreen = Color(0xFF0CF574); +const roundSilver = Color(0xFFD9D9D9); +const roundForest = Color(0xFF023618); +const roundTeal = Color(0xFF177E89); + +// Group Colors - White, Purple, Red, Blue, Orange, Yellow, Blue, Green, \ No newline at end of file diff --git a/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index d9153140..0c6eff86 100644 --- a/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,6 +1,6 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=C:\Users\tyler\flutter -FLUTTER_APPLICATION_PATH=C:\Users\tyler\4398 Project\project-groupmeet\project +FLUTTER_ROOT=/Users/jaredstef/Developer/Flutter/flutter +FLUTTER_APPLICATION_PATH=/Users/jaredstef/Developer/project-groupmeet/project COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build FLUTTER_BUILD_NAME=1.0.0 diff --git a/project/macos/Flutter/ephemeral/flutter_export_environment.sh b/project/macos/Flutter/ephemeral/flutter_export_environment.sh old mode 100644 new mode 100755 index 37deb8d2..a85425c2 --- a/project/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/project/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,7 +1,7 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=C:\Users\tyler\flutter" -export "FLUTTER_APPLICATION_PATH=C:\Users\tyler\4398 Project\project-groupmeet\project" +export "FLUTTER_ROOT=/Users/jaredstef/Developer/Flutter/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/jaredstef/Developer/project-groupmeet/project" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=1.0.0" diff --git a/project/pubspec.lock b/project/pubspec.lock index febdb904..0b8d30f0 100644 --- a/project/pubspec.lock +++ b/project/pubspec.lock @@ -302,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + flutter_material_color_picker: + dependency: "direct main" + description: + name: flutter_material_color_picker + sha256: "64432c3b13bbcc6f428bae9d2e75b1b3da1e6f3219979e08a3bd9f176781d9f2" + url: "https://pub.dev" + source: hosted + version: "1.1.0+2" flutter_platform_widgets: dependency: "direct main" description: diff --git a/project/pubspec.yaml b/project/pubspec.yaml index d2a1d7ce..8a1fd4e4 100644 --- a/project/pubspec.yaml +++ b/project/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: permission_handler: ^9.2.0 intl: ^0.17.0 date_time_picker: ^2.1.0 + flutter_material_color_picker: ^1.1.0+2 dev_dependencies: flutter_test: sdk: flutter From 11961447e7f54b14585328d5cddc3330fa62d95c Mon Sep 17 00:00:00 2001 From: jaredStef Date: Thu, 6 Apr 2023 04:39:30 -0400 Subject: [PATCH 2/2] Beginning Doc update branch. Replacing GroupMeet with Round in README and adding photo --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 55f37d26..59ce8825 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@
-# GroupMeet + +![Round]("./project/images/WordMark-Dark.png") [![Report Issue on Jira](https://img.shields.io/badge/Report%20Issues-Jira-0052CC?style=flat&logo=jira-software)](https://temple-cis-projects-in-cs.atlassian.net/jira/software/c/projects/DT/issues) [![Deploy Docs](https://github.com/ApplebaumIan/tu-cis-4398-docs-template/actions/workflows/deploy.yml/badge.svg)](https://github.com/ApplebaumIan/tu-cis-4398-docs-template/actions/workflows/deploy.yml) [![Documentation Website Link](https://img.shields.io/badge/-Documentation%20Website-brightgreen)](https://capstone-projects-2023-spring.github.io/project-groupmeet/) @@ -10,7 +11,7 @@

Project Abstract

-This document proposes a multi-platform mobile application called "GroupMeet" that creates an environment for students and social groups to easily organize each other's schedules for availability and share contact information. In academic group projects, students will be able to easily begin communications with each other and synchronize with each other's schedules to become more successful in their academic endeavors. In social groups, friends can now easily coordinate each other's busy lives and adjust accordingly to spontaneous events. With this application, there is little need for one particular individual to take charge; the responsibility lies on the application itself. +This document proposes a multi-platform mobile application called "Round" that creates an environment for students and social groups to easily organize each other's schedules for availability and share contact information. In academic group projects, students will be able to easily begin communications with each other and synchronize with each other's schedules to become more successful in their academic endeavors. In social groups, friends can now easily coordinate each other's busy lives and adjust accordingly to spontaneous events. With this application, there is little need for one particular individual to take charge; the responsibility lies on the application itself.

Collaborators

@@ -117,7 +118,7 @@ When the application is needed, the user will have to create an instance that ge

Conceptual Design

-IGroupMeet's programming environment will be in Dart using the Flutter framework. This will allow the application to be developed for both iOS and Android devices simultaneously. This is to ensure that the only requirement to use our application is owning a smartphone so that the most users can be reached and assisted with GroupMeet. As for hosting of the application, there is currently an attempt to acquire the necessary physical hardware, however this is a situation that can be circumnavigated. The backend will be utilizing a known cloud service such as AWS or Firebase. +Round's programming environment will be in Dart using the Flutter framework. This will allow the application to be developed for both iOS and Android devices simultaneously. This is to ensure that the only requirement to use our application is owning a smartphone so that the most users can be reached and assisted with Round. As for hosting of the application, there is currently an attempt to acquire the necessary physical hardware, however this is a situation that can be circumnavigated. The backend will be utilizing a known cloud service such as AWS or Firebase.

Background

@@ -125,7 +126,7 @@ Throughout one's academic career, it has been shown time and time again that one In the two scenarios above, the task of trying to find a block of time that works for everyone tends to be a very daunting one. Additionally, this particular task tends to be assigned to one unlucky individual. It can be assumed with a wide margin of certainty that if one with this assignment had the ability to make a task that can seem impossible at times, possible, they would be interested in resources that would aid in this daunting assignment. -Similar products to GroupMeet include official calendars with calendar sync integration like Google Calendars, or Calendar ([https://www.calendar.com/](https://www.calendar.com/)) which has dynamic scheduling integration. However, many of the latter's features are locked behind a paywall. This creates a very unfortunate problem where paying for a "premium" feature is just not convenient enough for short-term use. Additionally, combining calendars typically tends to mean the event details are shared in some shape or form, implying that all who have their calendars integrated in a setting previously mentioned risk having other friends and associates aware of exactly what is occurring at any point in one's personal life. This is a scenario that should be avoided as much as possible, as the sharing of personal or public information should be completely at ones own discretion rather than as a necessity to set up a meeting time for a task. GroupMeet aspires to make calendar sync integration an easy and seamless process with privacy in mind. +Similar products to Round include official calendars with calendar sync integration like Google Calendars, or Calendar ([https://www.calendar.com/](https://www.calendar.com/)) which has dynamic scheduling integration. However, many of the latter's features are locked behind a paywall. This creates a very unfortunate problem where paying for a "premium" feature is just not convenient enough for short-term use. Additionally, combining calendars typically tends to mean the event details are shared in some shape or form, implying that all who have their calendars integrated in a setting previously mentioned risk having other friends and associates aware of exactly what is occurring at any point in one's personal life. This is a scenario that should be avoided as much as possible, as the sharing of personal or public information should be completely at ones own discretion rather than as a necessity to set up a meeting time for a task. Round aspires to make calendar sync integration an easy and seamless process with privacy in mind.

Required Resources