From 4d3bb144444e99e69685f4261c8e89b88c39d6f4 Mon Sep 17 00:00:00 2001 From: Kevin De Silva Jayasinghe Date: Wed, 10 Nov 2021 11:40:21 -0800 Subject: [PATCH 01/24] initial commit with some version updates, added libraries, and new files for infrastructure development --- lib/app_networking.dart | 158 +++++++++ lib/core/models/schedule_of_classes.dart | 376 +++++++++++++++++++++ lib/core/services/schedule_of_classes.dart | 80 +++++ pubspec.yaml | 4 +- 4 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 lib/app_networking.dart create mode 100644 lib/core/models/schedule_of_classes.dart create mode 100644 lib/core/services/schedule_of_classes.dart diff --git a/lib/app_networking.dart b/lib/app_networking.dart new file mode 100644 index 0000000..d166fd4 --- /dev/null +++ b/lib/app_networking.dart @@ -0,0 +1,158 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:webreg_mobile_flutter/app_constants.dart'; +import 'package:webreg_mobile_flutter/app_styles.dart'; + +class NetworkHelper { + ///TODO: inside each service that file place a switch statement to handle all + ///TODO: different errors thrown by the Dio client DioErrorType.RESPONSE + const NetworkHelper(); + + static const int SSO_REFRESH_MAX_RETRIES = 3; + static const int SSO_REFRESH_RETRY_INCREMENT = 5000; + static const int SSO_REFRESH_RETRY_MULTIPLIER = 3; + + Future fetchData(String url) async { + Dio dio = new Dio(); + dio.options.connectTimeout = 20000; + dio.options.receiveTimeout = 20000; + dio.options.responseType = ResponseType.plain; + final _response = await dio.get(url); + + if (_response.statusCode == 200) { + // If server returns an OK response, return the body + return _response.data; + } else { + ///TODO: log this as a bug because the response was bad + // If that response was not OK, throw an error. + throw Exception('Failed to fetch data: ' + _response.data); + } + } + + Future authorizedFetch( + String url, Map headers) async { + Dio dio = new Dio(); + dio.options.connectTimeout = 20000; + dio.options.receiveTimeout = 20000; + dio.options.responseType = ResponseType.plain; + dio.options.headers = headers; + final _response = await dio.get( + url, + ); + if (_response.statusCode == 200) { + // If server returns an OK response, return the body + return _response.data; + } else { + ///TODO: log this as a bug because the response was bad + // If that response was not OK, throw an error. + + throw Exception('Failed to fetch data: ' + _response.data); + } + } + + // Widget getSilentLoginDialog() { + // return AlertDialog( + // title: const Text(LoginConstants.silentLoginFailedTitle), + // content: Text(LoginConstants.silentLoginFailedDesc), + // actions: [ + // TextButton( + // style: TextButton.styleFrom( + // primary: ucLabelColor, + // ), + // onPressed: () { + // Get.back(closeOverlays: true); + // }, + // child: const Text('OK'), + // ), + // ], + // ); + // } + + Future authorizedPost( + String url, Map? headers, dynamic body) async { + Dio dio = new Dio(); + dio.options.connectTimeout = 20000; + dio.options.receiveTimeout = 20000; + dio.options.headers = headers; + final _response = await dio.post(url, data: body); + if (_response.statusCode == 200 || _response.statusCode == 201) { + // If server returns an OK response, return the body + return _response.data; + } else if (_response.statusCode == 400) { + // If that response was not OK, throw an error. + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPostErrors + message); + } else if (_response.statusCode == 401) { + throw Exception(ErrorConstants.authorizedPostErrors + + ErrorConstants.invalidBearerToken); + } else if (_response.statusCode == 404) { + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPostErrors + message); + } else if (_response.statusCode == 500) { + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPostErrors + message); + } else if (_response.statusCode == 409) { + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.duplicateRecord + message); + } else { + throw Exception(ErrorConstants.authorizedPostErrors + 'unknown error'); + } + } + + Future authorizedPut( + String url, Map headers, dynamic body) async { + Dio dio = new Dio(); + dio.options.connectTimeout = 20000; + dio.options.receiveTimeout = 20000; + dio.options.headers = headers; + final _response = await dio.put(url, data: body); + + if (_response.statusCode == 200 || _response.statusCode == 201) { + // If server returns an OK response, return the body + return _response.data; + } else if (_response.statusCode == 400) { + // If that response was not OK, throw an error. + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPutErrors + message); + } else if (_response.statusCode == 401) { + throw Exception(ErrorConstants.authorizedPutErrors + + ErrorConstants.invalidBearerToken); + } else if (_response.statusCode == 404) { + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPutErrors + message); + } else if (_response.statusCode == 500) { + String message = _response.data['message'] ?? ''; + throw Exception(ErrorConstants.authorizedPutErrors + message); + } else { + throw Exception(ErrorConstants.authorizedPutErrors + 'unknown error'); + } + } + + Future authorizedDelete( + String url, Map headers) async { + Dio dio = new Dio(); + dio.options.connectTimeout = 20000; + dio.options.receiveTimeout = 20000; + dio.options.headers = headers; + try { + final _response = await dio.delete(url); + if (_response.statusCode == 200) { + // If server returns an OK response, return the body + return _response.data; + } else { + ///TODO: log this as a bug because the response was bad + // If that response was not OK, throw an error. + throw Exception('Failed to delete data: ' + _response.data); + } + } on TimeoutException catch (e) { + // Display an alert - i.e. no internet + print(e); + } catch (err) { + print(err); + return null; + } + } +} diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart new file mode 100644 index 0000000..0d2eab7 --- /dev/null +++ b/lib/core/models/schedule_of_classes.dart @@ -0,0 +1,376 @@ +// To parse this JSON data, do +// +// final scheduleOfClassesModel = scheduleOfClassesModelFromJson(jsonString); + +import 'dart:convert'; + +ScheduleOfClassesModel scheduleOfClassesModelFromJson(String str) => + ScheduleOfClassesModel.fromJson(json.decode(str)); + +String scheduleOfClassesModelToJson(ScheduleOfClassesModel data) => + json.encode(data.toJson()); + +class ScheduleOfClassesModel { + ScheduleOfClassesModel({ + this.metadata, + this.data, + }); + + Metadata metadata; + List data; + + factory ScheduleOfClassesModel.fromJson(Map json) => + ScheduleOfClassesModel( + metadata: json["metadata"] == null + ? null + : Metadata.fromJson(json["metadata"]), + data: json["data"] == null + ? null + : List.from(json["data"].map((x) => Datum.fromJson(x))), + ); + + Map toJson() => { + "metadata": metadata == null ? null : metadata.toJson(), + "data": data == null + ? null + : List.from(data.map((x) => x.toJson())), + }; +} + +class Datum { + Datum({ + this.subjectCode, + this.courseCode, + this.departmentCode, + this.courseTitle, + this.unitsMin, + this.unitsMax, + this.unitsInc, + this.academicLevel, + this.sections, + }); + + String subjectCode; + String courseCode; + String departmentCode; + String courseTitle; + int unitsMin; + int unitsMax; + int unitsInc; + String academicLevel; + List
sections; + + factory Datum.fromJson(Map json) => Datum( + subjectCode: json["subjectCode"] == null ? null : json["subjectCode"], + courseCode: json["courseCode"] == null ? null : json["courseCode"], + departmentCode: + json["departmentCode"] == null ? null : json["departmentCode"], + courseTitle: json["courseTitle"] == null ? null : json["courseTitle"], + unitsMin: json["unitsMin"] == null ? null : json["unitsMin"], + unitsMax: json["unitsMax"] == null ? null : json["unitsMax"], + unitsInc: json["unitsInc"] == null ? null : json["unitsInc"], + academicLevel: + json["academicLevel"] == null ? null : json["academicLevel"], + sections: json["sections"] == null + ? null + : List
.from( + json["sections"].map((x) => Section.fromJson(x))), + ); + + Map toJson() => { + "subjectCode": subjectCode == null ? null : subjectCode, + "courseCode": courseCode == null ? null : courseCode, + "departmentCode": departmentCode == null ? null : departmentCode, + "courseTitle": courseTitle == null ? null : courseTitle, + "unitsMin": unitsMin == null ? null : unitsMin, + "unitsMax": unitsMax == null ? null : unitsMax, + "unitsInc": unitsInc == null ? null : unitsInc, + "academicLevel": academicLevel == null ? null : academicLevel, + "sections": sections == null + ? null + : List.from(sections.map((x) => x.toJson())), + }; +} + +class Section { + Section({ + this.sectionId, + this.termCode, + this.sectionCode, + this.instructionType, + this.sectionStatus, + this.subtitle, + this.startDate, + this.endDate, + this.enrolledQuantity, + this.capacityQuantity, + this.stopEnrollmentFlag, + this.printFlag, + this.subterm, + this.planCode, + this.recurringMeetings, + this.additionalMeetings, + this.instructors, + }); + + String sectionId; + TermCode termCode; + String sectionCode; + InstructionType instructionType; + SectionStatus sectionStatus; + String subtitle; + DateTime startDate; + DateTime endDate; + int enrolledQuantity; + int capacityQuantity; + bool stopEnrollmentFlag; + String printFlag; + String subterm; + InstructionType planCode; + List recurringMeetings; + List additionalMeetings; + List instructors; + + factory Section.fromJson(Map json) => Section( + sectionId: json["sectionId"] == null ? null : json["sectionId"], + termCode: json["termCode"] == null + ? null + : termCodeValues.map[json["termCode"]], + sectionCode: json["sectionCode"] == null ? null : json["sectionCode"], + instructionType: json["instructionType"] == null + ? null + : instructionTypeValues.map[json["instructionType"]], + sectionStatus: json["sectionStatus"] == null + ? null + : sectionStatusValues.map[json["sectionStatus"]], + subtitle: json["subtitle"] == null ? null : json["subtitle"], + startDate: json["startDate"] == null + ? null + : DateTime.parse(json["startDate"]), + endDate: + json["endDate"] == null ? null : DateTime.parse(json["endDate"]), + enrolledQuantity: + json["enrolledQuantity"] == null ? null : json["enrolledQuantity"], + capacityQuantity: + json["capacityQuantity"] == null ? null : json["capacityQuantity"], + stopEnrollmentFlag: json["stopEnrollmentFlag"] == null + ? null + : json["stopEnrollmentFlag"], + printFlag: json["printFlag"] == null ? null : json["printFlag"], + subterm: json["subterm"] == null ? null : json["subterm"], + planCode: json["planCode"] == null + ? null + : instructionTypeValues.map[json["planCode"]], + recurringMeetings: json["recurringMeetings"] == null + ? null + : List.from(json["recurringMeetings"] + .map((x) => RecurringMeeting.fromJson(x))), + additionalMeetings: json["additionalMeetings"] == null + ? null + : List.from(json["additionalMeetings"].map((x) => x)), + instructors: json["instructors"] == null + ? null + : List.from( + json["instructors"].map((x) => Instructor.fromJson(x))), + ); + + Map toJson() => { + "sectionId": sectionId == null ? null : sectionId, + "termCode": termCode == null ? null : termCodeValues.reverse[termCode], + "sectionCode": sectionCode == null ? null : sectionCode, + "instructionType": instructionType == null + ? null + : instructionTypeValues.reverse[instructionType], + "sectionStatus": sectionStatus == null + ? null + : sectionStatusValues.reverse[sectionStatus], + "subtitle": subtitle == null ? null : subtitle, + "startDate": startDate == null + ? null + : "${startDate.year.toString().padLeft(4, '0')}-${startDate.month.toString().padLeft(2, '0')}-${startDate.day.toString().padLeft(2, '0')}", + "endDate": endDate == null + ? null + : "${endDate.year.toString().padLeft(4, '0')}-${endDate.month.toString().padLeft(2, '0')}-${endDate.day.toString().padLeft(2, '0')}", + "enrolledQuantity": enrolledQuantity == null ? null : enrolledQuantity, + "capacityQuantity": capacityQuantity == null ? null : capacityQuantity, + "stopEnrollmentFlag": + stopEnrollmentFlag == null ? null : stopEnrollmentFlag, + "printFlag": printFlag == null ? null : printFlag, + "subterm": subterm == null ? null : subterm, + "planCode": + planCode == null ? null : instructionTypeValues.reverse[planCode], + "recurringMeetings": recurringMeetings == null + ? null + : List.from(recurringMeetings.map((x) => x.toJson())), + "additionalMeetings": additionalMeetings == null + ? null + : List.from(additionalMeetings.map((x) => x)), + "instructors": instructors == null + ? null + : List.from(instructors.map((x) => x.toJson())), + }; +} + +enum InstructionType { LE, ST, SE } + +final instructionTypeValues = EnumValues({ + "LE": InstructionType.LE, + "SE": InstructionType.SE, + "ST": InstructionType.ST +}); + +class Instructor { + Instructor({ + this.pid, + this.instructorName, + this.primaryInstructor, + this.instructorEmailAddress, + this.workLoadUnitQty, + this.percentOfLoad, + }); + + Pid pid; + InstructorName instructorName; + bool primaryInstructor; + InstructorEmailAddress instructorEmailAddress; + int workLoadUnitQty; + int percentOfLoad; + + factory Instructor.fromJson(Map json) => Instructor( + pid: json["pid"] == null ? null : pidValues.map[json["pid"]], + instructorName: json["instructorName"] == null + ? null + : instructorNameValues.map[json["instructorName"]], + primaryInstructor: json["primaryInstructor"] == null + ? null + : json["primaryInstructor"], + instructorEmailAddress: json["instructorEmailAddress"] == null + ? null + : instructorEmailAddressValues.map[json["instructorEmailAddress"]], + workLoadUnitQty: + json["workLoadUnitQty"] == null ? null : json["workLoadUnitQty"], + percentOfLoad: + json["percentOfLoad"] == null ? null : json["percentOfLoad"], + ); + + Map toJson() => { + "pid": pid == null ? null : pidValues.reverse[pid], + "instructorName": instructorName == null + ? null + : instructorNameValues.reverse[instructorName], + "primaryInstructor": + primaryInstructor == null ? null : primaryInstructor, + "instructorEmailAddress": instructorEmailAddress == null + ? null + : instructorEmailAddressValues.reverse[instructorEmailAddress], + "workLoadUnitQty": workLoadUnitQty == null ? null : workLoadUnitQty, + "percentOfLoad": percentOfLoad == null ? null : percentOfLoad, + }; +} + +class RecurringMeeting { + RecurringMeeting({ + this.dayCode, + this.dayCodeIsis, + this.startTime, + this.endTime, + this.buildingCode, + this.roomCode, + }); + + DayCode dayCode; + DayCodeIsis dayCodeIsis; + String startTime; + String endTime; + BuildingCode buildingCode; + String roomCode; + + factory RecurringMeeting.fromJson(Map json) => + RecurringMeeting( + dayCode: + json["dayCode"] == null ? null : dayCodeValues.map[json["dayCode"]], + dayCodeIsis: json["dayCodeIsis"] == null + ? null + : dayCodeIsisValues.map[json["dayCodeIsis"]], + startTime: json["startTime"] == null ? null : json["startTime"], + endTime: json["endTime"] == null ? null : json["endTime"], + buildingCode: json["buildingCode"] == null + ? null + : buildingCodeValues.map[json["buildingCode"]], + roomCode: json["roomCode"] == null ? null : json["roomCode"], + ); + + Map toJson() => { + "dayCode": dayCode == null ? null : dayCodeValues.reverse[dayCode], + "dayCodeIsis": + dayCodeIsis == null ? null : dayCodeIsisValues.reverse[dayCodeIsis], + "startTime": startTime == null ? null : startTime, + "endTime": endTime == null ? null : endTime, + "buildingCode": buildingCode == null + ? null + : buildingCodeValues.reverse[buildingCode], + "roomCode": roomCode == null ? null : roomCode, + }; +} + +class Metadata { + Metadata({ + this.links, + this.totalCount, + }); + + List links; + int totalCount; + + factory Metadata.fromJson(Map json) => Metadata( + links: json["links"] == null + ? null + : List.from(json["links"].map((x) => Link.fromJson(x))), + totalCount: json["totalCount"] == null ? null : json["totalCount"], + ); + + Map toJson() => { + "links": links == null + ? null + : List.from(links.map((x) => x.toJson())), + "totalCount": totalCount == null ? null : totalCount, + }; +} + +class Link { + Link({ + this.rel, + this.href, + this.method, + }); + + String rel; + String href; + String method; + + factory Link.fromJson(Map json) => Link( + rel: json["rel"] == null ? null : json["rel"], + href: json["href"] == null ? null : json["href"], + method: json["method"] == null ? null : json["method"], + ); + + Map toJson() => { + "rel": rel == null ? null : rel, + "href": href == null ? null : href, + "method": method == null ? null : method, + }; +} + +class EnumValues { + Map map; + Map reverseMap; + + EnumValues(this.map); + + Map get reverse { + if (reverseMap == null) { + reverseMap = map.map((k, v) => new MapEntry(v, k)); + } + return reverseMap; + } +} diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart new file mode 100644 index 0000000..496a15c --- /dev/null +++ b/lib/core/services/schedule_of_classes.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:webreg_mobile_flutter/app_networking.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; + +class ScheduleOfClassesService { + ScheduleOfClassesService(); + bool _isLoading = false; + DateTime? _lastUpdated; + String? _error; + List? _classes; + + final NetworkHelper _networkHelper = NetworkHelper(); + final Map headers = { + "accept": "application/json", + }; + final String baseEndpoint = + "https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/"; + + Future fetchClasses() async { + _error = null; + _isLoading = true; + try { + /// fetch data + + String _response = await (_networkHelper.authorizedFetch( + baseEndpoint, headers)); //add parameters here + + /// parse data + final data = ScheduleOfClassesModel(_response); + _isLoading = false; + + _locations = data; + return true; + } catch (e) { + /// if the authorized fetch failed we know we have to refresh the + /// token for this service + print("IN CATCH"); + if (e.toString().contains("401")) { + print("Getting new token from fetchLocations"); + if (await getNewToken()) { + print("Getting new token from fetchLocations"); + return await fetchClasses(); + } + } + _error = e.toString(); + print(_error); + _isLoading = false; + return false; + } + } + + Future getNewToken() async { + final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; + final Map tokenHeaders = { + "content-type": 'application/x-www-form-urlencoded', + "Authorization": + "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" + }; + try { + final response = await _networkHelper.authorizedPost( + tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); + + headers["Authorization"] = "Bearer " + response["access_token"]; + + return true; + } catch (e) { + _error = e.toString(); + return false; + } + } + + bool get isLoading => _isLoading; + + String? get error => _error; + + DateTime? get lastUpdated => _lastUpdated; + + List? get classes => _classes; +} diff --git a/pubspec.yaml b/pubspec.yaml index 0411afa..6f8d452 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,12 +3,14 @@ description: A new Flutter project. publish_to: none version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: cupertino_icons: 1.0.0 flutter: sdk: flutter package_info_plus: 1.0.1 + dio: 4.0.0 + get: 4.1.4 dev_dependencies: flutter_test: sdk: flutter From 790e792587a28caf50d04f04cf41412f15da0e57 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Sun, 21 Nov 2021 22:42:36 -0800 Subject: [PATCH 02/24] Further screens and navigations dependent on live data --- lib/app_router.dart | 1 - lib/ui/calendar/calendar.dart | 10 +-- lib/ui/calendar/calendar_card.dart | 140 +++++++++++++++-------------- lib/ui/navigator/bottom.dart | 92 +++++++++---------- 4 files changed, 123 insertions(+), 120 deletions(-) diff --git a/lib/app_router.dart b/lib/app_router.dart index 1c1a1d0..f67f6cd 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -7,7 +7,6 @@ import 'package:flutter/widgets.dart'; class Router { static Route generateRoute(RouteSettings settings) { - print('route' + settings.name); switch (settings.name) { case RoutePaths.Home: return MaterialPageRoute(builder: (_) => BottomNavigation()); diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 48de6c3..14a66ee 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -177,16 +177,16 @@ class Calendar extends StatelessWidget { }), CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', 'CSE 110', - 'Center 109'), + 'Center 109', color), CalendarCard('10:00', '10:50', '2020-06-06T', 2, 'LE', 'CSE 110', - 'Center 109'), + 'Center 109', color), CalendarCard('10:00', '10:50', '2020-06-06T', 4, 'LE', 'CSE 110', - 'Center 109'), + 'Center 109', color), CalendarCard('11:00', '12:20', '2020-06-06T', 1, 'DI', 'CSE 100', - 'WLH 109'), + 'WLH 109', color), CalendarCard('11:00', '12:20', '2020-06-06T', 3, 'DI', 'CSE 100', - 'WLH 109'), + 'WLH 109', color), ])), BuildInfo(), ], diff --git a/lib/ui/calendar/calendar_card.dart b/lib/ui/calendar/calendar_card.dart index 1c77f98..f499c3c 100644 --- a/lib/ui/calendar/calendar_card.dart +++ b/lib/ui/calendar/calendar_card.dart @@ -3,12 +3,13 @@ import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/ui/calendar/bottom_course_card.dart'; - class CalendarCard extends StatefulWidget { final String startTime, endTime, datePrefix, type, title, location; final int dayOfWeek; + final Color color; - const CalendarCard(this.startTime, this.endTime, this.datePrefix, this.dayOfWeek, this.type, this.title, this.location); + const CalendarCard(this.startTime, this.endTime, this.datePrefix, + this.dayOfWeek, this.type, this.title, this.location, this.color); @override _CalendarCardState createState() => _CalendarCardState(); @@ -18,79 +19,86 @@ class _CalendarCardState extends State { static const earliestClass = '08:00'; double getTimeDifference(String start, String end, String prefix) { - double diff = DateTime.parse(prefix + end).difference(DateTime.parse(prefix + start)).inMinutes.toDouble(); + double diff = DateTime.parse(prefix + end) + .difference(DateTime.parse(prefix + start)) + .inMinutes + .toDouble(); print(diff.toString()); return diff; } - + @override Widget build(BuildContext context) { - double calendarCardWidth = (MediaQuery.of(context).size.width - CalendarStyles.calendarTimeWidth - 20) / 7; + double calendarCardWidth = (MediaQuery.of(context).size.width - + CalendarStyles.calendarTimeWidth - + 20) / + 7; bool _showModal = false; return Positioned( - top: getTimeDifference(earliestClass, widget.startTime, widget.datePrefix), - left: CalendarStyles.calendarTimeWidth + widget.dayOfWeek * calendarCardWidth, - child: GestureDetector( - onTap: () { - Scaffold.of(context).showBottomSheet( - (BuildContext context) { - return BottomCourseCard(context); - } - ); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(2.0)), - // border: Border.all(width: 1, color: ) - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.25), - spreadRadius: 0, - blurRadius: 2.5, - offset: Offset(1, 1), - ), - BoxShadow( - color: Colors.black.withOpacity(0.25), - spreadRadius: 0, - blurRadius: 2.5, - offset: Offset(-1, 1), - ), - ], - ), - height: getTimeDifference(widget.startTime, widget.endTime, widget.datePrefix), - width: calendarCardWidth, - child: Column( - children: [ - Container( - height: 12, + top: getTimeDifference( + earliestClass, widget.startTime, widget.datePrefix), + left: CalendarStyles.calendarTimeWidth + + widget.dayOfWeek * calendarCardWidth, + child: GestureDetector( + onTap: () { + Scaffold.of(context) + .showBottomSheet((BuildContext context) { + return BottomCourseCard(context); + }); + }, + child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(2.0), - topLeft: Radius.circular(2.0), - ), - color: lightBlue, + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(2.0)), + // border: Border.all(width: 1, color: ) + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + spreadRadius: 0, + blurRadius: 2.5, + offset: Offset(1, 1), + ), + BoxShadow( + color: Colors.black.withOpacity(0.25), + spreadRadius: 0, + blurRadius: 2.5, + offset: Offset(-1, 1), + ), + ], ), - child: Center( - child: Text(widget.type, style: TextStyle(fontSize: 8, fontWeight: FontWeight.bold)), // TODO, replace with real data - ) - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(widget.title, style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: -0.3)), - Text(widget.location, style: TextStyle(fontSize: 9, letterSpacing: -0.3)), - ] - ) - ) - ] - ) - ) - ) - ); + height: getTimeDifference( + widget.startTime, widget.endTime, widget.datePrefix), + width: calendarCardWidth, + child: Column(children: [ + Container( + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(2.0), + topLeft: Radius.circular(2.0), + ), + color: widget.color, + ), + child: Center( + child: Text(widget.type, + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight + .bold)), // TODO, replace with real data + )), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(widget.title, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + letterSpacing: -0.3)), + Text(widget.location, + style: TextStyle(fontSize: 9, letterSpacing: -0.3)), + ])) + ])))); } } - - diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index 7d73716..9c5eb78 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -10,11 +10,12 @@ class BottomNavigation extends StatefulWidget { _BottomNavigationState createState() => _BottomNavigationState(); } -class _BottomNavigationState extends State with SingleTickerProviderStateMixin { +class _BottomNavigationState extends State + with SingleTickerProviderStateMixin { var currentTab = [ - Calendar(Colors.yellow), + Calendar(Colors.blue.shade200), CourseListView(), - Calendar(Colors.green), + Calendar(Colors.green.shade200), ]; int currentIndex = 0; @@ -29,50 +30,45 @@ class _BottomNavigationState extends State with SingleTickerPr @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text("Webreg", style: TextStyle( - fontWeight: FontWeight.normal, - )), - actions: [ - SearchPlaceholder() - ] - ), - body: currentTab[currentIndex], - bottomNavigationBar: BottomNavigationBar( - type: BottomNavigationBarType.fixed, - currentIndex: currentIndex, - onTap: (index) { - setState(() { currentIndex = index; }); - }, - items: [ - BottomNavigationBarItem( - icon: Text("Calendar", style: textStyles), - activeIcon: Container( - child: Column( - children: [ - Text("Calendar", style: activeStyles), - - ] - ) + appBar: AppBar( + centerTitle: true, + title: Text("Webreg", + style: TextStyle( + fontWeight: FontWeight.normal, + )), + actions: [SearchPlaceholder()]), + body: currentTab[currentIndex], + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + currentIndex: currentIndex, + onTap: (index) { + setState(() { + currentIndex = index; + }); + }, + items: [ + BottomNavigationBarItem( + icon: Text("Calendar", style: textStyles), + activeIcon: Container( + child: Column(children: [ + Text("Calendar", style: activeStyles), + ])), + label: '', + ), + BottomNavigationBarItem( + icon: Text("List", style: textStyles), + activeIcon: Text("List", style: activeStyles), + label: '', ), - label: '', - ), - BottomNavigationBarItem( - icon: Text("List", style: textStyles), - activeIcon: Text("List", style: activeStyles), - label: '', - ), - BottomNavigationBarItem( - icon: Text("Finals", style: textStyles), - activeIcon: Text("Finals", style: activeStyles), - label: '', - ), - ], - showSelectedLabels: false, - showUnselectedLabels: false, - backgroundColor: vWhite, - ) - ); + BottomNavigationBarItem( + icon: Text("Finals", style: textStyles), + activeIcon: Text("Finals", style: activeStyles), + label: '', + ), + ], + showSelectedLabels: false, + showUnselectedLabels: false, + backgroundColor: vWhite, + )); } -} \ No newline at end of file +} From e9ec50f5981296563868c9c359cba93a3e0e7634 Mon Sep 17 00:00:00 2001 From: Kevin De Silva Jayasinghe Date: Wed, 1 Dec 2021 14:11:06 -0800 Subject: [PATCH 03/24] updated service and provider files more --- lib/core/models/schedule_of_classes.dart | 34 ++++----- lib/core/providers/schedule_of_classes.dart | 77 ++++++++++++++++++++ lib/core/services/schedule_of_classes.dart | 80 ++++++++++----------- 3 files changed, 134 insertions(+), 57 deletions(-) create mode 100644 lib/core/providers/schedule_of_classes.dart diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart index 0d2eab7..edabd07 100644 --- a/lib/core/models/schedule_of_classes.dart +++ b/lib/core/models/schedule_of_classes.dart @@ -113,23 +113,23 @@ class Section { this.instructors, }); - String sectionId; - TermCode termCode; - String sectionCode; - InstructionType instructionType; - SectionStatus sectionStatus; - String subtitle; - DateTime startDate; - DateTime endDate; - int enrolledQuantity; - int capacityQuantity; - bool stopEnrollmentFlag; - String printFlag; - String subterm; - InstructionType planCode; - List recurringMeetings; - List additionalMeetings; - List instructors; + String? sectionId; + TermCode? termCode; + String? sectionCode; + InstructionType? instructionType; + SectionStatus? sectionStatus; + String? subtitle; + DateTime? startDate; + DateTime? endDate; + int? enrolledQuantity; + int? capacityQuantity; + bool? stopEnrollmentFlag; + String? printFlag; + String? subterm; + InstructionType? planCode; + List? recurringMeetings; + List? additionalMeetings; + List? instructors; factory Section.fromJson(Map json) => Section( sectionId: json["sectionId"] == null ? null : json["sectionId"], diff --git a/lib/core/providers/schedule_of_classes.dart b/lib/core/providers/schedule_of_classes.dart new file mode 100644 index 0000000..a1ee754 --- /dev/null +++ b/lib/core/providers/schedule_of_classes.dart @@ -0,0 +1,77 @@ +import 'package:webreg_mobile_flutter/app_constants.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/services/schedule_of_classes.dart'; +import 'package:flutter/material.dart'; + +class ScheduleOfClassesProvider extends ChangeNotifier { + ScheduleOfClassesProvider() { + /// DEFAULT STATES + _isLoading = false; + _noResults = false; + + /// initialize services here + _scheduleOfClassesService = ScheduleOfClassesService(); + + _scheduleOfClassesModels = []; + } + + /// STATES + bool? _isLoading; + DateTime? _lastUpdated; + String? _error; + bool? _noResults; + + /// MODELS + List _scheduleOfClassesModels = []; + String? searchQuery; + String? term; + TextEditingController _searchBarController = TextEditingController(); + //UserDataProvider? _userDataProvider; + bool? lowerDiv; + bool? upperDiv; + bool? graduateDiv; + + /// SERVICES + late ScheduleOfClassesService _scheduleOfClassesService; + + void fetchClasses() async { + String SearchQuery = searchBarController.text; + String TextQuery = createQuery(SearchQuery); + _isLoading = true; + _error = null; + notifyListeners(); + + if (await _scheduleOfClassesService.fetchClasses(TextQuery)) { + _scheduleOfClassesModels = _scheduleOfClassesService.classes!; + _noResults = false; + + /// add things to show classes on screen + /// + /// conditionals for search history here + } else { + _error = _scheduleOfClassesService.error; + _noResults = true; + } + } + + String createQuery(String query) { + /// create api call format here + return query; + } + + ///SIMPLE GETTERS + bool? get isLoading => _isLoading; + String? get error => _error; + DateTime? get lastUpdated => _lastUpdated; + List get scheduleOfClassesModels => + _scheduleOfClassesModels; + //List get searchHistory => _searchHistory; + TextEditingController get searchBarController => _searchBarController; + bool? get noResults => _noResults; + + ///Settlers + set searchBarController(TextEditingController value) { + _searchBarController = value; + notifyListeners(); + } +} diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 496a15c..a437e67 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -4,45 +4,45 @@ import 'package:webreg_mobile_flutter/app_networking.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; class ScheduleOfClassesService { - ScheduleOfClassesService(); bool _isLoading = false; DateTime? _lastUpdated; String? _error; - List? _classes; - + List _classes = []; final NetworkHelper _networkHelper = NetworkHelper(); - final Map headers = { - "accept": "application/json", - }; + final String baseEndpoint = "https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/"; - Future fetchClasses() async { + Future fetchClasses(String query) async { _error = null; _isLoading = true; try { /// fetch data + String? _response = await _networkHelper + .fetchData(baseEndpoint + "?" + query); //add parameters here + if (_response != null) { + final ScheduleOfClassesModel data = + scheduleOfClassesModelFromJson(_response); + _classes = data as List; + } else { + /// parse data + _classes = []; - String _response = await (_networkHelper.authorizedFetch( - baseEndpoint, headers)); //add parameters here - - /// parse data - final data = ScheduleOfClassesModel(_response); + return false; + } _isLoading = false; - - _locations = data; return true; } catch (e) { /// if the authorized fetch failed we know we have to refresh the /// token for this service print("IN CATCH"); - if (e.toString().contains("401")) { - print("Getting new token from fetchLocations"); - if (await getNewToken()) { - print("Getting new token from fetchLocations"); - return await fetchClasses(); - } - } + // if (e.toString().contains("401")) { + // print("Getting new token from fetchLocations"); + // if (await getNewToken()) { + // print("Getting new token from fetchLocations"); + // return await fetchClasses(); + // } + // } _error = e.toString(); print(_error); _isLoading = false; @@ -50,25 +50,25 @@ class ScheduleOfClassesService { } } - Future getNewToken() async { - final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; - final Map tokenHeaders = { - "content-type": 'application/x-www-form-urlencoded', - "Authorization": - "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" - }; - try { - final response = await _networkHelper.authorizedPost( - tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); - - headers["Authorization"] = "Bearer " + response["access_token"]; - - return true; - } catch (e) { - _error = e.toString(); - return false; - } - } + // Future getNewToken() async { + // final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; + // final Map tokenHeaders = { + // "content-type": 'application/x-www-form-urlencoded', + // "Authorization": + // "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" + // }; + // try { + // final response = await _networkHelper.authorizedPost( + // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); + // + // headers["Authorization"] = "Bearer " + response["access_token"]; + // + // return true; + // } catch (e) { + // _error = e.toString(); + // return false; + // } + // } bool get isLoading => _isLoading; From 23ba8e4029e61d2bd075052d0ff984f7edb888f3 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Fri, 3 Dec 2021 08:04:22 -0800 Subject: [PATCH 04/24] Working on search page --- lib/backend/student_profile | 9 ++ lib/ui/calendar/calendar.dart | 2 + lib/ui/common/term_dropdown | 48 +++++++ lib/ui/list/course_list_view.dart | 81 ++--------- lib/ui/search/search_bar.dart | 214 +++++++++++++++--------------- 5 files changed, 181 insertions(+), 173 deletions(-) create mode 100644 lib/backend/student_profile create mode 100644 lib/ui/common/term_dropdown diff --git a/lib/backend/student_profile b/lib/backend/student_profile new file mode 100644 index 0000000..014a479 --- /dev/null +++ b/lib/backend/student_profile @@ -0,0 +1,9 @@ +class StudentProfile{ + static Map termMap; + + StudentProfile(){ + termMap = new Map(); + termMap.putIfAbsent("FA19", ) + } + ) +} \ No newline at end of file diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 14a66ee..1bc3224 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -3,6 +3,7 @@ import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar_card.dart'; import 'package:webreg_mobile_flutter/ui/common/build_info.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class Calendar extends StatelessWidget { Calendar(this.color); @@ -108,6 +109,7 @@ class Calendar extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10), child: Column( children: [ + TermDropdown(), // calendar header Container( height: CalendarStyles.calendarHeaderHeight, diff --git a/lib/ui/common/term_dropdown b/lib/ui/common/term_dropdown new file mode 100644 index 0000000..20b7702 --- /dev/null +++ b/lib/ui/common/term_dropdown @@ -0,0 +1,48 @@ +class TermDropdown extends StatefulWidget { + @override + _TermDropdownState createState() => _TermDropdownState(); +} + +// TODO +class _TermDropdownState extends State { + List dropdownItems = ['Fall 19', 'Winter 20', 'Spring 20', 'Fall 20']; + String _dropdownVal = 'Fall 19'; + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + margin: EdgeInsets.only(top: 10), + padding: EdgeInsets.symmetric(horizontal: 60), + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.alarm, color: Colors.black), + ] + ), + Center( + child: Text(_dropdownVal, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)) + ), + DropdownButton( + isExpanded: true, + underline: Container(height: 0), + icon: Icon(Icons.expand_more, color: Colors.black, size: 30), + onChanged: (String newVal) { + setState(() { + _dropdownVal = newVal; + }); + }, + items: dropdownItems.map>((String val) { + return DropdownMenuItem( + value: val, + child: Center(child: Text(val, style: TextStyle(fontSize: 18))) + ); + }).toList(), + ) + ] + ) + ); + } +} diff --git a/lib/ui/list/course_list_view.dart b/lib/ui/list/course_list_view.dart index aa06caa..c8efdb4 100644 --- a/lib/ui/list/course_list_view.dart +++ b/lib/ui/list/course_list_view.dart @@ -3,78 +3,25 @@ import 'package:webreg_mobile_flutter/ui/list/course_card.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class CourseListView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( - child: Column( - children: [ - TermDropdown(), - Expanded( - child: Container( - child: ListView.builder( - itemCount: 10, - padding: EdgeInsets.symmetric(vertical: 8), - itemBuilder: (BuildContext context, int index) { - return Container( - child: CourseCard(), - ); - } - ), - ) - ) - ] - ) - ); - } -} - -class TermDropdown extends StatefulWidget { - @override - _TermDropdownState createState() => _TermDropdownState(); -} - -// TODO -class _TermDropdownState extends State { - List dropdownItems = ['Fall 19', 'Winter 20', 'Spring 20', 'Fall 20']; - String _dropdownVal = 'Fall 19'; - - @override - Widget build(BuildContext context) { - return Container( - height: 40, - margin: EdgeInsets.only(top: 10), - padding: EdgeInsets.symmetric(horizontal: 60), - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.alarm, color: Colors.black), - ] - ), - Center( - child: Text(_dropdownVal, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)) - ), - DropdownButton( - isExpanded: true, - underline: Container(height: 0), - icon: Icon(Icons.expand_more, color: Colors.black, size: 30), - onChanged: (String newVal) { - setState(() { - _dropdownVal = newVal; - }); - }, - items: dropdownItems.map>((String val) { - return DropdownMenuItem( - value: val, - child: Center(child: Text(val, style: TextStyle(fontSize: 18))) + child: Column(children: [ + TermDropdown(), + Expanded( + child: Container( + child: ListView.builder( + itemCount: 10, + padding: EdgeInsets.symmetric(vertical: 8), + itemBuilder: (BuildContext context, int index) { + return Container( + child: CourseCard(), ); - }).toList(), - ) - ] - ) - ); + }), + )) + ])); } } diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index 0b6e63f..7c6a1aa 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -10,43 +10,40 @@ class SearchBar extends StatelessWidget { @override Widget build(BuildContext context) { return MediaQuery.removePadding( - context: context, - removeBottom: true, - child: AppBar( - titleSpacing: 0.0, - centerTitle: true, - title: Container( - decoration: new BoxDecoration( - color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), - border: Border.all(width: 1.0, color: Color(0xFF034263)), - ), - margin: EdgeInsets.symmetric(vertical: 10.0), - child: Search(), - ), - automaticallyImplyLeading: false, - leading: Center( - child:IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: () { - Navigator.pop(context); - } - ), - ), - actions: [ - IconButton( - icon: Icon(Icons.filter_list, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: this.setOpenFilters, - ), - ] - ) - ); + context: context, + removeBottom: true, + child: AppBar( + titleSpacing: 0.0, + centerTitle: true, + title: Container( + decoration: new BoxDecoration( + color: lightGray, + borderRadius: new BorderRadius.all(Radius.circular(100.0)), + border: Border.all(width: 1.0, color: Color(0xFF034263)), + ), + margin: EdgeInsets.symmetric(vertical: 10.0), + child: Search(), + ), + automaticallyImplyLeading: false, + leading: Center( + child: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: () { + Navigator.pop(context); + }), + ), + actions: [ + IconButton( + icon: Icon(Icons.filter_list, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: this.setOpenFilters, + ), + ])); } } @@ -65,7 +62,7 @@ class SearchBar extends StatelessWidget { // List _selectedFilters = List.filled(3, false); // AnimationController expandController; -// Animation animation; +// Animation animation; // void prepareAnimations() { // expandController = AnimationController( @@ -96,7 +93,7 @@ class SearchBar extends StatelessWidget { // // builder: (context) => Wrap( // // children: [ // // Container( -// // color: ColorPrimary, +// // color: ColorPrimary, // // height: 100, // // child: ListView( // // children: [ @@ -162,28 +159,35 @@ class TermDropdown extends StatefulWidget { } class _TermDropdownState extends State { - List dropdownItems = ['FA19', 'WI20', 'SP20', 'FA20']; - String _dropdownVal = 'FA19'; + List dropdownItems = [ + 'Fall 2019', + 'Winter 2020', + 'Sring 2020', + 'Fall 2020', + 'Winter 2021' + ]; + String _dropdownVal = 'Fall 2019'; @override Widget build(BuildContext context) { return DropdownButton( - underline: Container( - height: 0 - ), - value: _dropdownVal, - icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), - onChanged: (String newVal) { - setState(() { - _dropdownVal = newVal; - }); - }, - items: dropdownItems.map>((String val) { - return DropdownMenuItem( + underline: Container(height: 0), + value: _dropdownVal, + icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), + onChanged: (String newVal) { + setState(() { + _dropdownVal = newVal; + }); + }, + items: dropdownItems.map>((String val) { + return DropdownMenuItem( value: val, - child: Text(val, style: TextStyle(color: darkGray, fontSize: 14, fontWeight: FontWeight.bold)) - ); - }).toList(), + child: Text(val, + style: TextStyle( + color: darkGray, + fontSize: 14, + fontWeight: FontWeight.bold))); + }).toList(), ); } } @@ -200,56 +204,54 @@ class _SearchState extends State { @override Widget build(BuildContext context) { return Container( - height: 35, - child: Row(children: [ - Container( - margin: const EdgeInsets.only(left: 10.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - TermDropdown(), - Container( - width: 1.0, - color: darkGray, - margin: const EdgeInsets.only(right: 10.0), - ) - ], - ) - ), - Expanded( - child: TextField( - onChanged: (text) { - // _searchText = text; - if(text.length > 0) { - _icon = GestureDetector( - child: Icon(Icons.close, size: 20, color: darkGray), - onTap: () { - _searchText.clear(); + height: 35, + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TermDropdown(), + Container( + width: 1.0, + color: darkGray, + margin: const EdgeInsets.only(right: 10.0), + ) + ], + )), + Expanded( + child: TextField( + onChanged: (text) { + // _searchText = text; + if (text.length > 0) { + _icon = GestureDetector( + child: Icon(Icons.close, size: 20, color: darkGray), + onTap: () { + _searchText.clear(); + }); + } else { + _icon = Icon(Icons.search, size: 20, color: darkGray); } - ); - } else { - _icon = Icon(Icons.search, size: 20, color: darkGray); - } - }, - controller: _searchText, - autofocus: true, - textAlignVertical: TextAlignVertical.center, - style: TextStyle(fontSize: 16), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 0), - hintText: 'Search', - isDense: true, + }, + controller: _searchText, + autofocus: true, + textAlignVertical: TextAlignVertical.center, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 0), + hintText: 'Search', + isDense: true, + ), + ), ), - ), - ), - Container( - margin: const EdgeInsets.only(right: 10.0), - child: _icon, - ), - ], - ) - ); + Container( + margin: const EdgeInsets.only(right: 10.0), + child: _icon, + ), + ], + )); } -} \ No newline at end of file +} From 814e6bfcfb1346af9347e7fd0facfd3538dfb6f5 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Fri, 3 Dec 2021 10:04:03 -0800 Subject: [PATCH 05/24] Revert "Working on search page" This reverts commit 23ba8e4029e61d2bd075052d0ff984f7edb888f3. --- lib/backend/student_profile | 9 -- lib/ui/calendar/calendar.dart | 2 - lib/ui/common/term_dropdown | 48 ------- lib/ui/list/course_list_view.dart | 81 +++++++++-- lib/ui/search/search_bar.dart | 214 +++++++++++++++--------------- 5 files changed, 173 insertions(+), 181 deletions(-) delete mode 100644 lib/backend/student_profile delete mode 100644 lib/ui/common/term_dropdown diff --git a/lib/backend/student_profile b/lib/backend/student_profile deleted file mode 100644 index 014a479..0000000 --- a/lib/backend/student_profile +++ /dev/null @@ -1,9 +0,0 @@ -class StudentProfile{ - static Map termMap; - - StudentProfile(){ - termMap = new Map(); - termMap.putIfAbsent("FA19", ) - } - ) -} \ No newline at end of file diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 1bc3224..14a66ee 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -3,7 +3,6 @@ import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar_card.dart'; import 'package:webreg_mobile_flutter/ui/common/build_info.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class Calendar extends StatelessWidget { Calendar(this.color); @@ -109,7 +108,6 @@ class Calendar extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10), child: Column( children: [ - TermDropdown(), // calendar header Container( height: CalendarStyles.calendarHeaderHeight, diff --git a/lib/ui/common/term_dropdown b/lib/ui/common/term_dropdown deleted file mode 100644 index 20b7702..0000000 --- a/lib/ui/common/term_dropdown +++ /dev/null @@ -1,48 +0,0 @@ -class TermDropdown extends StatefulWidget { - @override - _TermDropdownState createState() => _TermDropdownState(); -} - -// TODO -class _TermDropdownState extends State { - List dropdownItems = ['Fall 19', 'Winter 20', 'Spring 20', 'Fall 20']; - String _dropdownVal = 'Fall 19'; - - @override - Widget build(BuildContext context) { - return Container( - height: 40, - margin: EdgeInsets.only(top: 10), - padding: EdgeInsets.symmetric(horizontal: 60), - child: Stack( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.alarm, color: Colors.black), - ] - ), - Center( - child: Text(_dropdownVal, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)) - ), - DropdownButton( - isExpanded: true, - underline: Container(height: 0), - icon: Icon(Icons.expand_more, color: Colors.black, size: 30), - onChanged: (String newVal) { - setState(() { - _dropdownVal = newVal; - }); - }, - items: dropdownItems.map>((String val) { - return DropdownMenuItem( - value: val, - child: Center(child: Text(val, style: TextStyle(fontSize: 18))) - ); - }).toList(), - ) - ] - ) - ); - } -} diff --git a/lib/ui/list/course_list_view.dart b/lib/ui/list/course_list_view.dart index c8efdb4..aa06caa 100644 --- a/lib/ui/list/course_list_view.dart +++ b/lib/ui/list/course_list_view.dart @@ -3,25 +3,78 @@ import 'package:webreg_mobile_flutter/ui/list/course_card.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class CourseListView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( - child: Column(children: [ - TermDropdown(), - Expanded( - child: Container( - child: ListView.builder( - itemCount: 10, - padding: EdgeInsets.symmetric(vertical: 8), - itemBuilder: (BuildContext context, int index) { - return Container( - child: CourseCard(), + child: Column( + children: [ + TermDropdown(), + Expanded( + child: Container( + child: ListView.builder( + itemCount: 10, + padding: EdgeInsets.symmetric(vertical: 8), + itemBuilder: (BuildContext context, int index) { + return Container( + child: CourseCard(), + ); + } + ), + ) + ) + ] + ) + ); + } +} + +class TermDropdown extends StatefulWidget { + @override + _TermDropdownState createState() => _TermDropdownState(); +} + +// TODO +class _TermDropdownState extends State { + List dropdownItems = ['Fall 19', 'Winter 20', 'Spring 20', 'Fall 20']; + String _dropdownVal = 'Fall 19'; + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + margin: EdgeInsets.only(top: 10), + padding: EdgeInsets.symmetric(horizontal: 60), + child: Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.alarm, color: Colors.black), + ] + ), + Center( + child: Text(_dropdownVal, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)) + ), + DropdownButton( + isExpanded: true, + underline: Container(height: 0), + icon: Icon(Icons.expand_more, color: Colors.black, size: 30), + onChanged: (String newVal) { + setState(() { + _dropdownVal = newVal; + }); + }, + items: dropdownItems.map>((String val) { + return DropdownMenuItem( + value: val, + child: Center(child: Text(val, style: TextStyle(fontSize: 18))) ); - }), - )) - ])); + }).toList(), + ) + ] + ) + ); } } diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index 7c6a1aa..0b6e63f 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -10,40 +10,43 @@ class SearchBar extends StatelessWidget { @override Widget build(BuildContext context) { return MediaQuery.removePadding( - context: context, - removeBottom: true, - child: AppBar( - titleSpacing: 0.0, - centerTitle: true, - title: Container( - decoration: new BoxDecoration( - color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), - border: Border.all(width: 1.0, color: Color(0xFF034263)), - ), - margin: EdgeInsets.symmetric(vertical: 10.0), - child: Search(), - ), - automaticallyImplyLeading: false, - leading: Center( - child: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: () { - Navigator.pop(context); - }), - ), - actions: [ - IconButton( - icon: Icon(Icons.filter_list, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: this.setOpenFilters, - ), - ])); + context: context, + removeBottom: true, + child: AppBar( + titleSpacing: 0.0, + centerTitle: true, + title: Container( + decoration: new BoxDecoration( + color: lightGray, + borderRadius: new BorderRadius.all(Radius.circular(100.0)), + border: Border.all(width: 1.0, color: Color(0xFF034263)), + ), + margin: EdgeInsets.symmetric(vertical: 10.0), + child: Search(), + ), + automaticallyImplyLeading: false, + leading: Center( + child:IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: () { + Navigator.pop(context); + } + ), + ), + actions: [ + IconButton( + icon: Icon(Icons.filter_list, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: this.setOpenFilters, + ), + ] + ) + ); } } @@ -62,7 +65,7 @@ class SearchBar extends StatelessWidget { // List _selectedFilters = List.filled(3, false); // AnimationController expandController; -// Animation animation; +// Animation animation; // void prepareAnimations() { // expandController = AnimationController( @@ -93,7 +96,7 @@ class SearchBar extends StatelessWidget { // // builder: (context) => Wrap( // // children: [ // // Container( -// // color: ColorPrimary, +// // color: ColorPrimary, // // height: 100, // // child: ListView( // // children: [ @@ -159,35 +162,28 @@ class TermDropdown extends StatefulWidget { } class _TermDropdownState extends State { - List dropdownItems = [ - 'Fall 2019', - 'Winter 2020', - 'Sring 2020', - 'Fall 2020', - 'Winter 2021' - ]; - String _dropdownVal = 'Fall 2019'; + List dropdownItems = ['FA19', 'WI20', 'SP20', 'FA20']; + String _dropdownVal = 'FA19'; @override Widget build(BuildContext context) { return DropdownButton( - underline: Container(height: 0), - value: _dropdownVal, - icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), - onChanged: (String newVal) { - setState(() { - _dropdownVal = newVal; - }); - }, - items: dropdownItems.map>((String val) { - return DropdownMenuItem( + underline: Container( + height: 0 + ), + value: _dropdownVal, + icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), + onChanged: (String newVal) { + setState(() { + _dropdownVal = newVal; + }); + }, + items: dropdownItems.map>((String val) { + return DropdownMenuItem( value: val, - child: Text(val, - style: TextStyle( - color: darkGray, - fontSize: 14, - fontWeight: FontWeight.bold))); - }).toList(), + child: Text(val, style: TextStyle(color: darkGray, fontSize: 14, fontWeight: FontWeight.bold)) + ); + }).toList(), ); } } @@ -204,54 +200,56 @@ class _SearchState extends State { @override Widget build(BuildContext context) { return Container( - height: 35, - child: Row( - children: [ - Container( - margin: const EdgeInsets.only(left: 10.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - TermDropdown(), - Container( - width: 1.0, - color: darkGray, - margin: const EdgeInsets.only(right: 10.0), - ) - ], - )), - Expanded( - child: TextField( - onChanged: (text) { - // _searchText = text; - if (text.length > 0) { - _icon = GestureDetector( - child: Icon(Icons.close, size: 20, color: darkGray), - onTap: () { - _searchText.clear(); - }); - } else { - _icon = Icon(Icons.search, size: 20, color: darkGray); + height: 35, + child: Row(children: [ + Container( + margin: const EdgeInsets.only(left: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TermDropdown(), + Container( + width: 1.0, + color: darkGray, + margin: const EdgeInsets.only(right: 10.0), + ) + ], + ) + ), + Expanded( + child: TextField( + onChanged: (text) { + // _searchText = text; + if(text.length > 0) { + _icon = GestureDetector( + child: Icon(Icons.close, size: 20, color: darkGray), + onTap: () { + _searchText.clear(); } - }, - controller: _searchText, - autofocus: true, - textAlignVertical: TextAlignVertical.center, - style: TextStyle(fontSize: 16), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 0), - hintText: 'Search', - isDense: true, - ), - ), + ); + } else { + _icon = Icon(Icons.search, size: 20, color: darkGray); + } + }, + controller: _searchText, + autofocus: true, + textAlignVertical: TextAlignVertical.center, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 0), + hintText: 'Search', + isDense: true, ), - Container( - margin: const EdgeInsets.only(right: 10.0), - child: _icon, - ), - ], - )); + ), + ), + Container( + margin: const EdgeInsets.only(right: 10.0), + child: _icon, + ), + ], + ) + ); } -} +} \ No newline at end of file From defde61990f07697f3e428858a8c58172f2462c9 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Fri, 3 Dec 2021 10:08:19 -0800 Subject: [PATCH 06/24] Changes to version --- lib/app_router.dart | 1 + lib/ui/list/course_card.dart | 1044 +++++++++++++++-------------- lib/ui/list/course_list_view.dart | 69 +- lib/ui/search/search_bar.dart | 204 +++--- 4 files changed, 660 insertions(+), 658 deletions(-) diff --git a/lib/app_router.dart b/lib/app_router.dart index f67f6cd..eec42fe 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -14,6 +14,7 @@ class Router { return MaterialPageRoute(builder: (_) => SearchView()); case RoutePaths.CourseListView: return MaterialPageRoute(builder: (_) => CourseListView()); + default RoutePat } } } \ No newline at end of file diff --git a/lib/ui/list/course_card.dart b/lib/ui/list/course_card.dart index 9de4a62..d7e6a86 100644 --- a/lib/ui/list/course_card.dart +++ b/lib/ui/list/course_card.dart @@ -6,445 +6,448 @@ import 'package:webreg_mobile_flutter/app_styles.dart'; class CourseCard extends StatelessWidget { static const MOCK_DATA = [ - { - 'lecture': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'LE', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': '', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': 'EN', - 'gradeOption': 'L', - 'creditHours': 4, - 'gradeOptionPlus': true, - 'creditHoursPlus': false, - 'courseTitle': 'Practicum in Pro Web Design', - 'enrollmentCapacity': 60, - 'enrollmentQuantity': 64, - 'countOnWaitlist': 2, - 'stopEnrollmentFlag': true, - 'classTimes': [ - { - 'dayCode': '2', - 'startDate': 1585551600000, - 'beginHHTime': '14', - 'beginMMTime': '0', - 'endHHTime': '15', - 'endMMTime': '20', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': '2019-03-15', - }, - { - 'dayCode': '4', - 'startDate': 1585551600000, - 'beginHHTime': '14', - 'beginMMTime': '0', - 'endHHTime': '15', - 'endMMTime': '20', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': '2019-03-15', - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], + { + 'lecture': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'LE', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': '', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': 'EN', + 'gradeOption': 'L', + 'creditHours': 4, + 'gradeOptionPlus': true, + 'creditHoursPlus': false, + 'courseTitle': 'Practicum in Pro Web Design', + 'enrollmentCapacity': 60, + 'enrollmentQuantity': 64, + 'countOnWaitlist': 2, + 'stopEnrollmentFlag': true, + 'classTimes': [ + { + 'dayCode': '2', + 'startDate': 1585551600000, + 'beginHHTime': '14', + 'beginMMTime': '0', + 'endHHTime': '15', + 'endMMTime': '20', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': '2019-03-15', + }, + { + 'dayCode': '4', + 'startDate': 1585551600000, + 'beginHHTime': '14', + 'beginMMTime': '0', + 'endHHTime': '15', + 'endMMTime': '20', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': '2019-03-15', + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, + 'final': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'LE', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': 'FI', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': null, + 'gradeOption': null, + 'creditHours': null, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': null, + 'enrollmentCapacity': null, + 'enrollmentQuantity': null, + 'countOnWaitlist': null, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '4', + 'startDate': 1585551600000, + 'beginHHTime': '15', + 'beginMMTime': '0', + 'endHHTime': '17', + 'endMMTime': '59', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': null, + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, + 'discussion': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'DI', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': '', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': null, + 'gradeOption': null, + 'creditHours': null, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': null, + 'enrollmentCapacity': null, + 'enrollmentQuantity': null, + 'countOnWaitlist': null, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '2', + 'startDate': 1585551600000, + 'beginHHTime': '11', + 'beginMMTime': '0', + 'endHHTime': '11', + 'endMMTime': '50', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': null, + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, }, - 'final': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'LE', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': 'FI', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': null, - 'gradeOption': null, - 'creditHours': null, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': null, - 'enrollmentCapacity': null, - 'enrollmentQuantity': null, - 'countOnWaitlist': null, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '4', - 'startDate': 1585551600000, - 'beginHHTime': '15', - 'beginMMTime': '0', - 'endHHTime': '17', - 'endMMTime': '59', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': null, - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], + { + 'lecture': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'LE', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': '', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': 'EN', + 'gradeOption': 'L', + 'creditHours': 4, + 'gradeOptionPlus': true, + 'creditHoursPlus': false, + 'courseTitle': 'Practicum in Pro Web Design', + 'enrollmentCapacity': 60, + 'enrollmentQuantity': 64, + 'countOnWaitlist': 2, + 'stopEnrollmentFlag': true, + 'classTimes': [ + { + 'dayCode': '2', + 'startDate': 1585551600000, + 'beginHHTime': '14', + 'beginMMTime': '0', + 'endHHTime': '15', + 'endMMTime': '20', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': '2019-03-15', + }, + { + 'dayCode': '4', + 'startDate': 1585551600000, + 'beginHHTime': '14', + 'beginMMTime': '0', + 'endHHTime': '15', + 'endMMTime': '20', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': '2019-03-15', + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, + 'final': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'LE', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': 'FI', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': null, + 'gradeOption': null, + 'creditHours': null, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': null, + 'enrollmentCapacity': null, + 'enrollmentQuantity': null, + 'countOnWaitlist': null, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '4', + 'startDate': 1585551600000, + 'beginHHTime': '15', + 'beginMMTime': '0', + 'endHHTime': '17', + 'endMMTime': '59', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': null, + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, + 'discussion': { + 'subjectCode': 'COGS', + 'courseCode': '187B', + 'instructionType': 'DI', + 'sectionNumber': '960510', + 'sectionCode': 'A00', + 'specialMeetingCode': '', + 'longDescription': '', + 'sectionStatus': null, + 'enrollmentStatus': null, + 'gradeOption': null, + 'creditHours': null, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': null, + 'enrollmentCapacity': null, + 'enrollmentQuantity': null, + 'countOnWaitlist': null, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '2', + 'startDate': 1585551600000, + 'beginHHTime': '11', + 'beginMMTime': '0', + 'endHHTime': '11', + 'endMMTime': '50', + 'buildingCode': 'HSS', + 'roomCode': '1346', + 'endDate': null, + }, + ], + 'instructors': [ + 'Kirsh, David Joel', + ], + }, }, - 'discussion': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'DI', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': '', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': null, - 'gradeOption': null, - 'creditHours': null, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': null, - 'enrollmentCapacity': null, - 'enrollmentQuantity': null, - 'countOnWaitlist': null, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '2', - 'startDate': 1585551600000, - 'beginHHTime': '11', - 'beginMMTime': '0', - 'endHHTime': '11', - 'endMMTime': '50', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': null, - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], + { + 'discussion': { + 'subjectCode': 'LTEA', + 'courseCode': '120A', + 'instructionType': 'LA', + 'sectionNumber': '2064', + 'sectionCode': 'A01', + 'specialMeetingCode': null, + 'longDescription': '', + 'enrollmentStatus': 'EN', + 'gradeOption': 'P', + 'creditHours': 4, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': 'Chinese Films', + 'enrollmentCapacity': 320, + 'enrollmentQuantity': 308, + 'countOnWaitlist': 1, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '1', + 'startDate': 1585551600000, + 'beginHHTime': 17, + 'beginMMTime': 0, + 'endHHTime': 19, + 'endMMTime': 50, + 'buildingCode': '', + 'roomCode': '', + 'endDate': null, + }, + ], + 'instructors': [ + 'Zhang, Yingjin', + ], + }, + 'lecture': { + 'subjectCode': 'LTEA', + 'courseCode': '120A', + 'instructionType': 'LE', + 'sectionNumber': '2063', + 'sectionCode': 'A00', + 'specialMeetingCode': '', + 'longDescription': 'Visions of the City', + 'enrollmentStatus': 'EN', + 'gradeOption': 'P', + 'creditHours': 4, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': 'Chinese Films', + 'enrollmentCapacity': 320, + 'enrollmentQuantity': 308, + 'countOnWaitlist': 1, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '1', + 'startDate': 1585551600000, + 'beginHHTime': 17, + 'beginMMTime': 0, + 'endHHTime': 19, + 'endMMTime': 50, + 'buildingCode': '', + 'roomCode': '', + 'endDate': null, + }, + ], + 'instructors': [ + 'Zhang, Yingjin', + ], + }, + 'final': { + 'subjectCode': 'LTEA', + 'courseCode': '120A', + 'instructionType': 'LE', + 'sectionNumber': '2063', + 'sectionCode': 'A00', + 'specialMeetingCode': 'FI', + 'longDescription': 'Visions of the City', + 'enrollmentStatus': null, + 'gradeOption': null, + 'creditHours': null, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': null, + 'enrollmentCapacity': null, + 'enrollmentQuantity': null, + 'countOnWaitlist': null, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '5', + 'startDate': 1591945200000, + 'beginHHTime': 19, + 'beginMMTime': 0, + 'endHHTime': 21, + 'endMMTime': 59, + 'buildingCode': '', + 'roomCode': '', + 'endDate': null, + }, + ], + 'instructors': [ + 'Zhang, Yingjin', + ], + }, }, - }, - { - 'lecture': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'LE', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': '', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': 'EN', - 'gradeOption': 'L', - 'creditHours': 4, - 'gradeOptionPlus': true, - 'creditHoursPlus': false, - 'courseTitle': 'Practicum in Pro Web Design', - 'enrollmentCapacity': 60, - 'enrollmentQuantity': 64, - 'countOnWaitlist': 2, - 'stopEnrollmentFlag': true, - 'classTimes': [ - { - 'dayCode': '2', - 'startDate': 1585551600000, - 'beginHHTime': '14', - 'beginMMTime': '0', - 'endHHTime': '15', - 'endMMTime': '20', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': '2019-03-15', - }, - { - 'dayCode': '4', - 'startDate': 1585551600000, - 'beginHHTime': '14', - 'beginMMTime': '0', - 'endHHTime': '15', - 'endMMTime': '20', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': '2019-03-15', - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], + { + 'lecture': { + 'subjectCode': 'PHIL', + 'courseCode': '27', + 'instructionType': 'LE', + 'sectionNumber': '5027', + 'sectionCode': 'A02', + 'specialMeetingCode': '', + 'longDescription': '', + 'enrollmentStatus': 'EN', + 'gradeOption': 'P', + 'creditHours': 4, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': 'Ethics And Society', + 'enrollmentCapacity': 37, + 'enrollmentQuantity': 39, + 'countOnWaitlist': 2, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '1', + 'startDate': 1585551600000, + 'beginHHTime': 14, + 'beginMMTime': 0, + 'endHHTime': 14, + 'endMMTime': 50, + 'buildingCode': '', + 'roomCode': '', + 'endDate': 1591340400000, + }, + ], + 'instructors': [ + 'Brandt, Reuven A', + ], + }, + 'discussion': { + 'subjectCode': 'PHIL', + 'courseCode': '27', + 'instructionType': 'DI', + 'sectionNumber': '5027', + 'sectionCode': 'A02', + 'specialMeetingCode': '', + 'longDescription': '', + 'enrollmentStatus': 'EN', + 'gradeOption': 'P', + 'creditHours': 4, + 'gradeOptionPlus': false, + 'creditHoursPlus': false, + 'courseTitle': 'Ethics And Society', + 'enrollmentCapacity': 37, + 'enrollmentQuantity': 39, + 'countOnWaitlist': 2, + 'stopEnrollmentFlag': false, + 'classTimes': [ + { + 'dayCode': '1', + 'startDate': 1585551600000, + 'beginHHTime': 14, + 'beginMMTime': 0, + 'endHHTime': 14, + 'endMMTime': 50, + 'buildingCode': '', + 'roomCode': '', + 'endDate': 1591340400000, + }, + ], + 'instructors': [ + 'Brandt, Reuven A', + ], + }, }, - 'final': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'LE', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': 'FI', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': null, - 'gradeOption': null, - 'creditHours': null, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': null, - 'enrollmentCapacity': null, - 'enrollmentQuantity': null, - 'countOnWaitlist': null, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '4', - 'startDate': 1585551600000, - 'beginHHTime': '15', - 'beginMMTime': '0', - 'endHHTime': '17', - 'endMMTime': '59', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': null, - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], - }, - 'discussion': { - 'subjectCode': 'COGS', - 'courseCode': '187B', - 'instructionType': 'DI', - 'sectionNumber': '960510', - 'sectionCode': 'A00', - 'specialMeetingCode': '', - 'longDescription': '', - 'sectionStatus': null, - 'enrollmentStatus': null, - 'gradeOption': null, - 'creditHours': null, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': null, - 'enrollmentCapacity': null, - 'enrollmentQuantity': null, - 'countOnWaitlist': null, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '2', - 'startDate': 1585551600000, - 'beginHHTime': '11', - 'beginMMTime': '0', - 'endHHTime': '11', - 'endMMTime': '50', - 'buildingCode': 'HSS', - 'roomCode': '1346', - 'endDate': null, - }, - ], - 'instructors': [ - 'Kirsh, David Joel', - ], - }, - }, - { - 'discussion': { - 'subjectCode': 'LTEA', - 'courseCode': '120A', - 'instructionType': 'LA', - 'sectionNumber': '2064', - 'sectionCode': 'A01', - 'specialMeetingCode': null, - 'longDescription': '', - 'enrollmentStatus': 'EN', - 'gradeOption': 'P', - 'creditHours': 4, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': 'Chinese Films', - 'enrollmentCapacity': 320, - 'enrollmentQuantity': 308, - 'countOnWaitlist': 1, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '1', - 'startDate': 1585551600000, - 'beginHHTime': 17, - 'beginMMTime': 0, - 'endHHTime': 19, - 'endMMTime': 50, - 'buildingCode': '', - 'roomCode': '', - 'endDate': null, - }, - ], - 'instructors': [ - 'Zhang, Yingjin', - ], - }, - 'lecture': { - 'subjectCode': 'LTEA', - 'courseCode': '120A', - 'instructionType': 'LE', - 'sectionNumber': '2063', - 'sectionCode': 'A00', - 'specialMeetingCode': '', - 'longDescription': 'Visions of the City', - 'enrollmentStatus': 'EN', - 'gradeOption': 'P', - 'creditHours': 4, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': 'Chinese Films', - 'enrollmentCapacity': 320, - 'enrollmentQuantity': 308, - 'countOnWaitlist': 1, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '1', - 'startDate': 1585551600000, - 'beginHHTime': 17, - 'beginMMTime': 0, - 'endHHTime': 19, - 'endMMTime': 50, - 'buildingCode': '', - 'roomCode': '', - 'endDate': null, - }, - ], - 'instructors': [ - 'Zhang, Yingjin', - ], - }, - 'final': { - 'subjectCode': 'LTEA', - 'courseCode': '120A', - 'instructionType': 'LE', - 'sectionNumber': '2063', - 'sectionCode': 'A00', - 'specialMeetingCode': 'FI', - 'longDescription': 'Visions of the City', - 'enrollmentStatus': null, - 'gradeOption': null, - 'creditHours': null, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': null, - 'enrollmentCapacity': null, - 'enrollmentQuantity': null, - 'countOnWaitlist': null, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '5', - 'startDate': 1591945200000, - 'beginHHTime': 19, - 'beginMMTime': 0, - 'endHHTime': 21, - 'endMMTime': 59, - 'buildingCode': '', - 'roomCode': '', - 'endDate': null, - }, - ], - 'instructors': [ - 'Zhang, Yingjin', - ], - }, - }, - { - 'lecture': { - 'subjectCode': 'PHIL', - 'courseCode': '27', - 'instructionType': 'LE', - 'sectionNumber': '5027', - 'sectionCode': 'A02', - 'specialMeetingCode': '', - 'longDescription': '', - 'enrollmentStatus': 'EN', - 'gradeOption': 'P', - 'creditHours': 4, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': 'Ethics And Society', - 'enrollmentCapacity': 37, - 'enrollmentQuantity': 39, - 'countOnWaitlist': 2, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '1', - 'startDate': 1585551600000, - 'beginHHTime': 14, - 'beginMMTime': 0, - 'endHHTime': 14, - 'endMMTime': 50, - 'buildingCode': '', - 'roomCode': '', - 'endDate': 1591340400000, - }, - ], - 'instructors': [ - 'Brandt, Reuven A', - ], - }, - 'discussion': { - 'subjectCode': 'PHIL', - 'courseCode': '27', - 'instructionType': 'DI', - 'sectionNumber': '5027', - 'sectionCode': 'A02', - 'specialMeetingCode': '', - 'longDescription': '', - 'enrollmentStatus': 'EN', - 'gradeOption': 'P', - 'creditHours': 4, - 'gradeOptionPlus': false, - 'creditHoursPlus': false, - 'courseTitle': 'Ethics And Society', - 'enrollmentCapacity': 37, - 'enrollmentQuantity': 39, - 'countOnWaitlist': 2, - 'stopEnrollmentFlag': false, - 'classTimes': [ - { - 'dayCode': '1', - 'startDate': 1585551600000, - 'beginHHTime': 14, - 'beginMMTime': 0, - 'endHHTime': 14, - 'endMMTime': 50, - 'buildingCode': '', - 'roomCode': '', - 'endDate': 1591340400000, - }, - ], - 'instructors': [ - 'Brandt, Reuven A', - ], - }, - }, -]; + ]; Widget renderActionButtons() { return Container( - width: 45, - decoration: BoxDecoration( - border: Border( + width: 45, + decoration: BoxDecoration( + border: Border( left: BorderSide(color: lightGray), - ) - ), - child: Column( - children: [ - IconButton(icon: Icon(Icons.autorenew, color: ColorPrimary)), - IconButton(icon: Icon(Icons.delete, color: ColorPrimary)), - IconButton(icon: Icon(Icons.add_circle, color: ColorPrimary)), - ] - ) - ); + )), + child: Column(children: [ + IconButton( + icon: Icon(Icons.autorenew, color: ColorPrimary), + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.delete, color: ColorPrimary), onPressed: () {}), + IconButton( + icon: Icon(Icons.add_circle, color: ColorPrimary), + onPressed: () {}, + ), + ])); } Widget renderSection() { @@ -452,29 +455,38 @@ class CourseCard extends StatelessWidget { // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - flex: 3, - child: Row( - children: [ - Text('A00', style: TextStyle(fontSize: 11, color: darkGray)), // TODO - Text(' LE', style: TextStyle(fontSize: 11, color: darkGray)) // TODO - ], - ) - ), + flex: 3, + child: Row( + children: [ + Text('A00', + style: TextStyle(fontSize: 11, color: darkGray)), // TODO + Text(' LE', + style: TextStyle(fontSize: 11, color: darkGray)) // TODO + ], + )), Expanded( - flex: 3, - child: Row( - children: [ - Text('M', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('W', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('F', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - ], - ) - ), + flex: 3, + child: Row( + children: [ + Text('M', + style: + TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + Text('T', + style: TextStyle(fontSize: 11, color: lightGray)), // TODO + Text('W', + style: + TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + Text('T', + style: TextStyle(fontSize: 11, color: lightGray)), // TODO + Text('F', + style: + TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + ], + )), Expanded( flex: 5, - child: Text('3:30p - 4:50p', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + child: Text('3:30p - 4:50p', + style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO ), Expanded( flex: 5, @@ -489,88 +501,90 @@ class CourseCard extends StatelessWidget { return Card( elevation: 0, shape: RoundedRectangleBorder( - side: new BorderSide(color: ColorPrimary, width: 2.0), - borderRadius: BorderRadius.circular(10.0) - ), + side: new BorderSide(color: ColorPrimary, width: 2.0), + borderRadius: BorderRadius.circular(10.0)), margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: ClipPath( child: Row( children: [ Expanded( child: Container( - margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // course header: units, course code, course name - Container( - child: Row( - children: [ + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // course header: units, course code, course name + Container( + child: Row(children: [ // units icon Container( - height: 30, - width: 30, - decoration: new BoxDecoration( - color: lightGray, - shape: BoxShape.circle, - ), - margin: EdgeInsets.only(right: 10), - child: Center( - child: Text( - '4' // TODO - ) - ) - ), + height: 30, + width: 30, + decoration: new BoxDecoration( + color: lightGray, + shape: BoxShape.circle, + ), + margin: EdgeInsets.only(right: 10), + child: Center( + child: Text('4' // TODO + ))), // course info Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('CSE 12', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), // TODO - Text('Enrolled - Letter', style: TextStyle(color: ColorPrimary, fontSize: 12)), // TODO - ], - ), - Text('Basic Data Struct & OO design') // TODO - ], - ) - ) - ] + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('CSE 12', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold)), // TODO + Text('Enrolled - Letter', + style: TextStyle( + color: ColorPrimary, + fontSize: 12)), // TODO + ], + ), + Text('Basic Data Struct & OO design') // TODO + ], + )) + ]), ), - ), - // instructor andd section id - Container( - margin: EdgeInsets.only(top: 8, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Gillespie, Gary N', style: TextStyle(color: ColorPrimary, fontSize: 12)), // TODO - Row( + // instructor andd section id + Container( + margin: EdgeInsets.only(top: 8, bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Section ID', style: TextStyle(color: darkGray, fontSize: 12)), // TODO - Text(' 983761', style: TextStyle(fontSize: 12)), // TODO - ] - ) - ] + Text('Gillespie, Gary N', + style: TextStyle( + color: ColorPrimary, + fontSize: 12)), // TODO + Row(children: [ + Text('Section ID', + style: TextStyle( + color: darkGray, fontSize: 12)), // TODO + Text(' 983761', + style: TextStyle(fontSize: 12)), // TODO + ]) + ]), ), - ), - // course sections: di, final - renderSection(), - renderSection(), - ], - ) - ), + // course sections: di, final + renderSection(), + renderSection(), + ], + )), ), renderActionButtons() ], ), clipper: ShapeBorderClipper( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)) - ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), ), ); } -} \ No newline at end of file +} diff --git a/lib/ui/list/course_list_view.dart b/lib/ui/list/course_list_view.dart index aa06caa..8f1d985 100644 --- a/lib/ui/list/course_list_view.dart +++ b/lib/ui/list/course_list_view.dart @@ -8,25 +8,20 @@ class CourseListView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( - child: Column( - children: [ - TermDropdown(), - Expanded( - child: Container( - child: ListView.builder( - itemCount: 10, - padding: EdgeInsets.symmetric(vertical: 8), - itemBuilder: (BuildContext context, int index) { - return Container( - child: CourseCard(), - ); - } - ), - ) - ) - ] - ) - ); + child: Column(children: [ + TermDropdown(), + Expanded( + child: Container( + child: ListView.builder( + itemCount: 10, + padding: EdgeInsets.symmetric(vertical: 8), + itemBuilder: (BuildContext context, int index) { + return Container( + child: CourseCard(), + ); + }), + )) + ])); } } @@ -43,38 +38,34 @@ class _TermDropdownState extends State { @override Widget build(BuildContext context) { return Container( - height: 40, - margin: EdgeInsets.only(top: 10), - padding: EdgeInsets.symmetric(horizontal: 60), - child: Stack( - children: [ + height: 40, + margin: EdgeInsets.only(top: 10), + padding: EdgeInsets.symmetric(horizontal: 60), + child: Stack(children: [ Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.alarm, color: Colors.black), - ] - ), + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.alarm, color: Colors.black), + ]), Center( - child: Text(_dropdownVal, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)) - ), + child: Text(_dropdownVal, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold))), DropdownButton( isExpanded: true, underline: Container(height: 0), icon: Icon(Icons.expand_more, color: Colors.black, size: 30), - onChanged: (String newVal) { + onChanged: (String? newVal) { setState(() { - _dropdownVal = newVal; + _dropdownVal = newVal!; }); }, items: dropdownItems.map>((String val) { return DropdownMenuItem( - value: val, - child: Center(child: Text(val, style: TextStyle(fontSize: 18))) - ); + value: val, + child: + Center(child: Text(val, style: TextStyle(fontSize: 18)))); }).toList(), ) - ] - ) - ); + ])); } } diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index 0b6e63f..e928673 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -10,43 +10,40 @@ class SearchBar extends StatelessWidget { @override Widget build(BuildContext context) { return MediaQuery.removePadding( - context: context, - removeBottom: true, - child: AppBar( - titleSpacing: 0.0, - centerTitle: true, - title: Container( - decoration: new BoxDecoration( - color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), - border: Border.all(width: 1.0, color: Color(0xFF034263)), - ), - margin: EdgeInsets.symmetric(vertical: 10.0), - child: Search(), - ), - automaticallyImplyLeading: false, - leading: Center( - child:IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: () { - Navigator.pop(context); - } - ), - ), - actions: [ - IconButton( - icon: Icon(Icons.filter_list, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), - alignment: Alignment.centerLeft, - iconSize: 25, - onPressed: this.setOpenFilters, - ), - ] - ) - ); + context: context, + removeBottom: true, + child: AppBar( + titleSpacing: 0.0, + centerTitle: true, + title: Container( + decoration: new BoxDecoration( + color: lightGray, + borderRadius: new BorderRadius.all(Radius.circular(100.0)), + border: Border.all(width: 1.0, color: Color(0xFF034263)), + ), + margin: EdgeInsets.symmetric(vertical: 10.0), + child: Search(), + ), + automaticallyImplyLeading: false, + leading: Center( + child: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: () { + Navigator.pop(context); + }), + ), + actions: [ + IconButton( + icon: Icon(Icons.filter_list, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: this.setOpenFilters, + ), + ])); } } @@ -65,7 +62,7 @@ class SearchBar extends StatelessWidget { // List _selectedFilters = List.filled(3, false); // AnimationController expandController; -// Animation animation; +// Animation animation; // void prepareAnimations() { // expandController = AnimationController( @@ -96,7 +93,7 @@ class SearchBar extends StatelessWidget { // // builder: (context) => Wrap( // // children: [ // // Container( -// // color: ColorPrimary, +// // color: ColorPrimary, // // height: 100, // // child: ListView( // // children: [ @@ -168,22 +165,23 @@ class _TermDropdownState extends State { @override Widget build(BuildContext context) { return DropdownButton( - underline: Container( - height: 0 - ), - value: _dropdownVal, - icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), - onChanged: (String newVal) { - setState(() { - _dropdownVal = newVal; - }); - }, - items: dropdownItems.map>((String val) { - return DropdownMenuItem( + underline: Container(height: 0), + value: _dropdownVal, + icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), + onChanged: (String? newVal) { + setState(() { + _dropdownVal = newVal!; + }); + }, + items: dropdownItems.map>((String val) { + return DropdownMenuItem( value: val, - child: Text(val, style: TextStyle(color: darkGray, fontSize: 14, fontWeight: FontWeight.bold)) - ); - }).toList(), + child: Text(val, + style: TextStyle( + color: darkGray, + fontSize: 14, + fontWeight: FontWeight.bold))); + }).toList(), ); } } @@ -200,56 +198,54 @@ class _SearchState extends State { @override Widget build(BuildContext context) { return Container( - height: 35, - child: Row(children: [ - Container( - margin: const EdgeInsets.only(left: 10.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - TermDropdown(), - Container( - width: 1.0, - color: darkGray, - margin: const EdgeInsets.only(right: 10.0), - ) - ], - ) - ), - Expanded( - child: TextField( - onChanged: (text) { - // _searchText = text; - if(text.length > 0) { - _icon = GestureDetector( - child: Icon(Icons.close, size: 20, color: darkGray), - onTap: () { - _searchText.clear(); + height: 35, + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TermDropdown(), + Container( + width: 1.0, + color: darkGray, + margin: const EdgeInsets.only(right: 10.0), + ) + ], + )), + Expanded( + child: TextField( + onChanged: (text) { + // _searchText = text; + if (text.length > 0) { + _icon = GestureDetector( + child: Icon(Icons.close, size: 20, color: darkGray), + onTap: () { + _searchText.clear(); + }); + } else { + _icon = Icon(Icons.search, size: 20, color: darkGray); } - ); - } else { - _icon = Icon(Icons.search, size: 20, color: darkGray); - } - }, - controller: _searchText, - autofocus: true, - textAlignVertical: TextAlignVertical.center, - style: TextStyle(fontSize: 16), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 0), - hintText: 'Search', - isDense: true, + }, + controller: _searchText, + autofocus: true, + textAlignVertical: TextAlignVertical.center, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 0), + hintText: 'Search', + isDense: true, + ), + ), ), - ), - ), - Container( - margin: const EdgeInsets.only(right: 10.0), - child: _icon, - ), - ], - ) - ); + Container( + margin: const EdgeInsets.only(right: 10.0), + child: _icon, + ), + ], + )); } -} \ No newline at end of file +} From edf48149ac12c557da6587930f4954cd1a3e97bc Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Fri, 10 Dec 2021 11:09:15 -0800 Subject: [PATCH 07/24] Compiling demo version --- analysis_options.yaml | 4 +- lib/app_constants.dart | 14 +- lib/app_router.dart | 15 +- lib/core/models/schedule_of_classes.dart | 515 +++++++------------- lib/core/providers/schedule_of_classes.dart | 3 + lib/core/services/schedule_of_classes.dart | 2 +- lib/ui/calendar/calendar.dart | 5 + lib/ui/list/course_card.dart | 4 +- lib/ui/search/search_detail.dart | 29 ++ pubspec.lock | 26 +- 10 files changed, 275 insertions(+), 342 deletions(-) create mode 100644 lib/ui/search/search_detail.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 5801b01..52dc4c2 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -20,8 +20,8 @@ analyzer: strong-mode: - implicit-casts: false - implicit-dynamic: false + # implicit-casts: false + # implicit-dynamic: false errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning diff --git a/lib/app_constants.dart b/lib/app_constants.dart index 09f0ffd..495a596 100644 --- a/lib/app_constants.dart +++ b/lib/app_constants.dart @@ -7,10 +7,22 @@ class RoutePaths { static const String SearchView = 'search_view'; static const String CourseListView = 'course_list_view'; static const String Login = 'login'; + static const String SearchDetail = 'search_detail'; } class CalendarStyles { static const double calendarHeaderHeight = 50; static const double calendarTimeWidth = 35; static const double calendarRowHeight = 60; -} \ No newline at end of file +} + +class ErrorConstants { + static const authorizedPostErrors = 'Failed to upload data: '; + static const authorizedPutErrors = 'Failed to update data: '; + static const invalidBearerToken = 'Invalid bearer token'; + static const duplicateRecord = + 'DioError [DioErrorType.response]: Http status error [409]'; + static const invalidMedia = + 'DioError [DioErrorType.response]: Http status error [415]'; + static const silentLoginFailed = "Silent login failed"; +} diff --git a/lib/app_router.dart b/lib/app_router.dart index eec42fe..bd9638d 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,10 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_view.dart'; import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart'; import 'package:webreg_mobile_flutter/ui/navigator/bottom.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_detail.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_view.dart'; +// ignore: avoid_classes_with_only_static_members class Router { static Route generateRoute(RouteSettings settings) { switch (settings.name) { @@ -14,7 +16,10 @@ class Router { return MaterialPageRoute(builder: (_) => SearchView()); case RoutePaths.CourseListView: return MaterialPageRoute(builder: (_) => CourseListView()); - default RoutePat + case RoutePaths.SearchDetail: + return MaterialPageRoute(builder: (_) => const SearchDetail()); + default: + return MaterialPageRoute(builder: (_) => BottomNavigation()); } } -} \ No newline at end of file +} diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart index edabd07..a35778d 100644 --- a/lib/core/models/schedule_of_classes.dart +++ b/lib/core/models/schedule_of_classes.dart @@ -1,376 +1,239 @@ -// To parse this JSON data, do -// -// final scheduleOfClassesModel = scheduleOfClassesModelFromJson(jsonString); - import 'dart:convert'; -ScheduleOfClassesModel scheduleOfClassesModelFromJson(String str) => +ScheduleOfClassesModel classScheduleModelFromJson(String str) => ScheduleOfClassesModel.fromJson(json.decode(str)); -String scheduleOfClassesModelToJson(ScheduleOfClassesModel data) => +String classScheduleModelToJson(ScheduleOfClassesModel data) => json.encode(data.toJson()); class ScheduleOfClassesModel { + Metadata? metadata; + List? courses; + ScheduleOfClassesModel({ this.metadata, - this.data, + this.courses, }); - Metadata metadata; - List data; - factory ScheduleOfClassesModel.fromJson(Map json) => ScheduleOfClassesModel( - metadata: json["metadata"] == null - ? null - : Metadata.fromJson(json["metadata"]), - data: json["data"] == null - ? null - : List.from(json["data"].map((x) => Datum.fromJson(x))), - ); + metadata: Metadata.fromJson(json['metadata']), courses: json["data"]); Map toJson() => { - "metadata": metadata == null ? null : metadata.toJson(), - "data": data == null - ? null - : List.from(data.map((x) => x.toJson())), + "metadata": metadata!.toJson(), + "data": List.from(courses!.map((x) => x.toJson())) }; } -class Datum { - Datum({ - this.subjectCode, - this.courseCode, - this.departmentCode, - this.courseTitle, - this.unitsMin, - this.unitsMax, - this.unitsInc, - this.academicLevel, - this.sections, - }); +class Metadata { + Metadata(); - String subjectCode; - String courseCode; - String departmentCode; - String courseTitle; - int unitsMin; - int unitsMax; - int unitsInc; - String academicLevel; - List
sections; + factory Metadata.fromJson(Map? json) => Metadata(); - factory Datum.fromJson(Map json) => Datum( - subjectCode: json["subjectCode"] == null ? null : json["subjectCode"], - courseCode: json["courseCode"] == null ? null : json["courseCode"], - departmentCode: - json["departmentCode"] == null ? null : json["departmentCode"], - courseTitle: json["courseTitle"] == null ? null : json["courseTitle"], - unitsMin: json["unitsMin"] == null ? null : json["unitsMin"], - unitsMax: json["unitsMax"] == null ? null : json["unitsMax"], - unitsInc: json["unitsInc"] == null ? null : json["unitsInc"], - academicLevel: - json["academicLevel"] == null ? null : json["academicLevel"], - sections: json["sections"] == null - ? null - : List
.from( - json["sections"].map((x) => Section.fromJson(x))), - ); + Map toJson() => {}; +} + +class CourseData { + String? subjectCode; + String? courseCode; + String? departmentCode; + String? courseTitle; + double? unitsMin; + double? unitsMax; + double? unitsInc; + String? academicLevel; + List? sections; + + CourseData( + {this.subjectCode, + this.courseCode, + this.departmentCode, + this.courseTitle, + this.unitsMin, + this.unitsMax, + this.unitsInc, + this.academicLevel, + this.sections}); + + factory CourseData.fromJson(Map json) => CourseData( + subjectCode: json['subjectCode'] ?? '', + courseCode: json['courseCode'] ?? '', + departmentCode: json['departmentCode'] ?? '', + courseTitle: json['courseTitle'] ?? '', + unitsMin: 0.0, + unitsMax: 0.0, + unitsInc: 0.0, + academicLevel: json['academicLevel'] ?? '', + sections: List.from( + json["sections"].map((x) => SectionData.fromJson(x)))); Map toJson() => { - "subjectCode": subjectCode == null ? null : subjectCode, - "courseCode": courseCode == null ? null : courseCode, - "departmentCode": departmentCode == null ? null : departmentCode, - "courseTitle": courseTitle == null ? null : courseTitle, - "unitsMin": unitsMin == null ? null : unitsMin, - "unitsMax": unitsMax == null ? null : unitsMax, - "unitsInc": unitsInc == null ? null : unitsInc, - "academicLevel": academicLevel == null ? null : academicLevel, - "sections": sections == null - ? null - : List.from(sections.map((x) => x.toJson())), + 'subjectCode': subjectCode, + 'courseCode': courseCode, + 'departmentCode': departmentCode, + 'courseTitle': courseTitle, + 'unitsMin': unitsMin, + 'unitsMax': unitsMax, + 'unitsInc': unitsInc, + 'academicLevel': academicLevel, + 'sections': List.from(sections!.map((x) => x.toJson())) }; } -class Section { - Section({ - this.sectionId, - this.termCode, - this.sectionCode, - this.instructionType, - this.sectionStatus, - this.subtitle, - this.startDate, - this.endDate, - this.enrolledQuantity, - this.capacityQuantity, - this.stopEnrollmentFlag, - this.printFlag, - this.subterm, - this.planCode, - this.recurringMeetings, - this.additionalMeetings, - this.instructors, - }); - +class SectionData { String? sectionId; - TermCode? termCode; + String? termCode; String? sectionCode; - InstructionType? instructionType; - SectionStatus? sectionStatus; + String? instructionType; + String? sectionStatus; String? subtitle; - DateTime? startDate; - DateTime? endDate; + String? startDate; + String? endDate; int? enrolledQuantity; int? capacityQuantity; bool? stopEnrollmentFlag; String? printFlag; String? subterm; - InstructionType? planCode; - List? recurringMeetings; - List? additionalMeetings; + String? planCode; + List? recurringMeetings; + List? additionalMeetings; List? instructors; - factory Section.fromJson(Map json) => Section( - sectionId: json["sectionId"] == null ? null : json["sectionId"], - termCode: json["termCode"] == null - ? null - : termCodeValues.map[json["termCode"]], - sectionCode: json["sectionCode"] == null ? null : json["sectionCode"], - instructionType: json["instructionType"] == null - ? null - : instructionTypeValues.map[json["instructionType"]], - sectionStatus: json["sectionStatus"] == null - ? null - : sectionStatusValues.map[json["sectionStatus"]], - subtitle: json["subtitle"] == null ? null : json["subtitle"], - startDate: json["startDate"] == null - ? null - : DateTime.parse(json["startDate"]), - endDate: - json["endDate"] == null ? null : DateTime.parse(json["endDate"]), - enrolledQuantity: - json["enrolledQuantity"] == null ? null : json["enrolledQuantity"], - capacityQuantity: - json["capacityQuantity"] == null ? null : json["capacityQuantity"], - stopEnrollmentFlag: json["stopEnrollmentFlag"] == null - ? null - : json["stopEnrollmentFlag"], - printFlag: json["printFlag"] == null ? null : json["printFlag"], - subterm: json["subterm"] == null ? null : json["subterm"], - planCode: json["planCode"] == null - ? null - : instructionTypeValues.map[json["planCode"]], - recurringMeetings: json["recurringMeetings"] == null - ? null - : List.from(json["recurringMeetings"] - .map((x) => RecurringMeeting.fromJson(x))), - additionalMeetings: json["additionalMeetings"] == null - ? null - : List.from(json["additionalMeetings"].map((x) => x)), - instructors: json["instructors"] == null - ? null - : List.from( - json["instructors"].map((x) => Instructor.fromJson(x))), - ); + SectionData( + {this.sectionId, + this.termCode, + this.sectionCode, + this.instructionType, + this.sectionStatus, + this.subtitle, + this.startDate, + this.endDate, + this.enrolledQuantity, + this.capacityQuantity, + this.stopEnrollmentFlag, + this.printFlag, + this.subterm, + this.planCode, + this.recurringMeetings, + this.additionalMeetings, + this.instructors}); + + factory SectionData.fromJson(Map json) => SectionData( + sectionId: json['sectionId'] ?? '', + termCode: json['termCode'] ?? '', + sectionCode: json['sectionCode'] ?? '', + instructionType: json['instructionType'] ?? '', + sectionStatus: json['sectionStatus'] ?? '', + subtitle: json['subtitle'] ?? '', + startDate: json['startDate'] ?? '', + endDate: json['endDate'] ?? '', + enrolledQuantity: json['enrolledQuantity'] ?? 0, + capacityQuantity: json['capacityQuantity'] ?? 0, + stopEnrollmentFlag: json['stopEnrollmentFlag'] ?? false, + printFlag: json['printFlag'] ?? '', + subterm: json['subterm'] ?? '', + planCode: json['planCode'] ?? '', + recurringMeetings: List.from( + json["recurringMeetings"].map((x) => SectionData.fromJson(x))), + additionalMeetings: List.from( + json["additionalMeetings"].map((x) => SectionData.fromJson(x))), + instructors: List.from( + json["instructors"].map((x) => SectionData.fromJson(x)))); Map toJson() => { - "sectionId": sectionId == null ? null : sectionId, - "termCode": termCode == null ? null : termCodeValues.reverse[termCode], - "sectionCode": sectionCode == null ? null : sectionCode, - "instructionType": instructionType == null - ? null - : instructionTypeValues.reverse[instructionType], - "sectionStatus": sectionStatus == null - ? null - : sectionStatusValues.reverse[sectionStatus], - "subtitle": subtitle == null ? null : subtitle, - "startDate": startDate == null - ? null - : "${startDate.year.toString().padLeft(4, '0')}-${startDate.month.toString().padLeft(2, '0')}-${startDate.day.toString().padLeft(2, '0')}", - "endDate": endDate == null - ? null - : "${endDate.year.toString().padLeft(4, '0')}-${endDate.month.toString().padLeft(2, '0')}-${endDate.day.toString().padLeft(2, '0')}", - "enrolledQuantity": enrolledQuantity == null ? null : enrolledQuantity, - "capacityQuantity": capacityQuantity == null ? null : capacityQuantity, - "stopEnrollmentFlag": - stopEnrollmentFlag == null ? null : stopEnrollmentFlag, - "printFlag": printFlag == null ? null : printFlag, - "subterm": subterm == null ? null : subterm, - "planCode": - planCode == null ? null : instructionTypeValues.reverse[planCode], - "recurringMeetings": recurringMeetings == null - ? null - : List.from(recurringMeetings.map((x) => x.toJson())), - "additionalMeetings": additionalMeetings == null - ? null - : List.from(additionalMeetings.map((x) => x)), - "instructors": instructors == null - ? null - : List.from(instructors.map((x) => x.toJson())), + 'sectionId': sectionId, + 'termCode': termCode, + 'sectionCode': sectionCode, + 'instructionType': instructionType, + 'sectionStatus': sectionStatus, + 'subtitle': subtitle, + 'startDate': startDate, + 'endDate': endDate, + 'enrolledQuantity': enrolledQuantity, + 'capacityQuantity': capacityQuantity, + 'stopEnrollmentFlag': stopEnrollmentFlag, + 'printFlag': printFlag, + 'subterm': subterm, + 'planCode': planCode, + 'recurringMeetings': + List.from(recurringMeetings!.map((x) => x.toJson())), + 'additionalMeetings': + List.from(additionalMeetings!.map((x) => x.toJson())), + 'instructors': List.from(instructors!.map((x) => x.toJson())) }; } -enum InstructionType { LE, ST, SE } - -final instructionTypeValues = EnumValues({ - "LE": InstructionType.LE, - "SE": InstructionType.SE, - "ST": InstructionType.ST -}); - -class Instructor { - Instructor({ - this.pid, - this.instructorName, - this.primaryInstructor, - this.instructorEmailAddress, - this.workLoadUnitQty, - this.percentOfLoad, - }); - - Pid pid; - InstructorName instructorName; - bool primaryInstructor; - InstructorEmailAddress instructorEmailAddress; - int workLoadUnitQty; - int percentOfLoad; - - factory Instructor.fromJson(Map json) => Instructor( - pid: json["pid"] == null ? null : pidValues.map[json["pid"]], - instructorName: json["instructorName"] == null - ? null - : instructorNameValues.map[json["instructorName"]], - primaryInstructor: json["primaryInstructor"] == null - ? null - : json["primaryInstructor"], - instructorEmailAddress: json["instructorEmailAddress"] == null - ? null - : instructorEmailAddressValues.map[json["instructorEmailAddress"]], - workLoadUnitQty: - json["workLoadUnitQty"] == null ? null : json["workLoadUnitQty"], - percentOfLoad: - json["percentOfLoad"] == null ? null : json["percentOfLoad"], - ); +class MeetingData { + String? meetingType; + String? meetingDate; + String? dayCode; + String? dayCodeIsis; + String? startTime; + String? endTime; + String? buildingCode; + String? roomCode; + + MeetingData( + {this.meetingType, + this.meetingDate, + this.dayCode, + this.dayCodeIsis, + this.startTime, + this.endTime, + this.buildingCode, + this.roomCode}); + factory MeetingData.fromJson(Map json) => MeetingData( + meetingType: json['meetingType'] ?? '', + meetingDate: json['meetingDate'] ?? '', + dayCode: json['dayCode'] ?? '', + dayCodeIsis: json['dayCodeIsis'] ?? '', + startTime: json['startTime'] ?? '', + endTime: json['endTime'] ?? '', + buildingCode: json['buildingCode'] ?? '', + roomCode: json['roomCode'] ?? ''); Map toJson() => { - "pid": pid == null ? null : pidValues.reverse[pid], - "instructorName": instructorName == null - ? null - : instructorNameValues.reverse[instructorName], - "primaryInstructor": - primaryInstructor == null ? null : primaryInstructor, - "instructorEmailAddress": instructorEmailAddress == null - ? null - : instructorEmailAddressValues.reverse[instructorEmailAddress], - "workLoadUnitQty": workLoadUnitQty == null ? null : workLoadUnitQty, - "percentOfLoad": percentOfLoad == null ? null : percentOfLoad, + 'meetingType': meetingType, + 'meetingDate': meetingDate, + 'dayCode': dayCode, + 'dayCodeIsis': dayCodeIsis, + 'startTime': startTime, + 'endTime': endTime, + 'buildingCode': buildingCode, + 'roomCode': roomCode }; } -class RecurringMeeting { - RecurringMeeting({ - this.dayCode, - this.dayCodeIsis, - this.startTime, - this.endTime, - this.buildingCode, - this.roomCode, - }); - - DayCode dayCode; - DayCodeIsis dayCodeIsis; - String startTime; - String endTime; - BuildingCode buildingCode; - String roomCode; - - factory RecurringMeeting.fromJson(Map json) => - RecurringMeeting( - dayCode: - json["dayCode"] == null ? null : dayCodeValues.map[json["dayCode"]], - dayCodeIsis: json["dayCodeIsis"] == null - ? null - : dayCodeIsisValues.map[json["dayCodeIsis"]], - startTime: json["startTime"] == null ? null : json["startTime"], - endTime: json["endTime"] == null ? null : json["endTime"], - buildingCode: json["buildingCode"] == null - ? null - : buildingCodeValues.map[json["buildingCode"]], - roomCode: json["roomCode"] == null ? null : json["roomCode"], - ); - - Map toJson() => { - "dayCode": dayCode == null ? null : dayCodeValues.reverse[dayCode], - "dayCodeIsis": - dayCodeIsis == null ? null : dayCodeIsisValues.reverse[dayCodeIsis], - "startTime": startTime == null ? null : startTime, - "endTime": endTime == null ? null : endTime, - "buildingCode": buildingCode == null - ? null - : buildingCodeValues.reverse[buildingCode], - "roomCode": roomCode == null ? null : roomCode, - }; -} - -class Metadata { - Metadata({ - this.links, - this.totalCount, - }); - - List links; - int totalCount; - - factory Metadata.fromJson(Map json) => Metadata( - links: json["links"] == null - ? null - : List.from(json["links"].map((x) => Link.fromJson(x))), - totalCount: json["totalCount"] == null ? null : json["totalCount"], - ); - - Map toJson() => { - "links": links == null - ? null - : List.from(links.map((x) => x.toJson())), - "totalCount": totalCount == null ? null : totalCount, - }; -} - -class Link { - Link({ - this.rel, - this.href, - this.method, - }); - - String rel; - String href; - String method; +class Instructor { + String? pid; + String? instructorName; + bool? primaryInstructor; + String? instructorEmailAddress; + double? workLoadUnitQty; + double? percentOfLoad; + + Instructor( + {this.pid, + this.instructorName, + this.primaryInstructor, + this.instructorEmailAddress, + this.workLoadUnitQty, + this.percentOfLoad}); - factory Link.fromJson(Map json) => Link( - rel: json["rel"] == null ? null : json["rel"], - href: json["href"] == null ? null : json["href"], - method: json["method"] == null ? null : json["method"], - ); + factory Instructor.fromJson(Map json) => Instructor( + pid: json['pid'] ?? '', + instructorName: json['instructorName'] ?? '', + primaryInstructor: json['primaryInstructor'] ?? false, + instructorEmailAddress: json['instructorEmailAddress'] ?? '', + workLoadUnitQty: json['workLoadUnitQty'] ?? '', + percentOfLoad: json['percentOfLoad'] ?? ''); Map toJson() => { - "rel": rel == null ? null : rel, - "href": href == null ? null : href, - "method": method == null ? null : method, + 'pid': pid, + 'instructorName': instructorName, + 'primaryInstructor': primaryInstructor, + 'instructorEmailAddress': instructorEmailAddress, + 'workLoadUnityQty': workLoadUnitQty, + 'percentOfLoad': percentOfLoad }; } - -class EnumValues { - Map map; - Map reverseMap; - - EnumValues(this.map); - - Map get reverse { - if (reverseMap == null) { - reverseMap = map.map((k, v) => new MapEntry(v, k)); - } - return reverseMap; - } -} diff --git a/lib/core/providers/schedule_of_classes.dart b/lib/core/providers/schedule_of_classes.dart index a1ee754..adc2cd4 100644 --- a/lib/core/providers/schedule_of_classes.dart +++ b/lib/core/providers/schedule_of_classes.dart @@ -34,6 +34,9 @@ class ScheduleOfClassesProvider extends ChangeNotifier { /// SERVICES late ScheduleOfClassesService _scheduleOfClassesService; + ScheduleOfClassesService get scheduleOfClassesService => + _scheduleOfClassesService; + void fetchClasses() async { String SearchQuery = searchBarController.text; String TextQuery = createQuery(SearchQuery); diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index a437e67..28177b4 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -22,7 +22,7 @@ class ScheduleOfClassesService { .fetchData(baseEndpoint + "?" + query); //add parameters here if (_response != null) { final ScheduleOfClassesModel data = - scheduleOfClassesModelFromJson(_response); + classScheduleModelFromJson(_response); _classes = data as List; } else { /// parse data diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 14a66ee..b7167d0 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -189,6 +189,11 @@ class Calendar extends StatelessWidget { 'WLH 109', color), ])), BuildInfo(), + TextButton( + child: const Text('Search Detail Page'), + onPressed: () { + Navigator.pushNamed(context, RoutePaths.SearchDetail); + }) ], )); } diff --git a/lib/ui/list/course_card.dart b/lib/ui/list/course_card.dart index d7e6a86..1f7d776 100644 --- a/lib/ui/list/course_card.dart +++ b/lib/ui/list/course_card.dart @@ -442,7 +442,9 @@ class CourseCard extends StatelessWidget { onPressed: () {}, ), IconButton( - icon: Icon(Icons.delete, color: ColorPrimary), onPressed: () {}), + icon: Icon(Icons.delete, color: ColorPrimary), + onPressed: () {}, + ), IconButton( icon: Icon(Icons.add_circle, color: ColorPrimary), onPressed: () {}, diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart new file mode 100644 index 0000000..c899878 --- /dev/null +++ b/lib/ui/search/search_detail.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; + +/* This UI page is used to show course offerings/details (prerequisites, sections, finals) +* after the user has searched and selected a course. +*/ +class SearchDetail extends StatefulWidget { + const SearchDetail({Key? key}) : super(key: key); + + @override + _SearchDetailState createState() => _SearchDetailState(); +} + +class _SearchDetailState extends State { + late ScheduleOfClassesProvider classesProvider; + @override + Widget build(BuildContext context) { + classesProvider = ScheduleOfClassesProvider(); + classesProvider.scheduleOfClassesService.fetchClasses( + 'departments=CSE&termCode=SP21'); //Proxy for a network request + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('Class Details'), + ), + body: Text(classesProvider.scheduleOfClassesModels.toString()), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index d7f289e..df4975b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.8.1" boolean_selector: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -50,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" fake_async: dependency: transitive description: @@ -79,6 +86,13 @@ packages: description: flutter source: sdk version: "0.0.0" + get: + dependency: "direct main" + description: + name: get + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.4" http: dependency: transitive description: @@ -113,14 +127,14 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" package_info_plus: dependency: "direct main" description: name: package_info_plus url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" package_info_plus_linux: dependency: transitive description: @@ -188,7 +202,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -223,7 +237,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.2" typed_data: dependency: transitive description: From b2470e43bfa4aa9e9032e8e0b5821b356696ba58 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Mon, 13 Dec 2021 14:50:17 -0800 Subject: [PATCH 08/24] Working network requests for Schedule of Classes --- lib/core/models/schedule_of_classes.dart | 14 +++++--- lib/core/providers/schedule_of_classes.dart | 6 ++-- lib/core/services/schedule_of_classes.dart | 40 +++++++++++++++++---- lib/ui/search/search_detail.dart | 15 ++++++-- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart index a35778d..ce40da2 100644 --- a/lib/core/models/schedule_of_classes.dart +++ b/lib/core/models/schedule_of_classes.dart @@ -1,5 +1,9 @@ import 'dart:convert'; +import 'dart:core'; + +import 'dart:core'; + ScheduleOfClassesModel classScheduleModelFromJson(String str) => ScheduleOfClassesModel.fromJson(json.decode(str)); @@ -17,8 +21,10 @@ class ScheduleOfClassesModel { factory ScheduleOfClassesModel.fromJson(Map json) => ScheduleOfClassesModel( - metadata: Metadata.fromJson(json['metadata']), courses: json["data"]); + metadata: Metadata.fromJson(json['metadata']), courses:List.from( json["data"].map((x) => CourseData.fromJson(x)))); + // List.from( + // json["data"].map((x) => ClassData.fromJson(x))) Map toJson() => { "metadata": metadata!.toJson(), "data": List.from(courses!.map((x) => x.toJson())) @@ -134,11 +140,11 @@ class SectionData { subterm: json['subterm'] ?? '', planCode: json['planCode'] ?? '', recurringMeetings: List.from( - json["recurringMeetings"].map((x) => SectionData.fromJson(x))), + json["recurringMeetings"].map((x) => MeetingData.fromJson(x))), additionalMeetings: List.from( - json["additionalMeetings"].map((x) => SectionData.fromJson(x))), + json["additionalMeetings"].map((x) => MeetingData.fromJson(x))), instructors: List.from( - json["instructors"].map((x) => SectionData.fromJson(x)))); + json["instructors"].map((x) => Instructor.fromJson(x)))); Map toJson() => { 'sectionId': sectionId, diff --git a/lib/core/providers/schedule_of_classes.dart b/lib/core/providers/schedule_of_classes.dart index adc2cd4..82166ce 100644 --- a/lib/core/providers/schedule_of_classes.dart +++ b/lib/core/providers/schedule_of_classes.dart @@ -12,7 +12,7 @@ class ScheduleOfClassesProvider extends ChangeNotifier { /// initialize services here _scheduleOfClassesService = ScheduleOfClassesService(); - _scheduleOfClassesModels = []; + _scheduleOfClassesModels = ScheduleOfClassesModel(); } /// STATES @@ -22,7 +22,7 @@ class ScheduleOfClassesProvider extends ChangeNotifier { bool? _noResults; /// MODELS - List _scheduleOfClassesModels = []; + ScheduleOfClassesModel _scheduleOfClassesModels = ScheduleOfClassesModel(); String? searchQuery; String? term; TextEditingController _searchBarController = TextEditingController(); @@ -66,7 +66,7 @@ class ScheduleOfClassesProvider extends ChangeNotifier { bool? get isLoading => _isLoading; String? get error => _error; DateTime? get lastUpdated => _lastUpdated; - List get scheduleOfClassesModels => + ScheduleOfClassesModel get scheduleOfClassesModels => _scheduleOfClassesModels; //List get searchHistory => _searchHistory; TextEditingController get searchBarController => _searchBarController; diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 28177b4..ab68ec1 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -7,26 +7,31 @@ class ScheduleOfClassesService { bool _isLoading = false; DateTime? _lastUpdated; String? _error; - List _classes = []; + ScheduleOfClassesModel? classes; final NetworkHelper _networkHelper = NetworkHelper(); - + final Map headers = { + "accept": "application/json", + }; final String baseEndpoint = - "https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/"; + "https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/classes/search"; Future fetchClasses(String query) async { _error = null; _isLoading = true; try { + await getNewToken(); /// fetch data String? _response = await _networkHelper - .fetchData(baseEndpoint + "?" + query); //add parameters here + .authorizedFetch(baseEndpoint + '?' + query, headers); + print(_response);//addarameters here if (_response != null) { final ScheduleOfClassesModel data = classScheduleModelFromJson(_response); - _classes = data as List; + classes = data as ScheduleOfClassesModel; + print(classes!.courses.toString()); } else { /// parse data - _classes = []; + return false; } @@ -76,5 +81,26 @@ class ScheduleOfClassesService { DateTime? get lastUpdated => _lastUpdated; - List? get classes => _classes; + ScheduleOfClassesModel? get _classes => classes; + Future getNewToken() async { + final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; + final Map tokenHeaders = { + "content-type": 'application/x-www-form-urlencoded', + "Authorization": + "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" + }; + //TODO: Have subscription set up for webreg mobile. Investigate alternatives to Dio. + try { + // var response = await _networkHelper.authorizedPost( + // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); + // TODO(Peter): Insert your own authetication token for demo. Will be replaced by application credentials + headers["Authorization"] = "Bearer " + ""; + //response["access_token"]; + + return true; + } catch (e) { + _error = e.toString(); + return false; + } + } } diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index c899878..3c7d96d 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; /* This UI page is used to show course offerings/details (prerequisites, sections, finals) @@ -16,14 +17,22 @@ class _SearchDetailState extends State { @override Widget build(BuildContext context) { classesProvider = ScheduleOfClassesProvider(); - classesProvider.scheduleOfClassesService.fetchClasses( - 'departments=CSE&termCode=SP21'); //Proxy for a network request return Scaffold( appBar: AppBar( centerTitle: true, title: const Text('Class Details'), ), - body: Text(classesProvider.scheduleOfClassesModels.toString()), + body: FutureBuilder( + future: classesProvider.scheduleOfClassesService.fetchClasses( + 'departments=CSE&termCode=SP21&limit=5'), + builder: (context, response){ + if (response.hasData){ + return Text(classesProvider.scheduleOfClassesService.classes!.toJson().toString()); + + }else{ + return const CircularProgressIndicator(); + } + }) ); } } From d166d3294f38e0ce46817aeed3a3f957bf3f680f Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Wed, 15 Dec 2021 12:57:33 -0800 Subject: [PATCH 09/24] working version of api integration, further development in progress --- lib/app_router.dart | 8 +- lib/core/services/schedule_of_classes.dart | 12 +- lib/ui/calendar/calendar.dart | 5 - lib/ui/search/search_bar.dart | 11 +- lib/ui/search/search_detail.dart | 134 +++++-- lib/ui/search/search_view.dart | 434 +++++++++++++++++---- 6 files changed, 476 insertions(+), 128 deletions(-) diff --git a/lib/app_router.dart b/lib/app_router.dart index bd9638d..ce29728 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart'; import 'package:webreg_mobile_flutter/ui/navigator/bottom.dart'; import 'package:webreg_mobile_flutter/ui/search/search_detail.dart'; import 'package:webreg_mobile_flutter/ui/search/search_view.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; // ignore: avoid_classes_with_only_static_members class Router { @@ -17,7 +19,11 @@ class Router { case RoutePaths.CourseListView: return MaterialPageRoute(builder: (_) => CourseListView()); case RoutePaths.SearchDetail: - return MaterialPageRoute(builder: (_) => const SearchDetail()); + final CourseData course = settings.arguments! as CourseData; + return MaterialPageRoute(builder: (_) { + return SearchDetail(data: course); + }); + default: return MaterialPageRoute(builder: (_) => BottomNavigation()); } diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index ab68ec1..027da84 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -20,10 +20,11 @@ class ScheduleOfClassesService { _isLoading = true; try { await getNewToken(); + /// fetch data - String? _response = await _networkHelper - .authorizedFetch(baseEndpoint + '?' + query, headers); - print(_response);//addarameters here + String? _response = await _networkHelper.authorizedFetch( + baseEndpoint + '?' + query, headers); + print(_response); //addarameters here if (_response != null) { final ScheduleOfClassesModel data = classScheduleModelFromJson(_response); @@ -32,7 +33,6 @@ class ScheduleOfClassesService { } else { /// parse data - return false; } _isLoading = false; @@ -87,7 +87,7 @@ class ScheduleOfClassesService { final Map tokenHeaders = { "content-type": 'application/x-www-form-urlencoded', "Authorization": - "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" + "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" }; //TODO: Have subscription set up for webreg mobile. Investigate alternatives to Dio. try { @@ -95,7 +95,7 @@ class ScheduleOfClassesService { // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); // TODO(Peter): Insert your own authetication token for demo. Will be replaced by application credentials headers["Authorization"] = "Bearer " + ""; - //response["access_token"]; + //response["access_token"]; return true; } catch (e) { diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index b7167d0..14a66ee 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -189,11 +189,6 @@ class Calendar extends StatelessWidget { 'WLH 109', color), ])), BuildInfo(), - TextButton( - child: const Text('Search Detail Page'), - onPressed: () { - Navigator.pushNamed(context, RoutePaths.SearchDetail); - }) ], )); } diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index e928673..e3fedc1 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; class SearchBar extends StatelessWidget { final VoidCallback setOpenFilters; @@ -193,8 +195,7 @@ class Search extends StatefulWidget { class _SearchState extends State { Widget _icon = Icon(Icons.search, size: 20, color: darkGray); - final _searchText = TextEditingController(); - + ScheduleOfClassesProvider provider = ScheduleOfClassesProvider(); @override Widget build(BuildContext context) { return Container( @@ -218,18 +219,18 @@ class _SearchState extends State { Expanded( child: TextField( onChanged: (text) { - // _searchText = text; + provider.searchBarController.text = text; if (text.length > 0) { _icon = GestureDetector( child: Icon(Icons.close, size: 20, color: darkGray), onTap: () { - _searchText.clear(); + provider.searchBarController.clear(); }); } else { _icon = Icon(Icons.search, size: 20, color: darkGray); } }, - controller: _searchText, + controller: provider.searchBarController, autofocus: true, textAlignVertical: TextAlignVertical.center, style: TextStyle(fontSize: 16), diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 3c7d96d..89a07a4 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,38 +1,118 @@ +import 'dart:html'; + import 'package:flutter/material.dart'; +import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; -import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; /* This UI page is used to show course offerings/details (prerequisites, sections, finals) * after the user has searched and selected a course. */ -class SearchDetail extends StatefulWidget { - const SearchDetail({Key? key}) : super(key: key); +class SearchDetail extends StatelessWidget { + const SearchDetail({Key? key, required this.data}) : super(key: key); + final CourseData data; @override - _SearchDetailState createState() => _SearchDetailState(); -} - -class _SearchDetailState extends State { - late ScheduleOfClassesProvider classesProvider; - @override - Widget build(BuildContext context) { - classesProvider = ScheduleOfClassesProvider(); - return Scaffold( + Widget build(BuildContext context) => Scaffold( appBar: AppBar( - centerTitle: true, - title: const Text('Class Details'), - ), - body: FutureBuilder( - future: classesProvider.scheduleOfClassesService.fetchClasses( - 'departments=CSE&termCode=SP21&limit=5'), - builder: (context, response){ - if (response.hasData){ - return Text(classesProvider.scheduleOfClassesService.classes!.toJson().toString()); - - }else{ - return const CircularProgressIndicator(); - } - }) - ); + centerTitle: true, + title: Text( + "${data.departmentCode} ${data.courseCode} \n${data.courseTitle}")), + body: Column( + children: [coursePrereqs(), courseDetails()], + )); + + Card coursePrereqs() { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + child: Container( + width: double.maxFinite, + height: 30, + child: const Center( + child: Text( + 'Course Prerequisites and Level Restrictions', + style: TextStyle( + color: ColorSecondary, + fontWeight: FontWeight.bold, + ), + )))); + } + + Column courseDetails() { + return Column(children: [ + Card( + margin: EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + child: + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + '${data.sections![0].instructors![0].instructorName}', + style: TextStyle( + color: ColorSecondary, + ), + ), + Row(children: [ + Text( + data.sections![0].sectionCode!, + style: TextStyle(color: darkGray), + ), + Text(" LE "), + Text(" " + + data.sections![0].recurringMeetings![0].dayCodeIsis! + + " "), + Text(" " + + data.sections![0].recurringMeetings![0].startTime! + + "-"), + Text(data.sections![0].recurringMeetings![0].endTime! + " "), + Text(data.sections![0].recurringMeetings![0].buildingCode! + " "), + Text(data.sections![0].recurringMeetings![0].roomCode! + " "), + ]), + Row(children: [ + Text("Enrolled: ${data.sections![0].enrolledQuantity} "), + Text("Capacity: ${data.sections![0].capacityQuantity}"), + ]), + ])), + ]); + // return Container( + // height: 500, + // child: ListView( + // children: [ + // Text('Title of class: ${data.courseTitle}'), + // Text( + // 'Instructors name: ${data.sections![0].instructors![0].instructorName}'), + // Text(data.toJson().toString()) + // ], + // ), + // ); } } + + + +// class _SearchDetailState extends State { +// late ScheduleOfClassesProvider classesProvider; +// @override +// Widget build(BuildContext context) { +// classesProvider = ScheduleOfClassesProvider(); +// return Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: const Text('Class Details'), +// ), +// body: FutureBuilder( +// future: classesProvider.scheduleOfClassesService +// .fetchClasses('departments=CSE&termCode=SP21&limit=5'), +// builder: (context, response) { +// if (response.hasData) { +// return Text(classesProvider.scheduleOfClassesService.classes! +// .toJson() +// .toString()); +// } else { +// return const CircularProgressIndicator(); +// } +// })); +// } +// } diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 1d980a2..88c8277 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -1,104 +1,370 @@ import 'package:flutter/material.dart'; +import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; -// contains search bar and search results class SearchView extends StatefulWidget { @override _SearchViewState createState() => _SearchViewState(); } class _SearchViewState extends State { - bool openFilters = false; - List selectedFilters = List.filled(3, false); - List filters = ['Show lower division', 'Show upper division', 'Show gradudate division']; - // Map filters = {'Show lower division': false, 'Show upper division': false, 'Show gradudate division': false}; - - void setOpenFilters() { - this.setState(() { - openFilters = !openFilters; - }); - } - + late Future> classes; + late String searchString; + late ScheduleOfClassesProvider classesProvider; + bool showList = false; @override - Widget build(BuildContext context) { + Widget build(BuildContext context) { + Widget _icon = Icon(Icons.search, size: 20, color: darkGray); + classesProvider = ScheduleOfClassesProvider(); return Scaffold( - appBar: PreferredSize( - preferredSize: Size.fromHeight(kToolbarHeight), - child: Hero( - tag: 'search_bar', - child: SearchBar(setOpenFilters), - ), - ), - body: Stack( - children: [ - Center( - child: Text( - "Search by course code\ne.g. ANTH 23", - style: TextStyle(color: darkGray, fontSize: 18), - textAlign: TextAlign.center, - ) - ), - openFilters ? Positioned( - top: 0, - left: 0, - child: Container( - width: MediaQuery.of(context).size.width, - padding: EdgeInsets.symmetric(vertical: 10), - height: 120, - decoration: new BoxDecoration(color: ColorPrimary), - child: ListView.builder( - padding: const EdgeInsets.all(8), - itemCount: selectedFilters.length, - itemBuilder: (BuildContext context, int index) { - return Container( - height: 30, - padding: EdgeInsets.symmetric(horizontal: 35), - // color: Colors.amber[colorCodes[index]], - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(filters[index], style: TextStyle(color: Colors.white, fontSize: 16)), - Switch( - value: selectedFilters[index], - onChanged: (value) { + appBar: AppBar( + titleSpacing: 0.0, + centerTitle: true, + title: Container( + decoration: new BoxDecoration( + color: lightGray, + borderRadius: new BorderRadius.all(Radius.circular(100.0)), + border: Border.all(width: 1.0, color: Color(0xFF034263)), + ), + margin: EdgeInsets.symmetric(vertical: 10.0), + child: Container( + height: 35, + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(left: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TermDropdown(), + Container( + width: 1.0, + color: darkGray, + margin: const EdgeInsets.only(right: 10.0), + ) + ], + )), + Expanded( + child: TextField( + onSubmitted: (text) { setState(() { - selectedFilters[index] = value; + searchString = text; + showList = true; + //classesProvider.scheduleOfClassesService.fetchClasses('departments=${text.split(' ')[0]}&termCode=WI21&limit=5&courseCodes=${text.split(' ')[1]}'); }); }, - activeTrackColor: Colors.green, - activeColor: Colors.white, + autofocus: true, + textAlignVertical: TextAlignVertical.center, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 0), + hintText: 'Search', + isDense: true, + ), ), - ] - ) - ); - } + ), + Container( + margin: const EdgeInsets.only(right: 10.0), + child: _icon, + ), + ], + )), + ), + automaticallyImplyLeading: false, + leading: Center( + child: IconButton( + icon: Icon(Icons.arrow_back, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: () { + Navigator.pop(context); + }), + ), + actions: [ + IconButton( + icon: Icon(Icons.filter_list, color: Colors.white), + padding: EdgeInsets.symmetric(horizontal: 9), + alignment: Alignment.centerLeft, + iconSize: 25, + onPressed: () {} //this.setOpenFilters, + ), + ]), + body: body(showList)); + } + + Widget body(bool showList) { + if (showList) { + return FutureBuilder( + future: classesProvider.scheduleOfClassesService.fetchClasses( + 'departments=${searchString.split(' ')[0]}&termCode=SP21&limit=5&courseCodes=${searchString.split(' ')[1]}'), + builder: (BuildContext context, AsyncSnapshot response) { + if (response.hasData) { + return buildResultsList(context); + } else { + return const CircularProgressIndicator(); + } + }, + ); + } else { + return Center( + child: Text( + "Search by course code\ne.g. ANTH 23", + style: TextStyle(color: darkGray, fontSize: 18), + textAlign: TextAlign.center, + )); + } + } + + Widget buildResultsList(BuildContext context) { + // List arguments = widget.args; + // loops through and adds buttons for the user to click on + /// add content into for loop here + // for (CourseData course in arguments) {} + ScheduleOfClassesModel model = + classesProvider.scheduleOfClassesService.classes!; + CourseData course = model.courses![0]; + List contentList = []; + contentList.add(ListTile( + title: Container( + child: Row(children: [ + // units icon + Container( + height: 30, + width: 30, + decoration: new BoxDecoration( + color: lightGray, + shape: BoxShape.circle, ), - // ListView( - // children: [ - // ListTile( - // title: Text('Show lower division', style: TextStyle(color: Colors.white)), - // // selected: _selectedFilters[0], - // // onTap: () => _selectedFilters[0] = true, - // ), - // ListTile( - // leading: Icon(Icons.favorite), - // title: Text('Show upper division'), - // // selected: _selectedFilters[1], - // // onTap: () => _selectedFilters[1] = true, - // ), - // ListTile( - // leading: Icon(Icons.favorite), - // title: Text('Show graduate division'), - // // selected: _selectedFilters[2], - // // onTap: () => _selectedFilters[2] = true, - // ), - // ] - // ) - ) - ) : SizedBox(), - ], + margin: EdgeInsets.only(right: 10), + child: Center( + child: Text('4' // TODO + ))), + // course info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(course.departmentCode! + " " + course.courseCode!, + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), // TODO + ], + ), + Text(course.courseTitle!) // TODO + ], + )) + ]), ), + onTap: () { + Navigator.pushNamed(context, RoutePaths.SearchDetail, + arguments: course); + // do something + }, + )); + // adds SizedBox to have a grey underline for the last item in the list + //contentList.add(SizedBox()); + ListView contentListView = ListView( + // physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: + ListTile.divideTiles(tiles: contentList, context: context).toList(), + ); + return Column( + children: [Expanded(child: contentListView)], ); } -} \ No newline at end of file +} + + + +// // contains search bar and search results +// class SearchView extends StatefulWidget { +// @override +// _SearchViewState createState() => _SearchViewState(); +// } + +// class _SearchViewState extends State { +// late ScheduleOfClassesProvider classesProvider; +// bool openFilters = false; +// List selectedFilters = List.filled(3, false); +// List filters = [ +// 'Show lower division', +// 'Show upper division', +// 'Show gradudate division' +// ]; +// // Map filters = {'Show lower division': false, 'Show upper division': false, 'Show gradudate division': false}; +// void setOpenFilters() { +// this.setState(() { +// openFilters = !openFilters; +// }); +// } + +// @override +// Widget build(BuildContext context) { +// classesProvider = ScheduleOfClassesProvider(); + +// return Scaffold( +// appBar: PreferredSize( +// preferredSize: Size.fromHeight(kToolbarHeight), +// child: Hero( +// tag: 'search_bar', +// child: SearchBar(setOpenFilters), +// ), +// ), +// body: Stack( +// children: [ + // Center( + // child: Text( + // "Search by course code\ne.g. ANTH 23", + // style: TextStyle(color: darkGray, fontSize: 18), + // textAlign: TextAlign.center, + // ) + // ), + // FutureBuilder( + // future: classesProvider.scheduleOfClassesService.fetchClasses( + // 'departments=${classesProvider.searchBarController.text.split(' ')[0]}&termCode=SP21&limit=5&courseCodes=${classesProvider.searchBarController.text.split(' ')[1]}'), + // builder: (BuildContext context, AsyncSnapshot response) { + // if (response.hasData) { + // return buildResultsList(context); + // } else { + // return const CircularProgressIndicator(); + // } + // }, + // ), + +// if (openFilters) +// Positioned( +// top: 0, +// left: 0, +// child: Container( +// width: MediaQuery.of(context).size.width, +// padding: EdgeInsets.symmetric(vertical: 10), +// height: 120, +// decoration: new BoxDecoration(color: ColorPrimary), +// child: ListView.builder( +// padding: const EdgeInsets.all(8), +// itemCount: selectedFilters.length, +// itemBuilder: (BuildContext context, int index) { +// return Container( +// height: 30, +// padding: EdgeInsets.symmetric(horizontal: 35), +// // color: Colors.amber[colorCodes[index]], +// child: Row( +// mainAxisAlignment: +// MainAxisAlignment.spaceBetween, +// children: [ +// Text(filters[index], +// style: const TextStyle( +// color: Colors.white, fontSize: 16)), +// Switch( +// value: selectedFilters[index], +// onChanged: (value) { +// setState(() { +// selectedFilters[index] = value; +// }); +// }, +// activeTrackColor: Colors.green, +// activeColor: Colors.white, +// ), +// ])); +// }), +// // ListView( +// // children: [ +// // ListTile( +// // title: Text('Show lower division', style: TextStyle(color: Colors.white)), +// // // selected: _selectedFilters[0], +// // // onTap: () => _selectedFilters[0] = true, +// // ), +// // ListTile( +// // leading: Icon(Icons.favorite), +// // title: Text('Show upper division'), +// // // selected: _selectedFilters[1], +// // // onTap: () => _selectedFilters[1] = true, +// // ), +// // ListTile( +// // leading: Icon(Icons.favorite), +// // title: Text('Show graduate division'), +// // // selected: _selectedFilters[2], +// // // onTap: () => _selectedFilters[2] = true, +// // ), +// // ] +// // ) +// )) +// else +// SizedBox(), +// ], +// ), +// ); +// } + + // Widget buildResultsList(BuildContext context) { + // // List arguments = widget.args; + // // loops through and adds buttons for the user to click on + // /// add content into for loop here + // // for (CourseData course in arguments) {} + // ScheduleOfClassesModel model = + // classesProvider.scheduleOfClassesService.classes!; + // CourseData course = model.courses![0]; + // List contentList = []; + // contentList.add(ListTile( + // title: Container( + // child: Row(children: [ + // // units icon + // Container( + // height: 30, + // width: 30, + // decoration: new BoxDecoration( + // color: lightGray, + // shape: BoxShape.circle, + // ), + // margin: EdgeInsets.only(right: 10), + // child: Center( + // child: Text('4' // TODO + // ))), + // // course info + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text(course.courseCode!, + // style: TextStyle( + // fontSize: 18, fontWeight: FontWeight.bold)), // TODO + // ], + // ), + // Text(course.courseTitle!) // TODO + // ], + // )) + // ]), + // ), + // onTap: () { + // Navigator.pushNamed(context, RoutePaths.SearchDetail, + // arguments: course); + // // do something + // }, + // )); + // // adds SizedBox to have a grey underline for the last item in the list + // //contentList.add(SizedBox()); + // ListView contentListView = ListView( + // // physics: NeverScrollableScrollPhysics(), + // shrinkWrap: true, + // children: + // ListTile.divideTiles(tiles: contentList, context: context).toList(), + // ); + // return Column( + // children: [Expanded(child: contentListView)], + // ); + // } +// } + + From 54030b9a83e83f0c6b05d20dd36672c9ff0f7557 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez Date: Thu, 16 Dec 2021 15:23:25 -0800 Subject: [PATCH 10/24] Section handling in progress --- lib/core/services/schedule_of_classes.dart | 2 +- lib/ui/search/search_detail.dart | 240 +++++++++++++++++---- pubspec.lock | 7 + pubspec.yaml | 1 + 4 files changed, 210 insertions(+), 40 deletions(-) diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 027da84..97fbcad 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -93,7 +93,7 @@ class ScheduleOfClassesService { try { // var response = await _networkHelper.authorizedPost( // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); - // TODO(Peter): Insert your own authetication token for demo. Will be replaced by application credentials + // TODO(Peter): Insert your own authenitcation token for demo. Will be replaced by application credentials headers["Authorization"] = "Bearer " + ""; //response["access_token"]; diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 89a07a4..236210c 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,5 +1,5 @@ import 'dart:html'; - +import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; @@ -18,7 +18,7 @@ class SearchDetail extends StatelessWidget { title: Text( "${data.departmentCode} ${data.courseCode} \n${data.courseTitle}")), body: Column( - children: [coursePrereqs(), courseDetails()], + children: [coursePrereqs(), courseDetails(data.sections![0])], )); Card coursePrereqs() { @@ -39,43 +39,172 @@ class SearchDetail extends StatelessWidget { )))); } - Column courseDetails() { - return Column(children: [ - Card( - margin: EdgeInsets.all(10), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), + Widget courseDetails(SectionData section) { + List meetings = section.recurringMeetings!; + MeetingData finalMeeting = section.additionalMeetings![0]; + SectionData lectureSection = SectionData(); + SectionData discussionSection = SectionData(); + List lectureSections = []; + List discussionSections = []; + MeetingData finalSection; + for (SectionData sections in data.sections!) { + if (sections.instructionType == 'LE') { + lectureSection = sections; + } + } + for (SectionData sections in data.sections!) { + if (sections.instructionType == 'DI') { + discussionSection = sections; + } + } + + // Find all lecture sections + // for (MeetingData section in meetings) { + // if (section.meetingType == 'LE') { + // lectureSections.add(section); + // } else if (section.meetingType == 'DI') { + // discussionSections.add(section); + // } else if (section.meetingType == 'FI') { + // finalSection = section; + // } + // } + + String instructorName = ""; + for (Instructor instructor in section.instructors!) { + if (instructor.primaryInstructor!) { + instructorName = instructor.instructorName!; + } + } + + // DAY Section + List days = [ + const Text('M', style: TextStyle(color: darkGray)), + const Text('T', style: TextStyle(color: darkGray)), + const Text('W', style: TextStyle(color: darkGray)), + const Text('T', style: TextStyle(color: darkGray)), + const Text('F', style: TextStyle(color: darkGray)) + ]; + for (MeetingData meeting in meetings) { + if (meeting.dayCode == 'MO') { + days[0] = const Text('M', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'TU') { + days[1] = const Text('T', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'WE') { + days[2] = const Text('W', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'TH') { + days[3] = const Text('T', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'FR') { + days[4] = const Text('F', style: TextStyle(color: ColorSecondary)); + } + } + + // String startTime = ""; + // String endTime = ""; + // // Calculate time + // int start = int.parse(meetings[0].startTime!); + // if (start > 1200) { + // int temp = start - 1200; + // startTime = temp.toString(). + // } + + // DateTime startTime = DateFormat.jm().parse(meetings[0].startTime!); + // DateTime endTime = DateFormat.jm().parse(meetings[0].endTime!); + Widget lectureCard = Card( + margin: EdgeInsets.all(2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(instructorName, style: const TextStyle(color: ColorSecondary)), + Padding(padding: EdgeInsets.all(5)), + Row(children: [ + Text(section.sectionCode!, style: const TextStyle(color: darkGray)), + const Padding(padding: EdgeInsets.all(10)), + const Text('LE'), + const Padding(padding: EdgeInsets.all(10)), + ...days, + const Padding(padding: EdgeInsets.all(10)), + Text(meetings[0].startTime! + ' - ' + meetings[0].endTime!), + const Padding(padding: EdgeInsets.all(10)), + Text(meetings[0].buildingCode!), + const Padding(padding: EdgeInsets.all(10)), + Text(meetings[0].roomCode!), + ]), + ])); + days = [ + const Text('M', style: TextStyle(color: darkGray)), + const Text('T', style: TextStyle(color: darkGray)), + const Text('W', style: TextStyle(color: darkGray)), + const Text('T', style: TextStyle(color: darkGray)), + const Text('F', style: TextStyle(color: darkGray)) + ]; + for (MeetingData meeting in meetings) { + if (meeting.dayCode == 'MO') { + days[0] = const Text('M', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'TU') { + days[1] = const Text('T', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'WE') { + days[2] = const Text('W', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'TH') { + days[3] = const Text('T', style: TextStyle(color: ColorSecondary)); + } else if (meeting.dayCode == 'FR') { + days[4] = const Text('F', style: TextStyle(color: ColorSecondary)); + } + } + Widget discussionCard = Card( + margin: EdgeInsets.all(2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + child: Row(children: [ + Text(section.sectionCode!, style: const TextStyle(color: darkGray)), + const Padding(padding: EdgeInsets.all(10)), + const Text('DI'), + const Padding(padding: EdgeInsets.all(10)), + ...days, + const Padding(padding: EdgeInsets.all(10)), + Text(discussionSection.recurringMeetings![0].startTime! + + ' - ' + + discussionSection.recurringMeetings![0].endTime!), + const Padding(padding: EdgeInsets.all(10)), + Text(discussionSection.recurringMeetings![0].buildingCode!), + const Padding(padding: EdgeInsets.all(10)), + Text(discussionSection.recurringMeetings![0].roomCode!), + ])); + + Widget finalCard = Card( + margin: EdgeInsets.all(2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + child: Row( + children: [ + const Text( + "FINAL", + style: TextStyle(color: darkGray), ), - child: - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '${data.sections![0].instructors![0].instructorName}', - style: TextStyle( - color: ColorSecondary, - ), - ), - Row(children: [ - Text( - data.sections![0].sectionCode!, - style: TextStyle(color: darkGray), - ), - Text(" LE "), - Text(" " + - data.sections![0].recurringMeetings![0].dayCodeIsis! + - " "), - Text(" " + - data.sections![0].recurringMeetings![0].startTime! + - "-"), - Text(data.sections![0].recurringMeetings![0].endTime! + " "), - Text(data.sections![0].recurringMeetings![0].buildingCode! + " "), - Text(data.sections![0].recurringMeetings![0].roomCode! + " "), - ]), - Row(children: [ - Text("Enrolled: ${data.sections![0].enrolledQuantity} "), - Text("Capacity: ${data.sections![0].capacityQuantity}"), - ]), - ])), - ]); + const Padding(padding: EdgeInsets.all(10)), + Text(lectureSection.additionalMeetings![0].meetingDate!), + const Padding(padding: EdgeInsets.all(10)), + Text(lectureSection.additionalMeetings![0].startTime! + + ' - ' + + lectureSection.additionalMeetings![0].endTime!), + const Padding(padding: EdgeInsets.all(10)), + Text(lectureSection.additionalMeetings![0].buildingCode!), + const Padding(padding: EdgeInsets.all(10)), + Text(lectureSection.additionalMeetings![0].roomCode!), + ], + ), + ); + return Column( + children: [lectureCard, discussionCard, finalCard], + ); + // Row(children: [ + // Text("Enrolled: ${data.sections![0].enrolledQuantity} "), + // Text("Capacity: ${data.sections![0].capacityQuantity}"), + // ]), + + // return Column(children: []); // return Container( // height: 500, // child: ListView( @@ -90,7 +219,40 @@ class SearchDetail extends StatelessWidget { } } - +// Card( +// margin: EdgeInsets.all(10), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(5.0), +// ), +// child: +// Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ +// Text( +// '${data.sections![0].instructors![0].instructorName}', +// style: TextStyle( +// color: ColorSecondary, +// ), +// ), +// Row(children: [ +// Text( +// data.sections![0].sectionCode!, +// style: TextStyle(color: darkGray), +// ), +// Text(" LE "), +// Text(" " + +// data.sections![0].recurringMeetings![0].dayCodeIsis! + +// " "), +// Text(" " + +// data.sections![0].recurringMeetings![0].startTime! + +// "-"), +// Text(data.sections![0].recurringMeetings![0].endTime! + " "), +// Text(data.sections![0].recurringMeetings![0].buildingCode! + " "), +// Text(data.sections![0].recurringMeetings![0].roomCode! + " "), +// ]), +// Row(children: [ +// Text("Enrolled: ${data.sections![0].enrolledQuantity} "), +// Text("Capacity: ${data.sections![0].capacityQuantity}"), +// ]), +// ])) // class _SearchDetailState extends State { // late ScheduleOfClassesProvider classesProvider; diff --git a/pubspec.lock b/pubspec.lock index df4975b..40560f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -107,6 +107,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6f8d452..c9c55a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: package_info_plus: 1.0.1 dio: 4.0.0 get: 4.1.4 + intl: 0.17.0 dev_dependencies: flutter_test: sdk: flutter From dca87aa6b9473c943da6801013409be79ad82327 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Fri, 31 Dec 2021 09:34:36 -0800 Subject: [PATCH 11/24] Polished version of schedule of classes integration --- lib/app_networking.dart | 2 - lib/core/services/schedule_of_classes.dart | 58 +-- lib/ui/calendar/calendar.dart | 1 - lib/ui/calendar/calendar_card.dart | 1 - lib/ui/common/build_info.dart | 1 - lib/ui/search/search_detail.dart | 487 ++++++++++++--------- lib/ui/search/search_view.dart | 273 ++---------- 7 files changed, 317 insertions(+), 506 deletions(-) diff --git a/lib/app_networking.dart b/lib/app_networking.dart index d166fd4..2b206d8 100644 --- a/lib/app_networking.dart +++ b/lib/app_networking.dart @@ -149,9 +149,7 @@ class NetworkHelper { } } on TimeoutException catch (e) { // Display an alert - i.e. no internet - print(e); } catch (err) { - print(err); return null; } } diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 97fbcad..92ff14d 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -8,12 +8,12 @@ class ScheduleOfClassesService { DateTime? _lastUpdated; String? _error; ScheduleOfClassesModel? classes; - final NetworkHelper _networkHelper = NetworkHelper(); + final NetworkHelper _networkHelper = const NetworkHelper(); final Map headers = { - "accept": "application/json", + 'accept': 'application/json', }; final String baseEndpoint = - "https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/classes/search"; + 'https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/classes/search'; Future fetchClasses(String query) async { _error = null; @@ -22,14 +22,12 @@ class ScheduleOfClassesService { await getNewToken(); /// fetch data - String? _response = await _networkHelper.authorizedFetch( + final String? _response = await _networkHelper.authorizedFetch( baseEndpoint + '?' + query, headers); - print(_response); //addarameters here if (_response != null) { final ScheduleOfClassesModel data = classScheduleModelFromJson(_response); - classes = data as ScheduleOfClassesModel; - print(classes!.courses.toString()); + classes = data; } else { /// parse data @@ -38,65 +36,25 @@ class ScheduleOfClassesService { _isLoading = false; return true; } catch (e) { - /// if the authorized fetch failed we know we have to refresh the - /// token for this service - print("IN CATCH"); - // if (e.toString().contains("401")) { - // print("Getting new token from fetchLocations"); - // if (await getNewToken()) { - // print("Getting new token from fetchLocations"); - // return await fetchClasses(); - // } - // } _error = e.toString(); - print(_error); _isLoading = false; return false; } } - // Future getNewToken() async { - // final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; - // final Map tokenHeaders = { - // "content-type": 'application/x-www-form-urlencoded', - // "Authorization": - // "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" - // }; - // try { - // final response = await _networkHelper.authorizedPost( - // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); - // - // headers["Authorization"] = "Bearer " + response["access_token"]; - // - // return true; - // } catch (e) { - // _error = e.toString(); - // return false; - // } - // } - bool get isLoading => _isLoading; String? get error => _error; DateTime? get lastUpdated => _lastUpdated; - ScheduleOfClassesModel? get _classes => classes; Future getNewToken() async { - final String tokenEndpoint = "https://api-qa.ucsd.edu:8243/token"; - final Map tokenHeaders = { - "content-type": 'application/x-www-form-urlencoded', - "Authorization": - "Basic djJlNEpYa0NJUHZ5akFWT0VRXzRqZmZUdDkwYTp2emNBZGFzZWpmaWZiUDc2VUJjNDNNVDExclVh" - }; - //TODO: Have subscription set up for webreg mobile. Investigate alternatives to Dio. + // TODO(p8gonzal): Have subscription set up for webreg mobile. Investigate alternatives to Dio. try { // var response = await _networkHelper.authorizedPost( // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); - // TODO(Peter): Insert your own authenitcation token for demo. Will be replaced by application credentials - headers["Authorization"] = "Bearer " + ""; - //response["access_token"]; - + // TODO(p8gonzal): Insert your own authenitcation token for demo. Will be replaced by application credentials + headers['Authorization'] = 'Bearer '; return true; } catch (e) { _error = e.toString(); diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 14a66ee..0f2ab34 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -92,7 +92,6 @@ class Calendar extends StatelessWidget { .difference(DateTime.parse(prefix + start)) .inMinutes .toDouble(); - print(diff.toString()); return diff; } diff --git a/lib/ui/calendar/calendar_card.dart b/lib/ui/calendar/calendar_card.dart index f499c3c..8ef58c6 100644 --- a/lib/ui/calendar/calendar_card.dart +++ b/lib/ui/calendar/calendar_card.dart @@ -23,7 +23,6 @@ class _CalendarCardState extends State { .difference(DateTime.parse(prefix + start)) .inMinutes .toDouble(); - print(diff.toString()); return diff; } diff --git a/lib/ui/common/build_info.dart b/lib/ui/common/build_info.dart index f05d8fd..3fb8418 100644 --- a/lib/ui/common/build_info.dart +++ b/lib/ui/common/build_info.dart @@ -49,7 +49,6 @@ class _BuildInfoState extends State { textAlign: TextAlign.center, )); } catch (err) { - print(err); return Container(); } } diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 236210c..4451c9d 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,6 +1,7 @@ -import 'dart:html'; -import 'package:intl/intl.dart'; +// ignore_for_file: always_specify_types + import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; @@ -16,20 +17,20 @@ class SearchDetail extends StatelessWidget { appBar: AppBar( centerTitle: true, title: Text( - "${data.departmentCode} ${data.courseCode} \n${data.courseTitle}")), + '${data.departmentCode} ${data.courseCode} \n${data.courseTitle}')), body: Column( - children: [coursePrereqs(), courseDetails(data.sections![0])], + children: [coursePrereqs(), courseDetails()], )); Card coursePrereqs() { return Card( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), + borderRadius: BorderRadius.circular(10.0), ), - child: Container( + child: const SizedBox( width: double.maxFinite, - height: 30, - child: const Center( + height: 50, + child: Center( child: Text( 'Course Prerequisites and Level Restrictions', style: TextStyle( @@ -39,106 +40,278 @@ class SearchDetail extends StatelessWidget { )))); } - Widget courseDetails(SectionData section) { - List meetings = section.recurringMeetings!; - MeetingData finalMeeting = section.additionalMeetings![0]; - SectionData lectureSection = SectionData(); - SectionData discussionSection = SectionData(); - List lectureSections = []; - List discussionSections = []; - MeetingData finalSection; - for (SectionData sections in data.sections!) { - if (sections.instructionType == 'LE') { - lectureSection = sections; + Widget courseDetails() { + // Determine types of sections + final List sectionTypes = []; + final List sectionCards = []; + final List sectionObjects = []; + for (final SectionData section in data.sections!) { + if (section.instructionType != 'LE' || !sectionTypes.contains('LE')) { + sectionTypes.add(section.instructionType!); + sectionObjects.add(section); } } - for (SectionData sections in data.sections!) { - if (sections.instructionType == 'DI') { - discussionSection = sections; - } + sectionTypes.add('FI'); + sectionObjects.add(SectionData()); + + //Build section cards for different instruction types + int sectionIndex = 0; + for (final String sectionType in sectionTypes.toList()) { + sectionCards + .add(buildSectionCard(sectionType, sectionObjects[sectionIndex])); + sectionIndex++; } + return Column(children: [...sectionCards]); + } - // Find all lecture sections - // for (MeetingData section in meetings) { - // if (section.meetingType == 'LE') { - // lectureSections.add(section); - // } else if (section.meetingType == 'DI') { - // discussionSections.add(section); - // } else if (section.meetingType == 'FI') { - // finalSection = section; - // } - // } + Card buildSectionCard(String sectionType, SectionData sectionObject) { + switch (sectionType) { + case 'LE': + { + // Accumalate all lecture meetings in section + SectionData lectureObject = SectionData(); + final List lectureMeetings = []; + for (final SectionData section in data.sections!) { + if (section.instructionType == 'LE') { + lectureMeetings.addAll(section.recurringMeetings!); + lectureObject = section; + } + } - String instructorName = ""; - for (Instructor instructor in section.instructors!) { - if (instructor.primaryInstructor!) { - instructorName = instructor.instructorName!; - } - } + // Instructor name + String instructorName = ''; + for (final Instructor instructor in lectureObject.instructors!) { + if (instructor.primaryInstructor!) { + instructorName = instructor.instructorName!; + } + } - // DAY Section - List days = [ - const Text('M', style: TextStyle(color: darkGray)), - const Text('T', style: TextStyle(color: darkGray)), - const Text('W', style: TextStyle(color: darkGray)), - const Text('T', style: TextStyle(color: darkGray)), - const Text('F', style: TextStyle(color: darkGray)) - ]; - for (MeetingData meeting in meetings) { - if (meeting.dayCode == 'MO') { - days[0] = const Text('M', style: TextStyle(color: ColorSecondary)); - } else if (meeting.dayCode == 'TU') { - days[1] = const Text('T', style: TextStyle(color: ColorSecondary)); - } else if (meeting.dayCode == 'WE') { - days[2] = const Text('W', style: TextStyle(color: ColorSecondary)); - } else if (meeting.dayCode == 'TH') { - days[3] = const Text('T', style: TextStyle(color: ColorSecondary)); - } else if (meeting.dayCode == 'FR') { - days[4] = const Text('F', style: TextStyle(color: ColorSecondary)); - } - } + // DAY Section + List days = resetDays(); + days = setDays(days, lectureMeetings); - // String startTime = ""; - // String endTime = ""; - // // Calculate time - // int start = int.parse(meetings[0].startTime!); - // if (start > 1200) { - // int temp = start - 1200; - // startTime = temp.toString(). - // } + // Time parsing + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm() + .format(DateTime.parse(prefix + lectureMeetings[0].startTime!)); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + lectureMeetings[0].endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); - // DateTime startTime = DateFormat.jm().parse(meetings[0].startTime!); - // DateTime endTime = DateFormat.jm().parse(meetings[0].endTime!); - Widget lectureCard = Card( - margin: EdgeInsets.all(2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), - ), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(instructorName, style: const TextStyle(color: ColorSecondary)), - Padding(padding: EdgeInsets.all(5)), - Row(children: [ - Text(section.sectionCode!, style: const TextStyle(color: darkGray)), - const Padding(padding: EdgeInsets.all(10)), - const Text('LE'), - const Padding(padding: EdgeInsets.all(10)), - ...days, - const Padding(padding: EdgeInsets.all(10)), - Text(meetings[0].startTime! + ' - ' + meetings[0].endTime!), - const Padding(padding: EdgeInsets.all(10)), - Text(meetings[0].buildingCode!), - const Padding(padding: EdgeInsets.all(10)), - Text(meetings[0].roomCode!), - ]), - ])); - days = [ + // Room Code and Number parsing + final String room = lectureMeetings[0].buildingCode! + + ' ' + + lectureMeetings[0].roomCode!.substring(1); + + // Construct Card widget + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + margin: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 2.0, bottom: 0.0), + child: SizedBox( + height: 50, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 0.0, right: 0.0, top: 5, bottom: 10), + child: Text( + instructorName, + style: const TextStyle(color: ColorSecondary), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + lectureObject.sectionCode!, + style: const TextStyle(color: darkGray), + ), + Text(sectionType), + ...days, + Text(startTime + ' - ' + endTime), + Text(room) + ], + ) + ], + ), + ), + ); + } + case 'DI': + { + // Accumalate all discussion meetings in section + final SectionData discussionObject = sectionObject; + final List discussionMeetings = []; + discussionMeetings.addAll(discussionObject.recurringMeetings!); + + // DAY Section + List days = resetDays(); + days = setDays(days, discussionMeetings); + + // Time + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format( + DateTime.parse(prefix + discussionMeetings[0].startTime!)); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + discussionMeetings[0].endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room parsing + final String room = discussionMeetings[0].buildingCode! + + ' ' + + discussionMeetings[0].roomCode!.substring(1); + + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + margin: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 2.0, bottom: 0.0), + child: SizedBox( + height: 35, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + discussionObject.sectionCode!, + style: const TextStyle(color: darkGray), + ), + Text(sectionType), + ...days, + Text(startTime + ' - ' + endTime), + Text(room) + ], + ), + )); + } + case 'LA': + { + // Accumalate all lab meetings in section + final SectionData labObject = sectionObject; + final List labMeetings = []; + labMeetings.addAll(sectionObject.recurringMeetings!); + + // DAY Section + List days = resetDays(); + days = setDays(days, labMeetings); + + // Time + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm() + .format(DateTime.parse(prefix + labMeetings[0].startTime!)); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + labMeetings[0].endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room parsing + final String room = labMeetings[0].buildingCode! + + ' ' + + labMeetings[0].roomCode!.substring(1); + + // Construct Card widget + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + margin: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 2.0, bottom: 0.0), + child: SizedBox( + height: 35, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + labObject.sectionCode!, + style: const TextStyle(color: darkGray), + ), + Text(sectionType), + ...days, + Text(startTime + ' - ' + endTime), + Text(room) + ], + ), + ), + ); + } + case 'FI': + { + // Accumalate all lecture meetings in section + final List finalMeetings = []; + for (final SectionData section in data.sections!) { + if (section.instructionType == 'LE') { + finalMeetings.addAll(section.additionalMeetings!); + } + } + + // Time + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm() + .format(DateTime.parse(prefix + finalMeetings[0].startTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + finalMeetings[0].endTime!)); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + final String finalDate = DateFormat.MMMMd('en_US') + .format(DateTime.parse(finalMeetings[0].meetingDate!)); + + // Parse building code and room + final String room = ' ' + + finalMeetings[0].buildingCode! + + ' ' + + finalMeetings[0].roomCode!.substring(1); + + // Construct Card widget + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + margin: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 2.0, bottom: 0.0), + child: SizedBox( + height: 35, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Text( + 'FINAL', + style: TextStyle(color: darkGray), + ), + Text(finalDate + ' ' + startTime + ' - ' + endTime), + Text(room) + ], + ), + )); + } + default: + return const Card( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text('No data available.'), + ), + ); + } + } + + List resetDays() { + return [ const Text('M', style: TextStyle(color: darkGray)), const Text('T', style: TextStyle(color: darkGray)), const Text('W', style: TextStyle(color: darkGray)), const Text('T', style: TextStyle(color: darkGray)), const Text('F', style: TextStyle(color: darkGray)) ]; - for (MeetingData meeting in meetings) { + } + + List setDays(List days, List meetings) { + for (final MeetingData meeting in meetings) { if (meeting.dayCode == 'MO') { days[0] = const Text('M', style: TextStyle(color: ColorSecondary)); } else if (meeting.dayCode == 'TU') { @@ -151,130 +324,6 @@ class SearchDetail extends StatelessWidget { days[4] = const Text('F', style: TextStyle(color: ColorSecondary)); } } - Widget discussionCard = Card( - margin: EdgeInsets.all(2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), - ), - child: Row(children: [ - Text(section.sectionCode!, style: const TextStyle(color: darkGray)), - const Padding(padding: EdgeInsets.all(10)), - const Text('DI'), - const Padding(padding: EdgeInsets.all(10)), - ...days, - const Padding(padding: EdgeInsets.all(10)), - Text(discussionSection.recurringMeetings![0].startTime! + - ' - ' + - discussionSection.recurringMeetings![0].endTime!), - const Padding(padding: EdgeInsets.all(10)), - Text(discussionSection.recurringMeetings![0].buildingCode!), - const Padding(padding: EdgeInsets.all(10)), - Text(discussionSection.recurringMeetings![0].roomCode!), - ])); - - Widget finalCard = Card( - margin: EdgeInsets.all(2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5.0), - ), - child: Row( - children: [ - const Text( - "FINAL", - style: TextStyle(color: darkGray), - ), - const Padding(padding: EdgeInsets.all(10)), - Text(lectureSection.additionalMeetings![0].meetingDate!), - const Padding(padding: EdgeInsets.all(10)), - Text(lectureSection.additionalMeetings![0].startTime! + - ' - ' + - lectureSection.additionalMeetings![0].endTime!), - const Padding(padding: EdgeInsets.all(10)), - Text(lectureSection.additionalMeetings![0].buildingCode!), - const Padding(padding: EdgeInsets.all(10)), - Text(lectureSection.additionalMeetings![0].roomCode!), - ], - ), - ); - return Column( - children: [lectureCard, discussionCard, finalCard], - ); - // Row(children: [ - // Text("Enrolled: ${data.sections![0].enrolledQuantity} "), - // Text("Capacity: ${data.sections![0].capacityQuantity}"), - // ]), - - // return Column(children: []); - // return Container( - // height: 500, - // child: ListView( - // children: [ - // Text('Title of class: ${data.courseTitle}'), - // Text( - // 'Instructors name: ${data.sections![0].instructors![0].instructorName}'), - // Text(data.toJson().toString()) - // ], - // ), - // ); + return days; } } - -// Card( -// margin: EdgeInsets.all(10), -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(5.0), -// ), -// child: -// Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ -// Text( -// '${data.sections![0].instructors![0].instructorName}', -// style: TextStyle( -// color: ColorSecondary, -// ), -// ), -// Row(children: [ -// Text( -// data.sections![0].sectionCode!, -// style: TextStyle(color: darkGray), -// ), -// Text(" LE "), -// Text(" " + -// data.sections![0].recurringMeetings![0].dayCodeIsis! + -// " "), -// Text(" " + -// data.sections![0].recurringMeetings![0].startTime! + -// "-"), -// Text(data.sections![0].recurringMeetings![0].endTime! + " "), -// Text(data.sections![0].recurringMeetings![0].buildingCode! + " "), -// Text(data.sections![0].recurringMeetings![0].roomCode! + " "), -// ]), -// Row(children: [ -// Text("Enrolled: ${data.sections![0].enrolledQuantity} "), -// Text("Capacity: ${data.sections![0].capacityQuantity}"), -// ]), -// ])) - -// class _SearchDetailState extends State { -// late ScheduleOfClassesProvider classesProvider; -// @override -// Widget build(BuildContext context) { -// classesProvider = ScheduleOfClassesProvider(); -// return Scaffold( -// appBar: AppBar( -// centerTitle: true, -// title: const Text('Class Details'), -// ), -// body: FutureBuilder( -// future: classesProvider.scheduleOfClassesService -// .fetchClasses('departments=CSE&termCode=SP21&limit=5'), -// builder: (context, response) { -// if (response.hasData) { -// return Text(classesProvider.scheduleOfClassesService.classes! -// .toJson() -// .toString()); -// } else { -// return const CircularProgressIndicator(); -// } -// })); -// } -// } diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 88c8277..77a7d30 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -1,9 +1,11 @@ +// ignore_for_file: use_key_in_widget_constructors, always_specify_types + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; -import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class SearchView extends StatefulWidget { @override @@ -17,20 +19,20 @@ class _SearchViewState extends State { bool showList = false; @override Widget build(BuildContext context) { - Widget _icon = Icon(Icons.search, size: 20, color: darkGray); + const Widget _icon = Icon(Icons.search, size: 20, color: darkGray); classesProvider = ScheduleOfClassesProvider(); return Scaffold( appBar: AppBar( titleSpacing: 0.0, centerTitle: true, title: Container( - decoration: new BoxDecoration( + decoration: BoxDecoration( color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), - border: Border.all(width: 1.0, color: Color(0xFF034263)), + borderRadius: const BorderRadius.all(Radius.circular(100.0)), + border: Border.all(width: 1.0, color: const Color(0xFF034263)), ), - margin: EdgeInsets.symmetric(vertical: 10.0), - child: Container( + margin: const EdgeInsets.symmetric(vertical: 10.0), + child: SizedBox( height: 35, child: Row( children: [ @@ -50,17 +52,16 @@ class _SearchViewState extends State { )), Expanded( child: TextField( - onSubmitted: (text) { + onSubmitted: (String text) { setState(() { searchString = text; showList = true; - //classesProvider.scheduleOfClassesService.fetchClasses('departments=${text.split(' ')[0]}&termCode=WI21&limit=5&courseCodes=${text.split(' ')[1]}'); }); }, autofocus: true, textAlignVertical: TextAlignVertical.center, - style: TextStyle(fontSize: 16), - decoration: InputDecoration( + style: const TextStyle(fontSize: 16), + decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 0), hintText: 'Search', @@ -78,8 +79,8 @@ class _SearchViewState extends State { automaticallyImplyLeading: false, leading: Center( child: IconButton( - icon: Icon(Icons.arrow_back, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), + icon: const Icon(Icons.arrow_back, color: Colors.white), + padding: const EdgeInsets.symmetric(horizontal: 9), alignment: Alignment.centerLeft, iconSize: 25, onPressed: () { @@ -88,8 +89,8 @@ class _SearchViewState extends State { ), actions: [ IconButton( - icon: Icon(Icons.filter_list, color: Colors.white), - padding: EdgeInsets.symmetric(horizontal: 9), + icon: const Icon(Icons.filter_list, color: Colors.white), + padding: const EdgeInsets.symmetric(horizontal: 9), alignment: Alignment.centerLeft, iconSize: 25, onPressed: () {} //this.setOpenFilters, @@ -102,7 +103,7 @@ class _SearchViewState extends State { if (showList) { return FutureBuilder( future: classesProvider.scheduleOfClassesService.fetchClasses( - 'departments=${searchString.split(' ')[0]}&termCode=SP21&limit=5&courseCodes=${searchString.split(' ')[1]}'), + 'subjectCodes=${searchString.split(' ')[0]}&termCode=SP21'), builder: (BuildContext context, AsyncSnapshot response) { if (response.hasData) { return buildResultsList(context); @@ -112,9 +113,9 @@ class _SearchViewState extends State { }, ); } else { - return Center( + return const Center( child: Text( - "Search by course code\ne.g. ANTH 23", + 'Search by course code\ne.g. ANTH 23', style: TextStyle(color: darkGray, fontSize: 18), textAlign: TextAlign.center, )); @@ -125,27 +126,24 @@ class _SearchViewState extends State { // List arguments = widget.args; // loops through and adds buttons for the user to click on /// add content into for loop here - // for (CourseData course in arguments) {} - ScheduleOfClassesModel model = + final ScheduleOfClassesModel model = classesProvider.scheduleOfClassesService.classes!; - CourseData course = model.courses![0]; - List contentList = []; - contentList.add(ListTile( - title: Container( - child: Row(children: [ + final List contentList = []; + for (final CourseData course in model.courses!) { + contentList.add(ListTile( + title: Row(children: [ // units icon Container( height: 30, width: 30, - decoration: new BoxDecoration( + decoration: const BoxDecoration( color: lightGray, shape: BoxShape.circle, ), - margin: EdgeInsets.only(right: 10), - child: Center( - child: Text('4' // TODO - ))), - // course info + margin: const EdgeInsets.only(right: 10), + child: const Center(child: Text('4'))), + + // Course info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -153,26 +151,23 @@ class _SearchViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(course.departmentCode! + " " + course.courseCode!, - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), // TODO + Text(course.departmentCode! + ' ' + course.courseCode!, + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)) ], ), - Text(course.courseTitle!) // TODO + Text(course.courseTitle!) ], )) ]), - ), - onTap: () { - Navigator.pushNamed(context, RoutePaths.SearchDetail, - arguments: course); - // do something - }, - )); + onTap: () { + Navigator.pushNamed(context, RoutePaths.SearchDetail, + arguments: course); + }, + )); + } // adds SizedBox to have a grey underline for the last item in the list - //contentList.add(SizedBox()); - ListView contentListView = ListView( - // physics: NeverScrollableScrollPhysics(), + final ListView contentListView = ListView( shrinkWrap: true, children: ListTile.divideTiles(tiles: contentList, context: context).toList(), @@ -182,189 +177,3 @@ class _SearchViewState extends State { ); } } - - - -// // contains search bar and search results -// class SearchView extends StatefulWidget { -// @override -// _SearchViewState createState() => _SearchViewState(); -// } - -// class _SearchViewState extends State { -// late ScheduleOfClassesProvider classesProvider; -// bool openFilters = false; -// List selectedFilters = List.filled(3, false); -// List filters = [ -// 'Show lower division', -// 'Show upper division', -// 'Show gradudate division' -// ]; -// // Map filters = {'Show lower division': false, 'Show upper division': false, 'Show gradudate division': false}; -// void setOpenFilters() { -// this.setState(() { -// openFilters = !openFilters; -// }); -// } - -// @override -// Widget build(BuildContext context) { -// classesProvider = ScheduleOfClassesProvider(); - -// return Scaffold( -// appBar: PreferredSize( -// preferredSize: Size.fromHeight(kToolbarHeight), -// child: Hero( -// tag: 'search_bar', -// child: SearchBar(setOpenFilters), -// ), -// ), -// body: Stack( -// children: [ - // Center( - // child: Text( - // "Search by course code\ne.g. ANTH 23", - // style: TextStyle(color: darkGray, fontSize: 18), - // textAlign: TextAlign.center, - // ) - // ), - // FutureBuilder( - // future: classesProvider.scheduleOfClassesService.fetchClasses( - // 'departments=${classesProvider.searchBarController.text.split(' ')[0]}&termCode=SP21&limit=5&courseCodes=${classesProvider.searchBarController.text.split(' ')[1]}'), - // builder: (BuildContext context, AsyncSnapshot response) { - // if (response.hasData) { - // return buildResultsList(context); - // } else { - // return const CircularProgressIndicator(); - // } - // }, - // ), - -// if (openFilters) -// Positioned( -// top: 0, -// left: 0, -// child: Container( -// width: MediaQuery.of(context).size.width, -// padding: EdgeInsets.symmetric(vertical: 10), -// height: 120, -// decoration: new BoxDecoration(color: ColorPrimary), -// child: ListView.builder( -// padding: const EdgeInsets.all(8), -// itemCount: selectedFilters.length, -// itemBuilder: (BuildContext context, int index) { -// return Container( -// height: 30, -// padding: EdgeInsets.symmetric(horizontal: 35), -// // color: Colors.amber[colorCodes[index]], -// child: Row( -// mainAxisAlignment: -// MainAxisAlignment.spaceBetween, -// children: [ -// Text(filters[index], -// style: const TextStyle( -// color: Colors.white, fontSize: 16)), -// Switch( -// value: selectedFilters[index], -// onChanged: (value) { -// setState(() { -// selectedFilters[index] = value; -// }); -// }, -// activeTrackColor: Colors.green, -// activeColor: Colors.white, -// ), -// ])); -// }), -// // ListView( -// // children: [ -// // ListTile( -// // title: Text('Show lower division', style: TextStyle(color: Colors.white)), -// // // selected: _selectedFilters[0], -// // // onTap: () => _selectedFilters[0] = true, -// // ), -// // ListTile( -// // leading: Icon(Icons.favorite), -// // title: Text('Show upper division'), -// // // selected: _selectedFilters[1], -// // // onTap: () => _selectedFilters[1] = true, -// // ), -// // ListTile( -// // leading: Icon(Icons.favorite), -// // title: Text('Show graduate division'), -// // // selected: _selectedFilters[2], -// // // onTap: () => _selectedFilters[2] = true, -// // ), -// // ] -// // ) -// )) -// else -// SizedBox(), -// ], -// ), -// ); -// } - - // Widget buildResultsList(BuildContext context) { - // // List arguments = widget.args; - // // loops through and adds buttons for the user to click on - // /// add content into for loop here - // // for (CourseData course in arguments) {} - // ScheduleOfClassesModel model = - // classesProvider.scheduleOfClassesService.classes!; - // CourseData course = model.courses![0]; - // List contentList = []; - // contentList.add(ListTile( - // title: Container( - // child: Row(children: [ - // // units icon - // Container( - // height: 30, - // width: 30, - // decoration: new BoxDecoration( - // color: lightGray, - // shape: BoxShape.circle, - // ), - // margin: EdgeInsets.only(right: 10), - // child: Center( - // child: Text('4' // TODO - // ))), - // // course info - // Expanded( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text(course.courseCode!, - // style: TextStyle( - // fontSize: 18, fontWeight: FontWeight.bold)), // TODO - // ], - // ), - // Text(course.courseTitle!) // TODO - // ], - // )) - // ]), - // ), - // onTap: () { - // Navigator.pushNamed(context, RoutePaths.SearchDetail, - // arguments: course); - // // do something - // }, - // )); - // // adds SizedBox to have a grey underline for the last item in the list - // //contentList.add(SizedBox()); - // ListView contentListView = ListView( - // // physics: NeverScrollableScrollPhysics(), - // shrinkWrap: true, - // children: - // ListTile.divideTiles(tiles: contentList, context: context).toList(), - // ); - // return Column( - // children: [Expanded(child: contentListView)], - // ); - // } -// } - - From 849c2edda71c8e0448e420381f3d9363e6e401cf Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Sat, 19 Feb 2022 21:16:27 -0800 Subject: [PATCH 12/24] Fixed formatting issue for class times --- lib/ui/search/search_detail.dart | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 4451c9d..e02ed17 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -46,7 +46,8 @@ class SearchDetail extends StatelessWidget { final List sectionCards = []; final List sectionObjects = []; for (final SectionData section in data.sections!) { - if (section.instructionType != 'LE' || !sectionTypes.contains('LE')) { + if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && + section.sectionStatus != 'CA') { sectionTypes.add(section.instructionType!); sectionObjects.add(section); } @@ -91,6 +92,8 @@ class SearchDetail extends StatelessWidget { days = setDays(days, lectureMeetings); // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(lectureMeetings[0]); const String prefix = '0000-01-01T'; String startTime = DateFormat.jm() .format(DateTime.parse(prefix + lectureMeetings[0].startTime!)); @@ -154,6 +157,8 @@ class SearchDetail extends StatelessWidget { days = setDays(days, discussionMeetings); // Time + // Checking for classes that start have *** format instead of **** + correctTimeFormat(discussionMeetings[0]); const String prefix = '0000-01-01T'; String startTime = DateFormat.jm().format( DateTime.parse(prefix + discussionMeetings[0].startTime!)); @@ -202,6 +207,8 @@ class SearchDetail extends StatelessWidget { days = setDays(days, labMeetings); // Time + // Checking for classes that start have *** format instead of **** + correctTimeFormat(labMeetings[0]); const String prefix = '0000-01-01T'; String startTime = DateFormat.jm() .format(DateTime.parse(prefix + labMeetings[0].startTime!)); @@ -251,6 +258,8 @@ class SearchDetail extends StatelessWidget { } // Time + // Checking for classes that start have *** format instead of **** + correctTimeFormat(finalMeetings[0]); const String prefix = '0000-01-01T'; String startTime = DateFormat.jm() .format(DateTime.parse(prefix + finalMeetings[0].startTime!)); @@ -326,4 +335,14 @@ class SearchDetail extends StatelessWidget { } return days; } + + // Fix an incorrectly formatted time + void correctTimeFormat(MeetingData meeting) { + while (meeting.startTime!.length < 4) { + meeting.startTime = '0' + meeting.startTime!; + } + while (meeting.endTime!.length < 4) { + meeting.endTime = '0' + meeting.endTime!; + } + } } From 00ab380c254c18f4744085624ea03606f23f6d8d Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Wed, 23 Feb 2022 12:31:02 -0800 Subject: [PATCH 13/24] Updated algorithm for multiple professors --- lib/ui/search/search_detail.dart | 79 ++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index e02ed17..8997af0 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,5 +1,7 @@ // ignore_for_file: always_specify_types +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; @@ -9,8 +11,9 @@ import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; * after the user has searched and selected a course. */ class SearchDetail extends StatelessWidget { - const SearchDetail({Key? key, required this.data}) : super(key: key); + SearchDetail({Key? key, required this.data}) : super(key: key); final CourseData data; + Map> instructorSections = Map(); @override Widget build(BuildContext context) => Scaffold( @@ -18,7 +21,7 @@ class SearchDetail extends StatelessWidget { centerTitle: true, title: Text( '${data.departmentCode} ${data.courseCode} \n${data.courseTitle}')), - body: Column( + body: ListView( children: [coursePrereqs(), courseDetails()], )); @@ -42,26 +45,63 @@ class SearchDetail extends StatelessWidget { Widget courseDetails() { // Determine types of sections - final List sectionTypes = []; + final List sectionCards = []; - final List sectionObjects = []; + // final List sectionTypes = []; + // final List sectionObjects = []; + instructorSections = new Map(); + for (final SectionData section in data.sections!) { - if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && - section.sectionStatus != 'CA') { - sectionTypes.add(section.instructionType!); - sectionObjects.add(section); - } + final String sectionLetter = section.sectionCode![0]; + instructorSections.update(sectionLetter, (value) { + value.add(section); + return value; + }, ifAbsent: () { + List sectionList = []; + sectionList.add(section); + return sectionList; + }); } - sectionTypes.add('FI'); - sectionObjects.add(SectionData()); + + instructorSections.forEach((key, value) { + final List sectionTypes = []; + final List sectionObjects = []; + for (final SectionData section in value) { + if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && + section.sectionStatus != 'CA') { + sectionTypes.add(section.instructionType!); + sectionObjects.add(section); + } + } + sectionTypes.add('FI'); + sectionObjects.add(SectionData()); + + int sectionIndex = 0; + for (final String sectionType in sectionTypes.toList()) { + sectionCards + .add(buildSectionCard(sectionType, sectionObjects[sectionIndex])); + sectionIndex++; + } + }); + + // print("\n${const JsonEncoder.withIndent(" ").convert(data)}\n"); + // for (final SectionData section in data.sections!) { + // if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && + // section.sectionStatus != 'CA') { + // sectionTypes.add(section.instructionType!); + // sectionObjects.add(section); + // } + // } + // sectionTypes.add('FI'); + // sectionObjects.add(SectionData()); //Build section cards for different instruction types - int sectionIndex = 0; - for (final String sectionType in sectionTypes.toList()) { - sectionCards - .add(buildSectionCard(sectionType, sectionObjects[sectionIndex])); - sectionIndex++; - } + // int sectionIndex = 0; + // for (final String sectionType in sectionTypes.toList()) { + // sectionCards + // .add(buildSectionCard(sectionType, sectionObjects[sectionIndex])); + // sectionIndex++; + // } return Column(children: [...sectionCards]); } @@ -72,7 +112,10 @@ class SearchDetail extends StatelessWidget { // Accumalate all lecture meetings in section SectionData lectureObject = SectionData(); final List lectureMeetings = []; - for (final SectionData section in data.sections!) { + //instructorSections[sectionObject.sectionCode![0]]; + + for (final SectionData section + in instructorSections[sectionObject.sectionCode![0]]!) { if (section.instructionType == 'LE') { lectureMeetings.addAll(section.recurringMeetings!); lectureObject = section; From 1d4d3fe9b1b033825b2f9c612bc15941587d7d67 Mon Sep 17 00:00:00 2001 From: Kevin De Silva Jayasinghe Date: Wed, 2 Mar 2022 17:48:24 -0800 Subject: [PATCH 14/24] one more case for sections labeled 'TU' --- lib/ui/search/search_detail.dart | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 8997af0..fb413d1 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -260,6 +260,58 @@ class SearchDetail extends StatelessWidget { startTime = startTime.toLowerCase().replaceAll(' ', ''); endTime = endTime.toLowerCase().replaceAll(' ', ''); + // Room parsing + final String room = labMeetings[0].buildingCode! + + ' ' + + labMeetings[0].roomCode!.substring(1); + + // Construct Card widget + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + margin: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 2.0, bottom: 0.0), + child: SizedBox( + height: 35, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + labObject.sectionCode!, + style: const TextStyle(color: darkGray), + ), + Text(sectionType), + ...days, + Text(startTime + ' - ' + endTime), + Text(room) + ], + ), + ), + ); + } + case 'TU': + { + // Accumalate all supplemental instruction meetings in section + final SectionData labObject = sectionObject; + final List labMeetings = []; + labMeetings.addAll(sectionObject.recurringMeetings!); + + // DAY Section + List days = resetDays(); + days = setDays(days, labMeetings); + + // Time + // Checking for classes that start have *** format instead of **** + correctTimeFormat(labMeetings[0]); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm() + .format(DateTime.parse(prefix + labMeetings[0].startTime!)); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + labMeetings[0].endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + // Room parsing final String room = labMeetings[0].buildingCode! + ' ' + From 62cd93a9040033716bc9c9ebe611415ee22b63a9 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Sun, 20 Mar 2022 08:44:55 -0700 Subject: [PATCH 15/24] Completed term picker and state issue --- lib/core/services/schedule_of_classes.dart | 1 - lib/generated_plugin_registrant.dart | 16 +++ lib/ui/search/search_bar.dart | 13 ++- lib/ui/search/search_view.dart | 109 +++++++++++++++++++-- 4 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 lib/generated_plugin_registrant.dart diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 92ff14d..0aa3298 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:webreg_mobile_flutter/app_networking.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..56b17ac --- /dev/null +++ b/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore_for_file: directives_ordering +// ignore_for_file: lines_longer_than_80_chars + +import 'package:package_info_plus_web/package_info_plus_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(Registrar registrar) { + PackageInfoPlugin.registerWith(registrar); + registrar.registerMessageHandler(); +} diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index e3fedc1..a8cfdf3 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -161,12 +161,17 @@ class TermDropdown extends StatefulWidget { } class _TermDropdownState extends State { - List dropdownItems = ['FA19', 'WI20', 'SP20', 'FA20']; - String _dropdownVal = 'FA19'; + List dropdownItems = ['SP21', 'FA21', 'WI22', 'SP22']; + String _dropdownVal = 'SP22'; + late DropdownButton dropdownButton; + + get dropDownValue { + return _dropdownVal; + } @override Widget build(BuildContext context) { - return DropdownButton( + dropdownButton = DropdownButton( underline: Container(height: 0), value: _dropdownVal, icon: Icon(Icons.arrow_drop_down, color: Colors.black, size: 20), @@ -185,6 +190,8 @@ class _TermDropdownState extends State { fontWeight: FontWeight.bold))); }).toList(), ); + + return dropdownButton; } } diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 77a7d30..a5188e0 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -17,10 +17,18 @@ class _SearchViewState extends State { late String searchString; late ScheduleOfClassesProvider classesProvider; bool showList = false; + late TermDropdown termDropdown; + List dropdownItems = ['SP21', 'FA21', 'WI22', 'SP22']; + String _dropdownVal = 'SP22'; + @override - Widget build(BuildContext context) { - const Widget _icon = Icon(Icons.search, size: 20, color: darkGray); + void didChangeDependencies() { + super.didChangeDependencies(); classesProvider = ScheduleOfClassesProvider(); + } + + @override + Widget build(BuildContext context) { return Scaffold( appBar: AppBar( titleSpacing: 0.0, @@ -42,7 +50,28 @@ class _SearchViewState extends State { crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - TermDropdown(), + DropdownButton( + underline: Container(height: 0), + value: _dropdownVal, + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black, size: 20), + onChanged: (String? newVal) { + setState(() { + _dropdownVal = newVal!; + }); + }, + items: dropdownItems + .map>( + (String val) { + return DropdownMenuItem( + value: val, + child: Text(val, + style: const TextStyle( + color: darkGray, + fontSize: 14, + fontWeight: FontWeight.bold))); + }).toList(), + ), Container( width: 1.0, color: darkGray, @@ -71,7 +100,8 @@ class _SearchViewState extends State { ), Container( margin: const EdgeInsets.only(right: 10.0), - child: _icon, + child: + const Icon(Icons.search, size: 20, color: darkGray), ), ], )), @@ -100,10 +130,60 @@ class _SearchViewState extends State { } Widget body(bool showList) { + bool validQuery = false; + String searchBuilder = ''; + String termCode = _dropdownVal; + if (showList) { + final List words = searchString.split(' '); + + switch (words.length) { + case 1: + { + //Verify that subject code could be valid + if (words[0].length > 4 || words[0].length < 3) { + validQuery = false; + break; + } + //Attempt to use the single query as a subject code, limit at 10 * #approx entries per course == 100 + validQuery = true; + searchBuilder = + 'subjectCodes=${words[0]}&termCode=$termCode&limit=100'; + } + break; + case 2: + { + final String firstWord = words[0]; + final String secondWord = words[1]; + // Verify that the course and subject code could be valid + if ((firstWord.length > 4 || firstWord.length < 3) && + (secondWord.length > 4 || secondWord.length < 3)) { + validQuery = false; + break; + } + validQuery = true; + searchBuilder = + 'subjectCodes=$firstWord&courseCodes=$secondWord&termCode=$termCode&limit=100'; + } + break; + default: + { + validQuery = false; + } + } + + if (!validQuery) { + return const Center( + child: Text( + 'Not a valid query. Please type in a valid course or subject code.', + style: TextStyle(color: darkGray, fontSize: 18), + textAlign: TextAlign.center, + )); + } + return FutureBuilder( - future: classesProvider.scheduleOfClassesService.fetchClasses( - 'subjectCodes=${searchString.split(' ')[0]}&termCode=SP21'), + future: classesProvider.scheduleOfClassesService + .fetchClasses(searchBuilder), builder: (BuildContext context, AsyncSnapshot response) { if (response.hasData) { return buildResultsList(context); @@ -128,6 +208,21 @@ class _SearchViewState extends State { /// add content into for loop here final ScheduleOfClassesModel model = classesProvider.scheduleOfClassesService.classes!; + + // Check for non valid courses + if (model.courses!.isEmpty) { + return Column( + children: const [ + Expanded( + child: Center( + child: Text( + 'Not a valid query. Please type in a valid course or subject code.', + style: TextStyle(color: darkGray, fontSize: 18), + textAlign: TextAlign.center, + ))) + ], + ); + } final List contentList = []; for (final CourseData course in model.courses!) { contentList.add(ListTile( @@ -141,7 +236,7 @@ class _SearchViewState extends State { shape: BoxShape.circle, ), margin: const EdgeInsets.only(right: 10), - child: const Center(child: Text('4'))), + child: Center(child: Text(course.unitsInc.toString()))), // Course info Expanded( From 1d8ed087ce7d94246574f5c87a1a171c552c4a4a Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:13:34 -0700 Subject: [PATCH 16/24] Updated term picker with qa release of lambda --- lib/core/services/schedule_of_classes.dart | 49 +++++++++++++++++++ lib/ui/search/search_view.dart | 56 ++++++++++++++-------- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 0aa3298..1d27bc1 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -1,8 +1,11 @@ import 'dart:async'; +import 'dart:convert'; import 'package:webreg_mobile_flutter/app_networking.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; class ScheduleOfClassesService { + final String ACADEMIC_TERM = + 'https://o17lydfach.execute-api.us-west-2.amazonaws.com/qa-peter/v1/term/current'; bool _isLoading = false; DateTime? _lastUpdated; String? _error; @@ -41,6 +44,52 @@ class ScheduleOfClassesService { } } + Future> fetchTerms() async { + final List terms = ['FA', 'WI', 'SP', 'S1', 'S2']; + + try { + /// fetch data + final Map headers = { + 'accept': 'application/json', + }; + final String _response = + await _networkHelper.authorizedFetch(ACADEMIC_TERM, headers); + + // final Map responseMapping = json.decode(_response); + final String currentTerm = json.decode(_response)['term_code']; + final String currentYear = currentTerm.substring(2, 4); + final String currentQuarter = currentTerm.substring(0, 2); + final int indexOfQuarter = terms.indexOf(currentQuarter); + + for (int i = indexOfQuarter; i < terms.length - 1; i++) { + final String lastQuarter = terms[terms.length - 1]; + for (int quarterIndex = terms.length - 1; + quarterIndex > 0; + quarterIndex--) { + terms[quarterIndex] = terms[quarterIndex - 1]; + } + terms[0] = lastQuarter; + } + + //WI21 - SP21 - S121 - S221 - FA21 + //SP21 - S121 - S221 - FA21 - WI22 + //S121 - S221 - FA21 - WI22 - SP22 + //S221 - FA21 - WI22 - SP22 - S122 + //FA21 - WI22 - SP22 - S122 - S222 + int fallIdx = terms.indexOf('FA'); + for (int i = 0; i < terms.length; i++) { + if (i > fallIdx) { + terms[i] = terms[i] + currentYear; + } else { + terms[i] = terms[i] + (int.parse(currentYear) - 1).toString(); + } + } + return terms; + } catch (e) { + return terms; + } + } + bool get isLoading => _isLoading; String? get error => _error; diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index a5188e0..7bc8cda 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -50,27 +50,43 @@ class _SearchViewState extends State { crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - DropdownButton( - underline: Container(height: 0), - value: _dropdownVal, - icon: const Icon(Icons.arrow_drop_down, - color: Colors.black, size: 20), - onChanged: (String? newVal) { - setState(() { - _dropdownVal = newVal!; - }); + FutureBuilder( + future: classesProvider.scheduleOfClassesService + .fetchTerms(), + builder: (BuildContext context, + AsyncSnapshot response) { + if (response.hasData) { + dropdownItems = + response.data as List; + _dropdownVal = + dropdownItems[dropdownItems.length - 1]; + return DropdownButton( + underline: Container(height: 0), + value: _dropdownVal, + icon: const Icon(Icons.arrow_drop_down, + color: Colors.black, size: 20), + onChanged: (String? newVal) { + setState(() { + _dropdownVal = newVal!; + }); + }, + items: dropdownItems + .map>( + (String val) { + return DropdownMenuItem( + value: val, + child: Text(val, + style: const TextStyle( + color: darkGray, + fontSize: 14, + fontWeight: + FontWeight.bold))); + }).toList(), + ); + } else { + return const CircularProgressIndicator(); + } }, - items: dropdownItems - .map>( - (String val) { - return DropdownMenuItem( - value: val, - child: Text(val, - style: const TextStyle( - color: darkGray, - fontSize: 14, - fontWeight: FontWeight.bold))); - }).toList(), ), Container( width: 1.0, From 0253f58996450b2627bfb902bfbd6cfbb55b3efd Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Tue, 5 Apr 2022 14:27:39 -0700 Subject: [PATCH 17/24] Cleaned up files, ready for CI/CD integration --- assets/images/UCSanDiegoLogo-nav.png | Bin 0 -> 7477 bytes lib/app_constants.dart | 23 ++-- lib/app_networking.dart | 66 ++++------ lib/app_provider.dart | 23 ++++ lib/app_router.dart | 8 +- lib/core/models/authentication.dart | 74 ++++++++++++ lib/core/models/schedule_of_classes.dart | 126 ++++++++++---------- lib/core/providers/schedule_of_classes.dart | 33 ++--- lib/core/providers/user.dart | 13 ++ lib/core/services/authentication.dart | 15 +++ lib/core/services/schedule_of_classes.dart | 41 +++---- lib/main.dart | 57 ++++++--- lib/ui/common/authentication_error.dart | 58 +++++++++ lib/ui/navigator/bottom.dart | 51 +++++--- lib/ui/search/search_detail.dart | 48 ++------ lib/ui/search/search_view.dart | 28 +++-- pubspec.yaml | 5 + web/static.html | 18 +++ 18 files changed, 438 insertions(+), 249 deletions(-) create mode 100644 assets/images/UCSanDiegoLogo-nav.png create mode 100644 lib/app_provider.dart create mode 100644 lib/core/models/authentication.dart create mode 100644 lib/core/providers/user.dart create mode 100644 lib/core/services/authentication.dart create mode 100644 lib/ui/common/authentication_error.dart create mode 100644 web/static.html diff --git a/assets/images/UCSanDiegoLogo-nav.png b/assets/images/UCSanDiegoLogo-nav.png new file mode 100644 index 0000000000000000000000000000000000000000..1ec8bb86ccb9dc641bc9ecf299b9fd38c3049b4b GIT binary patch literal 7477 zcmV-59m?W~P)Py6?@2^KRCodHoe8`hMU}=)fW8o;oagg`))UD*Tz#DEG2f(QaiU;q(iQBcf4 zSVSFALr_pb(GZXs5fOobVF^oE22cVbd)Su=I|+LTg!zA+I-S#XtNZqS@8;zNZvB4W ztFwIPRCRUVUaITf_knlceRmCT=m#jOoje)&*Rrc1{|wwUM@#=zY{05%Mum0u?Ah;n zcdV26$KGEh!#!Mn%YlAd6<@x-%?R@M>CMwM(3F9(-8AYbplQX(_?}XTXOur0Xv9ecJxo1Zn5T* zj(>YF23!rkVO-?bjGxsQ)PWrfMyFUO%w&38%P$*cSLKZPrsp-J0a8+la#HXcC&f1( z`4*F39Z+q>{L=B&KI>Y1w`&^RMoD}}x}CJu1axAPWK618kgqf{vOb1yn65gGX{P(w zIIuL7wZqw z#*P{YwN1%RQRZ(}TU^Ymb*Dg3<#|v^>Co*qC~8cg>>{6J)he+Z>vLG;e`)7J^O9d@ z8{Gu-1^+a=<+NIM$?6}x8^IBt+w4fl&b2hWnonp6%4$Cta0u`71`Quf{fjupxUs!V zFX_iYN8behrjnCI4G5HJq3-92*!MGz)#k@{h1sljzPDtf zydCOgkp~9(a`Diu&5s2AV9R4Qod2|(s-0a&g5|r*c+_Vb$Lf$<=eMI|qcl4Ze3J{F ztc~=&W`m1&1Hq}!NcIe6WDcS6lD*y84#71RJPQuOel>Moi0O}V576D0*FNVV+zZ6*uek8ptKOKD<3^OkBERf%oARlMGuGYRy@+}einSVsF z-PG0G4RIm)nAl;R2>$D1Kb;t5HshME8h_!E>V0+OR8DX6a-Do-oVu4to#Ho(lVH2T z+t}zwy^g=|ne@Ky$h0$5@pcuclZ%+A$_B0WAT(=89ipL+sUgb7o*!(3d?zFa`)}bt zEZN62kxwV)Djy@6GCX0++GU5V5c;?jyf?w7o||22FjjsXnsh36ljl4ow+7HPIQf$> z@|#9Bj&v`l{N6^6TYQX<#is$f8cYLg`ZymW8UKFZ?u?@(l*&QCzhOqM`A0c@=Pbzn=DGyRb>^_z2&zpsU$dHgr&x^Dim4-8ZqH5G z%x|@Etd^a!i}!n3yj+L4d#;2G*cJijLMleQ$ziX50)&u2QfPt$J;~vG2RC^`|JhZ z(&#rkIXih_%cfqaSC%gZ_WTwXdE*eDJ=~$AW&?i;@>fdwyA8fRf%gOCS=Pt*WUUR2 zH;j#bCAPh%^>`2%06k5M;(I%`-&+oEPs!p@Hi&)LvPrBscY^nJZ0kwgwv+xIS=KJT7mXKl%-(D} z`H}TPLzkX^2LsIBhI**wAn&mzt41FI8lFS2^#__h|2f#p$HX%J zFM_e)crXO$R{VT;de2vg#Xc11Hd)(PIyU)p0i-&YhLinKPRyIOZ#7yvZ$;K!4(;z=^k=G3|m2b9Oe6|`S&hK@{z1{4z^BL^b}^RjcSAP>ru)Uu(?dgFm~$DFw4(K}pd)jyhIsXKf^@E%zl=<3Wrw{(@n$2*OY670e|5j3%0^NT^3OG#|F`kqB5TBsnZSd8=4*Y z^s4wWU!AteJ;~Zp9`rHNDR;$rJ>%>Fh{>kcESLAGgKxOtY{io&;2&UqZ}&2s>Bd36 z87%DmCSxCFvenrBrC3VCr3TxwF7GQY<^;2wO*MSX=^64eyW*;R@~aq!HvH*cZR)1; zPtJe+oQUFII7f&hu6dA`cX5Th#!(G#MdPJv{11V`IJ6IZ(RZg1KlIsME~t9@CWp-qH6_2Lm_TMa5Mf_LAKV#NZ2(#+9x+!KJ#_z z1!x4=kqhaQO!<0Mw*%+EDvtN*HF35k#~V9x?;Qa?#agVcoBWrl7el`sS&xx=wA7r! z`q)ke-)UjrzN`b`P!g1t%Sd>?IpMB@Ji>He!z&z6aEIbQ5$MrO_X5uV-OTBc?o9a8 zsVfUPwu5jG_!L+iyaet97l3o?nnG5C`9j=m!H$+8h90_U)84054k|5OSP~z6H>i6E3%Sr-8+WSn#zP@qH=4<@f zBPjK3Z})O&`=8#{lQMn#Ta#_bUdV-ho|WnwIerHkR(GekJ!+#<$5m}y+?muyT0FV_ z8uPu5{{V5l&19Ej4YS2H@ymnfVtY^NwEbi2XIYzY)-ew9DV~S!KQOx7_MSrLj6N&Y{AfoW*J^yKow_+_@As+rp0LsOkLvUj-gPM+_QlMO+^;A4+^mhVDduKz4SqK0 zum)p=fg7qFE8M!*_*Rvl_OKCy0evTMbQvF?9-Bu2J(gw#`3vBuU}v+nqb~cz(~jMF90LZYII`gz2KEP9w_w{ljW)jyUWQV5$pvR`plvKao8s)&J%YRDjVtST zT|0d_+VWYAZBki0zJ89kIr=Xoz^7!GXTs~hdr=mZ|i(EY}t3lsekS57Uyk};awO8LI=Vu zu&i;)V$2`nTR9M}Nac)e^kKQsr>mm2vTr2e*MwD8;@B!XaoM6|>cW{0ee2NP)ry}N zmxi`XqUh8H`hzt={;0_Y90BwK%$M7OQaDJ6D|1MQFWe)Joq(;v;ORu;I~lN8(^8WB0WDaZF0v#CbU&an8mls+#GgpHKA zU4;(c1GGFzJ1r}8eqoTGzVU4o7H&LZoKQcl8ty8QJyWsoK-DS2~Ubg@`7=Ieg)wY^8-UTbUgom2Ufa@T+)UKb%PvFa+ z3XB)kl{26bw_7Ux&c~`-i3rv>CEKZ&x4UK#tn*5}sQ%EN9{u!59*KXUaGB5`6yAl182b7#e@j zI)FdFSuuZ|nmAqiFQ~%}oUDCfE|E_A3j-YxRz7Azo9jL`^W9}U`c=4DGrQIJ?FRTF zvsKhuo%Ad4VK_Gfoo&_%AArd&ZbFc^ayBn+8nSq}suIW}|U&iI= z%B3djgXK%rw2}Wbkom1m)2%i;F{_$QHTqcjDEoSQ@MZ4@@C}ZArsMmW!{1IH!k>{G!C4zh(}+Jxh4W z)0K6Ob_(kplmpuY=il1$I@=CrgL^EnFN_p!z_6bg`gXkWt;Y9g@NHqXxQ6q9YqP)i zae%=oI&cXs|q(cc34 z%G=(uQ!W7u0o_n$-+pp@{UEOTtJTM&%^u(jGfw5T4x$D^pWG#Zz5;d) z*QJI_0`}5Ejzw_lnfyaQKb`ZOyB?yZ3vtWfSr04+UIMog_bhd3A!b9ITDg0-k+3a~ zye_yCyS8D0DD+!nwl>g>;obB-zw_5lME@)(%tG3R5jz76&^?3MRAbY=)S32PVjq;P zQx?z>U}t8(HB;4nI2-F=sp7=GzOkXB_xcf7zeW#tsln+`7UU7&)4or*z87Aw**4+d zAoz7ztzC4JG`=>wL!93up!=NPv(F9(KSkD@$uGfeU@L5+vAu)v^IimoyaY%Yr??IUxUmx+qpZ3Dvp7X&AjW z8W`aGm5LkuAGf0ka{t!lJija_ypMucjB7Qv?aE@R?8JQ%_><8ms`%bV-2Ynpswro9 ze_fflb3?qn9IhMsp5U?lr}%LUV{t+&8%K_Hz;lT_=VPzD>%+eYnCfHEZv~y%w6GJ( z2IAGkPD=_6&Ns<@#(atDuQg4x2Mr9p5JX(yi~} z-qwmt+geJex3HZuX9QY$F=m6t7+wepb6D&b&5}27`0J6mSMu4&3f} z?`Jvw(9QR;U}x$_RX*lB!Va5-eyX>1RVLRHKwpu#sAdkkqww^H=;L5-=*YE3pi3YA zvdrv5{V3e868;Mkw#)?oF|ZfV&rBTvDwnsE<>_fV$J=o5f6?R4Puks&UUx!Z0}Ho; zVS}|MN+*RLFSI)z-0vG^$1DvV0a`G(ALvYK`2p%#S?^s7h5e!Vr#%YB1z2HQ^u7N3 zv|Z6=tQ+awR(sDcC`JBXb7e#GOqx-h{km%xhIA$EM&c z&@d|>T_Ns<^QE%<_*MjC!MQ-Mit|a;(BHy3e*B|B_Pu)FPT)?*_tmnt=9iuQavyjk z(f(&;qtDt$M}i4JzXLtpZyWkaiP+qPe=tyN!<(uo&oyugk1%->C&1C_XU?SmPVH>mm&aMTh25k0)l&l4~dSu!i+!g2ZI(~hzHv26I*R^sH^07XFWXhQG zi25G#N`K&k+TfSW0`hcx+r^PwurA}YA1%=;Iw0_1lyh}l04x*}WaPuD`3uCS| z6AdSAZ2BhH4b~pA-qUZ;B)4AQ#jkI(69V>QLVHt@^Aj5K3gBLACveWKYNyIauKf2T zEGM?Zt77W-$dUaRiscObyfKc2w+Se$A3;wZTiAC6q{?42>nh!FCx}fa-G_`9+CCs| zTRJ@do!nXI`B-~Cc0C?EVL6fY0UYT$UdFc!$k*o}>s4-Yi6VCWm`eVZ23e24)vw?o z%+C`-!xf7DobCspx5)Hs2>2r?{lbyksT%mw8E|}qjcYZw!^*Z5-v9tLKVVZ=$wD~% z!UqyvSE@UK4xph0a!|1AyFl6hKv7#1{b~4Mq+(+mafS9nzIL1tF0*m4nzr;-si9!s z%Q&I^h7$su{O2KHpPrl^Z2<)@* z=M$^M=wuK-%VL_ZMc;vo%k!}z=!G$eIc(pW;0I=B`~Lv0H@%{c@0<=XF8TT)$B%ZC z&7Z1iQ*Xrn5cvwPyUD9BrJf&g4vh3N^&8$np5K9u7xKnlB40*7;@jVi=q*=YB zkI8P(zbQCI$w_~;it=>o!cuH>orE>-us?jg1J4V(-qR<)t&)?9!Yf5J`$rAaBU`CT zdvD>?+Kx9$Npl>rf8i$lo_4R2Xi#79=t|i&3fhK!b!%ixs*GcPf`*_Isg=~*mBBcl zH9|=p|0YHJy81{TNBh?U9fM5^yhqqBz24u!kxPF-EA_$47YVrpT@n3B=51@8oJk7YO?#I`>8I9Lq~0=lX%1G;Df&|C@-k zR-``|>CM!KBEs6bTBS;SW)f$3ir59)Ko>K^$(hLDJqC`C?dT^msJ-o0HPOvR6WgPo za7^8HZXUDQ#&yB{?zvG{@pHfsa1Hntxkdp^8r=lCQ||3fa4(7dVcZYK=5L_ZRXvVH ze=4%=!v4@Lf42(#IRyONl8{s1(YXice*$Ls=MAl6cwyu~)xS)SHITzV*Jwq2hKn#vL|99$k-Kpg5l?k-rg7LkGb$px1-C6VS@Ky~EtZFyy0wu2#D9uu``F%>SOQ8K;whPK%)M zZ~Jq94dDvUA^%6vf9h>rmFb&S+x{864JMIC^Gf+IWx0!h9{aRB`+Hyspt+^n679wl zh^rTHjjyUt?1;`TZ2bJ zuI*ciE#s1Jc@Q60Tb1#>ow)T9+Zdl9=7tcU*G;!2g8nj>RbL|4nU)LrZLmh2Xn3Ch z4L{8na`6IJ&{3Ph|Jd3?>tBC$T=2uO*yQ;JJQ~Rdb2_gGg zJ!^*?$@k@PyYOzZn9#RjF{h4Bo(Vor$aQ+1SaFEg<5d2SRxB4bEzpXe{gY|o?O}1D z{p6b*c#}LYaCM9CA9<5)q>RLEWBHKv6O|``kwC|{)&``o8updJBB`9R;nQ4Heqlg< z4D>JKhZvol3X7M}Yn@2c;0RDy%n=4h@70e0E5(gt(~2o|bS#M1U!Q}m@+=j7=UK43h!GxYz#Np}d46d7RqVe2tE9FayN>^c4>7jvM~dTot?1;?on_%dh_2O- z=UN`**+ApiFgDos1pWkaF?#;?q9Gu)fo3C62lH+~2cACgm6I4e_q+)2I-uvJiRkfb zGR&=)(C$bg^`YJoz3J^dZNC6l&tW%%OTa&{rCtZ&np?=N52VhXn$g$9>MGds0Ypz0 zazBkjZT%Tsg#K!h7#YdECZ^MXPHOr7A$}&d3&A+#Y{CqGKlEQy??;^%{MxQFz(i2{ z3JiBAVjm>mcaTSeI^_joM#MyNXomdQ*j_`|cD@1L1kVAjk9Y`Z0`%{+^$vCILO%vU zzPxt8oPXqWRm>;P$FUs|UXM>VC#CfK{$=T2N_1Ue(&Mt~1(Kaf8@B5&Z_Es4Z#Z6^feTUrY4kUJyL8G+t0 z0(^M0d8=?$*Q;|qGPG2P$$tV6S&wy#wUnSk?un^I|yW&$vFYZcUC#Ew3oe`)t0>?6Wr^)QB@Ojf)oiBpjf!40M0{ymRb{&+f(f?xy@xdZS3{``4UaLNn~^~00000NkvXXu0mjfmsbw1 literal 0 HcmV?d00001 diff --git a/lib/app_constants.dart b/lib/app_constants.dart index 495a596..48a3769 100644 --- a/lib/app_constants.dart +++ b/lib/app_constants.dart @@ -6,8 +6,8 @@ class RoutePaths { static const String FinalsList = 'finals_list'; static const String SearchView = 'search_view'; static const String CourseListView = 'course_list_view'; - static const String Login = 'login'; static const String SearchDetail = 'search_detail'; + static const String AuthenticationError = 'authentication_error'; } class CalendarStyles { @@ -17,12 +17,21 @@ class CalendarStyles { } class ErrorConstants { - static const authorizedPostErrors = 'Failed to upload data: '; - static const authorizedPutErrors = 'Failed to update data: '; - static const invalidBearerToken = 'Invalid bearer token'; - static const duplicateRecord = + static const String authorizedPostErrors = 'Failed to upload data: '; + static const String authorizedPutErrors = 'Failed to update data: '; + static const String invalidBearerToken = 'Invalid bearer token'; + static const String duplicateRecord = 'DioError [DioErrorType.response]: Http status error [409]'; - static const invalidMedia = + static const String invalidMedia = 'DioError [DioErrorType.response]: Http status error [415]'; - static const silentLoginFailed = "Silent login failed"; + static const String silentLoginFailed = 'Silent login failed'; +} + +class LoginConstants { + static const String silentLoginFailedTitle = 'Oops! You\'re not logged in.'; + static const String silentLoginFailedDesc = + 'The system has logged you out (probably by mistake). Go to Profile to log back in.'; + static const String loginFailedTitle = 'Sorry, unable to sign you in.'; + static const String loginFailedDesc = + 'Be sure you are using the correct credentials; TritonLink login if you are a student, SSO (AD or Active Directory) if you are a Faculty/Staff.'; } diff --git a/lib/app_networking.dart b/lib/app_networking.dart index 2b206d8..53a1601 100644 --- a/lib/app_networking.dart +++ b/lib/app_networking.dart @@ -1,14 +1,10 @@ -import 'dart:async'; +// ignore_for_file: always_specify_types +import 'dart:async'; import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; class NetworkHelper { - ///TODO: inside each service that file place a switch statement to handle all - ///TODO: different errors thrown by the Dio client DioErrorType.RESPONSE const NetworkHelper(); static const int SSO_REFRESH_MAX_RETRIES = 3; @@ -16,17 +12,16 @@ class NetworkHelper { static const int SSO_REFRESH_RETRY_MULTIPLIER = 3; Future fetchData(String url) async { - Dio dio = new Dio(); + final Dio dio = Dio(); dio.options.connectTimeout = 20000; dio.options.receiveTimeout = 20000; dio.options.responseType = ResponseType.plain; - final _response = await dio.get(url); + final Response _response = await dio.get(url); if (_response.statusCode == 200) { // If server returns an OK response, return the body return _response.data; } else { - ///TODO: log this as a bug because the response was bad // If that response was not OK, throw an error. throw Exception('Failed to fetch data: ' + _response.data); } @@ -34,68 +29,48 @@ class NetworkHelper { Future authorizedFetch( String url, Map headers) async { - Dio dio = new Dio(); + final Dio dio = Dio(); dio.options.connectTimeout = 20000; dio.options.receiveTimeout = 20000; dio.options.responseType = ResponseType.plain; dio.options.headers = headers; - final _response = await dio.get( + final Response _response = await dio.get( url, ); if (_response.statusCode == 200) { // If server returns an OK response, return the body return _response.data; } else { - ///TODO: log this as a bug because the response was bad // If that response was not OK, throw an error. - throw Exception('Failed to fetch data: ' + _response.data); } } - // Widget getSilentLoginDialog() { - // return AlertDialog( - // title: const Text(LoginConstants.silentLoginFailedTitle), - // content: Text(LoginConstants.silentLoginFailedDesc), - // actions: [ - // TextButton( - // style: TextButton.styleFrom( - // primary: ucLabelColor, - // ), - // onPressed: () { - // Get.back(closeOverlays: true); - // }, - // child: const Text('OK'), - // ), - // ], - // ); - // } - Future authorizedPost( String url, Map? headers, dynamic body) async { - Dio dio = new Dio(); + final Dio dio = Dio(); dio.options.connectTimeout = 20000; dio.options.receiveTimeout = 20000; dio.options.headers = headers; - final _response = await dio.post(url, data: body); + final Response _response = await dio.post(url, data: body); if (_response.statusCode == 200 || _response.statusCode == 201) { // If server returns an OK response, return the body return _response.data; } else if (_response.statusCode == 400) { // If that response was not OK, throw an error. - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPostErrors + message); } else if (_response.statusCode == 401) { throw Exception(ErrorConstants.authorizedPostErrors + ErrorConstants.invalidBearerToken); } else if (_response.statusCode == 404) { - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPostErrors + message); } else if (_response.statusCode == 500) { - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPostErrors + message); } else if (_response.statusCode == 409) { - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.duplicateRecord + message); } else { throw Exception(ErrorConstants.authorizedPostErrors + 'unknown error'); @@ -104,27 +79,27 @@ class NetworkHelper { Future authorizedPut( String url, Map headers, dynamic body) async { - Dio dio = new Dio(); + final Dio dio = Dio(); dio.options.connectTimeout = 20000; dio.options.receiveTimeout = 20000; dio.options.headers = headers; - final _response = await dio.put(url, data: body); + final Response _response = await dio.put(url, data: body); if (_response.statusCode == 200 || _response.statusCode == 201) { // If server returns an OK response, return the body return _response.data; } else if (_response.statusCode == 400) { // If that response was not OK, throw an error. - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPutErrors + message); } else if (_response.statusCode == 401) { throw Exception(ErrorConstants.authorizedPutErrors + ErrorConstants.invalidBearerToken); } else if (_response.statusCode == 404) { - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPutErrors + message); } else if (_response.statusCode == 500) { - String message = _response.data['message'] ?? ''; + final String message = _response.data['message'] ?? ''; throw Exception(ErrorConstants.authorizedPutErrors + message); } else { throw Exception(ErrorConstants.authorizedPutErrors + 'unknown error'); @@ -133,21 +108,20 @@ class NetworkHelper { Future authorizedDelete( String url, Map headers) async { - Dio dio = new Dio(); + final Dio dio = Dio(); dio.options.connectTimeout = 20000; dio.options.receiveTimeout = 20000; dio.options.headers = headers; try { - final _response = await dio.delete(url); + final Response _response = await dio.delete(url); if (_response.statusCode == 200) { // If server returns an OK response, return the body return _response.data; } else { - ///TODO: log this as a bug because the response was bad // If that response was not OK, throw an error. throw Exception('Failed to delete data: ' + _response.data); } - } on TimeoutException catch (e) { + } on TimeoutException { // Display an alert - i.e. no internet } catch (err) { return null; diff --git a/lib/app_provider.dart b/lib/app_provider.dart new file mode 100644 index 0000000..7710236 --- /dev/null +++ b/lib/app_provider.dart @@ -0,0 +1,23 @@ +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; +import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; + +List providers = [ + ChangeNotifierProvider( + create: (_) { + return UserDataProvider(); + }, + ), + ChangeNotifierProxyProvider( + create: (_) { + final ScheduleOfClassesProvider scheduleOfClassesProvider = + ScheduleOfClassesProvider(); + scheduleOfClassesProvider.userDataProvider = UserDataProvider(); + return scheduleOfClassesProvider; + }, update: (_, UserDataProvider userDataProvider, + ScheduleOfClassesProvider? scheduleOfClassesProvider) { + scheduleOfClassesProvider!.userDataProvider = userDataProvider; + return scheduleOfClassesProvider; + }) +]; diff --git a/lib/app_router.dart b/lib/app_router.dart index ce29728..b1c4ef6 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/ui/common/authentication_error.dart'; import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart'; import 'package:webreg_mobile_flutter/ui/navigator/bottom.dart'; import 'package:webreg_mobile_flutter/ui/search/search_detail.dart'; import 'package:webreg_mobile_flutter/ui/search/search_view.dart'; -import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; // ignore: avoid_classes_with_only_static_members class Router { @@ -14,13 +13,16 @@ class Router { switch (settings.name) { case RoutePaths.Home: return MaterialPageRoute(builder: (_) => BottomNavigation()); + case RoutePaths.AuthenticationError: + return MaterialPageRoute( + builder: (_) => const AuthenticationError()); case RoutePaths.SearchView: return MaterialPageRoute(builder: (_) => SearchView()); case RoutePaths.CourseListView: return MaterialPageRoute(builder: (_) => CourseListView()); case RoutePaths.SearchDetail: final CourseData course = settings.arguments! as CourseData; - return MaterialPageRoute(builder: (_) { + return MaterialPageRoute(builder: (_) { return SearchDetail(data: course); }); diff --git a/lib/core/models/authentication.dart b/lib/core/models/authentication.dart new file mode 100644 index 0000000..22b0ff6 --- /dev/null +++ b/lib/core/models/authentication.dart @@ -0,0 +1,74 @@ +// To parse this JSON data, do +// +// final authenticationModel = authenticationModelFromJson(jsonString); + +import 'dart:convert'; + +import 'package:hive/hive.dart'; + +AuthenticationModel authenticationModelFromJson(String str) => + AuthenticationModel.fromJson(json.decode(str)); + +String authenticationModelToJson(AuthenticationModel data) => + json.encode(data.toJson()); + +@HiveType(typeId: 1) +class AuthenticationModel extends HiveObject { + @HiveField(0) + String? accessToken; + // Deprecated reserved field number - DO NOT REMOVE + // @HiveField(1) + // String refreshToken; + @HiveField(2) + String? pid; + @HiveField(3) + String? ucsdaffiliation; + @HiveField(4) + int? expiration; + + AuthenticationModel({ + this.accessToken, + this.pid, + this.ucsdaffiliation, + this.expiration, + }); + + factory AuthenticationModel.fromJson(Map json) { + return AuthenticationModel( + accessToken: json['access_token'], + pid: json['pid'], + ucsdaffiliation: json['ucsdaffiliation'] ?? '', + expiration: json['expiration'] ?? 0, + ); + } + + Map toJson() => { + 'access_token': accessToken, + 'pid': pid, + 'ucsdaffiliation': ucsdaffiliation ?? '', + 'expiration': expiration, + }; + + /// Checks if the token we got back is expired + bool isLoggedIn(DateTime? lastUpdated) { + /// User has not logged in previously - isLoggedIn FALSE + if (lastUpdated == null) { + return false; + } + + /// User has no expiration or accessToken - isLoggedIn FALSE + if (expiration == null || accessToken == null) { + return false; + } + + /// User has expiration and accessToken + if (DateTime.now() + .isBefore(lastUpdated.add(Duration(seconds: expiration!)))) { + /// Current datetime < expiration datetime - isLoggedIn TRUE + return true; + } else { + /// Current datetime > expiration datetime - isLoggedIn FALSE + return false; + } + } +} diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart index ce40da2..1b30243 100644 --- a/lib/core/models/schedule_of_classes.dart +++ b/lib/core/models/schedule_of_classes.dart @@ -1,7 +1,6 @@ -import 'dart:convert'; - -import 'dart:core'; +// ignore_for_file: always_specify_types +import 'dart:convert'; import 'dart:core'; ScheduleOfClassesModel classScheduleModelFromJson(String str) => @@ -11,9 +10,6 @@ String classScheduleModelToJson(ScheduleOfClassesModel data) => json.encode(data.toJson()); class ScheduleOfClassesModel { - Metadata? metadata; - List? courses; - ScheduleOfClassesModel({ this.metadata, this.courses, @@ -21,35 +17,28 @@ class ScheduleOfClassesModel { factory ScheduleOfClassesModel.fromJson(Map json) => ScheduleOfClassesModel( - metadata: Metadata.fromJson(json['metadata']), courses:List.from( json["data"].map((x) => CourseData.fromJson(x)))); + metadata: Metadata.fromJson(), + courses: List.from( + json['data'].map((x) => CourseData.fromJson(x)))); + + Metadata? metadata; + List? courses; - // List.from( - // json["data"].map((x) => ClassData.fromJson(x))) Map toJson() => { - "metadata": metadata!.toJson(), - "data": List.from(courses!.map((x) => x.toJson())) + 'metadata': metadata!.toJson(), + 'data': List.from(courses!.map((x) => x.toJson())) }; } class Metadata { Metadata(); - factory Metadata.fromJson(Map? json) => Metadata(); + factory Metadata.fromJson() => Metadata(); Map toJson() => {}; } class CourseData { - String? subjectCode; - String? courseCode; - String? departmentCode; - String? courseTitle; - double? unitsMin; - double? unitsMax; - double? unitsInc; - String? academicLevel; - List? sections; - CourseData( {this.subjectCode, this.courseCode, @@ -66,12 +55,22 @@ class CourseData { courseCode: json['courseCode'] ?? '', departmentCode: json['departmentCode'] ?? '', courseTitle: json['courseTitle'] ?? '', - unitsMin: 0.0, - unitsMax: 0.0, - unitsInc: 0.0, + unitsMin: json['unitsMin'] ?? '', + unitsMax: json['unitsMax'] ?? '', + unitsInc: json['unitsInc'] ?? '', academicLevel: json['academicLevel'] ?? '', sections: List.from( - json["sections"].map((x) => SectionData.fromJson(x)))); + json['sections'].map((x) => SectionData.fromJson(x)))); + + String? subjectCode; + String? courseCode; + String? departmentCode; + String? courseTitle; + double? unitsMin; + double? unitsMax; + double? unitsInc; + String? academicLevel; + List? sections; Map toJson() => { 'subjectCode': subjectCode, @@ -87,24 +86,6 @@ class CourseData { } class SectionData { - String? sectionId; - String? termCode; - String? sectionCode; - String? instructionType; - String? sectionStatus; - String? subtitle; - String? startDate; - String? endDate; - int? enrolledQuantity; - int? capacityQuantity; - bool? stopEnrollmentFlag; - String? printFlag; - String? subterm; - String? planCode; - List? recurringMeetings; - List? additionalMeetings; - List? instructors; - SectionData( {this.sectionId, this.termCode, @@ -140,11 +121,29 @@ class SectionData { subterm: json['subterm'] ?? '', planCode: json['planCode'] ?? '', recurringMeetings: List.from( - json["recurringMeetings"].map((x) => MeetingData.fromJson(x))), + json['recurringMeetings'].map((x) => MeetingData.fromJson(x))), additionalMeetings: List.from( - json["additionalMeetings"].map((x) => MeetingData.fromJson(x))), + json['additionalMeetings'].map((x) => MeetingData.fromJson(x))), instructors: List.from( - json["instructors"].map((x) => Instructor.fromJson(x)))); + json['instructors'].map((x) => Instructor.fromJson(x)))); + + String? sectionId; + String? termCode; + String? sectionCode; + String? instructionType; + String? sectionStatus; + String? subtitle; + String? startDate; + String? endDate; + int? enrolledQuantity; + int? capacityQuantity; + bool? stopEnrollmentFlag; + String? printFlag; + String? subterm; + String? planCode; + List? recurringMeetings; + List? additionalMeetings; + List? instructors; Map toJson() => { 'sectionId': sectionId, @@ -170,15 +169,6 @@ class SectionData { } class MeetingData { - String? meetingType; - String? meetingDate; - String? dayCode; - String? dayCodeIsis; - String? startTime; - String? endTime; - String? buildingCode; - String? roomCode; - MeetingData( {this.meetingType, this.meetingDate, @@ -188,6 +178,7 @@ class MeetingData { this.endTime, this.buildingCode, this.roomCode}); + factory MeetingData.fromJson(Map json) => MeetingData( meetingType: json['meetingType'] ?? '', meetingDate: json['meetingDate'] ?? '', @@ -198,6 +189,15 @@ class MeetingData { buildingCode: json['buildingCode'] ?? '', roomCode: json['roomCode'] ?? ''); + String? meetingType; + String? meetingDate; + String? dayCode; + String? dayCodeIsis; + String? startTime; + String? endTime; + String? buildingCode; + String? roomCode; + Map toJson() => { 'meetingType': meetingType, 'meetingDate': meetingDate, @@ -211,13 +211,6 @@ class MeetingData { } class Instructor { - String? pid; - String? instructorName; - bool? primaryInstructor; - String? instructorEmailAddress; - double? workLoadUnitQty; - double? percentOfLoad; - Instructor( {this.pid, this.instructorName, @@ -234,6 +227,13 @@ class Instructor { workLoadUnitQty: json['workLoadUnitQty'] ?? '', percentOfLoad: json['percentOfLoad'] ?? ''); + String? pid; + String? instructorName; + bool? primaryInstructor; + String? instructorEmailAddress; + double? workLoadUnitQty; + double? percentOfLoad; + Map toJson() => { 'pid': pid, 'instructorName': instructorName, diff --git a/lib/core/providers/schedule_of_classes.dart b/lib/core/providers/schedule_of_classes.dart index 82166ce..41ee0b3 100644 --- a/lib/core/providers/schedule_of_classes.dart +++ b/lib/core/providers/schedule_of_classes.dart @@ -1,7 +1,7 @@ -import 'package:webreg_mobile_flutter/app_constants.dart'; +import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/core/services/schedule_of_classes.dart'; -import 'package:flutter/material.dart'; class ScheduleOfClassesProvider extends ChangeNotifier { ScheduleOfClassesProvider() { @@ -22,7 +22,7 @@ class ScheduleOfClassesProvider extends ChangeNotifier { bool? _noResults; /// MODELS - ScheduleOfClassesModel _scheduleOfClassesModels = ScheduleOfClassesModel(); + ScheduleOfClassesModel _scheduleOfClassesModels = ScheduleOfClassesModel(); String? searchQuery; String? term; TextEditingController _searchBarController = TextEditingController(); @@ -37,24 +37,18 @@ class ScheduleOfClassesProvider extends ChangeNotifier { ScheduleOfClassesService get scheduleOfClassesService => _scheduleOfClassesService; - void fetchClasses() async { - String SearchQuery = searchBarController.text; - String TextQuery = createQuery(SearchQuery); - _isLoading = true; - _error = null; - notifyListeners(); + late UserDataProvider _userDataProvider; + + set userDataProvider(UserDataProvider userDataProvider) { + _userDataProvider = userDataProvider; + } - if (await _scheduleOfClassesService.fetchClasses(TextQuery)) { - _scheduleOfClassesModels = _scheduleOfClassesService.classes!; - _noResults = false; + Future fetchClasses(String query) async { + final Map headers = { + 'Authorization': 'Bearer ${_userDataProvider.accessToken}' + }; - /// add things to show classes on screen - /// - /// conditionals for search history here - } else { - _error = _scheduleOfClassesService.error; - _noResults = true; - } + return _scheduleOfClassesService.fetchClasses(headers, query); } String createQuery(String query) { @@ -68,7 +62,6 @@ class ScheduleOfClassesProvider extends ChangeNotifier { DateTime? get lastUpdated => _lastUpdated; ScheduleOfClassesModel get scheduleOfClassesModels => _scheduleOfClassesModels; - //List get searchHistory => _searchHistory; TextEditingController get searchBarController => _searchBarController; bool? get noResults => _noResults; diff --git a/lib/core/providers/user.dart b/lib/core/providers/user.dart new file mode 100644 index 0000000..3e70f89 --- /dev/null +++ b/lib/core/providers/user.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class UserDataProvider extends ChangeNotifier { + String? accessToken = ''; + + set setToken(String? token) { + accessToken = token; + } + + String? get getToken { + return accessToken; + } +} diff --git a/lib/core/services/authentication.dart b/lib/core/services/authentication.dart new file mode 100644 index 0000000..2729ac8 --- /dev/null +++ b/lib/core/services/authentication.dart @@ -0,0 +1,15 @@ +import 'package:webreg_mobile_flutter/core/models/authentication.dart'; + +import '../../app_networking.dart'; + +class AuthenticationService { + AuthenticationService(); + String? error; + AuthenticationModel? _data; + DateTime? _lastUpdated; + + final NetworkHelper _networkHelper = NetworkHelper(); + + + +} diff --git a/lib/core/services/schedule_of_classes.dart b/lib/core/services/schedule_of_classes.dart index 1d27bc1..946d454 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -2,40 +2,38 @@ import 'dart:async'; import 'dart:convert'; import 'package:webreg_mobile_flutter/app_networking.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; class ScheduleOfClassesService { - final String ACADEMIC_TERM = + final String academicTermAPI = 'https://o17lydfach.execute-api.us-west-2.amazonaws.com/qa-peter/v1/term/current'; bool _isLoading = false; DateTime? _lastUpdated; String? _error; ScheduleOfClassesModel? classes; + UserDataProvider? userDataProvider = UserDataProvider(); + final NetworkHelper _networkHelper = const NetworkHelper(); - final Map headers = { - 'accept': 'application/json', - }; + final String baseEndpoint = 'https://api-qa.ucsd.edu:8243/get_schedule_of_classes/v1/classes/search'; - Future fetchClasses(String query) async { + Future fetchClasses(Map headers, String query) async { _error = null; _isLoading = true; try { - await getNewToken(); - /// fetch data final String? _response = await _networkHelper.authorizedFetch( baseEndpoint + '?' + query, headers); if (_response != null) { + /// parse data final ScheduleOfClassesModel data = classScheduleModelFromJson(_response); classes = data; } else { - /// parse data - + _isLoading = false; return false; } - _isLoading = false; return true; } catch (e) { _error = e.toString(); @@ -45,7 +43,7 @@ class ScheduleOfClassesService { } Future> fetchTerms() async { - final List terms = ['FA', 'WI', 'SP', 'S1', 'S2']; + final List terms = ['FA', 'WI', 'SP', 'S1', 'S2']; try { /// fetch data @@ -53,7 +51,7 @@ class ScheduleOfClassesService { 'accept': 'application/json', }; final String _response = - await _networkHelper.authorizedFetch(ACADEMIC_TERM, headers); + await _networkHelper.authorizedFetch(academicTermAPI, headers); // final Map responseMapping = json.decode(_response); final String currentTerm = json.decode(_response)['term_code']; @@ -61,6 +59,7 @@ class ScheduleOfClassesService { final String currentQuarter = currentTerm.substring(0, 2); final int indexOfQuarter = terms.indexOf(currentQuarter); + //Rotate terms clockwise to present current term as last for (int i = indexOfQuarter; i < terms.length - 1; i++) { final String lastQuarter = terms[terms.length - 1]; for (int quarterIndex = terms.length - 1; @@ -71,12 +70,14 @@ class ScheduleOfClassesService { terms[0] = lastQuarter; } + // Illustration of possible term structure + // Update prefixes for each term //WI21 - SP21 - S121 - S221 - FA21 //SP21 - S121 - S221 - FA21 - WI22 //S121 - S221 - FA21 - WI22 - SP22 //S221 - FA21 - WI22 - SP22 - S122 //FA21 - WI22 - SP22 - S122 - S222 - int fallIdx = terms.indexOf('FA'); + final int fallIdx = terms.indexOf('FA'); for (int i = 0; i < terms.length; i++) { if (i > fallIdx) { terms[i] = terms[i] + currentYear; @@ -95,18 +96,4 @@ class ScheduleOfClassesService { String? get error => _error; DateTime? get lastUpdated => _lastUpdated; - - Future getNewToken() async { - // TODO(p8gonzal): Have subscription set up for webreg mobile. Investigate alternatives to Dio. - try { - // var response = await _networkHelper.authorizedPost( - // tokenEndpoint, tokenHeaders, "grant_type=client_credentials"); - // TODO(p8gonzal): Insert your own authenitcation token for demo. Will be replaced by application credentials - headers['Authorization'] = 'Bearer '; - return true; - } catch (e) { - _error = e.toString(); - return false; - } - } } diff --git a/lib/main.dart b/lib/main.dart index a643823..12ae619 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,35 +1,54 @@ +import 'dart:html'; + import 'package:flutter/material.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_placeholder.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:get/route_manager.dart'; +import 'package:provider/provider.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_router.dart' - as webregMobileRouter; +import 'package:webreg_mobile_flutter/app_provider.dart'; +import 'package:webreg_mobile_flutter/app_router.dart' as webreg_router; +import 'package:webreg_mobile_flutter/app_styles.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { + MyApp({Key? key}) : super(key: key); + + late String? _token = ''; + + void getParams() { + final Uri uri = Uri.dataFromString(window.location.href); + final Map params = uri.queryParameters; + if (params['token'] != null) { + _token = params['token']; + } + } + // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: ColorPrimary, - primaryColor: lightPrimaryColor, - accentColor: darkAccentColor, - brightness: Brightness.light, - buttonColor: lightButtonColor, - textTheme: lightThemeText, - iconTheme: lightIconTheme, - appBarTheme: lightAppBarTheme, - bottomSheetTheme: BottomSheetThemeData( - backgroundColor: Colors.black.withOpacity(0) + getParams(); + + return MultiProvider( + providers: providers, + child: GetMaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primaryColor: lightPrimaryColor, + brightness: Brightness.light, + textTheme: lightThemeText, + iconTheme: lightIconTheme, + appBarTheme: lightAppBarTheme, + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: Colors.black.withOpacity(0)), + colorScheme: ColorScheme.fromSwatch(primarySwatch: ColorPrimary) + .copyWith(secondary: darkAccentColor), ), + initialRoute: + _token == '' ? RoutePaths.AuthenticationError : RoutePaths.Home, + onGenerateRoute: webreg_router.Router.generateRoute, ), - initialRoute: RoutePaths.Home, - onGenerateRoute: webregMobileRouter.Router.generateRoute, ); } } diff --git a/lib/ui/common/authentication_error.dart b/lib/ui/common/authentication_error.dart new file mode 100644 index 0000000..e024288 --- /dev/null +++ b/lib/ui/common/authentication_error.dart @@ -0,0 +1,58 @@ +// ignore_for_file: always_specify_types + +import 'dart:html' as html; +import 'package:flutter/material.dart'; +import '../../app_constants.dart'; + +const String CLIENT_ID = ''; + +class AuthenticationError extends StatefulWidget { + const AuthenticationError({Key? key}) : super(key: key); + + @override + _AuthenticationErrorState createState() => _AuthenticationErrorState(); +} + +class _AuthenticationErrorState extends State { + String _token = ''; + String clientId = CLIENT_ID; + late html.WindowBase popUpWindow; + + @override + void initState() { + super.initState(); + + final Uri currentUrl = Uri.base; + // If token is not present, open SSO popup + if (!currentUrl.fragment.contains('access_token=')) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + html.window.location.href = + 'https://api-qa.ucsd.edu:8243/authorize?response_type=token&client_id=$clientId&redirect_uri=${currentUrl.origin}&scope=viewing_activity_read'; + }); + } else { + // If it is present, okay to move onto main page + final List fragments = currentUrl.fragment.split('&'); + _token = fragments + .firstWhere((String e) => e.startsWith('access_token=')) + .substring('access_token='.length); + WidgetsBinding.instance!.addPostFrameCallback((_) => setState(() { + _token = fragments + .firstWhere((String e) => e.startsWith('access_token=')) + .substring('access_token='.length); + })); + } + } + + @override + Widget build(BuildContext context) { + if (_token != '') { + Navigator.pushNamedAndRemoveUntil( + context, RoutePaths.Home, (Route route) => false); + } + return const Scaffold( + body: Center( + child: Text('Missing Authentication Token'), + ), + ); + } +} diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index 9c5eb78..d5963a4 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -1,18 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_placeholder.dart'; +import 'package:provider/provider.dart'; +import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar.dart'; import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart'; -import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/ui/search/search_placeholder.dart'; class BottomNavigation extends StatefulWidget { + const BottomNavigation({Key? key}) : super(key: key); + @override _BottomNavigationState createState() => _BottomNavigationState(); } class _BottomNavigationState extends State with SingleTickerProviderStateMixin { - var currentTab = [ + UserDataProvider userDataProvider = UserDataProvider(); + List currentTab = [ Calendar(Colors.blue.shade200), CourseListView(), Calendar(Colors.green.shade200), @@ -21,18 +25,30 @@ class _BottomNavigationState extends State static const TextStyle textStyles = TextStyle(fontSize: 16, color: darkGray); static const TextStyle activeStyles = TextStyle( - // decoration: TextDecoration.underline, fontWeight: FontWeight.bold, color: ColorPrimary, fontSize: 16, ); + @override + void didChangeDependencies() { + super.didChangeDependencies(); + userDataProvider = Provider.of(context, listen: false); + } @override Widget build(BuildContext context) { + String _token = ''; + final Uri currentUrl = Uri.base; + final List fragments = currentUrl.fragment.split('&'); + _token = fragments + .firstWhere((String e) => e.startsWith('access_token=')) + .substring('access_token='.length); + userDataProvider.setToken = _token; + return Scaffold( appBar: AppBar( centerTitle: true, - title: Text("Webreg", + title: const Text('Webreg', style: TextStyle( fontWeight: FontWeight.normal, )), @@ -41,28 +57,27 @@ class _BottomNavigationState extends State bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: currentIndex, - onTap: (index) { + onTap: (int index) { setState(() { currentIndex = index; }); }, items: [ BottomNavigationBarItem( - icon: Text("Calendar", style: textStyles), - activeIcon: Container( - child: Column(children: [ - Text("Calendar", style: activeStyles), - ])), + icon: const Text('Calendar', style: textStyles), + activeIcon: Column(children: const [ + Text('Calendar', style: activeStyles), + ]), label: '', ), - BottomNavigationBarItem( - icon: Text("List", style: textStyles), - activeIcon: Text("List", style: activeStyles), + const BottomNavigationBarItem( + icon: Text('List', style: textStyles), + activeIcon: Text('List', style: activeStyles), label: '', ), - BottomNavigationBarItem( - icon: Text("Finals", style: textStyles), - activeIcon: Text("Finals", style: activeStyles), + const BottomNavigationBarItem( + icon: Text('Finals', style: textStyles), + activeIcon: Text('Finals', style: activeStyles), label: '', ), ], diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index fb413d1..e95f0f1 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,7 +1,3 @@ -// ignore_for_file: always_specify_types - -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; @@ -13,7 +9,8 @@ import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; class SearchDetail extends StatelessWidget { SearchDetail({Key? key, required this.data}) : super(key: key); final CourseData data; - Map> instructorSections = Map(); + Map> instructorSections = + >{}; @override Widget build(BuildContext context) => Scaffold( @@ -43,27 +40,24 @@ class SearchDetail extends StatelessWidget { )))); } + // Determine types of sections Widget courseDetails() { - // Determine types of sections - final List sectionCards = []; - // final List sectionTypes = []; - // final List sectionObjects = []; - instructorSections = new Map(); + instructorSections = >{}; for (final SectionData section in data.sections!) { final String sectionLetter = section.sectionCode![0]; - instructorSections.update(sectionLetter, (value) { + instructorSections.update(sectionLetter, (List value) { value.add(section); return value; }, ifAbsent: () { - List sectionList = []; + final List sectionList = []; sectionList.add(section); return sectionList; }); } - instructorSections.forEach((key, value) { + instructorSections.forEach((String key, List value) { final List sectionTypes = []; final List sectionObjects = []; for (final SectionData section in value) { @@ -84,24 +78,6 @@ class SearchDetail extends StatelessWidget { } }); - // print("\n${const JsonEncoder.withIndent(" ").convert(data)}\n"); - // for (final SectionData section in data.sections!) { - // if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && - // section.sectionStatus != 'CA') { - // sectionTypes.add(section.instructionType!); - // sectionObjects.add(section); - // } - // } - // sectionTypes.add('FI'); - // sectionObjects.add(SectionData()); - - //Build section cards for different instruction types - // int sectionIndex = 0; - // for (final String sectionType in sectionTypes.toList()) { - // sectionCards - // .add(buildSectionCard(sectionType, sectionObjects[sectionIndex])); - // sectionIndex++; - // } return Column(children: [...sectionCards]); } @@ -172,7 +148,7 @@ class SearchDetail extends StatelessWidget { ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ Text( lectureObject.sectionCode!, style: const TextStyle(color: darkGray), @@ -225,7 +201,7 @@ class SearchDetail extends StatelessWidget { height: 35, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ Text( discussionObject.sectionCode!, style: const TextStyle(color: darkGray), @@ -276,7 +252,7 @@ class SearchDetail extends StatelessWidget { height: 35, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ Text( labObject.sectionCode!, style: const TextStyle(color: darkGray), @@ -328,7 +304,7 @@ class SearchDetail extends StatelessWidget { height: 35, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ Text( labObject.sectionCode!, style: const TextStyle(color: darkGray), @@ -383,7 +359,7 @@ class SearchDetail extends StatelessWidget { height: 35, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ + children: [ const Text( 'FINAL', style: TextStyle(color: darkGray), diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 7bc8cda..3c253d3 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -1,10 +1,12 @@ // ignore_for_file: use_key_in_widget_constructors, always_specify_types import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/ui/search/search_bar.dart'; class SearchView extends StatefulWidget { @@ -20,11 +22,15 @@ class _SearchViewState extends State { late TermDropdown termDropdown; List dropdownItems = ['SP21', 'FA21', 'WI22', 'SP22']; String _dropdownVal = 'SP22'; + bool firstRun = true; @override void didChangeDependencies() { super.didChangeDependencies(); - classesProvider = ScheduleOfClassesProvider(); + classesProvider = + Provider.of(context, listen: false); + classesProvider.userDataProvider = + Provider.of(context, listen: false); } @override @@ -55,11 +61,13 @@ class _SearchViewState extends State { .fetchTerms(), builder: (BuildContext context, AsyncSnapshot response) { - if (response.hasData) { - dropdownItems = - response.data as List; - _dropdownVal = - dropdownItems[dropdownItems.length - 1]; + if (response.hasData || !firstRun) { + if (firstRun) { + dropdownItems = + response.data as List; + _dropdownVal = dropdownItems[ + dropdownItems.length - 1]; + } return DropdownButton( underline: Container(height: 0), value: _dropdownVal, @@ -67,6 +75,7 @@ class _SearchViewState extends State { color: Colors.black, size: 20), onChanged: (String? newVal) { setState(() { + firstRun = false; _dropdownVal = newVal!; }); }, @@ -148,7 +157,7 @@ class _SearchViewState extends State { Widget body(bool showList) { bool validQuery = false; String searchBuilder = ''; - String termCode = _dropdownVal; + final String termCode = _dropdownVal; if (showList) { final List words = searchString.split(' '); @@ -198,8 +207,7 @@ class _SearchViewState extends State { } return FutureBuilder( - future: classesProvider.scheduleOfClassesService - .fetchClasses(searchBuilder), + future: classesProvider.fetchClasses(searchBuilder), builder: (BuildContext context, AsyncSnapshot response) { if (response.hasData) { return buildResultsList(context); @@ -252,7 +260,7 @@ class _SearchViewState extends State { shape: BoxShape.circle, ), margin: const EdgeInsets.only(right: 10), - child: Center(child: Text(course.unitsInc.toString()))), + child: Center(child: Text(course.unitsMax.toString()))), // Course info Expanded( diff --git a/pubspec.yaml b/pubspec.yaml index c9c55a3..baf8977 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" +encrypt: 5.0.0 dependencies: cupertino_icons: 1.0.0 flutter: @@ -12,6 +13,10 @@ dependencies: dio: 4.0.0 get: 4.1.4 intl: 0.17.0 + provider: ^6.0.2 + encrypt: ^5.0.1 + hive: ^2.0.5 + flutter_secure_storage: ^5.0.2 dev_dependencies: flutter_test: sdk: flutter diff --git a/web/static.html b/web/static.html new file mode 100644 index 0000000..2b105e0 --- /dev/null +++ b/web/static.html @@ -0,0 +1,18 @@ + + + + + + Authenticated + + + + + + + + + \ No newline at end of file From 288ee3eac432a6b8ec0b99fbc413d1f189dde51b Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:35:54 -0700 Subject: [PATCH 18/24] Working on new user profile int --- lib/core/models/user.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/core/models/user.dart diff --git a/lib/core/models/user.dart b/lib/core/models/user.dart new file mode 100644 index 0000000..f2ed62c --- /dev/null +++ b/lib/core/models/user.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'dart:core'; + +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; + +UserModel userModelFromJson(String str) => UserModel.fromJson(json.decode(str)); + +String userModelToJson(UserModel data) => json.encode(data.toJson()); + +class UserModel { + UserModel( + {this.enrolledCourses, this.waitlistedCourses, this.plannedCourses}); + + factory UserModel.fromJson(Map json) => UserModel( + enrolledCourses: List.from( + json['enrolledCourses'].map((x) => CourseData.fromJson(x))), + waitlistedCourses: List.from( + json['waitlistedCourses'].map((x) => CourseData.fromJson(x))), + plannedCourses: List.from( + json['plannedCourses'].map((x) => CourseData.fromJson(x)))); + + List? enrolledCourses; + List? waitlistedCourses; + List? plannedCourses; + + Map toJson() => { + 'enrolledCourses': List.from( + enrolledCourses!.map((CourseData x) => x.toJson())), + 'waitlistedCourses': List.from( + enrolledCourses!.map((CourseData x) => x.toJson())), + 'plannedCourses': List.from( + enrolledCourses!.map((CourseData x) => x.toJson())), + }; +} From 874496d4239419ce1a347e74f4c04e4135468a40 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:45:45 -0700 Subject: [PATCH 19/24] Created corresponding service, provider and model files for user profile --- lib/app_provider.dart | 10 ++++++ lib/core/models/profile.dart | 26 ++++++++++++++++ lib/core/providers/profile.dart | 54 +++++++++++++++++++++++++++++++++ lib/core/services/profile.dart | 39 ++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 lib/core/models/profile.dart create mode 100644 lib/core/providers/profile.dart create mode 100644 lib/core/services/profile.dart diff --git a/lib/app_provider.dart b/lib/app_provider.dart index 7710236..aa056b8 100644 --- a/lib/app_provider.dart +++ b/lib/app_provider.dart @@ -1,5 +1,6 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; +import 'package:webreg_mobile_flutter/core/providers/profile.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/user.dart'; @@ -19,5 +20,14 @@ List providers = [ ScheduleOfClassesProvider? scheduleOfClassesProvider) { scheduleOfClassesProvider!.userDataProvider = userDataProvider; return scheduleOfClassesProvider; + }), + ChangeNotifierProxyProvider(create: (_) { + final ProfileProvider profileProvider = ProfileProvider(); + profileProvider.userDataProvider = UserDataProvider(); + return profileProvider; + }, update: + (_, UserDataProvider userDataProvider, ProfileProvider? profileProvider) { + profileProvider!.userDataProvider = userDataProvider; + return profileProvider; }) ]; diff --git a/lib/core/models/profile.dart b/lib/core/models/profile.dart new file mode 100644 index 0000000..41c1d6c --- /dev/null +++ b/lib/core/models/profile.dart @@ -0,0 +1,26 @@ +// ignore_for_file: always_specify_types + +import 'dart:convert'; +import 'dart:core'; + +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; + +ProfileModel profileModelFromJson(String str) => + ProfileModel.fromJson(json.decode(str)); + +String profileModelToJson(ProfileModel data) => json.encode(data.toJson()); + +class ProfileModel { + ProfileModel({this.enrolledCourses}); + + factory ProfileModel.fromJson(Map json) => ProfileModel( + enrolledCourses: List.from( + json['enrolledCourses'].map((x) => CourseData.fromJson(x)))); + + List? enrolledCourses; + + Map toJson() => { + 'enrolledCourses': + List.from(enrolledCourses!.map((x) => x.toJson())) + }; +} diff --git a/lib/core/providers/profile.dart b/lib/core/providers/profile.dart new file mode 100644 index 0000000..94c0a91 --- /dev/null +++ b/lib/core/providers/profile.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:webreg_mobile_flutter/core/models/profile.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; +import 'package:webreg_mobile_flutter/core/services/profile.dart'; +import 'package:webreg_mobile_flutter/core/services/profile.dart'; + +class ProfileProvider extends ChangeNotifier { + ProfileProvider() { + /// Default States + _isLoading = false; + _noResults = false; + + /// Services initialized + _profileService = ProfileService(); + + /// Models initalized + _profileModel = ProfileModel(); + } + + /// STATES + bool? _isLoading; + DateTime? _lastUpdated; + String? _error; + bool? _noResults; + + /// MODELS + ProfileModel _profileModel = ProfileModel(); + + /// SERVICES + late ProfileService _profileService; + + late UserDataProvider _userDataProvider; + + set userDataProvider(UserDataProvider userDataProvider) { + _userDataProvider = userDataProvider; + } + + ProfileService get profileService => _profileService; + + Future fetchProfile() async { + final Map headers = { + 'Authorization': 'Bearer ${_userDataProvider.accessToken}' + }; + + return _profileService.fetchProfile(headers); + } + + /// SIMPLE GETTERS + bool? get isLoading => _isLoading; + String? get error => _error; + DateTime? get lastUpdated => _lastUpdated; + ProfileModel get profileModel => _profileModel; + bool? get noResults => _noResults; +} diff --git a/lib/core/services/profile.dart b/lib/core/services/profile.dart new file mode 100644 index 0000000..560e1e3 --- /dev/null +++ b/lib/core/services/profile.dart @@ -0,0 +1,39 @@ +import 'package:webreg_mobile_flutter/app_networking.dart'; +import 'package:webreg_mobile_flutter/core/models/profile.dart'; + +class ProfileService { + // TODO(p8gonzal): Note to not use this prototype. For development purposes only. + final String profilePrototype = + 'https://i4ghbvwuo9.execute-api.us-west-2.amazonaws.com/qa'; + + bool _isLoading = false; + DateTime? _lastUpdated; + String? _error; + ProfileModel? profile; + + final NetworkHelper _networkHelper = const NetworkHelper(); + + Future fetchProfile(Map headers) async { + _error = null; + _isLoading = true; + + try { + /// Fetch data + final String? _response = + await _networkHelper.authorizedFetch(profilePrototype, headers); + if (_response != null) { + /// Parse data + profile = profileModelFromJson(_response); + _isLoading = false; + } else { + _isLoading = false; + return false; + } + return true; + } catch (e) { + _error = e.toString(); + _isLoading = false; + return false; + } + } +} From a05c1cb9623ad92879f2cf8d25b6e41173031023 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 19 May 2022 14:09:37 -0700 Subject: [PATCH 20/24] Basic functionality achieved for finals, discussions, classes --- lib/core/models/profile.dart | 10 +- lib/core/models/schedule_of_classes.dart | 26 +- lib/core/services/profile.dart | 6 +- lib/generated_plugin_registrant.dart | 2 + lib/ui/calendar/calendar.dart | 743 ++++++++++++++++++----- lib/ui/common/authentication_error.dart | 2 +- lib/ui/navigator/bottom.dart | 12 +- 7 files changed, 637 insertions(+), 164 deletions(-) diff --git a/lib/core/models/profile.dart b/lib/core/models/profile.dart index 41c1d6c..0fe6875 100644 --- a/lib/core/models/profile.dart +++ b/lib/core/models/profile.dart @@ -13,11 +13,13 @@ String profileModelToJson(ProfileModel data) => json.encode(data.toJson()); class ProfileModel { ProfileModel({this.enrolledCourses}); - factory ProfileModel.fromJson(Map json) => ProfileModel( - enrolledCourses: List.from( - json['enrolledCourses'].map((x) => CourseData.fromJson(x)))); + factory ProfileModel.fromJson(Map json) { + return ProfileModel( + enrolledCourses: List.from( + json['enrolledCourses'].map((x) => SectionData.fromJson(x)))); + } - List? enrolledCourses; + List? enrolledCourses; Map toJson() => { 'enrolledCourses': diff --git a/lib/core/models/schedule_of_classes.dart b/lib/core/models/schedule_of_classes.dart index 1b30243..3d753c1 100644 --- a/lib/core/models/schedule_of_classes.dart +++ b/lib/core/models/schedule_of_classes.dart @@ -103,7 +103,11 @@ class SectionData { this.planCode, this.recurringMeetings, this.additionalMeetings, - this.instructors}); + this.instructors, + //***********************// Only for prototpying purposes (p8gonzal) + this.units, + this.subjectCode, + this.courseCode}); factory SectionData.fromJson(Map json) => SectionData( sectionId: json['sectionId'] ?? '', @@ -125,7 +129,11 @@ class SectionData { additionalMeetings: List.from( json['additionalMeetings'].map((x) => MeetingData.fromJson(x))), instructors: List.from( - json['instructors'].map((x) => Instructor.fromJson(x)))); + json['instructors'].map((x) => Instructor.fromJson(x))), + //***********************// Only for prototpying purposes (p8gonzal) + units: json['units'] ?? 0, + subjectCode: json['subjectCode'] ?? '', + courseCode: json['courseCode'] ?? ''); String? sectionId; String? termCode; @@ -144,6 +152,10 @@ class SectionData { List? recurringMeetings; List? additionalMeetings; List? instructors; + //***********************// Only for prototpying purposes (p8gonzal) + double? units; + String? subjectCode; + String? courseCode; Map toJson() => { 'sectionId': sectionId, @@ -164,7 +176,11 @@ class SectionData { List.from(recurringMeetings!.map((x) => x.toJson())), 'additionalMeetings': List.from(additionalMeetings!.map((x) => x.toJson())), - 'instructors': List.from(instructors!.map((x) => x.toJson())) + 'instructors': List.from(instructors!.map((x) => x.toJson())), + //***********************// Only for prototpying purposes (p8gonzal) + 'units': units, + 'subjectCode': subjectCode, + 'courseCode': courseCode }; } @@ -224,8 +240,8 @@ class Instructor { instructorName: json['instructorName'] ?? '', primaryInstructor: json['primaryInstructor'] ?? false, instructorEmailAddress: json['instructorEmailAddress'] ?? '', - workLoadUnitQty: json['workLoadUnitQty'] ?? '', - percentOfLoad: json['percentOfLoad'] ?? ''); + workLoadUnitQty: json['workLoadUnitQty'] ?? 1, + percentOfLoad: json['percentOfLoad'] ?? 100); String? pid; String? instructorName; diff --git a/lib/core/services/profile.dart b/lib/core/services/profile.dart index 560e1e3..0b74d90 100644 --- a/lib/core/services/profile.dart +++ b/lib/core/services/profile.dart @@ -4,7 +4,7 @@ import 'package:webreg_mobile_flutter/core/models/profile.dart'; class ProfileService { // TODO(p8gonzal): Note to not use this prototype. For development purposes only. final String profilePrototype = - 'https://i4ghbvwuo9.execute-api.us-west-2.amazonaws.com/qa'; + 'https://i4ghbvwuo9.execute-api.us-west-2.amazonaws.com/qa/profile'; bool _isLoading = false; DateTime? _lastUpdated; @@ -18,9 +18,10 @@ class ProfileService { _isLoading = true; try { + /// Fetch data final String? _response = - await _networkHelper.authorizedFetch(profilePrototype, headers); + await _networkHelper.fetchData(profilePrototype); if (_response != null) { /// Parse data profile = profileModelFromJson(_response); @@ -32,6 +33,7 @@ class ProfileService { return true; } catch (e) { _error = e.toString(); + print(_error); _isLoading = false; return false; } diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart index 56b17ac..256a7dc 100644 --- a/lib/generated_plugin_registrant.dart +++ b/lib/generated_plugin_registrant.dart @@ -5,12 +5,14 @@ // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars +import 'package:flutter_secure_storage_web/flutter_secure_storage_web.dart'; import 'package:package_info_plus_web/package_info_plus_web.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { + FlutterSecureStorageWeb.registerWith(registrar); PackageInfoPlugin.registerWith(registrar); registrar.registerMessageHandler(); } diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 0f2ab34..b9f57e9 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -1,154 +1,88 @@ +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +import 'package:webreg_mobile_flutter/core/models/profile.dart'; +import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; +import 'package:webreg_mobile_flutter/core/providers/profile.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar_card.dart'; import 'package:webreg_mobile_flutter/ui/common/build_info.dart'; -class Calendar extends StatelessWidget { - Calendar(this.color); +class Calendar extends StatefulWidget { + final String calendarType; - final Color color; + const Calendar(String s, {Key? key, required this.calendarType}) + : super(key: key); - static const earliestClass = '08:00'; - - static const times = [ - '8am', - '9am', - '10am', - '11am', - '12pm', - '1pm', - '2pm', - '3pm', - '4pm', - '5pm', - '6pm', - '7pm', - '8pm', - '9pm', - '10pm', - ]; - - static const dayOfWeek = [ - 'Mon', - 'Tues', - 'Wed', - 'Thurs', - 'Fri', - 'Sat', - 'Sun', - ]; + @override + State createState() => _CalendarViewState(); +} - static const courses = [ - { - 'datePrefix': '2020-06-06T', - 'startTime': '09:00', - 'endTime': '09:50', - 'dayOfWeek': 1, - 'type': 'LE', - 'title': 'CSE 120', - 'location': 'PCYNH 112', - }, - { - 'datePrefix': '2020-06-06T', - 'startTime': '09:00', - 'endTime': '09:50', - 'dayOfWeek': 3, - 'type': 'LE', - 'title': 'CSE 120', - 'location': 'PCYNH 112', - }, - { - 'datePrefix': '2020-06-06T', - 'startTime': '09:00', - 'endTime': '09:50', - 'dayOfWeek': 5, - 'type': 'LE', - 'title': 'CSE 120', - 'location': 'PCYNH 112', - }, - { - 'datePrefix': '2020-06-06T', - 'startTime': '14:00', - 'endTime': '15:20', - 'dayOfWeek': 1, - 'type': 'LE', - 'title': 'COGS 10', - 'location': 'WLH 110', - }, - { - 'datePrefix': '2020-06-06T', - 'startTime': '10:00', - 'endTime': '10:50', - 'dayOfWeek': 1, - 'type': 'DI', - 'title': 'CSE 123', - 'location': 'PCYNH 112', - }, - ]; +class _CalendarViewState extends State { + late ProfileProvider profileProvider; + late String calendarType; - double getTimeDifference(String start, String end, String prefix) { - double diff = DateTime.parse(prefix + end) - .difference(DateTime.parse(prefix + start)) - .inMinutes - .toDouble(); - return diff; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + profileProvider = Provider.of(context, listen: false); + profileProvider.userDataProvider = + Provider.of(context, listen: false); } @override Widget build(BuildContext context) { - double calendarCardWidth = (MediaQuery.of(context).size.width - - CalendarStyles.calendarTimeWidth - - 20) / - 7; - + calendarType = widget.calendarType; return Container( color: Colors.white, padding: EdgeInsets.symmetric(horizontal: 10), - child: Column( - children: [ - // calendar header - Container( - height: CalendarStyles.calendarHeaderHeight, - padding: EdgeInsets.only(top: 20, bottom: 15), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: lightGray), - ), - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.05), - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1), // changes position of shadow - ), - ], + child: Column(children: [ + // calendar header + Container( + height: CalendarStyles.calendarHeaderHeight, + padding: EdgeInsets.only(top: 20, bottom: 15), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: lightGray), ), - child: Row( - children: [ - SizedBox( - width: CalendarStyles.calendarTimeWidth, - ), - Expanded( - child: Row( - children: dayOfWeek - .map((day) => Expanded( - flex: 1, - child: Container( - child: Center( - child: Text(day, - style: TextStyle( - fontSize: 10, - letterSpacing: -0.1)))), - )) - .toList(), - )) - ], - )), - - Expanded( - child: Stack(children: [ + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.05), + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + SizedBox( + width: CalendarStyles.calendarTimeWidth, + ), + Expanded( + child: Row( + children: dayOfWeek + .map((String day) => Expanded( + flex: 1, + child: Container( + child: Center( + child: Text(day, + style: TextStyle( + fontSize: 10, + letterSpacing: -0.1)))), + )) + .toList(), + )) + ], + )), + + Expanded( + child: Stack( + children: [ // calendar body ListView.builder( itemCount: times.length, @@ -175,20 +109,533 @@ class Calendar extends StatelessWidget { ])); }), - CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', 'CSE 110', - 'Center 109', color), - CalendarCard('10:00', '10:50', '2020-06-06T', 2, 'LE', 'CSE 110', - 'Center 109', color), - CalendarCard('10:00', '10:50', '2020-06-06T', 4, 'LE', 'CSE 110', - 'Center 109', color), - - CalendarCard('11:00', '12:20', '2020-06-06T', 1, 'DI', 'CSE 100', - 'WLH 109', color), - CalendarCard('11:00', '12:20', '2020-06-06T', 3, 'DI', 'CSE 100', - 'WLH 109', color), - ])), - BuildInfo(), - ], - )); + //Extract relevant data from profile and create calendar cards + + FutureBuilder( + future: profileProvider.fetchProfile(), + builder: + (BuildContext context, AsyncSnapshot response) { + if (response.hasData) { + final ProfileModel model = + profileProvider.profileService.profile!; + + List? courseList = model.enrolledCourses; + + // Change for switching between Lectures/Discussion and Finals + if (calendarType == 'LECT_DISC') { + final List sectionCards = + []; + final List sectionTypes = []; + final List sectionObjects = + []; + for (final SectionData section in courseList!) { + if (section.sectionStatus != 'CA') { + sectionTypes.add(section.instructionType!); + sectionObjects.add(section); + } + } + int sectionIndex = 0; + List lectureObjects = []; + while (sectionTypes.contains('LE')) { + // Extract lecture times from lecture object + SectionData lectureObject = SectionData(); + + for (final String sectionType + in sectionTypes.toList()) { + if (sectionType == 'LE') { + lectureObject = sectionObjects[sectionIndex]; + break; + } + sectionCards.add(buildCalendarCard( + sectionType, sectionObjects[sectionIndex])); + sectionIndex++; + } + sectionTypes.removeAt(sectionIndex); + sectionObjects.removeAt(sectionIndex); + lectureObjects.add(lectureObject); + } + + for (SectionData lecture in lectureObjects) { + int meetingIndex = 0; + for (MeetingData meetingData + in lecture.recurringMeetings!.toList()) { + SectionData CopyOfLectureObject = + SectionData.fromJson(lecture.toJson()); + CopyOfLectureObject.recurringMeetings = []; + CopyOfLectureObject.recurringMeetings! + .add(lecture.recurringMeetings![meetingIndex]); + sectionTypes + .add(CopyOfLectureObject.instructionType!); + sectionObjects.add(CopyOfLectureObject); + meetingIndex++; + } + } + // sectionTypes.add('FI'); + // sectionObjects.add(SectionData()); + + sectionIndex = 0; + for (final String sectionType + in sectionTypes.toList()) { + sectionCards.add(buildCalendarCard( + sectionType, sectionObjects[sectionIndex])); + sectionIndex++; + } + + return Stack(children: [...sectionCards]); + } else { + final List sectionCards = + []; + final List sectionTypes = []; + final List sectionObjects = + []; + for (final SectionData section in courseList!) { + if (section.instructionType == 'LE') { + sectionTypes.add('FI'); + sectionObjects.add(section); + } + } + + int sectionIndex = 0; + for (final String sectionType + in sectionTypes.toList()) { + sectionCards.add(buildCalendarCard( + sectionType, sectionObjects[sectionIndex])); + sectionIndex++; + } + + return Stack(children: [...sectionCards]); + } + } else { + return CalendarCard('10:00', '10:50', '2020-06-06T', 0, + 'LE', '', '', Colors.blue.shade200); + } + }), + ], + )) + // BuildInfo(), + ])); + } + + // Fix an incorrectly formatted time + void correctTimeFormat(MeetingData meeting) { + while (meeting.startTime!.length < 4) { + meeting.startTime = '0' + meeting.startTime!; + } + while (meeting.endTime!.length < 4) { + meeting.endTime = '0' + meeting.endTime!; + } + } + + static const List dayOfWeek = [ + 'Mon', + 'Tues', + 'Wed', + 'Thurs', + 'Fri', + 'Sat', + 'Sun', + ]; + + static const Map dayMapping = { + 'MO': 0, + 'TU': 1, + 'WE': 2, + 'TH': 3, + 'FR': 4 + }; + + static const List times = [ + '8am', + '9am', + '10am', + '11am', + '12pm', + '1pm', + '2pm', + '3pm', + '4pm', + '5pm', + '6pm', + '7pm', + '8pm', + '9pm', + '10pm', + ]; + CalendarCard buildCalendarCard( + String sectionType, SectionData sectionObject) { + switch (sectionType) { + case 'LE': + { + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(sectionObject.recurringMeetings!.first); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.startTime!)); + String endTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = + sectionObject.recurringMeetings!.first.buildingCode! + + ' ' + + sectionObject.recurringMeetings!.first.roomCode!.substring(1); + // Construct Card widget + return CalendarCard( + sectionObject.recurringMeetings!.first.startTime!, + sectionObject.recurringMeetings!.first.endTime!, + prefix, + dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, + sectionType, + '${sectionObject.subjectCode} ${sectionObject.courseCode}', + room, + Colors.blue.shade200); + } + case 'DI': + { + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(sectionObject.recurringMeetings!.first); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.startTime!)); + String endTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = + sectionObject.recurringMeetings!.first.buildingCode! + + ' ' + + sectionObject.recurringMeetings!.first.roomCode!.substring(1); + + // Construct Card widget + return CalendarCard( + sectionObject.recurringMeetings!.first.startTime!, + sectionObject.recurringMeetings!.first.endTime!, + prefix, + dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, + sectionType, + '${sectionObject.subjectCode} ${sectionObject.courseCode}', + room, + Colors.blue.shade200); + } + case 'LA': + { + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(sectionObject.recurringMeetings!.first); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.startTime!)); + String endTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = + sectionObject.recurringMeetings!.first.buildingCode! + + ' ' + + sectionObject.recurringMeetings!.first.roomCode!.substring(1); + + // Construct Card widget + return CalendarCard( + startTime, + endTime, + prefix, + sectionObject.units!.toInt(), + sectionType, + "${sectionObject.subjectCode} ${sectionObject.courseCode}", + room, + Colors.blue.shade200); + } + case 'TU': + { + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(sectionObject.recurringMeetings!.first); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.startTime!)); + String endTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = + sectionObject.recurringMeetings!.first.buildingCode! + + ' ' + + sectionObject.recurringMeetings!.first.roomCode!.substring(1); + + // Construct Card widget + return CalendarCard( + startTime, + endTime, + prefix, + sectionObject.units!.toInt(), + sectionType, + "${sectionObject.subjectCode} ${sectionObject.courseCode}", + room, + Colors.blue.shade200); + } + case 'FI': + { + MeetingData finalMeeting = MeetingData(); + for (MeetingData meetingData in sectionObject.additionalMeetings!) { + if (meetingData.meetingType == 'FI') { + finalMeeting = meetingData; + } + } + + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(finalMeeting); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm() + .format(DateTime.parse(prefix + finalMeeting.startTime!)); + String endTime = DateFormat.jm() + .format(DateTime.parse(prefix + finalMeeting.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = finalMeeting.buildingCode! + + ' ' + + finalMeeting.roomCode!.substring(1); + + // Construct Card widget + return CalendarCard( + finalMeeting.startTime!, + finalMeeting.endTime!, + prefix, + dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, + sectionType, + "${sectionObject.subjectCode} ${sectionObject.courseCode}", + room, + Colors.blue.shade200); + } + default: + // Time parsing + // Checking for classes that start have *** format instead of **** + correctTimeFormat(sectionObject.recurringMeetings!.first); + const String prefix = '0000-01-01T'; + String startTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.startTime!)); + String endTime = DateFormat.jm().format(DateTime.parse( + prefix + sectionObject.recurringMeetings!.first.endTime!)); + startTime = startTime.toLowerCase().replaceAll(' ', ''); + endTime = endTime.toLowerCase().replaceAll(' ', ''); + + // Room Code and Number parsing + final String room = + sectionObject.recurringMeetings!.first.buildingCode! + + ' ' + + sectionObject.recurringMeetings!.first.roomCode!.substring(1); + + // Construct Card widget + return CalendarCard( + startTime, + endTime, + prefix, + sectionObject.units!.toInt(), + sectionType, + "${sectionObject.subjectCode} ${sectionObject.courseCode}", + room, + Colors.blue.shade200); + } } } + +// Calendar(this.color); + +// final Color color; + +// static const earliestClass = '08:00'; + + +// static const dayOfWeek = [ +// 'Mon', +// 'Tues', +// 'Wed', +// 'Thurs', +// 'Fri', +// 'Sat', +// 'Sun', +// ]; + +// static const courses = [ +// { +// 'datePrefix': '2020-06-06T', +// 'startTime': '09:00', +// 'endTime': '09:50', +// 'dayOfWeek': 1, +// 'type': 'LE', +// 'title': 'CSE 120', +// 'location': 'PCYNH 112', +// }, +// { +// 'datePrefix': '2020-06-06T', +// 'startTime': '09:00', +// 'endTime': '09:50', +// 'dayOfWeek': 3, +// 'type': 'LE', +// 'title': 'CSE 120', +// 'location': 'PCYNH 112', +// }, +// { +// 'datePrefix': '2020-06-06T', +// 'startTime': '09:00', +// 'endTime': '09:50', +// 'dayOfWeek': 5, +// 'type': 'LE', +// 'title': 'CSE 120', +// 'location': 'PCYNH 112', +// }, +// { +// 'datePrefix': '2020-06-06T', +// 'startTime': '14:00', +// 'endTime': '15:20', +// 'dayOfWeek': 1, +// 'type': 'LE', +// 'title': 'COGS 10', +// 'location': 'WLH 110', +// }, +// { +// 'datePrefix': '2020-06-06T', +// 'startTime': '10:00', +// 'endTime': '10:50', +// 'dayOfWeek': 1, +// 'type': 'DI', +// 'title': 'CSE 123', +// 'location': 'PCYNH 112', +// }, +// ]; + +// double getTimeDifference(String start, String end, String prefix) { +// double diff = DateTime.parse(prefix + end) +// .difference(DateTime.parse(prefix + start)) +// .inMinutes +// .toDouble(); +// return diff; +// } + +// @override +// Widget build(BuildContext context) { + // double calendarCardWidth = (MediaQuery.of(context).size.width - + // CalendarStyles.calendarTimeWidth - + // 20) / + // 7; + + // return Container( + // color: Colors.white, + // padding: EdgeInsets.symmetric(horizontal: 10), + // child: Column( + // children: [ + // // calendar header + // Container( + // height: CalendarStyles.calendarHeaderHeight, + // padding: EdgeInsets.only(top: 20, bottom: 15), + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide(color: lightGray), + // ), + // color: Colors.white, + // boxShadow: [ + // BoxShadow( + // color: Colors.grey.withOpacity(0.05), + // spreadRadius: 1, + // blurRadius: 2, + // offset: Offset(0, 1), // changes position of shadow + // ), + // ], + // ), + // child: Row( + // children: [ + // SizedBox( + // width: CalendarStyles.calendarTimeWidth, + // ), + // Expanded( + // child: Row( + // children: dayOfWeek + // .map((day) => Expanded( + // flex: 1, + // child: Container( + // child: Center( + // child: Text(day, + // style: TextStyle( + // fontSize: 10, + // letterSpacing: -0.1)))), + // )) + // .toList(), + // )) + // ], + // )), + + // Expanded( + // child: Stack(children: [ + // // calendar body + // ListView.builder( + // itemCount: times.length, + // // padding: EdgeInsets.symmetric(vertical: 8), + // itemBuilder: (BuildContext context, int index) { + // return Container( + // height: CalendarStyles.calendarRowHeight, + // margin: EdgeInsets.all(0), + // padding: EdgeInsets.all(0), + // decoration: BoxDecoration( + // border: Border( + // bottom: BorderSide(color: lightGray), + // ), + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // SizedBox( + // width: CalendarStyles.calendarTimeWidth, + // child: Center( + // child: Text(times[index], + // style: TextStyle(fontSize: 10)), + // )) + // ])); + // }), + + // //Extract relevant data from profile and create calendar cards + + // FutureBuilder( + // future: , + // builder: (BuildContext context, AsyncSnapshot response){ + // if(response.hasData){ + + + // }else{ + // return CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', '', + // '', color); + // } + + // }), + // ), + + // CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', 'CSE 110', + // 'Center 109', color), + // CalendarCard('10:00', '10:50', '2020-06-06T', 2, 'LE', 'CSE 110', + // 'Center 109', color), + // CalendarCard('10:00', '10:50', '2020-06-06T', 4, 'LE', 'CSE 110', + // 'Center 109', color), + + // CalendarCard('11:00', '12:20', '2020-06-06T', 1, 'DI', 'CSE 100', + // 'WLH 109', color), + // CalendarCard('11:00', '12:20', '2020-06-06T', 3, 'DI', 'CSE 100', + // 'WLH 109', color), + // ])), + // BuildInfo(), + // ], + // )); +// } + + +// } diff --git a/lib/ui/common/authentication_error.dart b/lib/ui/common/authentication_error.dart index e024288..4f1534e 100644 --- a/lib/ui/common/authentication_error.dart +++ b/lib/ui/common/authentication_error.dart @@ -4,7 +4,7 @@ import 'dart:html' as html; import 'package:flutter/material.dart'; import '../../app_constants.dart'; -const String CLIENT_ID = ''; +const String CLIENT_ID = 'nSu0wUDFf4FBiWognBJRamy_kZIa'; class AuthenticationError extends StatefulWidget { const AuthenticationError({Key? key}) : super(key: key); diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index d5963a4..51fe367 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -16,10 +16,14 @@ class BottomNavigation extends StatefulWidget { class _BottomNavigationState extends State with SingleTickerProviderStateMixin { UserDataProvider userDataProvider = UserDataProvider(); - List currentTab = [ - Calendar(Colors.blue.shade200), - CourseListView(), - Calendar(Colors.green.shade200), + List currentTab = [ + const Calendar( + 'LECT_DISC', + calendarType: 'LECT_DISC', + ), + CourseListView(), + // Finals Calendar + const Calendar('FINALS', calendarType: 'FINALS'), ]; int currentIndex = 0; From 0374b2e6e7106640010f3fa68bdc8e707c077bbb Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 19 May 2022 14:55:03 -0700 Subject: [PATCH 21/24] Added lab sections support --- lib/ui/calendar/calendar.dart | 270 ++++------------------------------ 1 file changed, 26 insertions(+), 244 deletions(-) diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index b9f57e9..8e862b9 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -136,28 +136,28 @@ class _CalendarViewState extends State { } int sectionIndex = 0; List lectureObjects = []; + + // Identify lecture objects while (sectionTypes.contains('LE')) { - // Extract lecture times from lecture object - SectionData lectureObject = SectionData(); - - for (final String sectionType - in sectionTypes.toList()) { - if (sectionType == 'LE') { - lectureObject = sectionObjects[sectionIndex]; - break; - } - sectionCards.add(buildCalendarCard( - sectionType, sectionObjects[sectionIndex])); - sectionIndex++; - } - sectionTypes.removeAt(sectionIndex); - sectionObjects.removeAt(sectionIndex); + int lectureIndex = sectionTypes.indexOf('LE'); + SectionData lectureObject = + sectionObjects.elementAt(lectureIndex); lectureObjects.add(lectureObject); + sectionTypes.removeAt(lectureIndex); + sectionObjects.removeAt(lectureIndex); + } + + sectionIndex = 0; + for (final String sectionType + in sectionTypes.toList()) { + sectionCards.add(buildCalendarCard( + sectionType, sectionObjects[sectionIndex])); + sectionIndex++; } - for (SectionData lecture in lectureObjects) { + for (final SectionData lecture in lectureObjects) { int meetingIndex = 0; - for (MeetingData meetingData + for (final MeetingData meetingData in lecture.recurringMeetings!.toList()) { SectionData CopyOfLectureObject = SectionData.fromJson(lecture.toJson()); @@ -318,7 +318,7 @@ class _CalendarViewState extends State { sectionObject.recurringMeetings!.first.endTime!, prefix, dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, - sectionType, + 'DI', '${sectionObject.subjectCode} ${sectionObject.courseCode}', room, Colors.blue.shade200); @@ -344,49 +344,20 @@ class _CalendarViewState extends State { // Construct Card widget return CalendarCard( - startTime, - endTime, - prefix, - sectionObject.units!.toInt(), - sectionType, - "${sectionObject.subjectCode} ${sectionObject.courseCode}", - room, - Colors.blue.shade200); - } - case 'TU': - { - // Time parsing - // Checking for classes that start have *** format instead of **** - correctTimeFormat(sectionObject.recurringMeetings!.first); - const String prefix = '0000-01-01T'; - String startTime = DateFormat.jm().format(DateTime.parse( - prefix + sectionObject.recurringMeetings!.first.startTime!)); - String endTime = DateFormat.jm().format(DateTime.parse( - prefix + sectionObject.recurringMeetings!.first.endTime!)); - startTime = startTime.toLowerCase().replaceAll(' ', ''); - endTime = endTime.toLowerCase().replaceAll(' ', ''); - - // Room Code and Number parsing - final String room = - sectionObject.recurringMeetings!.first.buildingCode! + - ' ' + - sectionObject.recurringMeetings!.first.roomCode!.substring(1); - - // Construct Card widget - return CalendarCard( - startTime, - endTime, + sectionObject.recurringMeetings!.first.startTime!, + sectionObject.recurringMeetings!.first.endTime!, prefix, - sectionObject.units!.toInt(), - sectionType, - "${sectionObject.subjectCode} ${sectionObject.courseCode}", + dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, + 'LA', + '${sectionObject.subjectCode} ${sectionObject.courseCode}', room, Colors.blue.shade200); } case 'FI': { MeetingData finalMeeting = MeetingData(); - for (MeetingData meetingData in sectionObject.additionalMeetings!) { + for (final MeetingData meetingData + in sectionObject.additionalMeetings!) { if (meetingData.meetingType == 'FI') { finalMeeting = meetingData; } @@ -414,7 +385,7 @@ class _CalendarViewState extends State { finalMeeting.endTime!, prefix, dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, - sectionType, + 'FI', "${sectionObject.subjectCode} ${sectionObject.courseCode}", room, Colors.blue.shade200); @@ -450,192 +421,3 @@ class _CalendarViewState extends State { } } } - -// Calendar(this.color); - -// final Color color; - -// static const earliestClass = '08:00'; - - -// static const dayOfWeek = [ -// 'Mon', -// 'Tues', -// 'Wed', -// 'Thurs', -// 'Fri', -// 'Sat', -// 'Sun', -// ]; - -// static const courses = [ -// { -// 'datePrefix': '2020-06-06T', -// 'startTime': '09:00', -// 'endTime': '09:50', -// 'dayOfWeek': 1, -// 'type': 'LE', -// 'title': 'CSE 120', -// 'location': 'PCYNH 112', -// }, -// { -// 'datePrefix': '2020-06-06T', -// 'startTime': '09:00', -// 'endTime': '09:50', -// 'dayOfWeek': 3, -// 'type': 'LE', -// 'title': 'CSE 120', -// 'location': 'PCYNH 112', -// }, -// { -// 'datePrefix': '2020-06-06T', -// 'startTime': '09:00', -// 'endTime': '09:50', -// 'dayOfWeek': 5, -// 'type': 'LE', -// 'title': 'CSE 120', -// 'location': 'PCYNH 112', -// }, -// { -// 'datePrefix': '2020-06-06T', -// 'startTime': '14:00', -// 'endTime': '15:20', -// 'dayOfWeek': 1, -// 'type': 'LE', -// 'title': 'COGS 10', -// 'location': 'WLH 110', -// }, -// { -// 'datePrefix': '2020-06-06T', -// 'startTime': '10:00', -// 'endTime': '10:50', -// 'dayOfWeek': 1, -// 'type': 'DI', -// 'title': 'CSE 123', -// 'location': 'PCYNH 112', -// }, -// ]; - -// double getTimeDifference(String start, String end, String prefix) { -// double diff = DateTime.parse(prefix + end) -// .difference(DateTime.parse(prefix + start)) -// .inMinutes -// .toDouble(); -// return diff; -// } - -// @override -// Widget build(BuildContext context) { - // double calendarCardWidth = (MediaQuery.of(context).size.width - - // CalendarStyles.calendarTimeWidth - - // 20) / - // 7; - - // return Container( - // color: Colors.white, - // padding: EdgeInsets.symmetric(horizontal: 10), - // child: Column( - // children: [ - // // calendar header - // Container( - // height: CalendarStyles.calendarHeaderHeight, - // padding: EdgeInsets.only(top: 20, bottom: 15), - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide(color: lightGray), - // ), - // color: Colors.white, - // boxShadow: [ - // BoxShadow( - // color: Colors.grey.withOpacity(0.05), - // spreadRadius: 1, - // blurRadius: 2, - // offset: Offset(0, 1), // changes position of shadow - // ), - // ], - // ), - // child: Row( - // children: [ - // SizedBox( - // width: CalendarStyles.calendarTimeWidth, - // ), - // Expanded( - // child: Row( - // children: dayOfWeek - // .map((day) => Expanded( - // flex: 1, - // child: Container( - // child: Center( - // child: Text(day, - // style: TextStyle( - // fontSize: 10, - // letterSpacing: -0.1)))), - // )) - // .toList(), - // )) - // ], - // )), - - // Expanded( - // child: Stack(children: [ - // // calendar body - // ListView.builder( - // itemCount: times.length, - // // padding: EdgeInsets.symmetric(vertical: 8), - // itemBuilder: (BuildContext context, int index) { - // return Container( - // height: CalendarStyles.calendarRowHeight, - // margin: EdgeInsets.all(0), - // padding: EdgeInsets.all(0), - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide(color: lightGray), - // ), - // ), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // SizedBox( - // width: CalendarStyles.calendarTimeWidth, - // child: Center( - // child: Text(times[index], - // style: TextStyle(fontSize: 10)), - // )) - // ])); - // }), - - // //Extract relevant data from profile and create calendar cards - - // FutureBuilder( - // future: , - // builder: (BuildContext context, AsyncSnapshot response){ - // if(response.hasData){ - - - // }else{ - // return CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', '', - // '', color); - // } - - // }), - // ), - - // CalendarCard('10:00', '10:50', '2020-06-06T', 0, 'LE', 'CSE 110', - // 'Center 109', color), - // CalendarCard('10:00', '10:50', '2020-06-06T', 2, 'LE', 'CSE 110', - // 'Center 109', color), - // CalendarCard('10:00', '10:50', '2020-06-06T', 4, 'LE', 'CSE 110', - // 'Center 109', color), - - // CalendarCard('11:00', '12:20', '2020-06-06T', 1, 'DI', 'CSE 100', - // 'WLH 109', color), - // CalendarCard('11:00', '12:20', '2020-06-06T', 3, 'DI', 'CSE 100', - // 'WLH 109', color), - // ])), - // BuildInfo(), - // ], - // )); -// } - - -// } From c096dec3304060552e69434abe004ace10bf2b9f Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 26 May 2022 16:29:20 -0700 Subject: [PATCH 22/24] Added better structure and commenting --- lib/ui/calendar/calendar.dart | 106 +++++++++++++++++++--------------- lib/ui/navigator/bottom.dart | 5 +- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/lib/ui/calendar/calendar.dart b/lib/ui/calendar/calendar.dart index 8e862b9..16b6fc9 100644 --- a/lib/ui/calendar/calendar.dart +++ b/lib/ui/calendar/calendar.dart @@ -1,4 +1,3 @@ -import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -9,14 +8,11 @@ import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/profile.dart'; import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/ui/calendar/calendar_card.dart'; -import 'package:webreg_mobile_flutter/ui/common/build_info.dart'; class Calendar extends StatefulWidget { + const Calendar({Key? key, required this.calendarType}) : super(key: key); final String calendarType; - const Calendar(String s, {Key? key, required this.calendarType}) - : super(key: key); - @override State createState() => _CalendarViewState(); } @@ -28,6 +24,7 @@ class _CalendarViewState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); + // Update the profile provider to access remote profile profileProvider = Provider.of(context, listen: false); profileProvider.userDataProvider = Provider.of(context, listen: false); @@ -35,32 +32,33 @@ class _CalendarViewState extends State { @override Widget build(BuildContext context) { + // Import arguement of calendar type: Lectures/Discussion or Finals calendarType = widget.calendarType; return Container( color: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 10), child: Column(children: [ - // calendar header + // Calendar header Container( height: CalendarStyles.calendarHeaderHeight, - padding: EdgeInsets.only(top: 20, bottom: 15), + padding: const EdgeInsets.only(top: 20, bottom: 15), decoration: BoxDecoration( - border: Border( + border: const Border( bottom: BorderSide(color: lightGray), ), color: Colors.white, - boxShadow: [ + boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.05), spreadRadius: 1, blurRadius: 2, - offset: Offset(0, 1), // changes position of shadow + offset: const Offset(0, 1), // changes position of shadow ), ], ), child: Row( children: [ - SizedBox( + const SizedBox( width: CalendarStyles.calendarTimeWidth, ), Expanded( @@ -68,12 +66,10 @@ class _CalendarViewState extends State { children: dayOfWeek .map((String day) => Expanded( flex: 1, - child: Container( - child: Center( - child: Text(day, - style: TextStyle( - fontSize: 10, - letterSpacing: -0.1)))), + child: Center( + child: Text(day, + style: const TextStyle( + fontSize: 10, letterSpacing: -0.1))), )) .toList(), )) @@ -90,9 +86,9 @@ class _CalendarViewState extends State { itemBuilder: (BuildContext context, int index) { return Container( height: CalendarStyles.calendarRowHeight, - margin: EdgeInsets.all(0), - padding: EdgeInsets.all(0), - decoration: BoxDecoration( + margin: const EdgeInsets.all(0), + padding: const EdgeInsets.all(0), + decoration: const BoxDecoration( border: Border( bottom: BorderSide(color: lightGray), ), @@ -104,49 +100,56 @@ class _CalendarViewState extends State { width: CalendarStyles.calendarTimeWidth, child: Center( child: Text(times[index], - style: TextStyle(fontSize: 10)), + style: const TextStyle(fontSize: 10)), )) ])); }), - //Extract relevant data from profile and create calendar cards - - FutureBuilder( + //Extract relevant data from the profile and create calendar cards + FutureBuilder( future: profileProvider.fetchProfile(), builder: (BuildContext context, AsyncSnapshot response) { + // When profile has not been fetched yet, returns a loading widget if (response.hasData) { final ProfileModel model = profileProvider.profileService.profile!; - List? courseList = model.enrolledCourses; + final List? courseList = + model.enrolledCourses; - // Change for switching between Lectures/Discussion and Finals + // For switching between Lectures/Discussion and Finals if (calendarType == 'LECT_DISC') { + // Lists to contain section objects and parallel list with section types final List sectionCards = []; final List sectionTypes = []; final List sectionObjects = []; + + // Idenitify all section types that are active for (final SectionData section in courseList!) { if (section.sectionStatus != 'CA') { sectionTypes.add(section.instructionType!); sectionObjects.add(section); } } + // Identify lecture objects int sectionIndex = 0; - List lectureObjects = []; + final List lectureObjects = + []; - // Identify lecture objects + // Isolate lecture objects because we need extrapolate days and times while (sectionTypes.contains('LE')) { - int lectureIndex = sectionTypes.indexOf('LE'); - SectionData lectureObject = + final int lectureIndex = sectionTypes.indexOf('LE'); + final SectionData lectureObject = sectionObjects.elementAt(lectureIndex); lectureObjects.add(lectureObject); sectionTypes.removeAt(lectureIndex); sectionObjects.removeAt(lectureIndex); } + // Begin building calendar card for discussions/labs sectionIndex = 0; for (final String sectionType in sectionTypes.toList()) { @@ -155,24 +158,24 @@ class _CalendarViewState extends State { sectionIndex++; } + // Create new section objects with the first meeting in {recurringMeetings} is the correct day and time for (final SectionData lecture in lectureObjects) { - int meetingIndex = 0; - for (final MeetingData meetingData - in lecture.recurringMeetings!.toList()) { - SectionData CopyOfLectureObject = + for (int meetingIndex = 0; + meetingIndex < lecture.recurringMeetings!.length; + meetingIndex++) { + final SectionData copyOfLectureObject = SectionData.fromJson(lecture.toJson()); - CopyOfLectureObject.recurringMeetings = []; - CopyOfLectureObject.recurringMeetings! + copyOfLectureObject.recurringMeetings = + []; + copyOfLectureObject.recurringMeetings! .add(lecture.recurringMeetings![meetingIndex]); sectionTypes - .add(CopyOfLectureObject.instructionType!); - sectionObjects.add(CopyOfLectureObject); - meetingIndex++; + .add(copyOfLectureObject.instructionType!); + sectionObjects.add(copyOfLectureObject); } } - // sectionTypes.add('FI'); - // sectionObjects.add(SectionData()); + // Build remaining section cards sectionIndex = 0; for (final String sectionType in sectionTypes.toList()) { @@ -183,11 +186,14 @@ class _CalendarViewState extends State { return Stack(children: [...sectionCards]); } else { + // Lists to contain section objects and parallel list with section types final List sectionCards = []; final List sectionTypes = []; final List sectionObjects = []; + + // Idenitfy all lecture objects as those contain final data for (final SectionData section in courseList!) { if (section.instructionType == 'LE') { sectionTypes.add('FI'); @@ -195,6 +201,7 @@ class _CalendarViewState extends State { } } + // Build calendar cards from lecture objects, isolates final meeting data int sectionIndex = 0; for (final String sectionType in sectionTypes.toList()) { @@ -216,7 +223,7 @@ class _CalendarViewState extends State { ])); } - // Fix an incorrectly formatted time + /// @param meeting Meeting object to standardize start time and end time void correctTimeFormat(MeetingData meeting) { while (meeting.startTime!.length < 4) { meeting.startTime = '0' + meeting.startTime!; @@ -226,7 +233,7 @@ class _CalendarViewState extends State { } } - static const List dayOfWeek = [ + static const List dayOfWeek = [ 'Mon', 'Tues', 'Wed', @@ -236,7 +243,7 @@ class _CalendarViewState extends State { 'Sun', ]; - static const Map dayMapping = { + static const Map dayMapping = { 'MO': 0, 'TU': 1, 'WE': 2, @@ -244,7 +251,7 @@ class _CalendarViewState extends State { 'FR': 4 }; - static const List times = [ + static const List times = [ '8am', '9am', '10am', @@ -261,6 +268,9 @@ class _CalendarViewState extends State { '9pm', '10pm', ]; + + /// @param sectionType Type of section for calendar card we are creating: LE, DI, LA, FI ... + /// @param sectionObject Section object containing course info needed to create calendar card CalendarCard buildCalendarCard( String sectionType, SectionData sectionObject) { switch (sectionType) { @@ -386,7 +396,7 @@ class _CalendarViewState extends State { prefix, dayMapping[sectionObject.recurringMeetings!.first.dayCode]!, 'FI', - "${sectionObject.subjectCode} ${sectionObject.courseCode}", + '${sectionObject.subjectCode} ${sectionObject.courseCode}', room, Colors.blue.shade200); } @@ -415,7 +425,7 @@ class _CalendarViewState extends State { prefix, sectionObject.units!.toInt(), sectionType, - "${sectionObject.subjectCode} ${sectionObject.courseCode}", + '${sectionObject.subjectCode} ${sectionObject.courseCode}', room, Colors.blue.shade200); } diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index 51fe367..3b4daa2 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -18,12 +18,11 @@ class _BottomNavigationState extends State UserDataProvider userDataProvider = UserDataProvider(); List currentTab = [ const Calendar( - 'LECT_DISC', calendarType: 'LECT_DISC', ), - CourseListView(), + CourseListView(), // Finals Calendar - const Calendar('FINALS', calendarType: 'FINALS'), + const Calendar(calendarType: 'FINALS'), ]; int currentIndex = 0; From 48fa61169249af386e18a1c41b7165f93bd3795b Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 9 Jun 2022 12:28:42 -0700 Subject: [PATCH 23/24] Fixed styling errors and renamed relevant files --- analysis_options.yaml | 3 - lib/app_provider.dart | 1 + lib/app_router.dart | 10 +- lib/app_styles.dart | 28 +- lib/core/models/authentication.dart | 25 +- lib/core/models/user.dart | 14 +- lib/core/providers/profile.dart | 1 - lib/core/services/authentication.dart | 8 - lib/core/services/profile.dart | 6 - lib/main.dart | 1 + lib/ui/calendar/bottom_course_card.dart | 369 +++++++----------- lib/ui/calendar/calendar_card.dart | 22 +- ...ion_error.dart => authentication_sso.dart} | 10 +- lib/ui/common/build_info.dart | 6 +- lib/ui/list/course_card.dart | 144 +++---- lib/ui/list/course_list_view.dart | 51 +-- lib/ui/navigator/bottom.dart | 6 +- lib/ui/search/search_bar.dart | 132 +------ lib/ui/search/search_detail.dart | 6 +- lib/ui/search/search_filters.dart | 12 +- lib/ui/search/search_placeholder.dart | 45 +-- lib/ui/search/search_view.dart | 2 +- pubspec.lock | 121 +++++- 23 files changed, 473 insertions(+), 550 deletions(-) rename lib/ui/common/{authentication_error.dart => authentication_sso.dart} (83%) diff --git a/analysis_options.yaml b/analysis_options.yaml index 52dc4c2..b05fa82 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,9 +19,6 @@ # Android Studio, and the `flutter analyze` command. analyzer: - strong-mode: - # implicit-casts: false - # implicit-dynamic: false errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning diff --git a/lib/app_provider.dart b/lib/app_provider.dart index aa056b8..7db97c7 100644 --- a/lib/app_provider.dart +++ b/lib/app_provider.dart @@ -4,6 +4,7 @@ import 'package:webreg_mobile_flutter/core/providers/profile.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/user.dart'; +// ignore: always_specify_types List providers = [ ChangeNotifierProvider( create: (_) { diff --git a/lib/app_router.dart b/lib/app_router.dart index b1c4ef6..15a1a47 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; -import 'package:webreg_mobile_flutter/ui/common/authentication_error.dart'; +import 'package:webreg_mobile_flutter/ui/common/authentication_sso.dart'; import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart'; import 'package:webreg_mobile_flutter/ui/navigator/bottom.dart'; import 'package:webreg_mobile_flutter/ui/search/search_detail.dart'; @@ -12,14 +12,15 @@ class Router { static Route generateRoute(RouteSettings settings) { switch (settings.name) { case RoutePaths.Home: + // TODO(p8gonzal): Do not add const to BottomNavigation(), will cause popup authentication failure return MaterialPageRoute(builder: (_) => BottomNavigation()); case RoutePaths.AuthenticationError: return MaterialPageRoute( - builder: (_) => const AuthenticationError()); + builder: (_) => const AuthenticationSSO()); case RoutePaths.SearchView: return MaterialPageRoute(builder: (_) => SearchView()); case RoutePaths.CourseListView: - return MaterialPageRoute(builder: (_) => CourseListView()); + return MaterialPageRoute(builder: (_) => const CourseListView()); case RoutePaths.SearchDetail: final CourseData course = settings.arguments! as CourseData; return MaterialPageRoute(builder: (_) { @@ -27,7 +28,8 @@ class Router { }); default: - return MaterialPageRoute(builder: (_) => BottomNavigation()); + return MaterialPageRoute( + builder: (_) => const BottomNavigation()); } } } diff --git a/lib/app_styles.dart b/lib/app_styles.dart index 6123e70..6f48f37 100644 --- a/lib/app_styles.dart +++ b/lib/app_styles.dart @@ -1,24 +1,26 @@ import 'package:flutter/material.dart'; /// App Styles -const headerStyle = TextStyle(fontSize: 35, fontWeight: FontWeight.w900); -const subHeaderStyle = TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500); +const TextStyle headerStyle = + TextStyle(fontSize: 35, fontWeight: FontWeight.w900); +const TextStyle subHeaderStyle = + TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500); // Theme agnostic styles -const agnosticDisabled = Color(0xFF8A8A8A); +const Color agnosticDisabled = Color(0xFF8A8A8A); /// App Layout // Card Layout -const cardMargin = 6.0; -const cardPaddingInner = 8.0; -const cardMinHeight = 60.0; -const listTileInnerPadding = 8.0; +const double cardMargin = 6.0; +const double cardPaddingInner = 8.0; +const double cardMinHeight = 60.0; +const double listTileInnerPadding = 8.0; //Card Heights -const cardContentMinHeight = 80.0; -const cardContentMaxHeight = 568.0; +const double cardContentMinHeight = 80.0; +const double cardContentMaxHeight = 568.0; -const webViewMinHeight = 20.0; +const double webViewMinHeight = 20.0; /// App Theme const MaterialColor ColorPrimary = MaterialColor( @@ -107,10 +109,10 @@ const Color lightTextFieldBorderColor = Color(0xFFFFFFFF); const Color lightAccentColor = Color(0xFFFFFFFF); const Color darkAccentColor = Color(0xFF333333); -const debugHeader = TextStyle(color: lightTextColor, fontSize: 14.0); -const debugRow = TextStyle(color: lightTextColor, fontSize: 12.0); +const TextStyle debugHeader = TextStyle(color: lightTextColor, fontSize: 14.0); +const TextStyle debugRow = TextStyle(color: lightTextColor, fontSize: 12.0); // Testing const Color c1 = Color.fromARGB(255, 255, 0, 0); const Color c2 = Color.fromARGB(255, 0, 255, 0); -const Color c3 = Color.fromARGB(255, 0, 0, 255); \ No newline at end of file +const Color c3 = Color.fromARGB(255, 0, 0, 255); diff --git a/lib/core/models/authentication.dart b/lib/core/models/authentication.dart index 22b0ff6..37194b3 100644 --- a/lib/core/models/authentication.dart +++ b/lib/core/models/authentication.dart @@ -14,18 +14,6 @@ String authenticationModelToJson(AuthenticationModel data) => @HiveType(typeId: 1) class AuthenticationModel extends HiveObject { - @HiveField(0) - String? accessToken; - // Deprecated reserved field number - DO NOT REMOVE - // @HiveField(1) - // String refreshToken; - @HiveField(2) - String? pid; - @HiveField(3) - String? ucsdaffiliation; - @HiveField(4) - int? expiration; - AuthenticationModel({ this.accessToken, this.pid, @@ -42,7 +30,18 @@ class AuthenticationModel extends HiveObject { ); } - Map toJson() => { + @HiveField(0) + String? accessToken; + // Deprecated reserved field number - DO NOT REMOVE + // @HiveField(1) + @HiveField(2) + String? pid; + @HiveField(3) + String? ucsdaffiliation; + @HiveField(4) + int? expiration; + + Map toJson() => { 'access_token': accessToken, 'pid': pid, 'ucsdaffiliation': ucsdaffiliation ?? '', diff --git a/lib/core/models/user.dart b/lib/core/models/user.dart index f2ed62c..53c22eb 100644 --- a/lib/core/models/user.dart +++ b/lib/core/models/user.dart @@ -12,18 +12,18 @@ class UserModel { {this.enrolledCourses, this.waitlistedCourses, this.plannedCourses}); factory UserModel.fromJson(Map json) => UserModel( - enrolledCourses: List.from( - json['enrolledCourses'].map((x) => CourseData.fromJson(x))), - waitlistedCourses: List.from( - json['waitlistedCourses'].map((x) => CourseData.fromJson(x))), - plannedCourses: List.from( - json['plannedCourses'].map((x) => CourseData.fromJson(x)))); + enrolledCourses: List.from(json['enrolledCourses'] + .map((Map x) => CourseData.fromJson(x))), + waitlistedCourses: List.from(json['waitlistedCourses'] + .map((Map x) => CourseData.fromJson(x))), + plannedCourses: List.from(json['plannedCourses'] + .map((Map x) => CourseData.fromJson(x)))); List? enrolledCourses; List? waitlistedCourses; List? plannedCourses; - Map toJson() => { + Map toJson() => { 'enrolledCourses': List.from( enrolledCourses!.map((CourseData x) => x.toJson())), 'waitlistedCourses': List.from( diff --git a/lib/core/providers/profile.dart b/lib/core/providers/profile.dart index 94c0a91..a00a88f 100644 --- a/lib/core/providers/profile.dart +++ b/lib/core/providers/profile.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/core/models/profile.dart'; import 'package:webreg_mobile_flutter/core/providers/user.dart'; import 'package:webreg_mobile_flutter/core/services/profile.dart'; -import 'package:webreg_mobile_flutter/core/services/profile.dart'; class ProfileProvider extends ChangeNotifier { ProfileProvider() { diff --git a/lib/core/services/authentication.dart b/lib/core/services/authentication.dart index 2729ac8..f80ef76 100644 --- a/lib/core/services/authentication.dart +++ b/lib/core/services/authentication.dart @@ -1,15 +1,7 @@ -import 'package:webreg_mobile_flutter/core/models/authentication.dart'; - -import '../../app_networking.dart'; class AuthenticationService { AuthenticationService(); String? error; - AuthenticationModel? _data; - DateTime? _lastUpdated; - - final NetworkHelper _networkHelper = NetworkHelper(); - } diff --git a/lib/core/services/profile.dart b/lib/core/services/profile.dart index 0b74d90..a11c298 100644 --- a/lib/core/services/profile.dart +++ b/lib/core/services/profile.dart @@ -6,8 +6,6 @@ class ProfileService { final String profilePrototype = 'https://i4ghbvwuo9.execute-api.us-west-2.amazonaws.com/qa/profile'; - bool _isLoading = false; - DateTime? _lastUpdated; String? _error; ProfileModel? profile; @@ -15,7 +13,6 @@ class ProfileService { Future fetchProfile(Map headers) async { _error = null; - _isLoading = true; try { @@ -25,16 +22,13 @@ class ProfileService { if (_response != null) { /// Parse data profile = profileModelFromJson(_response); - _isLoading = false; } else { - _isLoading = false; return false; } return true; } catch (e) { _error = e.toString(); print(_error); - _isLoading = false; return false; } } diff --git a/lib/main.dart b/lib/main.dart index 12ae619..d6877df 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ void main() { runApp(MyApp()); } +// ignore: must_be_immutable class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); diff --git a/lib/ui/calendar/bottom_course_card.dart b/lib/ui/calendar/bottom_course_card.dart index dc0f8f5..6bc5eba 100644 --- a/lib/ui/calendar/bottom_course_card.dart +++ b/lib/ui/calendar/bottom_course_card.dart @@ -1,65 +1,96 @@ +// ignore_for_file: use_key_in_widget_constructors + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; class BottomCourseCard extends StatefulWidget { + const BottomCourseCard(this.context); final BuildContext context; - BottomCourseCard(this.context); - @override _BottomCourseCardState createState() => _BottomCourseCardState(); } class _BottomCourseCardState extends State { void onClick(BuildContext context) { - showModalBottomSheet(context: context, builder: (BuildContext context) { - return Container( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Text('This is the modal bottom sheet. Tap anywhere to dismiss.', - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).accentColor, - fontSize: 24.0 - ) - ) - ) - ); - }); + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(32.0), + child: Text( + 'This is the modal bottom sheet. Tap anywhere to dismiss.', + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontSize: 24.0))); + }); } - Widget renderSection() { + Widget renderSection() { return Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - flex: 3, - child: Row( - children: [ - Text('A00', style: TextStyle(fontSize: 11, color: darkGray)), // TODO - Text(' LE', style: TextStyle(fontSize: 11, color: darkGray)) // TODO - ], - ) - ), - Expanded( - flex: 3, - child: Row( - children: [ - Text('M', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('W', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('F', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - ], - ) - ), + flex: 3, + child: Row( + children: const [ + Text('A00', + style: TextStyle( + fontSize: 11, + color: + darkGray)), // TODO(p8gonzal): Connect with class schedule api or local profile + Text(' LE', + style: TextStyle( + fontSize: 11, + color: + darkGray)) // TODO(p8gonzal): Connect with class schedule api or local profile + ], + )), Expanded( + flex: 3, + child: Row( + children: const [ + Text('M', + style: TextStyle( + fontSize: 11, + color: + ColorPrimary)), // TODO(p8gonzal): Connect with class schedule api or local profile + Text('T', + style: TextStyle( + fontSize: 11, + color: + lightGray)), // TODO(p8gonzal): Connect with class schedule api or local profile + Text('W', + style: TextStyle( + fontSize: 11, + color: + ColorPrimary)), // TODO(p8gonzal): Connect with class schedule api or local profile + Text('T', + style: TextStyle( + fontSize: 11, + color: + lightGray)), // TODO(p8gonzal): Connect with class schedule api or local profile + Text('F', + style: TextStyle( + fontSize: 11, + color: + ColorPrimary)), // TODO(p8gonzal): Connect with class schedule api or local profile + ], + )), + const Expanded( flex: 5, - child: Text('3:30p - 4:50p', style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + child: Text('3:30p - 4:50p', + style: TextStyle( + fontSize: 11, + color: + ColorPrimary)), // TODO(p8gonzal): Connect with class schedule api or local profile ), - Expanded( + const Expanded( flex: 5, - child: Text('PCYHN 106', style: TextStyle(fontSize: 11)), // TODO + child: Text('PCYHN 106', + style: TextStyle( + fontSize: + 11)), // TODO(p8gonzal): Connect with class schedule api or local profile ) ], ); @@ -70,27 +101,27 @@ class _BottomCourseCardState extends State { return Container( color: Colors.transparent, width: double.infinity, - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Container( // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(10.0) // ), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(10.0)), + borderRadius: const BorderRadius.all(Radius.circular(10.0)), // border: Border.all(width: 1, color: ) - boxShadow: [ + boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.25), spreadRadius: 0, blurRadius: 2.5, - offset: Offset(1, 1), + offset: const Offset(1, 1), ), BoxShadow( color: Colors.black.withOpacity(0.25), spreadRadius: 0, blurRadius: 2.5, - offset: Offset(-1, 1), + offset: const Offset(-1, 1), ), ], ), @@ -101,192 +132,94 @@ class _BottomCourseCardState extends State { children: [ // card title Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(10.0), - topLeft: Radius.circular(10.0), + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(10.0), + topLeft: Radius.circular(10.0), + ), + color: lightBlue, ), - color: lightBlue, - ), - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row(children: [ // units icon Container( - height: 35, - width: 35, - decoration: new BoxDecoration( - color: lightGray, - shape: BoxShape.circle, - ), - margin: EdgeInsets.only(right: 10), - child: Center( - child: Text( - '4',// TODO + height: 35, + width: 35, + decoration: const BoxDecoration( + color: lightGray, + shape: BoxShape.circle, + ), + margin: const EdgeInsets.only(right: 10), + child: const Center( + child: Text( + '4', // TODO(p8gonzal): Once data becomes available, unharcode style: TextStyle(fontSize: 18), - ) - ) - ), + ))), // course info Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('CSE 12', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), // TODO - GestureDetector( - child: Icon(Icons.close, size: 20, color: darkGray), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('CSE 12', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight + .bold)), // TODO(p8gonzal): Once data becomes available, unharcode + GestureDetector( + child: const Icon(Icons.close, + size: 20, color: darkGray), onTap: () { Navigator.pop(widget.context); - } - ) - ], - ), - Text('Basic Data Struct & OO design', style: TextStyle(fontSize: 16)) // TODO - ], - ) - ) - ] - ) - ), + }) + ], + ), + const Text('Basic Data Struct & OO design', + style: TextStyle( + fontSize: + 16)) // TODO(p8gonzal): Once data becomes available, unharcode + ], + )) + ])), Container( - margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - children: [ - // instructor andd section id - Container( - margin: EdgeInsets.only(top: 4, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Gillespie, Gary N', style: TextStyle(color: ColorPrimary, fontSize: 12)), // TODO - Row( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Column( + children: [ + // instructor andd section id + Container( + margin: const EdgeInsets.only(top: 4, bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Section ID', style: TextStyle(color: darkGray, fontSize: 12)), // TODO - Text(' 983761', style: TextStyle(fontSize: 12)), // TODO - ] - ) - ] + const Text('Gillespie, Gary N', + style: TextStyle( + color: ColorPrimary, + fontSize: + 12)), // TODO(p8gonzal): Once data becomes available, unharcode + Row(children: const [ + Text('Section ID', + style: TextStyle( + color: darkGray, + fontSize: + 12)), // TODO(p8gonzal): Once data becomes available, unharcode + Text(' 983761', + style: TextStyle( + fontSize: + 12)), // TODO(p8gonzal): Once data becomes available, unharcode + ]) + ]), ), - ), - // course sections: di, final - renderSection(), - renderSection(), - ], - ) - ) + // course sections: di, final + renderSection(), + renderSection(), + ], + )) ], ), ), ); - - // return ElevatedButton( - // child: const Text('showBottomSheet'), - // onPressed: () { - // Scaffold.of(context).showBottomSheet( - // (BuildContext context) { - // return Container( - // color: Colors.transparent, - // width: double.infinity, - // padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), - // child: Card( - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10.0) - // ), - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // mainAxisSize: MainAxisSize.min, - // children: [ - // // card title - // Container( - // decoration: BoxDecoration( - // borderRadius: BorderRadius.only( - // topRight: Radius.circular(10.0), - // topLeft: Radius.circular(10.0), - // ), - // color: lightBlue, - // ), - // padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - // child: Row( - // children: [ - // // units icon - // Container( - // height: 35, - // width: 35, - // decoration: new BoxDecoration( - // color: lightGray, - // shape: BoxShape.circle, - // ), - // margin: EdgeInsets.only(right: 10), - // child: Center( - // child: Text( - // '4',// TODO - // style: TextStyle(fontSize: 18), - // ) - // ) - // ), - // // course info - // Expanded( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text('CSE 12', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), // TODO - // GestureDetector( - // child: Icon(Icons.close, size: 20, color: darkGray), - // onTap: () { - // Navigator.pop(context); - // } - // ) - // ], - // ), - // Text('Basic Data Struct & OO design', style: TextStyle(fontSize: 16)) // TODO - // ], - // ) - // ) - // ] - // ) - // ), - // Container( - // margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - // child: Column( - // children: [ - // // instructor andd section id - // Container( - // margin: EdgeInsets.only(top: 4, bottom: 8), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text('Gillespie, Gary N', style: TextStyle(color: ColorPrimary, fontSize: 12)), // TODO - // Row( - // children: [ - // Text('Section ID', style: TextStyle(color: darkGray, fontSize: 12)), // TODO - // Text(' 983761', style: TextStyle(fontSize: 12)), // TODO - // ] - // ) - // ] - // ), - // ), - // // course sections: di, final - // renderSection(), - // renderSection(), - // ], - // ) - // ) - // ], - // ), - // ), - // ); - // }, - // ); - // }, - // ); } } - - diff --git a/lib/ui/calendar/calendar_card.dart b/lib/ui/calendar/calendar_card.dart index 8ef58c6..7e522ad 100644 --- a/lib/ui/calendar/calendar_card.dart +++ b/lib/ui/calendar/calendar_card.dart @@ -1,25 +1,26 @@ +// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/ui/calendar/bottom_course_card.dart'; class CalendarCard extends StatefulWidget { + const CalendarCard(this.startTime, this.endTime, this.datePrefix, + this.dayOfWeek, this.type, this.title, this.location, this.color); + final String startTime, endTime, datePrefix, type, title, location; final int dayOfWeek; final Color color; - const CalendarCard(this.startTime, this.endTime, this.datePrefix, - this.dayOfWeek, this.type, this.title, this.location, this.color); - @override _CalendarCardState createState() => _CalendarCardState(); } class _CalendarCardState extends State { - static const earliestClass = '08:00'; + static const String earliestClass = '08:00'; double getTimeDifference(String start, String end, String prefix) { - double diff = DateTime.parse(prefix + end) + final double diff = DateTime.parse(prefix + end) .difference(DateTime.parse(prefix + start)) .inMinutes .toDouble(); @@ -28,11 +29,10 @@ class _CalendarCardState extends State { @override Widget build(BuildContext context) { - double calendarCardWidth = (MediaQuery.of(context).size.width - + final double calendarCardWidth = (MediaQuery.of(context).size.width - CalendarStyles.calendarTimeWidth - 20) / 7; - bool _showModal = false; return Positioned( top: getTimeDifference( @@ -51,7 +51,7 @@ class _CalendarCardState extends State { color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(2.0)), // border: Border.all(width: 1, color: ) - boxShadow: [ + boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.25), spreadRadius: 0, @@ -82,9 +82,7 @@ class _CalendarCardState extends State { child: Center( child: Text(widget.type, style: TextStyle( - fontSize: 8, - fontWeight: FontWeight - .bold)), // TODO, replace with real data + fontSize: 8, fontWeight: FontWeight.bold)), )), Expanded( child: Column( diff --git a/lib/ui/common/authentication_error.dart b/lib/ui/common/authentication_sso.dart similarity index 83% rename from lib/ui/common/authentication_error.dart rename to lib/ui/common/authentication_sso.dart index 4f1534e..952d9d7 100644 --- a/lib/ui/common/authentication_error.dart +++ b/lib/ui/common/authentication_sso.dart @@ -4,16 +4,16 @@ import 'dart:html' as html; import 'package:flutter/material.dart'; import '../../app_constants.dart'; -const String CLIENT_ID = 'nSu0wUDFf4FBiWognBJRamy_kZIa'; +const String CLIENT_ID = ''; -class AuthenticationError extends StatefulWidget { - const AuthenticationError({Key? key}) : super(key: key); +class AuthenticationSSO extends StatefulWidget { + const AuthenticationSSO({Key? key}) : super(key: key); @override - _AuthenticationErrorState createState() => _AuthenticationErrorState(); + _AuthenticationSSOState createState() => _AuthenticationSSOState(); } -class _AuthenticationErrorState extends State { +class _AuthenticationSSOState extends State { String _token = ''; String clientId = CLIENT_ID; late html.WindowBase popUpWindow; diff --git a/lib/ui/common/build_info.dart b/lib/ui/common/build_info.dart index 3fb8418..d3b6422 100644 --- a/lib/ui/common/build_info.dart +++ b/lib/ui/common/build_info.dart @@ -1,7 +1,11 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; class BuildInfo extends StatefulWidget { + const BuildInfo({Key? key}) : super(key: key); + @override _BuildInfoState createState() => _BuildInfoState(); } @@ -27,7 +31,7 @@ class _BuildInfoState extends State { }); } - final String buildEnv = "##BUILD_ENV##"; + final String buildEnv = '##BUILD_ENV##'; @override Widget build(BuildContext context) { diff --git a/lib/ui/list/course_card.dart b/lib/ui/list/course_card.dart index 1f7d776..e488a6c 100644 --- a/lib/ui/list/course_card.dart +++ b/lib/ui/list/course_card.dart @@ -1,11 +1,13 @@ +// ignore_for_file: always_specify_types + import 'package:flutter/material.dart'; -import 'package:webreg_mobile_flutter/ui/search/search_placeholder.dart'; -import 'package:webreg_mobile_flutter/ui/calendar/calendar.dart'; -import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; +// (p8gonzal): Can use mock profile API to make this data no hard coded class CourseCard extends StatelessWidget { - static const MOCK_DATA = [ + const CourseCard({Key? key}) : super(key: key); + + static const List>> MOCK_DATA = [ { 'lecture': { 'subjectCode': 'COGS', @@ -432,21 +434,21 @@ class CourseCard extends StatelessWidget { Widget renderActionButtons() { return Container( width: 45, - decoration: BoxDecoration( + decoration: const BoxDecoration( border: Border( left: BorderSide(color: lightGray), )), child: Column(children: [ IconButton( - icon: Icon(Icons.autorenew, color: ColorPrimary), + icon: const Icon(Icons.autorenew, color: ColorPrimary), onPressed: () {}, ), IconButton( - icon: Icon(Icons.delete, color: ColorPrimary), + icon: const Icon(Icons.delete, color: ColorPrimary), onPressed: () {}, ), IconButton( - icon: Icon(Icons.add_circle, color: ColorPrimary), + icon: const Icon(Icons.add_circle, color: ColorPrimary), onPressed: () {}, ), ])); @@ -459,40 +461,30 @@ class CourseCard extends StatelessWidget { Expanded( flex: 3, child: Row( - children: [ - Text('A00', - style: TextStyle(fontSize: 11, color: darkGray)), // TODO - Text(' LE', - style: TextStyle(fontSize: 11, color: darkGray)) // TODO + children: const [ + Text('A00', style: TextStyle(fontSize: 11, color: darkGray)), + Text(' LE', style: TextStyle(fontSize: 11, color: darkGray)) ], )), Expanded( flex: 3, child: Row( - children: [ - Text('M', - style: - TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', - style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('W', - style: - TextStyle(fontSize: 11, color: ColorPrimary)), // TODO - Text('T', - style: TextStyle(fontSize: 11, color: lightGray)), // TODO - Text('F', - style: - TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + children: const [ + Text('M', style: TextStyle(fontSize: 11, color: ColorPrimary)), + Text('T', style: TextStyle(fontSize: 11, color: lightGray)), + Text('W', style: TextStyle(fontSize: 11, color: ColorPrimary)), + Text('T', style: TextStyle(fontSize: 11, color: lightGray)), + Text('F', style: TextStyle(fontSize: 11, color: ColorPrimary)), ], )), - Expanded( + const Expanded( flex: 5, child: Text('3:30p - 4:50p', - style: TextStyle(fontSize: 11, color: ColorPrimary)), // TODO + style: TextStyle(fontSize: 11, color: ColorPrimary)), ), - Expanded( + const Expanded( flex: 5, - child: Text('PCYHN 106', style: TextStyle(fontSize: 11)), // TODO + child: Text('PCYHN 106', style: TextStyle(fontSize: 11)), ) ], ); @@ -503,74 +495,68 @@ class CourseCard extends StatelessWidget { return Card( elevation: 0, shape: RoundedRectangleBorder( - side: new BorderSide(color: ColorPrimary, width: 2.0), + side: const BorderSide(color: ColorPrimary, width: 2.0), borderRadius: BorderRadius.circular(10.0)), - margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: ClipPath( child: Row( children: [ Expanded( child: Container( - margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ // course header: units, course code, course name - Container( - child: Row(children: [ - // units icon - Container( - height: 30, - width: 30, - decoration: new BoxDecoration( - color: lightGray, - shape: BoxShape.circle, - ), - margin: EdgeInsets.only(right: 10), - child: Center( - child: Text('4' // TODO - ))), - // course info - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('CSE 12', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold)), // TODO - Text('Enrolled - Letter', - style: TextStyle( - color: ColorPrimary, - fontSize: 12)), // TODO - ], - ), - Text('Basic Data Struct & OO design') // TODO - ], - )) - ]), - ), + Row(children: [ + // units icon + Container( + height: 30, + width: 30, + decoration: const BoxDecoration( + color: lightGray, + shape: BoxShape.circle, + ), + margin: const EdgeInsets.only(right: 10), + child: const Center(child: Text('4'))), + // course info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + Text('CSE 12', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold)), + Text('Enrolled - Letter', + style: TextStyle( + color: ColorPrimary, fontSize: 12)), + ], + ), + const Text('Basic Data Struct & OO design') + ], + )) + ]), // instructor andd section id Container( - margin: EdgeInsets.only(top: 8, bottom: 8), + margin: const EdgeInsets.only(top: 8, bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Gillespie, Gary N', + const Text('Gillespie, Gary N', style: TextStyle( - color: ColorPrimary, - fontSize: 12)), // TODO - Row(children: [ + color: ColorPrimary, fontSize: 12)), + Row(children: const [ Text('Section ID', style: TextStyle( - color: darkGray, fontSize: 12)), // TODO + color: darkGray, fontSize: 12)), Text(' 983761', - style: TextStyle(fontSize: 12)), // TODO + style: TextStyle(fontSize: 12)), ]) ]), ), diff --git a/lib/ui/list/course_list_view.dart b/lib/ui/list/course_list_view.dart index 8f1d985..97dec0f 100644 --- a/lib/ui/list/course_list_view.dart +++ b/lib/ui/list/course_list_view.dart @@ -1,59 +1,64 @@ +// ignore_for_file: prefer_const_literals_to_create_immutables + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/ui/list/course_card.dart'; -import 'package:webreg_mobile_flutter/ui/calendar/calendar.dart'; -import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; class CourseListView extends StatelessWidget { + const CourseListView({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Center( child: Column(children: [ - TermDropdown(), + const TermDropdown(), Expanded( - child: Container( - child: ListView.builder( - itemCount: 10, - padding: EdgeInsets.symmetric(vertical: 8), - itemBuilder: (BuildContext context, int index) { - return Container( - child: CourseCard(), - ); - }), - )) + child: ListView.builder( + itemCount: 10, + padding: const EdgeInsets.symmetric(vertical: 8), + itemBuilder: (BuildContext context, int index) { + return const CourseCard(); + })) ])); } } class TermDropdown extends StatefulWidget { + const TermDropdown({Key? key}) : super(key: key); + @override _TermDropdownState createState() => _TermDropdownState(); } -// TODO +// TODO(p8gonzal): Can be replaced with live API used in filter for search. class _TermDropdownState extends State { - List dropdownItems = ['Fall 19', 'Winter 20', 'Spring 20', 'Fall 20']; + List dropdownItems = [ + 'Fall 19', + 'Winter 20', + 'Spring 20', + 'Fall 20' + ]; String _dropdownVal = 'Fall 19'; @override Widget build(BuildContext context) { return Container( height: 40, - margin: EdgeInsets.only(top: 10), - padding: EdgeInsets.symmetric(horizontal: 60), + margin: const EdgeInsets.only(top: 10), + padding: const EdgeInsets.symmetric(horizontal: 60), child: Stack(children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.alarm, color: Colors.black), + const Icon(Icons.alarm, color: Colors.black), ]), Center( child: Text(_dropdownVal, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold))), + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.bold))), DropdownButton( isExpanded: true, underline: Container(height: 0), - icon: Icon(Icons.expand_more, color: Colors.black, size: 30), + icon: const Icon(Icons.expand_more, color: Colors.black, size: 30), onChanged: (String? newVal) { setState(() { _dropdownVal = newVal!; @@ -62,8 +67,8 @@ class _TermDropdownState extends State { items: dropdownItems.map>((String val) { return DropdownMenuItem( value: val, - child: - Center(child: Text(val, style: TextStyle(fontSize: 18)))); + child: Center( + child: Text(val, style: const TextStyle(fontSize: 18)))); }).toList(), ) ])); diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index 3b4daa2..acdb1ff 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -1,3 +1,5 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; @@ -20,7 +22,7 @@ class _BottomNavigationState extends State const Calendar( calendarType: 'LECT_DISC', ), - CourseListView(), + const CourseListView(), // Finals Calendar const Calendar(calendarType: 'FINALS'), ]; @@ -55,7 +57,7 @@ class _BottomNavigationState extends State style: TextStyle( fontWeight: FontWeight.normal, )), - actions: [SearchPlaceholder()]), + actions: const [SearchPlaceholder()]), body: currentTab[currentIndex], bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, diff --git a/lib/ui/search/search_bar.dart b/lib/ui/search/search_bar.dart index a8cfdf3..d4d3844 100644 --- a/lib/ui/search/search_bar.dart +++ b/lib/ui/search/search_bar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_import, prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; @@ -5,10 +7,9 @@ import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart'; import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart'; class SearchBar extends StatelessWidget { + const SearchBar(this.setOpenFilters, {Key? key}) : super(key: key); final VoidCallback setOpenFilters; - SearchBar(this.setOpenFilters); - @override Widget build(BuildContext context) { return MediaQuery.removePadding( @@ -18,9 +19,9 @@ class SearchBar extends StatelessWidget { titleSpacing: 0.0, centerTitle: true, title: Container( - decoration: new BoxDecoration( + decoration: BoxDecoration( color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), + borderRadius: BorderRadius.all(Radius.circular(100.0)), border: Border.all(width: 1.0, color: Color(0xFF034263)), ), margin: EdgeInsets.symmetric(vertical: 10.0), @@ -43,129 +44,26 @@ class SearchBar extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 9), alignment: Alignment.centerLeft, iconSize: 25, - onPressed: this.setOpenFilters, + onPressed: setOpenFilters, ), ])); } } -// class Filters extends StatefulWidget { -// Widget child; -// bool expand; -// Filters({this.expand = false, this.child}); - -// @override -// _FiltersState createState() => _FiltersState(); -// } - -// class _FiltersState extends State with SingleTickerProviderStateMixin { -// bool expand = false; -// Widget child; -// List _selectedFilters = List.filled(3, false); - -// AnimationController expandController; -// Animation animation; - -// void prepareAnimations() { -// expandController = AnimationController( -// vsync: this, -// duration: Duration(milliseconds: 500) -// ); -// animation = CurvedAnimation( -// parent: expandController, -// curve: Curves.fastOutSlowIn, -// ); -// } - -// void _runExpandCheck() { -// if(widget.expand) { -// expandController.forward(); -// } -// else { -// expandController.reverse(); -// } -// } - -// // void handleBottomSheet(BuildContext context) { -// // _openBottomSheet = !_openBottomSheet; - -// // if(_openBottomSheet) { -// // showBottomSheet( -// // context: context, -// // builder: (context) => Wrap( -// // children: [ -// // Container( -// // color: ColorPrimary, -// // height: 100, -// // child: ListView( -// // children: [ -// // ListTile( -// // title: Text('Show lower division', style: TextStyle(color: Colors.white)), -// // selected: _selectedFilters[0], -// // onTap: () => _selectedFilters[0] = true, -// // ), -// // ListTile( -// // leading: Icon(Icons.favorite), -// // title: Text('Show upper division'), -// // selected: _selectedFilters[1], -// // onTap: () => _selectedFilters[1] = true, -// // ), -// // ListTile( -// // leading: Icon(Icons.favorite), -// // title: Text('Show graduate division'), -// // selected: _selectedFilters[2], -// // onTap: () => _selectedFilters[2] = true, -// // ), -// // ] -// // ) -// // ) -// // ] -// // )); -// // } else { -// // Navigator.pop(context); -// // } -// // } -// // -// @override -// void didUpdateWidget(Filters oldWidget) { -// super.didUpdateWidget(oldWidget); -// _runExpandCheck(); -// } - -// @override -// void dispose() { -// expandController.dispose(); -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// return SizeTransition( -// axisAlignment: 1.0, -// sizeFactor: animation, -// child: widget.child -// ); -// // return IconButton( -// // icon: Icon(Icons.filter_list, color: Colors.white), -// // padding: EdgeInsets.symmetric(horizontal: 9), -// // alignment: Alignment.centerLeft, -// // iconSize: 25, -// // onPressed: () { handleBottomSheet(context); }, -// // ); -// } -// } - class TermDropdown extends StatefulWidget { + const TermDropdown({Key? key}) : super(key: key); + @override _TermDropdownState createState() => _TermDropdownState(); } +// TODO(p8gonzal): Can be replaced by live API used in search class _TermDropdownState extends State { - List dropdownItems = ['SP21', 'FA21', 'WI22', 'SP22']; + List dropdownItems = ['SP21', 'FA21', 'WI22', 'SP22']; String _dropdownVal = 'SP22'; late DropdownButton dropdownButton; - get dropDownValue { + String get dropDownValue { return _dropdownVal; } @@ -196,6 +94,8 @@ class _TermDropdownState extends State { } class Search extends StatefulWidget { + const Search({Key? key}) : super(key: key); + @override _SearchState createState() => _SearchState(); } @@ -205,7 +105,7 @@ class _SearchState extends State { ScheduleOfClassesProvider provider = ScheduleOfClassesProvider(); @override Widget build(BuildContext context) { - return Container( + return SizedBox( height: 35, child: Row( children: [ @@ -225,9 +125,9 @@ class _SearchState extends State { )), Expanded( child: TextField( - onChanged: (text) { + onChanged: (String text) { provider.searchBarController.text = text; - if (text.length > 0) { + if (text.isNotEmpty) { _icon = GestureDetector( child: Icon(Icons.close, size: 20, color: darkGray), onTap: () { diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index e95f0f1..20ef833 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -1,3 +1,5 @@ +// ignore_for_file: must_be_immutable + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; @@ -19,7 +21,7 @@ class SearchDetail extends StatelessWidget { title: Text( '${data.departmentCode} ${data.courseCode} \n${data.courseTitle}')), body: ListView( - children: [coursePrereqs(), courseDetails()], + children: [coursePrereqs(), courseDetails()], )); Card coursePrereqs() { @@ -137,7 +139,7 @@ class SearchDetail extends StatelessWidget { height: 50, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ + children: [ Padding( padding: const EdgeInsets.only( left: 0.0, right: 0.0, top: 5, bottom: 10), diff --git a/lib/ui/search/search_filters.dart b/lib/ui/search/search_filters.dart index 8ef7d4b..75b8bb6 100644 --- a/lib/ui/search/search_filters.dart +++ b/lib/ui/search/search_filters.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:webreg_mobile_flutter/app_constants.dart'; -import 'package:webreg_mobile_flutter/app_styles.dart'; class SearchFilters extends StatefulWidget { + const SearchFilters({Key? key}) : super(key: key); + @override - SearchFiltersState createState() => new SearchFiltersState(); + SearchFiltersState createState() => SearchFiltersState(); } class SearchFiltersState extends State { @override Widget build(BuildContext context) { - return ExpansionTile( - // title: , + return const ExpansionTile( + title: Text('Filters'), ); } -} \ No newline at end of file +} diff --git a/lib/ui/search/search_placeholder.dart b/lib/ui/search/search_placeholder.dart index 8ce5a4e..9c8badd 100644 --- a/lib/ui/search/search_placeholder.dart +++ b/lib/ui/search/search_placeholder.dart @@ -1,36 +1,37 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter/material.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_styles.dart'; class SearchPlaceholder extends StatelessWidget { + const SearchPlaceholder({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Hero( - tag: 'search_bar', - child: RawMaterialButton( - onPressed: () { - Navigator.pushNamed(context, RoutePaths.SearchView); - }, - child: Container( - width: 63.0, - margin: EdgeInsets.symmetric(horizontal: 7.0, vertical: 10.0), - decoration: new BoxDecoration( - color: lightGray, - borderRadius: new BorderRadius.all(Radius.circular(100.0)), - ), - child: Container( - alignment: Alignment.centerRight, - margin: EdgeInsets.only(right: 5.0), - child: Icon(Icons.search, color: Colors.black, size: 25), - ) - ) - ) - ); + tag: 'search_bar', + child: RawMaterialButton( + onPressed: () { + Navigator.pushNamed(context, RoutePaths.SearchView); + }, + child: Container( + width: 63.0, + margin: + const EdgeInsets.symmetric(horizontal: 7.0, vertical: 10.0), + decoration: BoxDecoration( + color: lightGray, + borderRadius: BorderRadius.all(const Radius.circular(100.0)), + ), + child: Container( + alignment: Alignment.centerRight, + margin: const EdgeInsets.only(right: 5.0), + child: + const Icon(Icons.search, color: Colors.black, size: 25), + )))); } - @override State createState() { - // TODO: implement createState throw UnimplementedError(); } } diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 3c253d3..04d546d 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -1,4 +1,4 @@ -// ignore_for_file: use_key_in_widget_constructors, always_specify_types +// ignore_for_file: use_key_in_widget_constructors, always_specify_types, cast_nullable_to_non_nullable import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/pubspec.lock b/pubspec.lock index 40560f5..f572d93 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,13 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -21,7 +35,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -43,6 +57,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -57,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + encrypt: + dependency: "direct main" + description: + name: encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.1" fake_async: dependency: transitive description: @@ -76,6 +111,48 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -93,6 +170,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.4" + hive: + dependency: "direct main" + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" http: dependency: transitive description: @@ -127,7 +211,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" meta: dependency: transitive description: @@ -135,6 +219,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" package_info_plus: dependency: "direct main" description: @@ -197,7 +288,21 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.2" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" sky_engine: dependency: transitive description: flutter @@ -244,7 +349,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.3" typed_data: dependency: transitive description: @@ -258,7 +363,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" win32: dependency: transitive description: @@ -267,5 +372,5 @@ packages: source: hosted version: "2.0.5" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.0.0" From b110df2f0cbe72bf0bb891109cebc8c867173a80 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 9 Jun 2022 13:27:41 -0700 Subject: [PATCH 24/24] Added better comments --- lib/main.dart | 1 + lib/ui/common/authentication_sso.dart | 1 + lib/ui/search/search_detail.dart | 8 ++++++++ lib/ui/search/search_view.dart | 12 +++++++++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index d6877df..ddc3815 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ class MyApp extends StatelessWidget { late String? _token = ''; + // Check if token is present in the UI, true when access from Campus Mobile void getParams() { final Uri uri = Uri.dataFromString(window.location.href); final Map params = uri.queryParameters; diff --git a/lib/ui/common/authentication_sso.dart b/lib/ui/common/authentication_sso.dart index 952d9d7..e0854c6 100644 --- a/lib/ui/common/authentication_sso.dart +++ b/lib/ui/common/authentication_sso.dart @@ -23,6 +23,7 @@ class _AuthenticationSSOState extends State { super.initState(); final Uri currentUrl = Uri.base; + // If token is not present, open SSO popup if (!currentUrl.fragment.contains('access_token=')) { WidgetsBinding.instance!.addPostFrameCallback((_) { diff --git a/lib/ui/search/search_detail.dart b/lib/ui/search/search_detail.dart index 20ef833..b34e675 100644 --- a/lib/ui/search/search_detail.dart +++ b/lib/ui/search/search_detail.dart @@ -24,6 +24,7 @@ class SearchDetail extends StatelessWidget { children: [coursePrereqs(), courseDetails()], )); + // TODO(p8gonzal): Need an API with this specific data Card coursePrereqs() { return Card( shape: RoundedRectangleBorder( @@ -47,6 +48,7 @@ class SearchDetail extends StatelessWidget { final List sectionCards = []; instructorSections = >{}; + // Groups courses based on the professor for (final SectionData section in data.sections!) { final String sectionLetter = section.sectionCode![0]; instructorSections.update(sectionLetter, (List value) { @@ -59,9 +61,13 @@ class SearchDetail extends StatelessWidget { }); } + // Collect all section types except for lecture sections instructorSections.forEach((String key, List value) { final List sectionTypes = []; final List sectionObjects = []; + + // Ensure we only add one lecture object to the list of section data + // Fails when lecture has already been added for (final SectionData section in value) { if ((section.instructionType != 'LE' || !sectionTypes.contains('LE')) && section.sectionStatus != 'CA') { @@ -69,9 +75,11 @@ class SearchDetail extends StatelessWidget { sectionObjects.add(section); } } + // Add an empty final section model, will be filled by lecture model later sectionTypes.add('FI'); sectionObjects.add(SectionData()); + // Build section cards based off the list int sectionIndex = 0; for (final String sectionType in sectionTypes.toList()) { sectionCards diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 04d546d..cb95a72 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -61,6 +61,8 @@ class _SearchViewState extends State { .fetchTerms(), builder: (BuildContext context, AsyncSnapshot response) { + // Only call lambda upon first run to avoid unneccessary network load + if (response.hasData || !firstRun) { if (firstRun) { dropdownItems = @@ -68,6 +70,7 @@ class _SearchViewState extends State { _dropdownVal = dropdownItems[ dropdownItems.length - 1]; } + //Otherwise will use local reference return DropdownButton( underline: Container(height: 0), value: _dropdownVal, @@ -154,15 +157,18 @@ class _SearchViewState extends State { body: body(showList)); } + //Body will change based on if search has been completed by the API or not Widget body(bool showList) { bool validQuery = false; String searchBuilder = ''; final String termCode = _dropdownVal; + // Will be true when user has clicked into search bar if (showList) { final List words = searchString.split(' '); switch (words.length) { + // This case is true when looking for course only (e.g. CSE) case 1: { //Verify that subject code could be valid @@ -176,6 +182,7 @@ class _SearchViewState extends State { 'subjectCodes=${words[0]}&termCode=$termCode&limit=100'; } break; + // This is the case when searching for specific course (e.g. CSE 110) case 2: { final String firstWord = words[0]; @@ -205,7 +212,7 @@ class _SearchViewState extends State { textAlign: TextAlign.center, )); } - + // Will build results list when schedule of classes api has returned return FutureBuilder( future: classesProvider.fetchClasses(searchBuilder), builder: (BuildContext context, AsyncSnapshot response) { @@ -217,6 +224,7 @@ class _SearchViewState extends State { }, ); } else { + // When user has not clicked into search bar, hint is displayed return const Center( child: Text( 'Search by course code\ne.g. ANTH 23', @@ -226,6 +234,7 @@ class _SearchViewState extends State { } } + // Method builds results list once a model of the repsonse has been completed Widget buildResultsList(BuildContext context) { // List arguments = widget.args; // loops through and adds buttons for the user to click on @@ -280,6 +289,7 @@ class _SearchViewState extends State { )) ]), onTap: () { + // Go to detail view for a specific course, passing in data model Navigator.pushNamed(context, RoutePaths.SearchDetail, arguments: course); },