Skip to content

Commit e31ce5a

Browse files
Merge pull request #194 from cuappdev/omar/service-alerts
Omar/service alerts
2 parents 8ab96c7 + ac68149 commit e31ce5a

12 files changed

Lines changed: 726 additions & 239 deletions

TCAT.xcodeproj/project.pbxproj

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
222BDE16215C1CEE0040DD93 /* WhatsNewHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222BDE15215C1CEE0040DD93 /* WhatsNewHeaderView.swift */; };
1212
224EB5A5214F2F01008232C2 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224EB5A4214F2F01008232C2 /* AppShortcuts.swift */; };
1313
226C8D762173AF69009985E3 /* VersionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226C8D752173AF69009985E3 /* VersionStore.swift */; };
14-
2292486921B891A30004279C /* Endpoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2292486721B891A30004279C /* Endpoints.swift */; };
15-
2292486A21B891A30004279C /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2292486821B891A30004279C /* Models.swift */; };
14+
228F353921C71D2800556E6F /* alertResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 228F353821C71D2800556E6F /* alertResponse.json */; };
1615
2292486C21B8ECB90004279C /* ServiceAlertsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2292486B21B8ECB90004279C /* ServiceAlertsViewController.swift */; };
16+
2292486E21BB82180004279C /* ServiceAlertTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2292486D21BB82180004279C /* ServiceAlertTableViewCell.swift */; };
17+
22948BFC221B75C5003FC43F /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22948BFA221B75C5003FC43F /* Network.swift */; };
18+
22948BFD221B75C5003FC43F /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22948BFB221B75C5003FC43F /* Models.swift */; };
1719
4036E23543D87A22BF6B0023 /* Pods_TCATUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08977D7DB800C37CAF625E1 /* Pods_TCATUITests.framework */; };
1820
449A7C791D80D0E80019300C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449A7C781D80D0E80019300C /* AppDelegate.swift */; };
1921
449A7C7E1D80D0E80019300C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 449A7C7C1D80D0E80019300C /* Main.storyboard */; };
@@ -143,9 +145,11 @@
143145
222BDE15215C1CEE0040DD93 /* WhatsNewHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewHeaderView.swift; path = Views/WhatsNewHeaderView.swift; sourceTree = "<group>"; };
144146
224EB5A4214F2F01008232C2 /* AppShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppShortcuts.swift; path = Utilities/AppShortcuts.swift; sourceTree = "<group>"; };
145147
226C8D752173AF69009985E3 /* VersionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VersionStore.swift; path = Utilities/VersionStore.swift; sourceTree = "<group>"; };
146-
2292486721B891A30004279C /* Endpoints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Endpoints.swift; path = Utilities/Endpoints.swift; sourceTree = "<group>"; };
147-
2292486821B891A30004279C /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Models.swift; path = Utilities/Models.swift; sourceTree = "<group>"; };
148-
2292486B21B8ECB90004279C /* ServiceAlertsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceAlertsViewController.swift; sourceTree = "<group>"; };
148+
228F353821C71D2800556E6F /* alertResponse.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = alertResponse.json; sourceTree = "<group>"; };
149+
2292486B21B8ECB90004279C /* ServiceAlertsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ServiceAlertsViewController.swift; path = Controllers/ServiceAlertsViewController.swift; sourceTree = "<group>"; };
150+
2292486D21BB82180004279C /* ServiceAlertTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ServiceAlertTableViewCell.swift; path = Cells/ServiceAlertTableViewCell.swift; sourceTree = "<group>"; };
151+
22948BFA221B75C5003FC43F /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Network.swift; path = Utilities/Network.swift; sourceTree = "<group>"; };
152+
22948BFB221B75C5003FC43F /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Models.swift; path = Utilities/Models.swift; sourceTree = "<group>"; };
149153
449A7C751D80D0E80019300C /* TCAT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TCAT.app; sourceTree = BUILT_PRODUCTS_DIR; };
150154
449A7C781D80D0E80019300C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
151155
449A7C7D1D80D0E80019300C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -307,12 +311,20 @@
307311
name = Pods;
308312
sourceTree = "<group>";
309313
};
314+
228F353721C71CD700556E6F /* JSON Responses */ = {
315+
isa = PBXGroup;
316+
children = (
317+
228F353821C71D2800556E6F /* alertResponse.json */,
318+
);
319+
path = "JSON Responses";
320+
sourceTree = "<group>";
321+
};
310322
2292486621B891790004279C /* Network */ = {
311323
isa = PBXGroup;
312324
children = (
313-
2292486721B891A30004279C /* Endpoints.swift */,
325+
22948BFB221B75C5003FC43F /* Models.swift */,
326+
22948BFA221B75C5003FC43F /* Network.swift */,
314327
BF8AF5F22206BBF60091AB8C /* Utilities.swift */,
315-
2292486821B891A30004279C /* Models.swift */,
316328
DD3D9C201F94297100B164D4 /* Reachability.swift */,
317329
);
318330
name = Network;
@@ -351,6 +363,7 @@
351363
DD2F988E1E50D8660005F6BC /* Base.lproj */,
352364
DD2F988F1E50D89C0005F6BC /* Cells */,
353365
DD47EA501E50D77D009C68EB /* Controllers */,
366+
228F353721C71CD700556E6F /* JSON Responses */,
354367
DD2F98911E50D8C90005F6BC /* Model */,
355368
2292486621B891790004279C /* Network */,
356369
50A19BA81E4BC5BC00AD6768 /* Supporting Files */,
@@ -431,6 +444,7 @@
431444
DDB49C971E8857E000A99C35 /* BusStopTableViewCell.swift */,
432445
DDB49C951E8857E000A99C35 /* LargeDetailTableViewCell.swift */,
433446
DDC83CA41E52B0D400D4EDDF /* RouteTableViewCell.swift */,
447+
2292486D21BB82180004279C /* ServiceAlertTableViewCell.swift */,
434448
503EC37C1E969ADF00B3D4D4 /* PlaceTableViewCell.swift */,
435449
DDB49C961E8857E000A99C35 /* SmallDetailTableViewCell.swift */,
436450
);
@@ -671,6 +685,7 @@
671685
BF8460A52172A5220027FB62 /* SFProDisplay-Semibold.otf in Resources */,
672686
D94BD3042159A21800C9214E /* GoogleService-Info.plist in Resources */,
673687
BF8460A62172A5220027FB62 /* SFProDisplay-Medium.otf in Resources */,
688+
228F353921C71D2800556E6F /* alertResponse.json in Resources */,
674689
449A7C831D80D0E80019300C /* LaunchScreen.storyboard in Resources */,
675690
BF8460AF2172B0F80027FB62 /* SFUIText-Regular.otf in Resources */,
676691
BF8460AC2172B0F80027FB62 /* SFUIText-Semibold.otf in Resources */,
@@ -885,7 +900,6 @@
885900
DD2F989B1E50F99A0005F6BC /* Route.swift in Sources */,
886901
DD87F20A1E878CB5000C244B /* Loader.swift in Sources */,
887902
DD3D9C341F942CF000B164D4 /* CoordinateVisitor.swift in Sources */,
888-
2292486A21B891A30004279C /* Models.swift in Sources */,
889903
DD3D9C211F94297100B164D4 /* Reachability.swift in Sources */,
890904
DDBFEAA11E787008002BBD96 /* RouteSelectionView.swift in Sources */,
891905
222BDE16215C1CEE0040DD93 /* WhatsNewHeaderView.swift in Sources */,
@@ -898,6 +912,7 @@
898912
D94BD30121599E3200C9214E /* EventPayload.swift in Sources */,
899913
DDE3BC231F5863D50014D2BB /* Place.swift in Sources */,
900914
BF6095271FD7091B0013E538 /* CustomNavigationController.swift in Sources */,
915+
22948BFC221B75C5003FC43F /* Network.swift in Sources */,
901916
BF5CBAD11FCE6CCE00478C6F /* RouteDetail+DrawerViewController.swift in Sources */,
902917
DDB49C991E8857E000A99C35 /* SmallDetailTableViewCell.swift in Sources */,
903918
BF5C52A4207B96F0003C93B9 /* LiveIndicator.swift in Sources */,
@@ -917,9 +932,11 @@
917932
BF456E0320489990005C611D /* BusLocation.swift in Sources */,
918933
CC27BD091FF6C91C00DAA7D5 /* Analytics.swift in Sources */,
919934
DD36A7021F6637CA00E4789E /* Direction.swift in Sources */,
935+
22948BFD221B75C5003FC43F /* Models.swift in Sources */,
920936
BF84609E217139A30027FB62 /* Styles.swift in Sources */,
921937
DDB49C941E8857D000A99C35 /* RouteDetail+ContentViewController.swift in Sources */,
922938
BFD5B4E41FAADFAE00955C37 /* WalkPath.swift in Sources */,
939+
2292486E21BB82180004279C /* ServiceAlertTableViewCell.swift in Sources */,
923940
BF1209FB1F7759C200A7C930 /* BusLocationView.swift in Sources */,
924941
2292486C21B8ECB90004279C /* ServiceAlertsViewController.swift in Sources */,
925942
BF164D5F221DFCDE0069953E /* LegacyModels.swift in Sources */,

TCAT/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
108108
}
109109

110110
func applicationWillEnterForeground(_ application: UIApplication) {
111-
// Called as part of the transition from the background to the inactive state; here y/Users/mattbarker016ou can undo many of the changes made on entering the background.
111+
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
112112
}
113113

114114
func applicationDidBecomeActive(_ application: UIApplication) {
@@ -265,7 +265,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
265265
self.userDefaults.set(encodedObject, forKey: Constants.UserDefaults.allBusStops)
266266
}
267267
}, failure: { error in
268-
print("getBusStops error:", error)
268+
print("getBusStops error:", error.localizedDescription)
269269
self.handleGetAllStopsError()
270270
})
271271
}
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//
2+
// ServiceAlertTableViewCell.swift
3+
// TCAT
4+
//
5+
// Created by Omar Rasheed on 12/7/18.
6+
// Copyright © 2018 cuappdev. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import SnapKit
11+
12+
class ServiceAlertTableViewCell: UITableViewCell {
13+
14+
static let identifier: String = "serviceAlertCell"
15+
private let fileName: String = "serviceAlertTableViewCell"
16+
var alert: Alert?
17+
var rowNum: Int!
18+
19+
let borderInset = 16
20+
let busIconSpacing = 10
21+
var maxIconsPerRow: Int {
22+
let iconWidth = Int(BusIconType.directionSmall.width)
23+
let screenWidth = Int(UIScreen.main.bounds.width)
24+
let totalConstraintInset = borderInset * 2
25+
26+
return (screenWidth - totalConstraintInset + busIconSpacing) / (iconWidth + busIconSpacing)
27+
}
28+
29+
var timeSpanLabel: UILabel!
30+
var descriptionLabel: UILabel!
31+
var affectedRoutesLabel: UILabel!
32+
var affectedRoutesStackView: UIStackView?
33+
var topSeparator: UIView?
34+
35+
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
36+
super.init(style: style, reuseIdentifier: reuseIdentifier)
37+
38+
setupTimeSpanLabel()
39+
setupDescriptionLabel()
40+
}
41+
42+
func setData() {
43+
if let fromDate = alert?.fromDate, let toDate = alert?.toDate {
44+
timeSpanLabel.text = formatTimeString(fromDate, toDate: toDate)
45+
}
46+
47+
descriptionLabel.text = alert?.message
48+
if let routes = alert?.routes, !routes.isEmpty {
49+
setupAffectedRoutesStackView()
50+
setupaffectedRoutesLabel()
51+
}
52+
53+
if rowNum > 0 {
54+
setupTopSeparator()
55+
}
56+
}
57+
58+
private func setupTimeSpanLabel() {
59+
60+
timeSpanLabel = UILabel()
61+
timeSpanLabel.numberOfLines = 0
62+
timeSpanLabel.font = .getFont(.semibold, size: 18)
63+
timeSpanLabel.textColor = Colors.primaryText
64+
65+
contentView.addSubview(timeSpanLabel)
66+
}
67+
68+
private func setupDescriptionLabel() {
69+
70+
descriptionLabel = UILabel()
71+
descriptionLabel.numberOfLines = 0
72+
descriptionLabel.font = .getFont(.regular, size: 14)
73+
descriptionLabel.textColor = Colors.primaryText
74+
75+
contentView.addSubview(descriptionLabel)
76+
}
77+
78+
private func setupaffectedRoutesLabel() {
79+
80+
affectedRoutesLabel = UILabel()
81+
affectedRoutesLabel.font = .getFont(.semibold, size: 18)
82+
affectedRoutesLabel.textColor = Colors.primaryText
83+
affectedRoutesLabel.text = Constants.General.affectedRoutes
84+
85+
contentView.addSubview(affectedRoutesLabel)
86+
}
87+
88+
private func setupAffectedRoutesStackView() {
89+
90+
if var routes = alert?.routes, !routes.isEmpty {
91+
affectedRoutesStackView = UIStackView()
92+
for _ in 0..<rowCount() {
93+
var subviews = [BusIcon]()
94+
for _ in 0..<maxIconsPerRow {
95+
if !routes.isEmpty {
96+
let route = routes.removeFirst()
97+
subviews.append(BusIcon(type: .directionSmall, number: route))
98+
}
99+
}
100+
let rowStackView = UIStackView(arrangedSubviews: subviews)
101+
rowStackView.axis = .horizontal
102+
rowStackView.spacing = 10
103+
rowStackView.alignment = .leading
104+
affectedRoutesStackView?.addArrangedSubview(rowStackView)
105+
}
106+
}
107+
108+
guard let stackView = affectedRoutesStackView else { return }
109+
stackView.axis = .vertical
110+
stackView.alignment = .top
111+
stackView.spacing = 10
112+
contentView.addSubview(stackView)
113+
}
114+
115+
private func setupTopSeparator() {
116+
117+
topSeparator = UIView()
118+
topSeparator?.backgroundColor = Colors.backgroundWash
119+
120+
contentView.addSubview(topSeparator!)
121+
}
122+
123+
func descriptionLabelConstraints(topConstraint: UIView) {
124+
descriptionLabel.snp.remakeConstraints { (make) in
125+
if topConstraint == contentView {
126+
make.top.equalToSuperview().inset(borderInset)
127+
} else {
128+
make.top.equalTo(topConstraint.snp.bottom).offset(12)
129+
}
130+
make.leading.trailing.equalToSuperview().inset(borderInset)
131+
if let text = descriptionLabel.text {
132+
let width = contentView.frame.width - (CGFloat)(2 * borderInset)
133+
let heightValue = ceil(text.heightWithConstrainedWidth(width: width, font: descriptionLabel.font))
134+
make.height.equalTo(ceil(heightValue))
135+
} else {
136+
make.height.equalTo(descriptionLabel.intrinsicContentSize.height)
137+
}
138+
}
139+
}
140+
141+
func timeSpanLabelConstraints(topConstraint: UIView) {
142+
timeSpanLabel.snp.remakeConstraints { (make) in
143+
if topConstraint == contentView {
144+
make.top.equalToSuperview().inset(borderInset)
145+
} else {
146+
make.top.equalTo(topConstraint.snp.bottom).offset(12)
147+
}
148+
make.leading.trailing.equalToSuperview().inset(borderInset)
149+
make.height.equalTo(timeSpanLabel.intrinsicContentSize.height)
150+
}
151+
}
152+
153+
func topSeparatorConstraints() {
154+
if let topSeparator = topSeparator {
155+
topSeparator.snp.remakeConstraints { (make) in
156+
make.top.leading.trailing.equalToSuperview()
157+
make.height.equalTo(8)
158+
}
159+
}
160+
}
161+
162+
override func updateConstraints() {
163+
if let topSeparator = topSeparator, timeSpanLabel.isDescendant(of: contentView) {
164+
// Both topSeparator and timeSpanLabel exist
165+
topSeparatorConstraints()
166+
timeSpanLabelConstraints(topConstraint: topSeparator)
167+
descriptionLabelConstraints(topConstraint: timeSpanLabel)
168+
} else if timeSpanLabel.isDescendant(of: contentView) {
169+
// Only timeSpanLabel, no topSeparator
170+
timeSpanLabelConstraints(topConstraint: contentView)
171+
descriptionLabelConstraints(topConstraint: timeSpanLabel)
172+
} else if let topSeparator = topSeparator {
173+
// Only topSeparator, no timeSpanLabel
174+
topSeparatorConstraints()
175+
descriptionLabelConstraints(topConstraint: topSeparator)
176+
} else {
177+
// Neither
178+
descriptionLabelConstraints(topConstraint: contentView)
179+
}
180+
181+
if let stackView = affectedRoutesStackView {
182+
// When both a separator view and stackView are required
183+
affectedRoutesLabel.snp.remakeConstraints { (make) in
184+
make.leading.equalTo(descriptionLabel)
185+
make.top.equalTo(descriptionLabel.snp.bottom).offset(24)
186+
make.width.equalTo(affectedRoutesLabel.intrinsicContentSize.width)
187+
make.height.equalTo(affectedRoutesLabel.intrinsicContentSize.height)
188+
}
189+
stackView.snp.remakeConstraints { (make) in
190+
make.top.equalTo(affectedRoutesLabel.snp.bottom).offset(8)
191+
make.leading.equalTo(descriptionLabel)
192+
make.trailing.bottom.equalToSuperview().inset(borderInset)
193+
}
194+
} else {
195+
descriptionLabel.snp.makeConstraints { (make) in
196+
make.bottom.equalToSuperview().inset(borderInset)
197+
}
198+
}
199+
super.updateConstraints()
200+
}
201+
202+
private func formatTimeString(_ fromDate: String, toDate: String) -> String {
203+
204+
let newformatter = DateFormatter()
205+
newformatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sZZZZ"
206+
newformatter.locale = Locale(identifier: "en_US_POSIX")
207+
208+
let fromDate = newformatter.date(from: fromDate)
209+
let toDate = newformatter.date(from: toDate)
210+
211+
let formatter = DateFormatter()
212+
formatter.dateFormat = "EEEE M/d"
213+
214+
if let unWrappedFromDate = fromDate, let unWrappedToDate = toDate {
215+
let formattedFromDate = formatter.string(from: unWrappedFromDate)
216+
let formattedToDate = formatter.string(from: unWrappedToDate)
217+
218+
return "\(formattedFromDate) - \(formattedToDate)"
219+
}
220+
221+
timeSpanLabel.removeFromSuperview()
222+
return "Time: Unknown"
223+
}
224+
225+
private func rowCount() -> Int {
226+
guard let routes = alert?.routes else { return 0 }
227+
if routes.count > maxIconsPerRow {
228+
let addExtra = routes.count % maxIconsPerRow > 0 ? 1 : 0
229+
let rowCount = routes.count / maxIconsPerRow
230+
231+
return rowCount + addExtra
232+
} else {
233+
return 1
234+
}
235+
}
236+
237+
private func getDayOfWeek(_ today:Date) -> Int? {
238+
let myCalendar = Calendar(identifier: .gregorian)
239+
let weekDay = myCalendar.component(.weekday, from: today)
240+
return weekDay
241+
}
242+
243+
override func prepareForReuse() {
244+
if let stackView = affectedRoutesStackView {
245+
stackView.removeFromSuperview()
246+
}
247+
248+
affectedRoutesStackView = nil
249+
250+
if let routesLabel = affectedRoutesLabel {
251+
routesLabel.removeFromSuperview()
252+
}
253+
254+
affectedRoutesLabel = nil
255+
}
256+
required init?(coder aDecoder: NSCoder) {
257+
fatalError("init(coder:) has not been implemented")
258+
}
259+
}
260+
261+

0 commit comments

Comments
 (0)