Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
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.
24 changes: 23 additions & 1 deletion lib/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,32 @@ 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';
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 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";
}

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.';
}
156 changes: 156 additions & 0 deletions lib/app_networking.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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<dynamic> 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<dynamic> authorizedFetch(
String url, Map<String, String> 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<dynamic> authorizedPost(
String url, Map<String, String>? 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<dynamic> authorizedPut(
String url, Map<String, String> 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<dynamic> authorizedDelete(
String url, Map<String, String> 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
} catch (err) {
return null;
}
}
}
25 changes: 25 additions & 0 deletions lib/app_provider.dart
Original file line number Diff line number Diff line change
@@ -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<SingleChildWidget> providers = [
ChangeNotifierProvider<UserDataProvider>(
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<UserDataProvider, ScheduleOfClassesProvider>(
create: (_) {
return ScheduleOfClassesProvider();
}, update: (_, UserDataProvider userDataProvider,
ScheduleOfClassesProvider? scheduleOfClassesProvider) {
scheduleOfClassesProvider!.userDataProvider = userDataProvider;
return scheduleOfClassesProvider;
})
];
27 changes: 22 additions & 5 deletions lib/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
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/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: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';
import 'package:webreg_mobile_flutter/core/models/schedule_of_classes.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:
return MaterialPageRoute<void>(builder: (_) => BottomNavigation());
case RoutePaths.AuthenticationError:
return MaterialPageRoute<void>(builder: (_) => AuthenticationError());
// case RoutePaths.Login:
// return MaterialPageRoute<void>(builder: (_) => Login());
case RoutePaths.SearchView:
return MaterialPageRoute<void>(builder: (_) => SearchView());
case RoutePaths.CourseListView:
return MaterialPageRoute<void>(builder: (_) => CourseListView());
case RoutePaths.SearchDetail:
final CourseData course = settings.arguments! as CourseData;
return MaterialPageRoute(builder: (_) {
return SearchDetail(data: course);
});

default:
return MaterialPageRoute<void>(builder: (_) => BottomNavigation());
}
}
}
}
74 changes: 74 additions & 0 deletions lib/core/models/authentication.dart
Original file line number Diff line number Diff line change
@@ -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<String, dynamic> json) {
return AuthenticationModel(
accessToken: json['access_token'],
pid: json['pid'],
ucsdaffiliation: json['ucsdaffiliation'] ?? '',
expiration: json['expiration'] ?? 0,
);
}

Map<String, dynamic> 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;
}
}
}
Loading