Skip to content

Commit

Permalink
Merge pull request #555 from pennlabs/jhawk0224/dining-bug-fixes
Browse files Browse the repository at this point in the history
Dining login bug fixes
  • Loading branch information
JHawk0224 authored Sep 29, 2024
2 parents 969a9cc + 1e36c79 commit b46f352
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 134 deletions.
49 changes: 49 additions & 0 deletions PennMobile/Dining/Controllers/DiningLoginController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ import WebKit
import PennMobileShared

class DiningLoginController: UIViewController, WKUIDelegate, WKNavigationDelegate, SHA256Hashable {
static let activityIndicatorAnimationDuration: TimeInterval = 0.1

final private var webView: WKWebView!
private var activityIndicatorBackground: UIVisualEffectView!
private var activityIndicator: UIActivityIndicatorView!

var clientId = "5c09c08b240a56d22f06b46789d0528a"

var urlStr: String {
return
"https://prod.campusexpress.upenn.edu/api/v1/oauth/authorize"
}
final private let loginScreen = "https://weblogin.pennkey.upenn.edu/idp/profile/SAML2/Redirect/SSO?execution=e1"

var delegate: DiningLoginControllerDelegate!

Expand All @@ -45,6 +49,27 @@ class DiningLoginController: UIViewController, WKUIDelegate, WKNavigationDelegat
url.appendQueryItem(name: "redirect_uri", value: "https://pennlabs.org/pennmobile/ios/campus_express_callback/")

webView.load(URLRequest(url: url))

activityIndicatorBackground = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
activityIndicatorBackground.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
activityIndicatorBackground.layer.cornerRadius = 16
activityIndicatorBackground.clipsToBounds = true
activityIndicatorBackground.layer.opacity = 0

activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.center = CGPoint(x: activityIndicatorBackground.bounds.midX, y: activityIndicatorBackground.bounds.midY)
activityIndicatorBackground.contentView.addSubview(activityIndicator)

self.view.addSubview(activityIndicatorBackground)
self.view.bringSubviewToFront(activityIndicatorBackground)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let activityIndicatorBackground {
activityIndicatorBackground.center = view.center
view.bringSubviewToFront(activityIndicatorBackground)
}
}

var handleCancel: (() -> Void)?
Expand Down Expand Up @@ -74,6 +99,13 @@ class DiningLoginController: UIViewController, WKUIDelegate, WKNavigationDelegat
}

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
view.isUserInteractionEnabled = true
UIView.animate(withDuration: Self.activityIndicatorAnimationDuration) {
self.activityIndicatorBackground.layer.opacity = 0
} completion: { _ in
self.activityIndicator.stopAnimating()
}

if let url = navigationResponse.response.url, url.absoluteString.contains("https://pennlabs.org/pennmobile/ios/campus_express_callback/") {
let queryParams = url.queryParameters

Expand Down Expand Up @@ -110,6 +142,23 @@ class DiningLoginController: UIViewController, WKUIDelegate, WKNavigationDelegat
decisionHandler(.allow)
}

func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .formSubmitted,
webView.url?.absoluteString.contains(loginScreen) == true {
activityIndicator.startAnimating()
view.isUserInteractionEnabled = false

UIView.animate(withDuration: Self.activityIndicatorAnimationDuration) {
self.activityIndicatorBackground.layer.opacity = 1
}
}

decisionHandler(.allow)
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let url = webView.url else {
return
Expand Down
192 changes: 68 additions & 124 deletions PennMobile/Dining/SwiftUI/DiningAnalyticsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ struct DiningAnalyticsView: View {
@State var showDiningLoginView = false
@State var notLoggedInAlertShowing = false
@State var showSettingsSheet = false
@Environment(\.colorScheme) var colorScheme
@Environment(\.presentationMode) var presentationMode
func showCorrectAlert () -> Alert {
func showCorrectAlert() -> Alert {
if !Account.isLoggedIn {
return Alert(title: Text("You must log in to access this feature."), message: Text("Please login on the \"More\" tab."), dismissButton: .default(Text("Ok"), action: { presentationMode.wrappedValue.dismiss() }))
} else {
Expand All @@ -27,143 +28,86 @@ struct DiningAnalyticsView: View {
}
}
var body: some View {
if #available(iOS 16.0, *) {
let dollarHistory = $diningAnalyticsViewModel.dollarHistory
let swipeHistory = $diningAnalyticsViewModel.swipeHistory
HStack {
Spacer()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showSettingsSheet.toggle()
}) {
Image(systemName: "gear")
.imageScale(.large)
let dollarHistory = $diningAnalyticsViewModel.dollarHistory
let swipeHistory = $diningAnalyticsViewModel.swipeHistory
VStack {
if Account.isLoggedIn, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration {
if dollarHistory.wrappedValue.isEmpty && swipeHistory.wrappedValue.isEmpty {
ZStack {
let image = Image("DiningAnalyticsBackground")
.resizable()
.ignoresSafeArea()

switch colorScheme {
case .dark:
image
.colorInvert()
.hueRotation(.degrees(180))
.saturation(0.8)
.contrast(0.8)
default:
image
}
}
}
if Account.isLoggedIn, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration {
if dollarHistory.wrappedValue.isEmpty && swipeHistory.wrappedValue.isEmpty {
ZStack {
Image("DiningAnalyticsBackground")
.resizable()
.ignoresSafeArea()
VStack(spacing: 24) {
Text("No Dining Plan Found")
.font(.system(size: 48, weight: .regular))
Text("Dining Analytics may not appear until the day after the semester begins.")
}
.frame(maxWidth: 280)
.padding(.bottom, 64)
.multilineTextAlignment(.center)
.foregroundColor(.black)
.opacity(0.6)

VStack(spacing: 24) {
Text("No Dining Plan Found")
.font(.system(size: 48, weight: .regular))
Text("Dining Analytics may not appear until the day after the semester begins.")
}
} else {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Only show dollar history view if there is data for the graph
if !dollarHistory.wrappedValue.isEmpty {
CardView {
GraphView(type: .dollars, data: dollarHistory, predictedZeroDate: $diningAnalyticsViewModel.dollarPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedDollarSemesterEndBalance)
}
.frame(maxWidth: 280)
.padding(.bottom, 64)
.multilineTextAlignment(.center)
.opacity(0.6)
}
} else {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Only show dollar history view if there is data for the graph
if !dollarHistory.wrappedValue.isEmpty {
CardView {
GraphView(type: .dollars, data: dollarHistory, predictedZeroDate: $diningAnalyticsViewModel.dollarPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedDollarSemesterEndBalance)
}
// Only show swipe history view if there is data for the graph
if !swipeHistory.wrappedValue.isEmpty {
CardView {
GraphView(type: .swipes, data: swipeHistory, predictedZeroDate: $diningAnalyticsViewModel.swipesPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedSwipesSemesterEndBalance)
}
}
// Only show swipe history view if there is data for the graph
if !swipeHistory.wrappedValue.isEmpty {
CardView {
GraphView(type: .swipes, data: swipeHistory, predictedZeroDate: $diningAnalyticsViewModel.swipesPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedSwipesSemesterEndBalance)
}
Spacer()
}
.padding()
Spacer()
}
.padding()
}
}
}
.task {
guard Account.isLoggedIn, KeychainAccessible.instance.getDiningToken() != nil, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration else {
showMissingDiningTokenAlert = true
return
}
}
.alert(isPresented: $showMissingDiningTokenAlert) {
showCorrectAlert()
}
.sheet(isPresented: $showDiningLoginView) {
DiningLoginNavigationView()
.environmentObject(diningAnalyticsViewModel)
}
.navigationTitle(Text("Dining Analytics"))
.sheet(isPresented: $showSettingsSheet) {
DiningSettingsView(viewModel: diningAnalyticsViewModel) // Replace with your settings view
}
} else {
let dollarXYHistory = Binding(
get: {
getSmoothedData(from: diningAnalyticsViewModel.dollarHistory)
},
// one directional Binding, setter does not work
set: { _ in }
)
let swipeXYHistory = Binding(
get: { getSmoothedData(from: diningAnalyticsViewModel.swipeHistory) },
// one directional Binding, setter does not work
set: { _ in }
)
HStack {
if Account.isLoggedIn, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration {
if swipeXYHistory.wrappedValue.isEmpty && dollarXYHistory.wrappedValue.isEmpty {
ZStack {
Image("DiningAnalyticsBackground")
.resizable()
.ignoresSafeArea()
Text("No Dining\nPlan Found\n ")
.multilineTextAlignment(.center)
.font(.system(size: 48, weight: .regular))
.foregroundColor(.black)
.opacity(0.6)
}
} else {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
// Only show dollar history view if there is data for the graph
if !dollarXYHistory.wrappedValue.isEmpty {
CardView {
PredictionsGraphView(type: "dollars", data: dollarXYHistory, predictedZeroDate: $diningAnalyticsViewModel.dollarPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedDollarSemesterEndBalance, axisLabelsYX: $diningAnalyticsViewModel.dollarAxisLabel, slope: $diningAnalyticsViewModel.dollarSlope)
}
}
// Only show swipe history view if there is data for the graph
if !swipeXYHistory.wrappedValue.isEmpty {
CardView {
PredictionsGraphView(type: "swipes", data: swipeXYHistory, predictedZeroDate: $diningAnalyticsViewModel.swipesPredictedZeroDate, predictedSemesterEndValue: $diningAnalyticsViewModel.predictedSwipesSemesterEndBalance, axisLabelsYX: $diningAnalyticsViewModel.swipeAxisLabel, slope: $diningAnalyticsViewModel.swipeSlope)
}
}
Spacer()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showSettingsSheet.toggle()
}) {
Image(systemName: "gear")
.imageScale(.large)
}
.padding()
}
}
}
}
.task {
guard Account.isLoggedIn, KeychainAccessible.instance.getDiningToken() != nil, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration else {
showMissingDiningTokenAlert = true
return
}
}
.alert(isPresented: $showMissingDiningTokenAlert) {
showCorrectAlert()
}
.sheet(isPresented: $showDiningLoginView) {
DiningLoginNavigationView()
.environmentObject(diningAnalyticsViewModel)
}
.navigationTitle("Analytics")
.sheet(isPresented: $showSettingsSheet) {
DiningSettingsView(viewModel: diningAnalyticsViewModel)
}
.task {
guard Account.isLoggedIn, KeychainAccessible.instance.getDiningToken() != nil, let diningExpiration = UserDefaults.standard.getDiningTokenExpiration(), Date() <= diningExpiration else {
showMissingDiningTokenAlert = true
return
}
}
.alert(isPresented: $showMissingDiningTokenAlert) {
showCorrectAlert()
}
.sheet(isPresented: $showDiningLoginView) {
DiningLoginNavigationView()
.environmentObject(diningAnalyticsViewModel)
}
.navigationTitle("Dining Analytics")
.sheet(isPresented: $showSettingsSheet) {
DiningSettingsView(viewModel: diningAnalyticsViewModel) // Replace with your settings view
}
}
}

Expand Down
16 changes: 8 additions & 8 deletions PennMobile/Dining/SwiftUI/DiningLoginNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import SwiftUI
import PennMobileShared

struct DiningLoginNavigationView: View {
@Environment(\.presentationMode) var presentationMode
@Environment(\.dismiss) var dismiss
@EnvironmentObject var diningAnalyticsViewModel: DiningAnalyticsViewModel

var body: some View {
NavigationView {
DiningLoginViewSwiftUI()
DiningLoginViewSwiftUI(onDismiss: { dismiss() })
.navigationBarTitle(Text("Login"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Cancel")
})
.environmentObject(diningAnalyticsViewModel)
.navigationBarItems(trailing: Button(action: {
dismiss()
}) {
Text("Cancel")
})
.environmentObject(diningAnalyticsViewModel)
}
}
}
4 changes: 2 additions & 2 deletions PennMobile/Dining/SwiftUI/DiningLoginViewSwiftUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import WebKit
import PennMobileShared

struct DiningLoginViewSwiftUI: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentationMode
var onDismiss: () -> Void
@EnvironmentObject var diningAnalyticsViewModel: DiningAnalyticsViewModel

func makeUIViewController(context: Context) -> DiningLoginController {
Expand All @@ -32,7 +32,7 @@ struct DiningLoginViewSwiftUI: UIViewControllerRepresentable {
}

func dismissDiningLoginController() {
parent.presentationMode.wrappedValue.dismiss()
self.parent.onDismiss()
Task.init {
await DiningViewModelSwiftUI.instance.refreshBalance()
await parent.diningAnalyticsViewModel.refresh()
Expand Down

0 comments on commit b46f352

Please sign in to comment.