From 9fb3ae801e307ffc877e415d1a677e9b2f735f4c Mon Sep 17 00:00:00 2001 From: Chris Gu <54943842+cgu2020@users.noreply.github.com> Date: Sun, 28 Apr 2024 00:59:11 -0400 Subject: [PATCH] Achievement page (#207) * beginning structure, wip * Filters mostly working now (except Category) Filters mostly working now (except Category) * removed commented code removed commented code * Implemented Filtering changes (except for categories) Implemented Filtering changes (except for categories) * removed api keys from server env * Enrollment type enum * fixed filtering bugs after merging * added friendly names, fixed category filtering * Achievements Page UI work * fixed miles away value in preview * fix bug where listview breaks * fix formatting * Achievements UI no backend * Achievement model blocked by achievementTracker\ * UI adjustments * Achievements UI completed, Hint icon fixed * fixed issues caused by conflict, edited cells on profile page * removed some old print statements --------- Co-authored-by: nit0107 <68392119+nit0107@users.noreply.github.com> Co-authored-by: Nitya Co-authored-by: cathli66 Co-authored-by: neketka Co-authored-by: cathli66 --- admin/src/all.dto.ts | 4 +- game/assets/icons/achievementgold.svg | 61 ++++++ game/assets/icons/achievementsilver.svg | 33 +++ game/ios/Podfile.lock | 58 ++--- game/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- game/lib/achievements/achievement_cell.dart | 159 ++++++++++++++ game/lib/achievements/achievements_page.dart | 207 ++++++++++++++++++ game/lib/api/game_api.dart | 27 +-- game/lib/api/game_client_dto.dart | 20 +- game/lib/api/geopoint.dart | 17 +- game/lib/challenges/challenge_cell.dart | 3 + game/lib/challenges/challenges_page.dart | 1 - game/lib/details_page/details_page.dart | 5 +- game/lib/gameplay/challenge_completed.dart | 2 - game/lib/gameplay/gameplay_page.dart | 26 +-- game/lib/interests/interests_page.dart | 5 +- game/lib/main.dart | 4 +- game/lib/model/achievement_model.dart | 30 +++ game/lib/preview/preview.dart | 8 +- game/lib/profile/achievement_cell.dart | 5 +- game/lib/profile/completed_cell.dart | 81 ++----- game/lib/profile/profile_page.dart | 28 ++- game/lib/register_page/register_page.dart | 30 +-- game/lib/splash_page/splash_page.dart | 3 +- server/src/auth/auth.service.ts | 4 +- server/src/auth/login.dto.ts | 4 +- 27 files changed, 632 insertions(+), 197 deletions(-) create mode 100644 game/assets/icons/achievementgold.svg create mode 100644 game/assets/icons/achievementsilver.svg create mode 100644 game/lib/achievements/achievement_cell.dart create mode 100644 game/lib/achievements/achievements_page.dart create mode 100644 game/lib/model/achievement_model.dart diff --git a/admin/src/all.dto.ts b/admin/src/all.dto.ts index a2c0e9ca..d63ba62c 100644 --- a/admin/src/all.dto.ts +++ b/admin/src/all.dto.ts @@ -102,8 +102,8 @@ export interface RequestAchievementDataDto { export interface LoginDto { idToken: string; - lat: number; - long: number; + latF: number; + longF: number; username?: string; year?: string; college?: string; diff --git a/game/assets/icons/achievementgold.svg b/game/assets/icons/achievementgold.svg new file mode 100644 index 00000000..ce365544 --- /dev/null +++ b/game/assets/icons/achievementgold.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/game/assets/icons/achievementsilver.svg b/game/assets/icons/achievementsilver.svg new file mode 100644 index 00000000..09a3cc5a --- /dev/null +++ b/game/assets/icons/achievementsilver.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/ios/Podfile.lock b/game/ios/Podfile.lock index 7797c472..423ceaac 100644 --- a/game/ios/Podfile.lock +++ b/game/ios/Podfile.lock @@ -1,25 +1,25 @@ PODS: - - AppAuth (1.7.3): - - AppAuth/Core (= 1.7.3) - - AppAuth/ExternalUserAgent (= 1.7.3) - - AppAuth/Core (1.7.3) - - AppAuth/ExternalUserAgent (1.7.3): + - AppAuth (1.7.4): + - AppAuth/Core (= 1.7.4) + - AppAuth/ExternalUserAgent (= 1.7.4) + - AppAuth/Core (1.7.4) + - AppAuth/ExternalUserAgent (1.7.4): - AppAuth/Core - device_info (0.0.1): - Flutter - device_info_plus (0.0.1): - Flutter - - Firebase (10.23.0): - - Firebase/Core (= 10.23.0) - - Firebase/Analytics (10.23.0): + - Firebase (10.24.0): + - Firebase/Core (= 10.24.0) + - Firebase/Analytics (10.24.0): - Firebase/Core - - Firebase/Core (10.23.0): + - Firebase/Core (10.24.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.23.0) - - Firebase/CoreOnly (10.23.0): - - FirebaseCore (= 10.23.0) - - FirebaseAnalytics (10.23.0): - - FirebaseAnalytics/AdIdSupport (= 10.23.0) + - FirebaseAnalytics (~> 10.24.0) + - Firebase/CoreOnly (10.24.0): + - FirebaseCore (= 10.24.0) + - FirebaseAnalytics (10.24.0): + - FirebaseAnalytics/AdIdSupport (= 10.24.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -27,22 +27,22 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.23.0): + - FirebaseAnalytics/AdIdSupport (10.24.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.23.0) + - GoogleAppMeasurement (= 10.24.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseCore (10.23.0): + - FirebaseCore (10.24.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreInternal (10.23.0): + - FirebaseCoreInternal (10.24.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.23.0): + - FirebaseInstallations (10.24.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) @@ -70,14 +70,14 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.23.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.23.0) + - GoogleAppMeasurement/AdIdSupport (10.24.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.24.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.23.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.24.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -216,15 +216,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - AppAuth: a13994980c1ec792f7e2e665acd4d4aa6be43240 + AppAuth: 182c5b88630569df5acb672720534756c29b3358 device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - Firebase: 333ec7c6b12fa09c77b5162cda6b862102211d50 - FirebaseAnalytics: 45f6e2e5ef8ccbb90be73ae983c3b20fa78837f7 - FirebaseCore: 63efb128decaebb04c4033ed4e05fb0bb1cccef1 - FirebaseCoreInternal: 6a292e6f0bece1243a737e81556e56e5e19282e3 - FirebaseInstallations: 42d6ead4605d6eafb3b6683674e80e18eb6f2c35 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Firebase: 91fefd38712feb9186ea8996af6cbdef41473442 + FirebaseAnalytics: b5efc493eb0f40ec560b04a472e3e1a15d39ca13 + FirebaseCore: 11dc8a16dfb7c5e3c3f45ba0e191a33ac4f50894 + FirebaseCoreInternal: bcb5acffd4ea05e12a783ecf835f2210ce3dc6af + FirebaseInstallations: 8f581fca6478a50705d2bd2abd66d306e0f5736e + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_config: f48f0d47a284f1791aacce2687eabb3309ba7a41 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 diff --git a/game/ios/Runner.xcodeproj/project.pbxproj b/game/ios/Runner.xcodeproj/project.pbxproj index c7300787..6bc91567 100644 --- a/game/ios/Runner.xcodeproj/project.pbxproj +++ b/game/ios/Runner.xcodeproj/project.pbxproj @@ -159,7 +159,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/game/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/game/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b52b2e69..e67b2808 100644 --- a/game/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/game/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ 0 ? tasksFinished / totalTasks : 0) * + constraints.maxWidth, + height: 13, + alignment: Alignment.centerLeft, + child: Container( + decoration: new BoxDecoration( + color: Color.fromARGB(197, 237, 86, 86), + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(16.0)), + ), + ), + ), + Container( + height: 3, + width: (totalTasks > 0 ? tasksFinished / totalTasks : 0) * + constraints.maxWidth - + 16, + margin: EdgeInsets.only(left: 8, top: 3), + alignment: Alignment.centerLeft, + decoration: new BoxDecoration( + color: Color(0x99F3C6C6), + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + ), + ]); + })), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + tasksFinished.toString() + "/" + totalTasks.toString(), + ), + ), + ], + ); + } +} + +class AchievementCell extends StatefulWidget { + final SvgPicture thumbnail; + final String description; + final int tasksFinished; + final int totalTasks; + + const AchievementCell( + this.description, this.thumbnail, this.tasksFinished, this.totalTasks, + {Key? key}) + : super(key: key); + + @override + State createState() => + _AchievementCellState(description, thumbnail, tasksFinished, totalTasks); +} + +class _AchievementCellState extends State { + final String description; + final SvgPicture thumbnail; + final int tasksFinished; + final int totalTasks; + // newly added field + // final int totalDistance; + + _AchievementCellState( + this.description, this.thumbnail, this.tasksFinished, this.totalTasks + // newly added field + // this.totalDistance + ); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () async {}, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Color.fromARGB(255, 198, 198, 198), + blurRadius: 2, + offset: Offset(0, 4), + ), + ], + ), + child: Container( + margin: EdgeInsets.all(10), + height: 64, + child: Row( + children: [ + Container(margin: EdgeInsets.only(right: 12), child: thumbnail), + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + description, + style: TextStyle( + color: Color.fromARGB(204, 0, 0, 0), + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + ), + ), + ), + Spacer(), + Align( + alignment: Alignment.bottomCenter, + child: LoadingBar(3, 4)), + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/game/lib/achievements/achievements_page.dart b/game/lib/achievements/achievements_page.dart new file mode 100644 index 00000000..869af9f1 --- /dev/null +++ b/game/lib/achievements/achievements_page.dart @@ -0,0 +1,207 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:game/achievements/achievement_cell.dart'; +import 'package:game/api/game_api.dart'; +import 'package:game/api/game_client_dto.dart'; +import 'package:game/model/challenge_model.dart'; +import 'package:game/model/event_model.dart'; +import 'package:game/model/group_model.dart'; +import 'package:game/utils/utility_functions.dart'; +import 'package:game/model/tracker_model.dart'; +import 'package:game/model/achievement_model.dart'; +import 'package:game/model/user_model.dart'; +import 'package:provider/provider.dart'; + +class AchievementCellDto { + AchievementCellDto({ + required this.location, + required this.name, + required this.lat, + required this.long, + required this.thumbnail, + required this.complete, + required this.description, + required this.difficulty, + required this.points, + required this.eventId, + }); + late String location; + late String name; + late double? lat; + late double? long; + late SvgPicture thumbnail; + late bool complete; + late String description; + late String difficulty; + late int points; + late String eventId; +} + +class AchievementsPage extends StatefulWidget { + const AchievementsPage({super.key}); + + @override + State createState() => _AchievementsPageState(); +} + +class _AchievementsPageState extends State { + List selectedCategories = []; + List selectedLocations = []; + String selectedDifficulty = ''; + + List eventData = []; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Color(0xFFED5656), + ), + child: SafeArea( + bottom: false, + child: Scaffold( + appBar: AppBar( + title: const Text('Achievements'), + ), + body: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + color: Color.fromARGB(255, 255, 248, 241), + ), + child: Padding( + padding: EdgeInsets.all(30), + child: Column( + children: [ + Expanded(child: Consumer5( + builder: (context, + myEventModel, + groupModel, + trackerModel, + challengeModel, + apiClient, + child) { + if (myEventModel.searchResults == null) { + myEventModel.searchEvents( + 0, + 1000, + [ + EventTimeLimitationDto.PERPETUAL, + EventTimeLimitationDto.LIMITED_TIME + ], + false, + false, + false); + } + final events = myEventModel.searchResults ?? []; + if (!events.any((element) => + element.id == groupModel.curEventId)) { + final curEvent = myEventModel + .getEventById(groupModel.curEventId ?? ""); + if (curEvent != null) events.add(curEvent); + } + eventData.clear(); + + for (EventDto event in events) { + var tracker = + trackerModel.trackerByEventId(event.id); + var numberCompleted = + tracker?.prevChallenges.length ?? 0; + var complete = + (numberCompleted == event.challenges?.length); + var locationCount = event.challenges?.length ?? 0; + DateTime now = DateTime.now(); + DateTime endtime = + HttpDate.parse(event.endTime ?? ""); + + Duration timeTillExpire = endtime.difference(now); + if (locationCount != 1) continue; + var challenge = challengeModel + .getChallengeById(event.challenges?[0] ?? ""); + + // print("Doing Event with now/endtime " + event.description.toString() + now.toString() + "/" + endtime.toString()); + if (challenge == null) { + // print("Challenge is null for event " + event.description.toString()); + + continue; + } + final challengeLocation = + challenge.location?.name ?? ""; + + bool eventMatchesDifficultySelection; + bool eventMatchesCategorySelection; + bool eventMatchesLocationSelection; + + if (selectedDifficulty.length == 0 || + selectedDifficulty == event.difficulty?.name) + eventMatchesDifficultySelection = true; + else + eventMatchesDifficultySelection = false; + + if (selectedLocations.length > 0) { + if (selectedLocations.contains(challengeLocation)) + eventMatchesLocationSelection = true; + else + eventMatchesLocationSelection = false; + } else + eventMatchesLocationSelection = true; + + if (selectedCategories.length > 0) { + if (selectedCategories + .contains(event.category?.name)) + eventMatchesCategorySelection = true; + else + eventMatchesCategorySelection = false; + } else + eventMatchesCategorySelection = true; + if (!complete && + !timeTillExpire.isNegative && + eventMatchesDifficultySelection && + eventMatchesCategorySelection && + eventMatchesLocationSelection) { + eventData.add(AchievementCellDto( + location: + friendlyLocation[challenge.location] ?? "", + name: event.name ?? "", + lat: challenge.latF ?? null, + long: challenge.longF ?? null, + thumbnail: SvgPicture.asset( + "assets/icons/achievementsilver.svg"), + complete: complete, + description: event.description ?? "", + difficulty: + friendlyDifficulty[event.difficulty] ?? "", + points: challenge.points ?? 0, + eventId: event.id, + )); + } else if (event.id == groupModel.curEventId) { + apiClient.serverApi?.setCurrentEvent( + SetCurrentEventDto(eventId: "")); + } + } + + return ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 3), + itemCount: eventData.length, + itemBuilder: (context, index) { + return AchievementCell( + key: UniqueKey(), + eventData[index].description, + eventData[index].thumbnail, + 3, + 4); + }, + physics: BouncingScrollPhysics(), + separatorBuilder: (context, index) { + return SizedBox(height: 10); + }, + ); + })) + ], + ), + )), + ))); + } +} diff --git a/game/lib/api/game_api.dart b/game/lib/api/game_api.dart index 3e5ddfba..af5b539f 100644 --- a/game/lib/api/game_api.dart +++ b/game/lib/api/game_api.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:game/api/game_client_api.dart'; +import 'package:game/api/game_client_dto.dart'; import 'package:game/api/game_server_api.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -134,7 +135,7 @@ class ApiClient extends ChangeNotifier { Future connect( String idToken, Uri url, - String enrollmentType, + LoginEnrollmentTypeDto enrollmentType, String year, String username, String college, @@ -142,22 +143,22 @@ class ApiClient extends ChangeNotifier { List interests) async { final pos = await GeoPoint.current(); if (true) { + final loginDto = LoginDto( + idToken: idToken, + latF: pos?.lat ?? 0, + longF: pos?.long ?? 0, + enrollmentType: enrollmentType, + username: username, + college: college, + major: major, + interests: interests.join(","), + aud: Platform.isIOS ? LoginAudDto.ios : LoginAudDto.android); + final loginResponse = await http.post(url, headers: { 'Content-Type': 'application/json; charset=UTF-8', }, - body: jsonEncode({ - "idToken": idToken, - "lat": pos?.lat.toString() ?? "0", - "enrollmentType": enrollmentType, - "year": year, - "username": username, - "college": college, - "major": major, - "interests": interests.join(","), - "long": pos?.long.toString() ?? "0", - "aud": Platform.isIOS ? "ios" : "android" - })); + body: jsonEncode(loginDto.toJson())); if (loginResponse.statusCode == 201 && loginResponse.body != "") { final responseBody = jsonDecode(loginResponse.body); diff --git a/game/lib/api/game_client_dto.dart b/game/lib/api/game_client_dto.dart index 5ac30a62..b1e6521c 100644 --- a/game/lib/api/game_client_dto.dart +++ b/game/lib/api/game_client_dto.dart @@ -258,8 +258,8 @@ class LoginDto { Map toJson() { Map fields = {}; fields['idToken'] = idToken; - fields['lat'] = lat; - fields['long'] = long; + fields['latF'] = latF; + fields['longF'] = longF; if (username != null) { fields['username'] = username; } @@ -284,8 +284,8 @@ class LoginDto { LoginDto.fromJson(Map fields) { idToken = fields["idToken"]; - lat = fields["lat"]; - long = fields["long"]; + latF = fields["latF"]!.toDouble(); + longF = fields["longF"]!.toDouble(); username = fields.containsKey('username') ? (fields["username"]) : null; year = fields.containsKey('year') ? (fields["year"]) : null; college = fields.containsKey('college') ? (fields["college"]) : null; @@ -300,8 +300,8 @@ class LoginDto { void partialUpdate(LoginDto other) { idToken = other.idToken; - lat = other.lat; - long = other.long; + latF = other.latF; + longF = other.longF; username = other.username == null ? username : other.username; year = other.year == null ? year : other.year; college = other.college == null ? college : other.college; @@ -313,8 +313,8 @@ class LoginDto { LoginDto({ required this.idToken, - required this.lat, - required this.long, + required this.latF, + required this.longF, this.username, this.year, this.college, @@ -325,8 +325,8 @@ class LoginDto { }); late String idToken; - late int lat; - late int long; + late double latF; + late double longF; late String? username; late String? year; late String? college; diff --git a/game/lib/api/geopoint.dart b/game/lib/api/geopoint.dart index bb0b90ad..a17b6ae2 100644 --- a/game/lib/api/geopoint.dart +++ b/game/lib/api/geopoint.dart @@ -12,9 +12,6 @@ class GeoPoint { double get long => _long; double get heading => _heading; - static bool _isRequestingLocationPermissions = false; - static bool _isRequestingLocation = false; - GeoPoint( double lat, double long, @@ -25,38 +22,36 @@ class GeoPoint { _heading = heading; } - static Future current() async { + static Future current() async { var serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { + print("Failed to enable location!!!!!!!!"); // Location services are not enabled don't continue // accessing the position and request users of the // App to enable the location services. return Future.error('Location services are disabled.'); } - if (_isRequestingLocationPermissions || _isRequestingLocation) { - //To handle the case where a request is already occuring. - - return null; - } - try { - _isRequestingLocationPermissions = true; var permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { + print("permissions denied"); permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { + print("permissions denied again"); return Future.error('Location services are disabled.'); } } if (permission == LocationPermission.deniedForever) { // Permissions are denied forever, handle appropriately. + print("permissions denied"); return Future.error( 'Location permissions are permanently denied, we cannot request permissions.'); } final pos = await Geolocator.getCurrentPosition(); return GeoPoint(pos.latitude, pos.longitude, pos.heading); } catch (e) { + print(e); return Future.error(e.toString()); } } diff --git a/game/lib/challenges/challenge_cell.dart b/game/lib/challenges/challenge_cell.dart index 82b39e98..6fbe82ca 100644 --- a/game/lib/challenges/challenge_cell.dart +++ b/game/lib/challenges/challenge_cell.dart @@ -76,6 +76,9 @@ class _ChallengeCellState extends State { return GestureDetector( onTap: () async { await showModalBottomSheet( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(10.0)), + ), context: context, isScrollControlled: true, builder: (context) => Preview( diff --git a/game/lib/challenges/challenges_page.dart b/game/lib/challenges/challenges_page.dart index f3be1d39..4bb77c77 100644 --- a/game/lib/challenges/challenges_page.dart +++ b/game/lib/challenges/challenges_page.dart @@ -10,7 +10,6 @@ import 'package:game/model/tracker_model.dart'; import 'package:game/model/user_model.dart'; import 'package:game/utils/utility_functions.dart'; import 'package:provider/provider.dart'; -import 'package:velocity_x/velocity_x.dart'; import 'challenge_cell.dart'; import 'package:game/journeys/filter_form.dart'; diff --git a/game/lib/details_page/details_page.dart b/game/lib/details_page/details_page.dart index e0234d26..049672d2 100644 --- a/game/lib/details_page/details_page.dart +++ b/game/lib/details_page/details_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:game/api/game_client_dto.dart'; import 'package:game/journeys/journeys_page.dart'; import 'package:game/main.dart'; import 'package:game/navigation_page/bottom_navbar.dart'; @@ -15,12 +16,12 @@ import 'package:velocity_x/velocity_x.dart'; class DetailsPageWidget extends StatefulWidget { DetailsPageWidget( {Key? key, - required String this.userType, + required LoginEnrollmentTypeDto this.userType, required String? this.idToken, required GoogleSignInAccount? this.user}) : super(key: key); final scaffoldKey = GlobalKey(); - final String userType; + final LoginEnrollmentTypeDto userType; final String? idToken; final GoogleSignInAccount? user; diff --git a/game/lib/gameplay/challenge_completed.dart b/game/lib/gameplay/challenge_completed.dart index 4e58cda3..d0b64783 100644 --- a/game/lib/gameplay/challenge_completed.dart +++ b/game/lib/gameplay/challenge_completed.dart @@ -152,8 +152,6 @@ class _ChallengeCompletedState extends State { var total_pts = 0; List completedChallenges = []; for (PrevChallengeDto prevChal in (tracker.prevChallenges ?? [])) { - print(prevChal.dateCompleted.toString()); - print(prevChal.hintsUsed); var completedChal = challengeModel.getChallengeById(prevChal.challengeId); if (completedChal == null) continue; diff --git a/game/lib/gameplay/gameplay_page.dart b/game/lib/gameplay/gameplay_page.dart index 7451c659..d039cb18 100644 --- a/game/lib/gameplay/gameplay_page.dart +++ b/game/lib/gameplay/gameplay_page.dart @@ -43,6 +43,17 @@ class _GameplayPageState extends State { late StreamSubscription positionStream; + final Map friendlyLocation = { + "ENG_QUAD": "Eng Quad", + "ARTS_QUAD": "Arts Quad", + "AG_QUAD": "Ag Quad", + "NORTH_CAMPUS": "North Campus", + "WEST_CAMPUS": "West Campus", + "COLLEGETOWN": "Collegetown", + "ITHACA_COMMONS": "Ithaca Commons", + "ANY": "Cornell", + }; + @override void initState() { startPositionStream(); @@ -161,13 +172,7 @@ class _GameplayPageState extends State { vertical: 4.0, horizontal: 8.0), child: Text( (event.challenges!.length > 1 - ? "Journey " + - (tracker.prevChallenges.length + - 1) - .toString() + - "/" + - event.challenges!.length - .toString() + ? "Journey" : "Challenge"), style: TextStyle( fontSize: 14, @@ -222,13 +227,6 @@ class _GameplayPageState extends State { "assets/icons/bearcoins.svg"), Text( ' ' + - ((tracker.hintsUsed > 0) - ? ((challenge.points ?? 0) - - tracker.hintsUsed * - 25) - .toString() + - '/' - : '') + (challenge.points ?? 0).toString() + " PTS", style: TextStyle( diff --git a/game/lib/interests/interests_page.dart b/game/lib/interests/interests_page.dart index d4343778..333103b8 100644 --- a/game/lib/interests/interests_page.dart +++ b/game/lib/interests/interests_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:game/api/game_client_dto.dart'; import 'package:game/journeys/journeys_page.dart'; import 'package:game/main.dart'; import 'package:game/navigation_page/bottom_navbar.dart'; @@ -11,7 +12,7 @@ import 'package:dropdown_button2/dropdown_button2.dart'; class InterestsPageWidget extends StatefulWidget { InterestsPageWidget( {Key? key, - required String this.userType, + required LoginEnrollmentTypeDto this.userType, required String? this.idToken, required GoogleSignInAccount? this.user, required String this.username, @@ -20,7 +21,7 @@ class InterestsPageWidget extends StatefulWidget { required String? this.year}) : super(key: key); final scaffoldKey = GlobalKey(); - final String userType; + final LoginEnrollmentTypeDto userType; final String? idToken; final GoogleSignInAccount? user; final String username; diff --git a/game/lib/main.dart b/game/lib/main.dart index 667e62d5..e3715026 100644 --- a/game/lib/main.dart +++ b/game/lib/main.dart @@ -113,7 +113,9 @@ class MyApp extends StatelessWidget { ], supportedLocales: const [Locale('en', '')], theme: ThemeData( - fontFamily: 'Poppins', primarySwatch: ColorPalette.BigRed), + useMaterial3: false, + fontFamily: 'Poppins', + primarySwatch: ColorPalette.BigRed), home: LoadingPageWidget()))); } } diff --git a/game/lib/model/achievement_model.dart b/game/lib/model/achievement_model.dart new file mode 100644 index 00000000..c7c7bbb8 --- /dev/null +++ b/game/lib/model/achievement_model.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:game/api/game_api.dart'; +import 'package:game/api/game_client_dto.dart'; + +/** + * This file represents the model for the achievements. Whenever a achievement is updated, added or deleted from the backend, the model is updated and notifies the Consumer so that the front end can be modified. + */ +class AchievementModel extends ChangeNotifier { + Map _achievementsById = {}; + ApiClient _client; + + AchievementModel(ApiClient client) : _client = client { + /** + * Stream that listens to updates on the achievements. + */ + client.clientApi.updateAchievementDataStream.listen((event) { + if (event.deleted) { + _achievementsById.remove(event.achievement.id); + } else { + _achievementsById[event.achievement.id] = event.achievement; + } + notifyListeners(); + }); + + client.clientApi.connectedStream.listen((event) { + _achievementsById.clear(); + notifyListeners(); + }); + } +} diff --git a/game/lib/preview/preview.dart b/game/lib/preview/preview.dart index f827f689..994f975a 100644 --- a/game/lib/preview/preview.dart +++ b/game/lib/preview/preview.dart @@ -171,16 +171,15 @@ class _PreviewState extends State { height: MediaQuery.of(context).size.height * 0.75, child: ClipRRect( borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.0), - topRight: Radius.circular(20.0), + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), ), - //Overall Container child: Container( height: MediaQuery.of(context).size.height * 0.75, width: MediaQuery.of(context).size.width, color: Colors.white, - child: Text("Cannot find challenge lat and long")), + child: Text("Error: Cannot find challenge lat and long")), ), ); } @@ -427,7 +426,6 @@ class _PreviewState extends State { .serverApi ?.setCurrentEvent( SetCurrentEventDto(eventId: eventId)); - print("setting current event to " + eventId); Navigator.pop(context); Navigator.push( context, diff --git a/game/lib/profile/achievement_cell.dart b/game/lib/profile/achievement_cell.dart index 8dd070f3..a318df20 100644 --- a/game/lib/profile/achievement_cell.dart +++ b/game/lib/profile/achievement_cell.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +@Deprecated('achievements/achievement_cell.dart') /** * Widget that represents each individual achievement * @param name: Name of the achievement @@ -92,10 +93,6 @@ Widget achievementCell( ), ), ]), - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: SvgPicture.asset("assets/icons/location.svg"), - ), Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( diff --git a/game/lib/profile/completed_cell.dart b/game/lib/profile/completed_cell.dart index ca3723d2..194ec6ed 100644 --- a/game/lib/profile/completed_cell.dart +++ b/game/lib/profile/completed_cell.dart @@ -32,8 +32,8 @@ Widget completedCell(String name, String picture, String type, String date, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: Image( - width: 80, - height: 80, + width: 64, + height: 64, image: NetworkImage(picture), fit: BoxFit.cover, )), @@ -64,73 +64,20 @@ Widget completedCell(String name, String picture, String type, String date, ], ), Padding( - padding: const EdgeInsets.only(top: 8.0), + padding: const EdgeInsets.only(top: 5.0), child: Container( width: 200, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 80, - height: 25, - decoration: BoxDecoration( - border: Border.all( - color: Colors.purple, // Set the outline color - width: 2.0, // Set the outline width - ), - borderRadius: BorderRadius.circular( - 15.0), // Set border radius - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/icons/flag.svg', - ), - ], - ), - ), - Container( - width: 50, - height: 25, - decoration: BoxDecoration( - color: Color.fromRGBO(249, 236, 217, 1), - borderRadius: BorderRadius.circular( - 20.0), // Set border radius - ), - child: Center( - child: Text( - difficulty, - style: TextStyle( - fontSize: 10, - ), - ), - ), - ), - Container( - width: 50, - height: 25, - decoration: BoxDecoration( - border: Border.all( - color: Color.fromRGBO( - 189, 135, 31, 1), // Set the outline color - width: 1.38, // Set the outline width - ), - color: Color.fromRGBO(255, 199, 55, 1), - borderRadius: BorderRadius.circular( - 15.0), // Set border radius - ), - child: Center( - child: Text( - points.toString() + " PTS", - style: TextStyle( - fontSize: 10, - ), - ), - ), - ) - ], - ), + child: Row(children: [ + SvgPicture.asset( + "assets/icons/bearcoins.svg", + width: 20, + ), + Text(' ' + points.toString() + " PTS", + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Color(0xFFC17E19))) + ]), ), ), ], diff --git a/game/lib/profile/profile_page.dart b/game/lib/profile/profile_page.dart index 9aa12470..13cb552c 100644 --- a/game/lib/profile/profile_page.dart +++ b/game/lib/profile/profile_page.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:game/achievements/achievements_page.dart'; import 'package:game/api/game_client_dto.dart'; import 'package:game/model/challenge_model.dart'; import 'package:game/model/event_model.dart'; import 'package:game/model/tracker_model.dart'; import 'package:game/model/user_model.dart'; -import 'package:game/profile/achievement_cell.dart'; +import 'package:game/achievements/achievement_cell.dart'; import 'package:game/profile/completed_cell.dart'; import 'package:game/profile/settings_page.dart'; import 'package:game/utils/utility_functions.dart'; @@ -143,7 +144,8 @@ class _ProfilePageState extends State { TextButton( onPressed: () { // Handle button press, e.g., navigate to details page - print('View Details button pressed'); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => AchievementsPage())); }, child: Text( 'View Details →', @@ -157,13 +159,21 @@ class _ProfilePageState extends State { ), ), //To be replaced with real data - achievementCell("Complete three challenges on the Arts quad", 4, - 6, "assets/images/38582.jpg"), - achievementCell( - "Complete three challenges on the Engineering quad", - 4, - 6, - "assets/images/38582.jpg"), + Padding( + padding: EdgeInsets.only(left: 30, right: 30), + child: Column(children: [ + AchievementCell( + "Complete three challenges", + SvgPicture.asset("assets/icons/achievementsilver.svg"), + 4, + 6), + SizedBox(height: 10), + AchievementCell( + "Complete three challenges", + SvgPicture.asset("assets/icons/achievementsilver.svg"), + 4, + 6), + ])), //Completed Events Padding( padding: const EdgeInsets.only(left: 24, right: 24.0), diff --git a/game/lib/register_page/register_page.dart b/game/lib/register_page/register_page.dart index 15199ae5..1c0cdbf4 100644 --- a/game/lib/register_page/register_page.dart +++ b/game/lib/register_page/register_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game/api/game_client_dto.dart'; import 'package:game/details_page/details_page.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -23,18 +24,11 @@ class _RegisterPageWidgetState extends State { super.initState(); } - final List identityOptions = [ - "Undergraduate Student", - "Graduate Student", - "Faculty/Staff", - "Alumni" - ]; - - Map map1 = { - "Undergraduate Student": "UNDERGRADUATE", - "Graduate Student": "GRADUATE", - "Faculty/Staff": "FACULTY", - "Alumni": "ALUMNI" + Map entryToEnrollmentType = { + "Undergraduate Student": LoginEnrollmentTypeDto.UNDERGRADUATE, + "Graduate Student": LoginEnrollmentTypeDto.GRADUATE, + "Faculty/Staff": LoginEnrollmentTypeDto.FACULTY, + "Alumni": LoginEnrollmentTypeDto.ALUMNI }; @override @@ -62,7 +56,7 @@ class _RegisterPageWidgetState extends State { )), SizedBox(height: 20.0), Column( - children: identityOptions.map((entry) { + children: entryToEnrollmentType.keys.map((name) { return Padding( padding: const EdgeInsets.only(top: 20.0), child: TextButton( @@ -71,17 +65,17 @@ class _RegisterPageWidgetState extends State { RoundedRectangleBorder( side: BorderSide( width: 2.0, - color: entry == _selectedOption + color: name == _selectedOption ? Color.fromARGB(255, 255, 170, 91) : Color.fromARGB(255, 217, 217, 217)), borderRadius: BorderRadius.circular(10.0))), - backgroundColor: entry == _selectedOption + backgroundColor: name == _selectedOption ? MaterialStatePropertyAll( Color.fromARGB(102, 255, 170, 91)) : MaterialStatePropertyAll(Colors.white)), onPressed: () => { setState(() { - _selectedOption = entry; + _selectedOption = name; }) }, child: Container( @@ -89,7 +83,7 @@ class _RegisterPageWidgetState extends State { height: 50, child: Align( alignment: Alignment.center, - child: Text(entry, + child: Text(name, style: TextStyle( color: Colors.black, fontSize: 16, @@ -125,7 +119,7 @@ class _RegisterPageWidgetState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (context) => DetailsPageWidget( - userType: map1[_selectedOption]!, + userType: entryToEnrollmentType[_selectedOption]!, user: widget.user, idToken: widget.idToken, ), diff --git a/game/lib/splash_page/splash_page.dart b/game/lib/splash_page/splash_page.dart index ad739337..9aab9ae9 100644 --- a/game/lib/splash_page/splash_page.dart +++ b/game/lib/splash_page/splash_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game/api/game_client_dto.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:game/main.dart'; import 'package:provider/provider.dart'; @@ -112,7 +113,7 @@ class SplashPageWidget extends StatelessWidget { final connectionResult = await client.connect( id!, Uri.parse(endpoint_string), - "GUEST", + LoginEnrollmentTypeDto.GUEST, "", "", "", diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index a1a1c1e4..266421f1 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -134,8 +134,8 @@ export class AuthService { req.college ?? '', req.major ?? '', req.interests?.split(',') ?? [], - req.lat, - req.long, + req.latF, + req.longF, authType, idToken.id, req.enrollmentType, diff --git a/server/src/auth/login.dto.ts b/server/src/auth/login.dto.ts index 01e6dabf..8d96d8f6 100644 --- a/server/src/auth/login.dto.ts +++ b/server/src/auth/login.dto.ts @@ -4,8 +4,8 @@ */ export interface LoginDto { idToken: string; - lat: number; - long: number; + latF: number; + longF: number; username?: string; year?: string; college?: string;