Skip to content

Commit

Permalink
add better error handling, marquee text, and fied SSL networking
Browse files Browse the repository at this point in the history
  • Loading branch information
bwees committed Aug 3, 2024
1 parent 051f56c commit 311ae1d
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 67 deletions.
27 changes: 23 additions & 4 deletions targets/watch/APIManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,28 @@ enum NetworkError: Error {
case invalidResponse
}

class SSLBypassDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
// Allow any certificate
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}

class APIManager: ObservableObject {
@Published var baseData: GetBaseDataResponse?
@Published var error: Error?


private var authKey: String = ""
var cancellables = Set<AnyCancellable>()
let session = URLSession(configuration: .default, delegate: SSLBypassDelegate(), delegateQueue: nil)


func fetchData() {
getAuthentication()
Expand Down Expand Up @@ -49,7 +64,9 @@ class APIManager: ObservableObject {
request.httpMethod = "GET"
request.httpShouldHandleCookies = false

return URLSession.shared.dataTaskPublisher(for: request)


return session.dataTaskPublisher(for: request)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
Expand All @@ -76,14 +93,15 @@ class APIManager: ObservableObject {
request.httpMethod = "POST"
request.addValue(auth, forHTTPHeaderField: "cookie")

return URLSession.shared.dataTaskPublisher(for: request)
return session.dataTaskPublisher(for: request)
.map(\.data)
.decode(type: GetBaseDataResponse.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}

func getPatternPaths(routeKeys: [String]) -> AnyPublisher<[GetPatternPathsResponse], Error> {

let bodyData = routeKeys.map { "routeKeys%5B%5D=\($0)" }.joined(separator: "&")
guard let url = URL(string: "https://aggiespirit.ts.tamu.edu/RouteMap/GetPatternPaths") else {
return Fail(error: NetworkError.invalidURL).eraseToAnyPublisher()
Expand All @@ -96,7 +114,7 @@ class APIManager: ObservableObject {

request.httpBody = bodyData.data(using: .utf8)

return URLSession.shared.dataTaskPublisher(for: request)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
Expand All @@ -109,6 +127,7 @@ class APIManager: ObservableObject {
}

func getNextDepartureTimes(routeId: String, directionIds: [String], stopCode: String) -> AnyPublisher<GetNextDepartTimesResponse, Error> {

var bodyData = [String]()
for (i, directionId) in directionIds.enumerated() {
let directionData = "routeDirectionKeys[\(i)][routeKey]=\(routeId)&routeDirectionKeys[\(i)][directionKey]=\(directionId)&stopCode=\(stopCode)"
Expand All @@ -126,7 +145,7 @@ class APIManager: ObservableObject {
request.setValue("application/x-www-form-urlencoded; charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.httpBody = bodyString.data(using: .utf8)

return URLSession.shared.dataTaskPublisher(for: request)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
Expand Down
2 changes: 1 addition & 1 deletion targets/watch/APITypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct PatternPath: Codable {
let patternKey: String
let directionKey: String
let patternPoints: [PatternPoint]
let segmentPaths: [String] // Always blank... leaving as Any for now
// let segmentPaths: [Any] // Always blank... leaving as Any for now
}

struct MapRoute: Codable {
Expand Down
26 changes: 26 additions & 0 deletions targets/watch/ErrorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ErrorView.swift
// ReveilleRides
//
// Created by Brandon Wees on 8/2/24.
//

import SwiftUI

struct ErrorView: View {
var text: String

var body: some View {
VStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.gray)
Text(text)
.multilineTextAlignment(.center)
.foregroundStyle(.gray)
}
}
}

#Preview {
ErrorView(text: "There was an error")
}
10 changes: 8 additions & 2 deletions targets/watch/Info.plist
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
140 changes: 140 additions & 0 deletions targets/watch/Marquee_Main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import SwiftUI
import Combine
import Foundation

public struct MarqueeText : View {
public var text: String
public var font: UIFont
public var leftFade: CGFloat
public var rightFade: CGFloat
public var startDelay: Double
public var alignment: Alignment

@State private var animate = false
var isCompact = false

public var body : some View {
let stringWidth = text.widthOfString(usingFont: font)
let stringHeight = text.heightOfString(usingFont: font)

let animation = Animation
.linear(duration: Double(stringWidth) / 30)
.delay(startDelay)
.repeatForever(autoreverses: false)

let nullAnimation = Animation
.linear(duration: 0)

return ZStack {
GeometryReader { geo in
if stringWidth > geo.size.width { // don't use self.animate as conditional here
Group {
Text(self.text)
.lineLimit(1)
.font(.init(font))
.offset(x: self.animate ? -stringWidth - stringHeight * 2 : 0)
.animation(self.animate ? animation : nullAnimation, value: self.animate)
.onAppear {
DispatchQueue.main.async {
self.animate = geo.size.width < stringWidth
}
}
.fixedSize(horizontal: true, vertical: false)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)

Text(self.text)
.lineLimit(1)
.font(.init(font))
.offset(x: self.animate ? 0 : stringWidth + stringHeight * 2)
.animation(self.animate ? animation : nullAnimation, value: self.animate)
.onAppear {
DispatchQueue.main.async {
self.animate = geo.size.width < stringWidth
}
}
.fixedSize(horizontal: true, vertical: false)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
}
.onValueChanged(of: self.text, perform: {text in
self.animate = geo.size.width < stringWidth
})

.offset(x: leftFade)
.mask(
HStack(spacing:0) {
Rectangle()
.frame(width:2)
.opacity(0)
LinearGradient(gradient: Gradient(colors: [Color.black.opacity(0), Color.black]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/)
.frame(width:leftFade)
LinearGradient(gradient: Gradient(colors: [Color.black, Color.black]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/)
LinearGradient(gradient: Gradient(colors: [Color.black, Color.black.opacity(0)]), startPoint: /*@START_MENU_TOKEN@*/.leading/*@END_MENU_TOKEN@*/, endPoint: /*@START_MENU_TOKEN@*/.trailing/*@END_MENU_TOKEN@*/)
.frame(width:rightFade)
Rectangle()
.frame(width:2)
.opacity(0)
})
.frame(width: geo.size.width + leftFade)
.offset(x: leftFade * -1)
} else {
Text(self.text)
.font(.init(font))
.onValueChanged(of: self.text, perform: {text in
self.animate = geo.size.width < stringWidth
})
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: alignment)
}
}
}
.frame(height: stringHeight)
.frame(maxWidth: isCompact ? stringWidth : nil)
.onDisappear {
self.animate = false
}
}

public init(text: String, font: UIFont, leftFade: CGFloat, rightFade: CGFloat, startDelay: Double, alignment: Alignment? = nil) {
self.text = text
self.font = font
self.leftFade = leftFade
self.rightFade = rightFade
self.startDelay = startDelay
self.alignment = alignment != nil ? alignment! : .topLeading
}
}

extension MarqueeText {
public func makeCompact(_ compact: Bool = true) -> Self {
var view = self
view.isCompact = compact
return view
}
}

extension String {

func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}

func heightOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.height
}
}

extension View {
/// A backwards compatible wrapper for iOS 14 `onChange`
@ViewBuilder func onValueChanged<T: Equatable>(of value: T, perform onChange: @escaping (T) -> Void) -> some View {
if #available(iOS 14.0, *) {
self.onChange(of: value, perform: onChange)
} else {
self.onReceive(Just(value)) { (value) in
onChange(value)
}
}
}
}
77 changes: 44 additions & 33 deletions targets/watch/RouteCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,56 @@ struct RouteCell: View {
var color: Color
var subtitle: String

@State var showNextLine = false
@State var rowHeight: CGFloat = 84

let titleFont = UIFont.preferredFont(forTextStyle: .headline)

var body: some View {
HStack {
VStack {
HStack {
VStack {
HStack(alignment: .center) {
Text(number)
.frame(height: 22)
.padding([.horizontal], 6)
.font(.system(size: 16).bold())
.minimumScaleFactor(0.1)
.lineLimit(1)
.background(color)
.clipShape(.rect(cornerSize: CGSize(width: 8, height: 8)))

MarqueeText(
text: name,
font: titleFont,
leftFade: 8,
rightFade: 8,
startDelay: 1
)
.padding([.leading], 2)
}

if subtitle != "" {
HStack {
Text(number)
.padding([.vertical], 2)
.padding([.horizontal], 6)
.font(.system(size: 16).bold())
.minimumScaleFactor(0.1)
.lineLimit(1)
.background(color)
.clipShape(.rect(cornerSize: CGSize(width: 8, height: 8)))
Text(name)
.font(.headline)
.frame(alignment: .leading)
.lineLimit(1)
MarqueeText(
text: subtitle,
font: UIFont.systemFont(ofSize: 12),
leftFade: 8,
rightFade: 8,
startDelay: 2
)
.padding([.leading], 4)
Spacer()
}
if subtitle != "" {
HStack {
Text(subtitle)
.font(.system(size: 12))
.frame(alignment: .leading)
.foregroundStyle(.secondary)
.lineLimit(1)

Spacer()
}
}
}
.padding([.leading], 4)

Spacer()

Image(systemName: "chevron.right")
.foregroundStyle(.tertiary)
}
.padding([.vertical], 8)

// .padding([.leading], 4)

Spacer()

Image(systemName: "chevron.right")
.foregroundStyle(.tertiary)
}
.padding([.vertical], 8)
}
}

Expand Down
Loading

0 comments on commit 311ae1d

Please sign in to comment.