Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile-Calendar and SOC-Search Implementation #29

Open
wants to merge 25 commits into
base: experimental
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d3bb14
initial commit with some version updates, added libraries, and new fi…
kevdesilva01 Nov 10, 2021
790e792
Further screens and navigations dependent on live data
p8gonzal Nov 22, 2021
e9ec50f
updated service and provider files more
kevdesilva01 Dec 1, 2021
23ba8e4
Working on search page
p8gonzal Dec 3, 2021
9165091
Merge remote-tracking branch 'kevin/kevins-working-branch' into exper…
p8gonzal Dec 3, 2021
814e6bf
Revert "Working on search page"
p8gonzal Dec 3, 2021
defde61
Changes to version
p8gonzal Dec 3, 2021
edf4814
Compiling demo version
p8gonzal Dec 10, 2021
b2470e4
Working network requests for Schedule of Classes
p8gonzal Dec 13, 2021
d166d32
working version of api integration, further development in progress
p8gonzal Dec 15, 2021
54030b9
Section handling in progress
p8gonzal Dec 16, 2021
dca87aa
Polished version of schedule of classes integration
p8gonzal Dec 31, 2021
849c2ed
Fixed formatting issue for class times
p8gonzal Feb 20, 2022
00ab380
Updated algorithm for multiple professors
p8gonzal Feb 23, 2022
1d4d3fe
one more case for sections labeled 'TU'
kevdesilva01 Mar 3, 2022
62cd93a
Completed term picker and state issue
p8gonzal Mar 20, 2022
1d8ed08
Updated term picker with qa release of lambda
p8gonzal Mar 23, 2022
0253f58
Cleaned up files, ready for CI/CD integration
p8gonzal Apr 5, 2022
288ee3e
Working on new user profile int
p8gonzal Apr 13, 2022
874496d
Created corresponding service, provider and model files for user profile
p8gonzal Apr 19, 2022
a05c1cb
Basic functionality achieved for finals, discussions, classes
p8gonzal May 19, 2022
0374b2e
Added lab sections support
p8gonzal May 19, 2022
c096dec
Added better structure and commenting
p8gonzal May 26, 2022
48fa611
Fixed styling errors and renamed relevant files
p8gonzal Jun 9, 2022
b110df2
Added better comments
p8gonzal Jun 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
# Android Studio, and the `flutter analyze` command.

analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
errors:
# treat missing required parameters as a warning (not a hint)
missing_required_param: warning
Expand Down
Binary file added assets/images/UCSanDiegoLogo-nav.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions lib/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,32 @@ class RoutePaths {
static const String FinalsList = 'finals_list';
static const String SearchView = 'search_view';
static const String CourseListView = 'course_list_view';
static const String Login = 'login';
static const String SearchDetail = 'search_detail';
static const String AuthenticationError = 'authentication_error';
}

class CalendarStyles {
static const double calendarHeaderHeight = 50;
static const double calendarTimeWidth = 35;
static const double calendarRowHeight = 60;
}
}

class ErrorConstants {
static const String authorizedPostErrors = 'Failed to upload data: ';
static const String authorizedPutErrors = 'Failed to update data: ';
static const String invalidBearerToken = 'Invalid bearer token';
static const String duplicateRecord =
'DioError [DioErrorType.response]: Http status error [409]';
static const String invalidMedia =
'DioError [DioErrorType.response]: Http status error [415]';
static const String silentLoginFailed = 'Silent login failed';
}

class LoginConstants {
static const String silentLoginFailedTitle = 'Oops! You\'re not logged in.';
static const String silentLoginFailedDesc =
'The system has logged you out (probably by mistake). Go to Profile to log back in.';
static const String loginFailedTitle = 'Sorry, unable to sign you in.';
static const String loginFailedDesc =
'Be sure you are using the correct credentials; TritonLink login if you are a student, SSO (AD or Active Directory) if you are a Faculty/Staff.';
}
130 changes: 130 additions & 0 deletions lib/app_networking.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// ignore_for_file: always_specify_types

import 'dart:async';
import 'package:dio/dio.dart';
import 'package:webreg_mobile_flutter/app_constants.dart';

class NetworkHelper {
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<dynamic> fetchData(String url) async {
final Dio dio = Dio();
dio.options.connectTimeout = 20000;
dio.options.receiveTimeout = 20000;
dio.options.responseType = ResponseType.plain;
final Response _response = await dio.get(url);

if (_response.statusCode == 200) {
// If server returns an OK response, return the body
return _response.data;
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to fetch data: ' + _response.data);
}
}

Future<dynamic> authorizedFetch(
String url, Map<String, String> headers) async {
final Dio dio = Dio();
dio.options.connectTimeout = 20000;
dio.options.receiveTimeout = 20000;
dio.options.responseType = ResponseType.plain;
dio.options.headers = headers;
final Response _response = await dio.get(
url,
);
if (_response.statusCode == 200) {
// If server returns an OK response, return the body
return _response.data;
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to fetch data: ' + _response.data);
}
}

Future<dynamic> authorizedPost(
String url, Map<String, String>? headers, dynamic body) async {
final Dio dio = Dio();
dio.options.connectTimeout = 20000;
dio.options.receiveTimeout = 20000;
dio.options.headers = headers;
final Response _response = await dio.post(url, data: body);
if (_response.statusCode == 200 || _response.statusCode == 201) {
// If server returns an OK response, return the body
return _response.data;
} else if (_response.statusCode == 400) {
// If that response was not OK, throw an error.
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPostErrors + message);
} else if (_response.statusCode == 401) {
throw Exception(ErrorConstants.authorizedPostErrors +
ErrorConstants.invalidBearerToken);
} else if (_response.statusCode == 404) {
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPostErrors + message);
} else if (_response.statusCode == 500) {
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPostErrors + message);
} else if (_response.statusCode == 409) {
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.duplicateRecord + message);
} else {
throw Exception(ErrorConstants.authorizedPostErrors + 'unknown error');
}
}

Future<dynamic> authorizedPut(
String url, Map<String, String> headers, dynamic body) async {
final Dio dio = Dio();
dio.options.connectTimeout = 20000;
dio.options.receiveTimeout = 20000;
dio.options.headers = headers;
final Response _response = await dio.put(url, data: body);

if (_response.statusCode == 200 || _response.statusCode == 201) {
// If server returns an OK response, return the body
return _response.data;
} else if (_response.statusCode == 400) {
// If that response was not OK, throw an error.
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPutErrors + message);
} else if (_response.statusCode == 401) {
throw Exception(ErrorConstants.authorizedPutErrors +
ErrorConstants.invalidBearerToken);
} else if (_response.statusCode == 404) {
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPutErrors + message);
} else if (_response.statusCode == 500) {
final String message = _response.data['message'] ?? '';
throw Exception(ErrorConstants.authorizedPutErrors + message);
} else {
throw Exception(ErrorConstants.authorizedPutErrors + 'unknown error');
}
}

Future<dynamic> authorizedDelete(
String url, Map<String, String> headers) async {
final Dio dio = Dio();
dio.options.connectTimeout = 20000;
dio.options.receiveTimeout = 20000;
dio.options.headers = headers;
try {
final Response _response = await dio.delete(url);
if (_response.statusCode == 200) {
// If server returns an OK response, return the body
return _response.data;
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to delete data: ' + _response.data);
}
} on TimeoutException {
// Display an alert - i.e. no internet
} catch (err) {
return null;
}
}
}
34 changes: 34 additions & 0 deletions lib/app_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'package:webreg_mobile_flutter/core/providers/profile.dart';
import 'package:webreg_mobile_flutter/core/providers/schedule_of_classes.dart';
import 'package:webreg_mobile_flutter/core/providers/user.dart';

// ignore: always_specify_types
List<SingleChildWidget> providers = [
ChangeNotifierProvider<UserDataProvider>(
create: (_) {
return UserDataProvider();
},
),
ChangeNotifierProxyProvider<UserDataProvider, ScheduleOfClassesProvider>(
create: (_) {
final ScheduleOfClassesProvider scheduleOfClassesProvider =
ScheduleOfClassesProvider();
scheduleOfClassesProvider.userDataProvider = UserDataProvider();
return scheduleOfClassesProvider;
}, update: (_, UserDataProvider userDataProvider,
ScheduleOfClassesProvider? scheduleOfClassesProvider) {
scheduleOfClassesProvider!.userDataProvider = userDataProvider;
return scheduleOfClassesProvider;
}),
ChangeNotifierProxyProvider<UserDataProvider, ProfileProvider>(create: (_) {
final ProfileProvider profileProvider = ProfileProvider();
profileProvider.userDataProvider = UserDataProvider();
return profileProvider;
}, update:
(_, UserDataProvider userDataProvider, ProfileProvider? profileProvider) {
profileProvider!.userDataProvider = userDataProvider;
return profileProvider;
})
];
27 changes: 21 additions & 6 deletions lib/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import 'package:flutter/material.dart';
import 'package:webreg_mobile_flutter/app_constants.dart';
import 'package:webreg_mobile_flutter/ui/search/search_view.dart';
import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.dart';
import 'package:webreg_mobile_flutter/ui/common/authentication_sso.dart';
import 'package:webreg_mobile_flutter/ui/list/course_list_view.dart';
import 'package:webreg_mobile_flutter/ui/navigator/bottom.dart';
import 'package: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<dynamic> generateRoute(RouteSettings settings) {
print('route' + settings.name);
switch (settings.name) {
case RoutePaths.Home:
// TODO(p8gonzal): Do not add const to BottomNavigation(), will cause popup authentication failure
return MaterialPageRoute<void>(builder: (_) => BottomNavigation());
case RoutePaths.AuthenticationError:
return MaterialPageRoute<void>(
builder: (_) => const AuthenticationSSO());
case RoutePaths.SearchView:
return MaterialPageRoute<void>(builder: (_) => SearchView());
case RoutePaths.CourseListView:
return MaterialPageRoute<void>(builder: (_) => CourseListView());
return MaterialPageRoute<void>(builder: (_) => const CourseListView());
case RoutePaths.SearchDetail:
final CourseData course = settings.arguments! as CourseData;
return MaterialPageRoute<void>(builder: (_) {
return SearchDetail(data: course);
});

default:
return MaterialPageRoute<void>(
builder: (_) => const BottomNavigation());
}
}
}
}
28 changes: 15 additions & 13 deletions lib/app_styles.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import 'package:flutter/material.dart';

/// App Styles
const headerStyle = TextStyle(fontSize: 35, fontWeight: FontWeight.w900);
const subHeaderStyle = TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500);
const TextStyle headerStyle =
TextStyle(fontSize: 35, fontWeight: FontWeight.w900);
const TextStyle subHeaderStyle =
TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500);

// Theme agnostic styles
const agnosticDisabled = Color(0xFF8A8A8A);
const Color agnosticDisabled = Color(0xFF8A8A8A);

/// App Layout
// Card Layout
const cardMargin = 6.0;
const cardPaddingInner = 8.0;
const cardMinHeight = 60.0;
const listTileInnerPadding = 8.0;
const double cardMargin = 6.0;
const double cardPaddingInner = 8.0;
const double cardMinHeight = 60.0;
const double listTileInnerPadding = 8.0;

//Card Heights
const cardContentMinHeight = 80.0;
const cardContentMaxHeight = 568.0;
const double cardContentMinHeight = 80.0;
const double cardContentMaxHeight = 568.0;

const webViewMinHeight = 20.0;
const double webViewMinHeight = 20.0;

/// App Theme
const MaterialColor ColorPrimary = MaterialColor(
Expand Down Expand Up @@ -107,10 +109,10 @@ const Color lightTextFieldBorderColor = Color(0xFFFFFFFF);
const Color lightAccentColor = Color(0xFFFFFFFF);
const Color darkAccentColor = Color(0xFF333333);

const debugHeader = TextStyle(color: lightTextColor, fontSize: 14.0);
const debugRow = TextStyle(color: lightTextColor, fontSize: 12.0);
const TextStyle debugHeader = TextStyle(color: lightTextColor, fontSize: 14.0);
const TextStyle debugRow = TextStyle(color: lightTextColor, fontSize: 12.0);

// Testing
const Color c1 = Color.fromARGB(255, 255, 0, 0);
const Color c2 = Color.fromARGB(255, 0, 255, 0);
const Color c3 = Color.fromARGB(255, 0, 0, 255);
const Color c3 = Color.fromARGB(255, 0, 0, 255);
73 changes: 73 additions & 0 deletions lib/core/models/authentication.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 {
AuthenticationModel({
this.accessToken,
this.pid,
this.ucsdaffiliation,
this.expiration,
});

factory AuthenticationModel.fromJson(Map<String, dynamic> json) {
return AuthenticationModel(
accessToken: json['access_token'],
pid: json['pid'],
ucsdaffiliation: json['ucsdaffiliation'] ?? '',
expiration: json['expiration'] ?? 0,
);
}

@HiveField(0)
String? accessToken;
// Deprecated reserved field number - DO NOT REMOVE
// @HiveField(1)
@HiveField(2)
String? pid;
@HiveField(3)
String? ucsdaffiliation;
@HiveField(4)
int? expiration;

Map<String, dynamic> toJson() => <String, Object?>{
'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;
}
}
}
Loading