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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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 adafbb55e9e1ded6fbaa770e09d062a96c7ce6cd Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Wed, 9 Feb 2022 12:16:38 -0800 Subject: [PATCH 12/16] Added client id placeholder --- assets/images/UCSanDiegoLogo-nav.png | Bin 0 -> 7477 bytes lib/app_constants.dart | 10 + lib/app_provider.dart | 25 +++ lib/app_router.dart | 6 + lib/core/models/authentication.dart | 74 +++++++ lib/core/providers/schedule_of_classes.dart | 29 ++- lib/core/providers/user.dart | 23 +++ lib/core/services/authentication.dart | 15 ++ lib/core/services/schedule_of_classes.dart | 25 +-- lib/generated_plugin_registrant.dart | 18 ++ lib/main.dart | 62 ++++-- lib/ui/common/authentication_error.dart | 82 ++++++++ lib/ui/login/login.dart | 215 ++++++++++++++++++++ lib/ui/navigator/bottom.dart | 15 ++ lib/ui/search/search_view.dart | 13 +- pubspec.lock | 121 ++++++++++- pubspec.yaml | 5 + web/static.html | 18 ++ 18 files changed, 692 insertions(+), 64 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/generated_plugin_registrant.dart create mode 100644 lib/ui/common/authentication_error.dart create mode 100644 lib/ui/login/login.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..8650609 100644 --- a/lib/app_constants.dart +++ b/lib/app_constants.dart @@ -8,6 +8,7 @@ class RoutePaths { 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 { @@ -26,3 +27,12 @@ class ErrorConstants { 'DioError [DioErrorType.response]: Http status error [415]'; static const silentLoginFailed = "Silent login failed"; } + +class LoginConstants { + static const silentLoginFailedTitle = 'Oops! You\'re not logged in.'; + static const silentLoginFailedDesc = + 'The system has logged you out (probably by mistake). Go to Profile to log back in.'; + static const loginFailedTitle = 'Sorry, unable to sign you in.'; + static const 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_provider.dart b/lib/app_provider.dart new file mode 100644 index 0000000..32cccd2 --- /dev/null +++ b/lib/app_provider.dart @@ -0,0 +1,25 @@ +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: (_) { + var _userDataProvider = UserDataProvider(); + + /// try to load any persistent saved data + /// once loaded from memory get the user's online profile + _userDataProvider = UserDataProvider(); + return _userDataProvider; + }, + ), + ChangeNotifierProxyProvider( + create: (_) { + 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..3b1b5fb 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -2,7 +2,9 @@ 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/login/login.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'; @@ -14,6 +16,10 @@ class Router { switch (settings.name) { case RoutePaths.Home: return MaterialPageRoute(builder: (_) => BottomNavigation()); + case RoutePaths.AuthenticationError: + return MaterialPageRoute(builder: (_) => AuthenticationError()); + // case RoutePaths.Login: + // return MaterialPageRoute(builder: (_) => Login()); case RoutePaths.SearchView: return MaterialPageRoute(builder: (_) => SearchView()); case RoutePaths.CourseListView: 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/providers/schedule_of_classes.dart b/lib/core/providers/schedule_of_classes.dart index 82166ce..1702704 100644 --- a/lib/core/providers/schedule_of_classes.dart +++ b/lib/core/providers/schedule_of_classes.dart @@ -1,5 +1,6 @@ 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/providers/user.dart'; import 'package:webreg_mobile_flutter/core/services/schedule_of_classes.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ class ScheduleOfClassesProvider extends ChangeNotifier { bool? _noResults; /// MODELS - ScheduleOfClassesModel _scheduleOfClassesModels = ScheduleOfClassesModel(); + ScheduleOfClassesModel _scheduleOfClassesModels = ScheduleOfClassesModel(); String? searchQuery; String? term; TextEditingController _searchBarController = TextEditingController(); @@ -37,24 +38,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) { diff --git a/lib/core/providers/user.dart b/lib/core/providers/user.dart new file mode 100644 index 0000000..99d2a9b --- /dev/null +++ b/lib/core/providers/user.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; +import 'dart:typed_data' show Uint8List; + +import 'package:encrypt/encrypt.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:hive/hive.dart'; +import 'package:pointycastle/asymmetric/api.dart'; +import 'package:pointycastle/asymmetric/oaep.dart'; +import 'package:pointycastle/pointycastle.dart' as pc; + +class UserDataProvider extends ChangeNotifier { + String? accessToken = ''; + + set setToken(String? token) { + accessToken = token; + notifyListeners(); + } + + 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 92ff14d..3c3ad7f 100644 --- a/lib/core/services/schedule_of_classes.dart +++ b/lib/core/services/schedule_of_classes.dart @@ -2,24 +2,25 @@ import 'dart:async'; 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 { 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(); + //await getNewToken(); /// fetch data final String? _response = await _networkHelper.authorizedFetch( @@ -47,18 +48,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/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart new file mode 100644 index 0000000..256a7dc --- /dev/null +++ b/lib/generated_plugin_registrant.dart @@ -0,0 +1,18 @@ +// +// Generated file. Do not edit. +// + +// 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/main.dart b/lib/main.dart index a643823..d11e153 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,35 +1,63 @@ +import 'dart:html'; + import 'package:flutter/material.dart'; +import 'package:get/route_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:webreg_mobile_flutter/app_provider.dart'; import 'package:webreg_mobile_flutter/ui/search/search_placeholder.dart'; import 'package:webreg_mobile_flutter/app_styles.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_router.dart' as webregMobileRouter; +import 'dart:js' as js; + +void getParams() { + var uri = Uri.dataFromString(window.location.href); + Map params = uri.queryParameters; + String? _token = params['token']; +} void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { + late String? _token = ''; +// Our current app URL + + void getParams() { + var uri = Uri.dataFromString(window.location.href); + 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) + final currentUri = Uri.base; + getParams(); + + + return MultiProvider( + providers: providers, + child: GetMaterialApp( + 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)), ), + initialRoute: + _token == '' ? RoutePaths.AuthenticationError : RoutePaths.Home, + onGenerateRoute: webregMobileRouter.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..1cdec1b --- /dev/null +++ b/lib/ui/common/authentication_error.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.dart'; +import 'dart:html' as html; + +import '../../app_constants.dart'; + +class AuthenticationError extends StatefulWidget { + @override + _AuthenticationErrorState createState() => _AuthenticationErrorState(); +} + +class _AuthenticationErrorState extends State { + late UserDataProvider userDataProvider; + String _token = ''; + String clientId = 'CLIENT_ID_PLACEHOLDER'; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + userDataProvider = Provider.of(context); + } + + @override + void initState() { + super.initState(); + + final currentUrl = Uri.base; + if (!currentUrl.fragment.contains('access_token=')) { + // You are not connected so redirect to the Twitch authentication page. + WidgetsBinding.instance!.addPostFrameCallback((_) { + html.window.location.assign( + 'https://api-qa.ucsd.edu:8243/authorize?response_type=token&client_id=$clientId&redirect_uri=${currentUrl.origin}&scope=viewing_activity_read', + ); + // final fragments = currentUrl.fragment.split('&'); + // _token = fragments + // .firstWhere((e) => e.startsWith('access_token=')) + // .substring('access_token='.length); + // userDataProvider.accessToken = _token; + }); + } else { + final fragments = currentUrl.fragment.split('&'); + _token = fragments + .firstWhere((e) => e.startsWith('access_token=')) + .substring('access_token='.length); + userDataProvider.accessToken = _token; + } + } +// @override +// void initState() { +// super.initState(); + +// final currentUrl = Uri.base; +// if (!currentUrl.fragment.contains('access_token=')) { +// // You are not connected so redirect to the Twitch authentication page. +// WidgetsBinding.instance!.addPostFrameCallback((_) { +// html.window.location.assign( +// 'https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=$clientId&redirect_uri=${currentUrl.origin}&scope=viewing_activity_read', +// ); +// }); +// } else { +// // You are connected, you can grab the code from the url. +// final fragments = currentUrl.fragment.split('&'); +// _token = fragments +// .firstWhere((e) => e.startsWith('access_token=')) +// .substring('access_token='.length); +// } +// } + + @override + Widget build(BuildContext context) { + if (userDataProvider.accessToken != '') { + Navigator.pushNamedAndRemoveUntil( + context, RoutePaths.Home, (route) => false); + } + return const Scaffold( + body: Center( + child: Text('Missing Authentication Token'), + ), + ); + } +} diff --git a/lib/ui/login/login.dart b/lib/ui/login/login.dart new file mode 100644 index 0000000..bd44df4 --- /dev/null +++ b/lib/ui/login/login.dart @@ -0,0 +1,215 @@ +// 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/providers/user.dart'; + +// class Login extends StatefulWidget { +// @override +// _LoginState createState() => _LoginState(); +// } + +// class _LoginState extends State { +// late UserDataProvider _userDataProvider; +// final TextEditingController _emailTextFieldController = +// TextEditingController(); +// final TextEditingController _passwordTextFieldController = +// TextEditingController(); +// bool _passwordObscured = true; + +// @override +// void didChangeDependencies() { +// super.didChangeDependencies(); +// _userDataProvider = Provider.of(context); +// } + +// @override +// Widget build(BuildContext buildContext) { +// return Scaffold( +// appBar: AppBar( +// elevation: 0.0, +// title: const Text('UC San Diego WebReg'), +// ), +// backgroundColor: lightPrimaryColor, +// body: !_userDataProvider.isLoading! +// ? buildLoginWidget() +// : const CircularProgressIndicator(), +// ); +// } + +// Widget buildLoginWidget() { +// return Center( +// child: ConstrainedBox( +// constraints: const BoxConstraints(maxWidth: 300), +// child: SingleChildScrollView( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// Image.asset( +// 'assets/images/UCSanDiegoLogo-nav.png', +// fit: BoxFit.contain, +// height: 50, +// color: Colors.white, +// ), +// const SizedBox(height: 100.0), +// Padding( +// padding: const EdgeInsets.only(top: 0.0), +// child: Container( +// decoration: BoxDecoration( +// borderRadius: +// const BorderRadius.all(Radius.circular(100)), +// color: Theme.of(context) +// .colorScheme +// .secondary), //lightTextFieldBorderColor, +// child: TextField( +// style: const TextStyle( +// textBaseline: TextBaseline.alphabetic, +// color: Colors.black), +// decoration: const InputDecoration( +// hintText: 'UCSD Email', +// focusedBorder: OutlineInputBorder( +// borderRadius: +// BorderRadius.all(Radius.circular(5)), +// ), +// enabledBorder: OutlineInputBorder( +// borderRadius: +// BorderRadius.all(Radius.circular(5)), +// ), +// contentPadding: EdgeInsets.only(left: 10), +// hintStyle: TextStyle(color: ColorPrimary), +// fillColor: Colors.white, +// filled: true, +// ), +// keyboardType: TextInputType.emailAddress, +// controller: _emailTextFieldController, +// ), +// )), +// const SizedBox(height: 15), +// Container( +// decoration: BoxDecoration( +// borderRadius: +// const BorderRadius.all(Radius.circular(100)), +// color: Theme.of(context).colorScheme.secondary), +// child: TextField( +// style: const TextStyle( +// textBaseline: TextBaseline.alphabetic, +// color: Colors.black, +// ), +// decoration: InputDecoration( +// hintText: 'Password', +// suffixIcon: IconButton( +// icon: Icon( +// // Based on passwordObscured state choose the icon +// _passwordObscured +// ? Icons.visibility_off +// : Icons.visibility, +// color: Theme.of(context).primaryColorDark, +// ), +// onPressed: () => _toggle(), +// ), +// focusedBorder: const OutlineInputBorder( +// borderSide: BorderSide( +// color: Colors.black, +// ), +// borderRadius: BorderRadius.all(Radius.circular(5)), +// ), +// enabledBorder: const OutlineInputBorder( +// borderSide: BorderSide( +// color: Colors.black, +// ), +// borderRadius: BorderRadius.all(Radius.circular(5)), +// ), +// contentPadding: const EdgeInsets.only(left: 10), +// hintStyle: const TextStyle(color: ColorPrimary), +// fillColor: Colors.white, +// filled: true, +// ), +// obscureText: _passwordObscured, +// controller: _passwordTextFieldController, +// ), +// ), +// const SizedBox(height: 20), +// Padding( +// padding: const EdgeInsets.only(top: 30.0), +// child: Row( +// children: [ +// Expanded( +// child: TextButton( +// style: TextButton.styleFrom( +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(5.0), +// side: const BorderSide(color: Colors.white), +// ), +// primary: ColorPrimary, +// textStyle: const TextStyle( +// color: lightButtonTextColor, +// )), +// child: const Text( +// 'Log in', +// style: TextStyle( +// color: Colors.white, +// decoration: TextDecoration.underline, +// ), +// ), +// onPressed: _userDataProvider.isLoading! +// ? null +// : () { +// _userDataProvider +// .manualLogin( +// _emailTextFieldController.text, +// _passwordTextFieldController.text) +// .then(( isLoggedIn) async { +// if (isLoggedIn) { +// Navigator.pushNamedAndRemoveUntil(context, +// RoutePaths.Home, (_) => false); +// } else { +// showAlertDialog(context); +// } +// }); +// }, + +// // ), +// )) +// ], +// ), +// ), +// ])))); +// } + +// // Toggles the password show status +// void _toggle() { +// setState(() { +// _passwordObscured = !_passwordObscured; +// }); +// } + +// showAlertDialog(BuildContext context) { +// // set up the button +// final Widget okButton = TextButton( +// style: TextButton.styleFrom( +// primary: Theme.of(context).buttonColor, +// ), +// child: const Text('OK'), +// onPressed: () { +// Navigator.of(context).pop(); +// }, +// ); + +// // set up the AlertDialog +// final AlertDialog alert = AlertDialog( +// title: const Text(LoginConstants.loginFailedTitle), +// content: const Text(LoginConstants.loginFailedDesc), +// actions: [ +// okButton, +// ], +// ); + +// // show the dialog +// showDialog( +// context: context, +// builder: (BuildContext context) { +// return alert; +// }, +// ); +// } +// } diff --git a/lib/ui/navigator/bottom.dart b/lib/ui/navigator/bottom.dart index 9c5eb78..1e37bfe 100644 --- a/lib/ui/navigator/bottom.dart +++ b/lib/ui/navigator/bottom.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:webreg_mobile_flutter/core/providers/user.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/ui/list/course_list_view.dart'; @@ -12,6 +14,7 @@ class BottomNavigation extends StatefulWidget { class _BottomNavigationState extends State with SingleTickerProviderStateMixin { + UserDataProvider userDataProvider = UserDataProvider(); var currentTab = [ Calendar(Colors.blue.shade200), CourseListView(), @@ -26,9 +29,21 @@ class _BottomNavigationState extends State color: ColorPrimary, fontSize: 16, ); + // @override + // void didChangeDependencies() { + // userDataProvider = Provider.of(context); + // } @override Widget build(BuildContext context) { + String _token = ''; + final currentUrl = Uri.base; + final fragments = currentUrl.fragment.split('&'); + _token = fragments + .firstWhere((e) => e.startsWith('access_token=')) + .substring('access_token='.length); + userDataProvider.setToken = _token; + return Scaffold( appBar: AppBar( centerTitle: true, diff --git a/lib/ui/search/search_view.dart b/lib/ui/search/search_view.dart index 77a7d30..5716539 100644 --- a/lib/ui/search/search_view.dart +++ b/lib/ui/search/search_view.dart @@ -17,10 +17,17 @@ class _SearchViewState extends State { late String searchString; late ScheduleOfClassesProvider classesProvider; bool showList = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + classesProvider = ScheduleOfClassesProvider(); + + } + @override Widget build(BuildContext context) { const Widget _icon = Icon(Icons.search, size: 20, color: darkGray); - classesProvider = ScheduleOfClassesProvider(); return Scaffold( appBar: AppBar( titleSpacing: 0.0, @@ -102,8 +109,8 @@ class _SearchViewState extends State { Widget body(bool showList) { if (showList) { return FutureBuilder( - future: classesProvider.scheduleOfClassesService.fetchClasses( - 'subjectCodes=${searchString.split(' ')[0]}&termCode=SP21'), + future: classesProvider.fetchClasses( + 'subjectCodes=${searchString.split(' ')[0]}&termCode=SP22'), builder: (BuildContext context, AsyncSnapshot response) { if (response.hasData) { return buildResultsList(context); diff --git a/pubspec.lock b/pubspec.lock index 40560f5..7bddd23 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.0" + 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.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" 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.0.5" 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.5.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.2" 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" 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 b4dcca8f188f1931b0f44ae12f6ecb717fee17bc Mon Sep 17 00:00:00 2001 From: "Bryant, Charles" Date: Fri, 22 Apr 2022 11:06:13 -0700 Subject: [PATCH 13/16] Resolve dart err --- lib/ui/search/search_filters.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ui/search/search_filters.dart b/lib/ui/search/search_filters.dart index 8ef7d4b..72e1a99 100644 --- a/lib/ui/search/search_filters.dart +++ b/lib/ui/search/search_filters.dart @@ -1,6 +1,4 @@ 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 { @override @@ -11,7 +9,7 @@ class SearchFiltersState extends State { @override Widget build(BuildContext context) { return ExpansionTile( - // title: , + title: Text('tmp'), ); } -} \ No newline at end of file +} From a3997554f24675bb4f5292bc30968a00938b7e05 Mon Sep 17 00:00:00 2001 From: "Bryant, Charles" Date: Fri, 22 Apr 2022 11:41:57 -0700 Subject: [PATCH 14/16] Refactor ci/cd --- scripts/codemagic-ci/build-notifier.js | 4 ++-- scripts/codemagic-ci/post-publish.sh | 6 +++++- scripts/codemagic-ci/set-env.js | 4 ---- scripts/codemagic-ci/verify-env.sh | 7 +------ web/index.html | 2 +- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/scripts/codemagic-ci/build-notifier.js b/scripts/codemagic-ci/build-notifier.js index 8323f4b..10b1cff 100644 --- a/scripts/codemagic-ci/build-notifier.js +++ b/scripts/codemagic-ci/build-notifier.js @@ -90,9 +90,9 @@ const buildNotify = async () => { } else if (ENV_VARS.buildPlatform === 'WEB') { if (saveArtifactWebSuccess) { if (ENV_VARS.buildEnv === 'PROD') { - teamsMessage += 'URL:/webreg-mobile/' + ENV_VARS.appVersion + '-' + finalBuildNumber + '/index.html' + teamsMessage += 'URL:/webreg-mobile/index.html' } else if (ENV_VARS.buildEnv === 'QA') { - teamsMessage += 'URL:/webreg-mobile/' + ENV_VARS.appVersion + '-' + finalBuildNumber + '/index.html' + teamsMessage += 'URL:/webreg-mobile/index.html' } teamsMessage += 'Archive:' + buildArtifacts.buildWebFinalFilename + '' } else { diff --git a/scripts/codemagic-ci/post-publish.sh b/scripts/codemagic-ci/post-publish.sh index 69a7ef8..b2016ff 100644 --- a/scripts/codemagic-ci/post-publish.sh +++ b/scripts/codemagic-ci/post-publish.sh @@ -7,8 +7,12 @@ if [ "$FCI_BUILD_STEP_STATUS" == "success" ]; then echo " Moving build/web -> $APP_VERSION-$PROJECT_BUILD_NUMBER ..." cd build/ mv web "$APP_VERSION-$PROJECT_BUILD_NUMBER" + + echo " Removing old build assets from S3 ..." + aws s3 rm s3://ucsd-its-sandbox-wts-charles/webreg-mobile --recursive + echo " Syncing build assets to S3 ..." - aws s3 sync $APP_VERSION-$PROJECT_BUILD_NUMBER/ s3://ucsd-its-sandbox-wts-charles/webreg-mobile/$APP_VERSION-$PROJECT_BUILD_NUMBER + aws s3 sync $APP_VERSION-$PROJECT_BUILD_NUMBER/ s3://ucsd-its-sandbox-wts-charles/webreg-mobile elif [ "$FCI_BUILD_STEP_STATUS" == "skipped" ]; then echo "Build skipped - exiting." exit 1 diff --git a/scripts/codemagic-ci/set-env.js b/scripts/codemagic-ci/set-env.js index 01d28f5..46c2341 100644 --- a/scripts/codemagic-ci/set-env.js +++ b/scripts/codemagic-ci/set-env.js @@ -19,8 +19,6 @@ const prodEnvReplacements = async (targetEnv) => { envItem.QA.forEach((replacement, index) => { if (replacement === '"##BUILD_ENV##"') { data = data.replace(replacement, '"' + targetEnv + '"') - } else if (replacement === '') { - data = data.replace(replacement, '') } else if (replacement === 'version: 1.0.0+1') { data = data.replace(replacement, 'version: ' + appVersion + '+' + buildNumber) } else { @@ -45,8 +43,6 @@ const qaEnvReplacements = async (targetEnv) => { envItem.QA.forEach((replacement, index) => { if (replacement === '"##BUILD_ENV##"') { data = data.replace(replacement, '"' + targetEnv + '"') - } else if (replacement === '') { - data = data.replace(replacement, '') } else if (replacement === 'version: 1.0.0+1') { data = data.replace(replacement, 'version: ' + appVersion + '+' + buildNumber) } else { diff --git a/scripts/codemagic-ci/verify-env.sh b/scripts/codemagic-ci/verify-env.sh index 912b04b..3e563e5 100644 --- a/scripts/codemagic-ci/verify-env.sh +++ b/scripts/codemagic-ci/verify-env.sh @@ -8,9 +8,7 @@ if [ "$1" == "PROD" ]; then qa_dash=$(grep -rio "https.*-qa" lib | wc -l | sed -e "s/^[ \t]*//") dev_slash=$(grep -rio "https.*\/dev" lib | wc -l | sed -e "s/^[ \t]*//") dev_dash=$(grep -rio "https.*-dev" lib | wc -l | sed -e "s/^[ \t]*//") - base_href=$(grep -rio "##BASE_HREF##" web | wc -l | sed -e "s/^[ \t]*//") - invalid_count=$((qa_slash + qa_dash + dev_slash + dev_dash + base_href)) if [ "$invalid_count" -eq 0 ]; then echo "\nverify-env.sh PROD: PASS" @@ -20,15 +18,13 @@ if [ "$1" == "PROD" ]; then grep -rin "https.*-qa" lib grep -rin "https.*\/dev" lib grep -rin "https.*-dev" lib - grep -rin "##BASE_HREF##" web exit 1 fi elif [ "$1" == "QA" ]; then prod_slash=$(grep -rio "https.*\/prod" lib | wc -l | sed -e "s/^[ \t]*//") prod_dash=$(grep -rio "https.*-prod" lib | wc -l | sed -e "s/^[ \t]*//") - base_href=$(grep -rio "##BASE_HREF##" web | wc -l | sed -e "s/^[ \t]*//") - invalid_count=$((prod_slash + prod_dash + base_href)) + invalid_count=$((prod_slash + prod_dash)) if [ "$invalid_count" -eq 0 ]; then echo "\nset-env-qa: PASS" @@ -36,7 +32,6 @@ elif [ "$1" == "QA" ]; then echo "\nset-env-qa: FAIL (erors: ${invalid_count})" grep -rin "https.*\/prod" lib grep -rin "https.*-prod" lib - grep -rin "##BASE_HREF##" web exit 1 fi else diff --git a/web/index.html b/web/index.html index 01e1089..56b35bf 100644 --- a/web/index.html +++ b/web/index.html @@ -1,7 +1,7 @@ - + From 4b7fc62d11ca536fcac463080c6f7143c7c6712e Mon Sep 17 00:00:00 2001 From: "Bryant, Charles" Date: Fri, 22 Apr 2022 11:59:11 -0700 Subject: [PATCH 15/16] Update build path --- scripts/codemagic-ci/build-notifier.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/codemagic-ci/build-notifier.js b/scripts/codemagic-ci/build-notifier.js index 10b1cff..6973e5f 100644 --- a/scripts/codemagic-ci/build-notifier.js +++ b/scripts/codemagic-ci/build-notifier.js @@ -90,9 +90,9 @@ const buildNotify = async () => { } else if (ENV_VARS.buildPlatform === 'WEB') { if (saveArtifactWebSuccess) { if (ENV_VARS.buildEnv === 'PROD') { - teamsMessage += 'URL:/webreg-mobile/index.html' + teamsMessage += 'URL:/webreg-mobile/index.html' } else if (ENV_VARS.buildEnv === 'QA') { - teamsMessage += 'URL:/webreg-mobile/index.html' + teamsMessage += 'URL:/webreg-mobile/index.html' } teamsMessage += 'Archive:' + buildArtifacts.buildWebFinalFilename + '' } else { From 83a0f172b8c9b71a674e82661dc8627d94f66b38 Mon Sep 17 00:00:00 2001 From: Peter Gonzalez <42155702+p8gonzal@users.noreply.github.com> Date: Thu, 26 May 2022 16:34:07 -0700 Subject: [PATCH 16/16] Updated url strategy --- lib/main.dart | 6 +++--- pubspec.lock | 2 +- pubspec.yaml | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d11e153..ba17954 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:webreg_mobile_flutter/app_styles.dart'; import 'package:webreg_mobile_flutter/app_constants.dart'; import 'package:webreg_mobile_flutter/app_router.dart' as webregMobileRouter; import 'dart:js' as js; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; void getParams() { var uri = Uri.dataFromString(window.location.href); @@ -17,6 +18,7 @@ void getParams() { } void main() { + setUrlStrategy(PathUrlStrategy()); runApp(MyApp()); } @@ -27,8 +29,7 @@ class MyApp extends StatelessWidget { void getParams() { var uri = Uri.dataFromString(window.location.href); Map params = uri.queryParameters; - if(params['token'] != null) - _token = params['token']; + if (params['token'] != null) _token = params['token']; } // This widget is the root of your application. @@ -37,7 +38,6 @@ class MyApp extends StatelessWidget { final currentUri = Uri.base; getParams(); - return MultiProvider( providers: providers, child: GetMaterialApp( diff --git a/pubspec.lock b/pubspec.lock index 7bddd23..36c5e7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -159,7 +159,7 @@ packages: source: sdk version: "0.0.0" flutter_web_plugins: - dependency: transitive + dependency: "direct main" description: flutter source: sdk version: "0.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index baf8977..d9b6063 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,6 +6,8 @@ environment: sdk: ">=2.12.0 <3.0.0" encrypt: 5.0.0 dependencies: + flutter_web_plugins: + sdk: flutter cupertino_icons: 1.0.0 flutter: sdk: flutter