Skip to content

Commit ea6e24e

Browse files
feat: Program Screen Error Handling (openedx#448)
1 parent b2539a6 commit ea6e24e

File tree

11 files changed

+321
-142
lines changed

11 files changed

+321
-142
lines changed

Core/Core.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141F1D2F2B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift */; };
145145
142EDD6C2B831D1400F9F320 /* BranchSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 142EDD6B2B831D1400F9F320 /* BranchSDK */; };
146146
14769D3C2B9822EE00AB36D4 /* CoreAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14769D3B2B9822EE00AB36D4 /* CoreAnalytics.swift */; };
147+
9784D47E2BF7762800AFEFFF /* FullScreenErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */; };
147148
A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; };
148149
A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; };
149150
A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; };
@@ -339,6 +340,7 @@
339340
349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Core.framework; sourceTree = BUILT_PRODUCTS_DIR; };
340341
3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = "<group>"; };
341342
60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = "<group>"; };
343+
9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullScreenErrorView.swift; sourceTree = "<group>"; };
342344
9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = "<group>"; };
343345
A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentConfig.swift; sourceTree = "<group>"; };
344346
A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = "<group>"; };
@@ -715,6 +717,7 @@
715717
0770DE7728D0C49E006D8A5D /* Base */ = {
716718
isa = PBXGroup;
717719
children = (
720+
9784D47C2BF7761F00AFEFFF /* FullScreenErrorView */,
718721
064987882B4D69FE0071642A /* Webview */,
719722
E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */,
720723
02A4833B29B8C57800D33F33 /* DownloadView.swift */,
@@ -766,6 +769,14 @@
766769
path = Analytics;
767770
sourceTree = "<group>";
768771
};
772+
9784D47C2BF7761F00AFEFFF /* FullScreenErrorView */ = {
773+
isa = PBXGroup;
774+
children = (
775+
9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */,
776+
);
777+
path = FullScreenErrorView;
778+
sourceTree = "<group>";
779+
};
769780
BA30427C2B20B235009B64B7 /* SocialAuth */ = {
770781
isa = PBXGroup;
771782
children = (
@@ -1124,6 +1135,7 @@
11241135
0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */,
11251136
02A4833A29B8A9AB00D33F33 /* DownloadManager.swift in Sources */,
11261137
06078B702BA49C3100576798 /* Dictionary+JSON.swift in Sources */,
1138+
9784D47E2BF7762800AFEFFF /* FullScreenErrorView.swift in Sources */,
11271139
027BD3AE2909475000392132 /* KeyboardScrollerOptions.swift in Sources */,
11281140
BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */,
11291141
141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// FullScreenErrorView.swift
3+
// Course
4+
//
5+
// Created by Shafqat Muneer on 5/14/24.
6+
//
7+
8+
import SwiftUI
9+
import Theme
10+
11+
public struct FullScreenErrorView: View {
12+
13+
public enum ErrorType {
14+
case noInternet
15+
case noInternetWithReload
16+
case generic
17+
}
18+
19+
private let errorType: ErrorType
20+
private var action: () -> Void = {}
21+
22+
public init(
23+
type: ErrorType
24+
) {
25+
self.errorType = type
26+
}
27+
28+
public init(
29+
type: ErrorType,
30+
action: @escaping () -> Void
31+
) {
32+
self.errorType = type
33+
self.action = action
34+
}
35+
36+
public var body: some View {
37+
GeometryReader { proxy in
38+
VStack(spacing: 28) {
39+
Spacer()
40+
switch errorType {
41+
case .noInternet, .noInternetWithReload:
42+
CoreAssets.noWifi.swiftUIImage
43+
.renderingMode(.template)
44+
.foregroundStyle(Color.primary)
45+
.scaledToFit()
46+
47+
Text(CoreLocalization.Error.Internet.noInternetTitle)
48+
.font(Theme.Fonts.titleLarge)
49+
.foregroundColor(Theme.Colors.textPrimary)
50+
51+
Text(CoreLocalization.Error.Internet.noInternetDescription)
52+
.font(Theme.Fonts.bodyLarge)
53+
.foregroundColor(Theme.Colors.textPrimary)
54+
.multilineTextAlignment(.center)
55+
.padding(.horizontal, 50)
56+
case .generic:
57+
CoreAssets.notAvaliable.swiftUIImage
58+
.renderingMode(.template)
59+
.foregroundStyle(Color.primary)
60+
.scaledToFit()
61+
62+
Text(CoreLocalization.View.Snackbar.tryAgainBtn)
63+
.font(Theme.Fonts.titleLarge)
64+
.foregroundColor(Theme.Colors.textPrimary)
65+
66+
Text(CoreLocalization.Error.unknownError)
67+
.font(Theme.Fonts.bodyLarge)
68+
.foregroundColor(Theme.Colors.textPrimary)
69+
.multilineTextAlignment(.center)
70+
.padding(.horizontal, 50)
71+
}
72+
73+
if errorType != .noInternet {
74+
UnitButtonView(
75+
type: .reload,
76+
action: {
77+
self.action()
78+
}
79+
)
80+
}
81+
Spacer()
82+
}
83+
.frame(maxWidth: .infinity, maxHeight: proxy.size.height)
84+
.background(
85+
Theme.Colors.background
86+
)
87+
}
88+
}
89+
}
90+
91+
#if DEBUG
92+
struct FullScreenErrorView_Previews: PreviewProvider {
93+
static var previews: some View {
94+
FullScreenErrorView(type: .noInternetWithReload)
95+
}
96+
}
97+
#endif

Core/Core/View/Base/Webview/WebView.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public protocol WebViewNavigationDelegate: AnyObject {
1717
shouldLoad request: URLRequest,
1818
navigationAction: WKNavigationAction
1919
) async -> Bool
20+
21+
func showWebViewError()
2022
}
2123

2224
public struct WebView: UIViewRepresentable {
@@ -39,17 +41,20 @@ public struct WebView: UIViewRepresentable {
3941
var webViewNavDelegate: WebViewNavigationDelegate?
4042

4143
var refreshCookies: () async -> Void
44+
var webViewType: String?
4245

4346
public init(
4447
viewModel: ViewModel,
4548
isLoading: Binding<Bool>,
4649
refreshCookies: @escaping () async -> Void,
47-
navigationDelegate: WebViewNavigationDelegate? = nil
50+
navigationDelegate: WebViewNavigationDelegate? = nil,
51+
webViewType: String? = nil
4852
) {
4953
self.viewModel = viewModel
5054
self._isLoading = isLoading
5155
self.refreshCookies = refreshCookies
5256
self.webViewNavDelegate = navigationDelegate
57+
self.webViewType = webViewType
5358
}
5459

5560
public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {
@@ -70,6 +75,10 @@ public struct WebView: UIViewRepresentable {
7075

7176
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
7277
webView.isHidden = false
78+
DispatchQueue.main.async {
79+
self.parent.isLoading = false
80+
self.parent.webViewNavDelegate?.showWebViewError()
81+
}
7382
}
7483

7584
public func webView(
@@ -78,6 +87,10 @@ public struct WebView: UIViewRepresentable {
7887
withError error: Error
7988
) {
8089
webView.isHidden = false
90+
DispatchQueue.main.async {
91+
self.parent.isLoading = false
92+
self.parent.webViewNavDelegate?.showWebViewError()
93+
}
8194
}
8295

8396
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
@@ -172,7 +185,7 @@ public struct WebView: UIViewRepresentable {
172185

173186
private func addObservers() {
174187
cancellables.removeAll()
175-
NotificationCenter.default.publisher(for: .webviewReloadNotification, object: nil)
188+
NotificationCenter.default.publisher(for: Notification.Name(parent.webViewType ?? ""), object: nil)
176189
.sink { [weak self] _ in
177190
self?.reload()
178191
}
@@ -188,8 +201,16 @@ public struct WebView: UIViewRepresentable {
188201
fileprivate var webview: WKWebView?
189202

190203
@objc private func reload() {
191-
parent.isLoading = true
192-
webview?.reload()
204+
DispatchQueue.main.async {
205+
self.parent.isLoading = true
206+
}
207+
if webview?.url?.absoluteString.isEmpty ?? true,
208+
let url = URL(string: parent.viewModel.url) {
209+
let request = URLRequest(url: url)
210+
webview?.load(request)
211+
} else {
212+
webview?.reload()
213+
}
193214
}
194215

195216
public func userContentController(

Course/Course.xcodeproj/project.pbxproj

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@
314314
02B6B3B528E1D10700232911 /* Domain */,
315315
02EAE2CA28E1F0A700529644 /* Presentation */,
316316
97EA4D822B84EFA900663F58 /* Managers */,
317-
97CA95212B875EA200A9EDEA /* Views */,
318317
02B6B3B428E1C49400232911 /* Localizable.strings */,
319318
02C355372C08DCD700501342 /* Localizable.stringsdict */,
320319
);
@@ -529,13 +528,20 @@
529528
path = Mock;
530529
sourceTree = "<group>";
531530
};
532-
97CA95212B875EA200A9EDEA /* Views */ = {
531+
9784D4752BF39EEF00AFEFFF /* CalendarSyncProgressView */ = {
533532
isa = PBXGroup;
534533
children = (
535534
97C99C352B9A08FE004EEDE2 /* CalendarSyncProgressView.swift */,
535+
);
536+
path = CalendarSyncProgressView;
537+
sourceTree = "<group>";
538+
};
539+
9784D4762BF39EFD00AFEFFF /* DatesSuccessView */ = {
540+
isa = PBXGroup;
541+
children = (
536542
97CA95242B875EE200A9EDEA /* DatesSuccessView.swift */,
537543
);
538-
path = Views;
544+
path = DatesSuccessView;
539545
sourceTree = "<group>";
540546
};
541547
97EA4D822B84EFA900663F58 /* Managers */ = {
@@ -586,6 +592,8 @@
586592
BAD9CA482B2C88D500DE790A /* Subviews */ = {
587593
isa = PBXGroup;
588594
children = (
595+
9784D4762BF39EFD00AFEFFF /* DatesSuccessView */,
596+
9784D4752BF39EEF00AFEFFF /* CalendarSyncProgressView */,
589597
02D4FC2C2BBD7C7500C47748 /* MessageSectionView */,
590598
BAC0E0D92B32F0A2006B68A9 /* CourseVideoDownloadBarView */,
591599
BA58CF622B471047005B102E /* VideoDownloadQualityBarView */,

Course/Course/Presentation/Unit/CourseUnitView.swift

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public struct CourseUnitView: View {
189189
Spacer(minLength: 150)
190190
}
191191
} else {
192-
NoInternetView()
192+
FullScreenErrorView(type: .noInternet)
193193
}
194194

195195
} else {
@@ -219,7 +219,7 @@ public struct CourseUnitView: View {
219219
Spacer(minLength: 150)
220220
}
221221
} else {
222-
NoInternetView()
222+
FullScreenErrorView(type: .noInternet)
223223
}
224224
}
225225
// MARK: Web
@@ -233,7 +233,7 @@ public struct CourseUnitView: View {
233233
)
234234
// not need to add frame limit there because we did that with injection
235235
} else {
236-
NoInternetView()
236+
FullScreenErrorView(type: .noInternet)
237237
}
238238
} else {
239239
EmptyView()
@@ -247,7 +247,7 @@ public struct CourseUnitView: View {
247247
Spacer()
248248
.frame(minHeight: 100)
249249
} else {
250-
NoInternetView()
250+
FullScreenErrorView(type: .noInternet)
251251
}
252252
} else {
253253
EmptyView()
@@ -275,7 +275,7 @@ public struct CourseUnitView: View {
275275
//No need iPad paddings there bacause they were added
276276
//to PostsView that placed inside DiscussionView
277277
} else {
278-
NoInternetView()
278+
FullScreenErrorView(type: .noInternet)
279279
}
280280
} else {
281281
EmptyView()
@@ -586,25 +586,3 @@ struct CourseUnitView_Previews: PreviewProvider {
586586
}
587587
//swiftlint:enable all
588588
#endif
589-
590-
struct NoInternetView: View {
591-
592-
var body: some View {
593-
VStack(spacing: 28) {
594-
Spacer()
595-
CoreAssets.noWifi.swiftUIImage
596-
.renderingMode(.template)
597-
.foregroundStyle(Color.primary)
598-
.scaledToFit()
599-
Text(CoreLocalization.Error.Internet.noInternetTitle)
600-
.font(Theme.Fonts.titleLarge)
601-
.foregroundColor(Theme.Colors.textPrimary)
602-
Text(CoreLocalization.Error.Internet.noInternetDescription)
603-
.font(Theme.Fonts.bodyLarge)
604-
.foregroundColor(Theme.Colors.textPrimary)
605-
.multilineTextAlignment(.center)
606-
.padding(.horizontal, 50)
607-
Spacer()
608-
}.frame(maxWidth: .infinity, maxHeight: .infinity)
609-
}
610-
}

0 commit comments

Comments
 (0)