Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Program Screen Error Handling #448

Merged
merged 7 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 12 additions & 0 deletions Core/Core.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141F1D2F2B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift */; };
142EDD6C2B831D1400F9F320 /* BranchSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 142EDD6B2B831D1400F9F320 /* BranchSDK */; };
14769D3C2B9822EE00AB36D4 /* CoreAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14769D3B2B9822EE00AB36D4 /* CoreAnalytics.swift */; };
9784D47E2BF7762800AFEFFF /* FullScreenErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */; };
A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; };
A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; };
A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; };
Expand Down Expand Up @@ -340,6 +341,7 @@
349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App_Core.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
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>"; };
9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullScreenErrorView.swift; sourceTree = "<group>"; };
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>"; };
A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentConfig.swift; sourceTree = "<group>"; };
A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -717,6 +719,7 @@
0770DE7728D0C49E006D8A5D /* Base */ = {
isa = PBXGroup;
children = (
9784D47C2BF7761F00AFEFFF /* FullScreenErrorView */,
064987882B4D69FE0071642A /* Webview */,
E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */,
02A4833B29B8C57800D33F33 /* DownloadView.swift */,
Expand Down Expand Up @@ -769,6 +772,14 @@
path = Analytics;
sourceTree = "<group>";
};
9784D47C2BF7761F00AFEFFF /* FullScreenErrorView */ = {
isa = PBXGroup;
children = (
9784D47D2BF7762800AFEFFF /* FullScreenErrorView.swift */,
);
path = FullScreenErrorView;
sourceTree = "<group>";
};
BA30427C2B20B235009B64B7 /* SocialAuth */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1127,6 +1138,7 @@
0260E58028FD792800BBBE18 /* WebUnitViewModel.swift in Sources */,
02A4833A29B8A9AB00D33F33 /* DownloadManager.swift in Sources */,
06078B702BA49C3100576798 /* Dictionary+JSON.swift in Sources */,
9784D47E2BF7762800AFEFFF /* FullScreenErrorView.swift in Sources */,
027BD3AE2909475000392132 /* KeyboardScrollerOptions.swift in Sources */,
BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */,
141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */,
Expand Down
1 change: 0 additions & 1 deletion Core/Core/Extensions/Notification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public extension Notification.Name {
static let onActualVersionReceived = Notification.Name("onActualVersionReceived")
static let onAppUpgradeAccountSettingsTapped = Notification.Name("onAppUpgradeAccountSettingsTapped")
static let onNewVersionAvaliable = Notification.Name("onNewVersionAvaliable")
static let webviewReloadNotification = Notification.Name("webviewReloadNotification")
static let onBlockCompletion = Notification.Name.init("onBlockCompletion")
static let shiftCourseDates = Notification.Name("shiftCourseDates")
static let profileUpdated = Notification.Name("profileUpdated")
Expand Down
94 changes: 94 additions & 0 deletions Core/Core/View/Base/FullScreenErrorView/FullScreenErrorView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// FullScreenErrorView.swift
// Course
//
// Created by Shafqat Muneer on 5/14/24.
//

import SwiftUI
import Theme

public struct FullScreenErrorView: View {

public enum ErrorType {
case noInternet
case noInternetWithReload
case generic
}

private let errorType: ErrorType
private var reloadAction: () -> Void = {}

public init(
type: ErrorType
) {
self.errorType = type
}

public init(
type: ErrorType,
reloadAction: @escaping () -> Void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we generalize it for the future and call it just action?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's updated with a more generic name.

) {
self.errorType = type
self.reloadAction = reloadAction
}

public var body: some View {
GeometryReader { proxy in
VStack(spacing: 28) {
Spacer()
switch errorType {
case .noInternet, .noInternetWithReload:
CoreAssets.noWifi.swiftUIImage
.renderingMode(.template)
.foregroundStyle(Color.primary)
.scaledToFit()

Text(CoreLocalization.Error.Internet.noInternetTitle)
.font(Theme.Fonts.titleLarge)
.foregroundColor(Theme.Colors.textPrimary)

Text(CoreLocalization.Error.Internet.noInternetDescription)
.font(Theme.Fonts.bodyLarge)
.foregroundColor(Theme.Colors.textPrimary)
.multilineTextAlignment(.center)
.padding(.horizontal, 50)
case .generic:
CoreAssets.notAvaliable.swiftUIImage
.renderingMode(.template)
.foregroundStyle(Color.primary)
.scaledToFit()

Text(CoreLocalization.View.Snackbar.tryAgainBtn)
.font(Theme.Fonts.titleLarge)
.foregroundColor(Theme.Colors.textPrimary)

Text(CoreLocalization.Error.unknownError)
.font(Theme.Fonts.bodyLarge)
.foregroundColor(Theme.Colors.textPrimary)
.multilineTextAlignment(.center)
.padding(.horizontal, 50)
}

if errorType != .noInternet {
UnitButtonView(type: .reload, action: {
self.reloadAction()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting

UnitButtonView(
    type: .reload, action: {
        self.reloadAction()
    }
 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 🎉

}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: proxy.size.height)
.background(
Theme.Colors.background
)
}
}
}

#if DEBUG
struct FullScreenErrorView_Previews: PreviewProvider {
static var previews: some View {
FullScreenErrorView(type: .noInternetWithReload)
}
}
#endif
29 changes: 25 additions & 4 deletions Core/Core/View/Base/Webview/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public protocol WebViewNavigationDelegate: AnyObject {
shouldLoad request: URLRequest,
navigationAction: WKNavigationAction
) async -> Bool

func showWebViewError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It nice and can work with passing the error, but how about having a more detailed method like

func webView(
        _ webView: WKWebView,
        didFail navigation: WKNavigation,
        withError error: Error
    )

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our use case, displaying specific error details is not required. We are currently only showing internet error or generic error. Therefore, detailed information is not required at this time. We can reconsider this approach if there arises a need for more detailed error messages in the future.

}

public struct WebView: UIViewRepresentable {
Expand All @@ -39,17 +41,20 @@ public struct WebView: UIViewRepresentable {
var webViewNavDelegate: WebViewNavigationDelegate?

var refreshCookies: () async -> Void
var webViewType: String?

public init(
viewModel: ViewModel,
isLoading: Binding<Bool>,
refreshCookies: @escaping () async -> Void,
navigationDelegate: WebViewNavigationDelegate? = nil
navigationDelegate: WebViewNavigationDelegate? = nil,
webViewType: String? = nil
) {
self.viewModel = viewModel
self._isLoading = isLoading
self.refreshCookies = refreshCookies
self.webViewNavDelegate = navigationDelegate
self.webViewType = webViewType
}

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

public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
webView.isHidden = false
DispatchQueue.main.async {
self.parent.isLoading = false
}
parent.webViewNavDelegate?.showWebViewError()
}

public func webView(
Expand All @@ -78,6 +87,10 @@ public struct WebView: UIViewRepresentable {
withError error: Error
) {
webView.isHidden = false
DispatchQueue.main.async {
self.parent.isLoading = false
}
parent.webViewNavDelegate?.showWebViewError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about

            DispatchQueue.main.async {
                self.parent.isLoading = false
                self.parent.webViewNavDelegate?.showWebViewError()
            }

and remove DispatchQueue.main from the showWebViewError function.
wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it's more appropriate I believe. Thanks

}

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

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

@objc private func reload() {
parent.isLoading = true
webview?.reload()
DispatchQueue.main.async {
self.parent.isLoading = true
}
if webview?.url?.absoluteString.isEmpty ?? true,
let url = URL(string: parent.viewModel.url) {
let request = URLRequest(url: url)
webview?.load(request)
} else {
webview?.reload()
}
}

public func userContentController(
Expand Down
14 changes: 11 additions & 3 deletions Course/Course.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@
02B6B3B528E1D10700232911 /* Domain */,
02EAE2CA28E1F0A700529644 /* Presentation */,
97EA4D822B84EFA900663F58 /* Managers */,
97CA95212B875EA200A9EDEA /* Views */,
02B6B3B428E1C49400232911 /* Localizable.strings */,
);
path = Course;
Expand Down Expand Up @@ -526,13 +525,20 @@
path = Mock;
sourceTree = "<group>";
};
97CA95212B875EA200A9EDEA /* Views */ = {
9784D4752BF39EEF00AFEFFF /* CalendarSyncProgressView */ = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to update this? Asking because it doesn't seem relevant to the work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated location from:

Course/Views/CalendarSyncProgressView.swift
Course/Views/DatesSuccessView.swift

To:

Course/Presentation/Subviews/CalendarSyncProgressView/CalendarSyncProgressView.swift
Course/Presentation/Subviews/DatesSuccessView/DatesSuccessView.swift

I found that we already have subviews folder and it seems more appropriate to place it there.

isa = PBXGroup;
children = (
97C99C352B9A08FE004EEDE2 /* CalendarSyncProgressView.swift */,
);
path = CalendarSyncProgressView;
sourceTree = "<group>";
};
9784D4762BF39EFD00AFEFFF /* DatesSuccessView */ = {
isa = PBXGroup;
children = (
97CA95242B875EE200A9EDEA /* DatesSuccessView.swift */,
);
path = Views;
path = DatesSuccessView;
sourceTree = "<group>";
};
97EA4D822B84EFA900663F58 /* Managers */ = {
Expand Down Expand Up @@ -592,6 +598,8 @@
BAD9CA482B2C88D500DE790A /* Subviews */ = {
isa = PBXGroup;
children = (
9784D4762BF39EFD00AFEFFF /* DatesSuccessView */,
9784D4752BF39EEF00AFEFFF /* CalendarSyncProgressView */,
02D4FC2C2BBD7C7500C47748 /* MessageSectionView */,
BAC0E0D92B32F0A2006B68A9 /* CourseVideoDownloadBarView */,
BA58CF622B471047005B102E /* VideoDownloadQualityBarView */,
Expand Down
32 changes: 5 additions & 27 deletions Course/Course/Presentation/Unit/CourseUnitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public struct CourseUnitView: View {
Spacer(minLength: 150)
}
} else {
NoInternetView()
FullScreenErrorView(type: .noInternet)
}

} else {
Expand Down Expand Up @@ -219,7 +219,7 @@ public struct CourseUnitView: View {
Spacer(minLength: 150)
}
} else {
NoInternetView()
FullScreenErrorView(type: .noInternet)
}
}
// MARK: Web
Expand All @@ -233,7 +233,7 @@ public struct CourseUnitView: View {
)
// not need to add frame limit there because we did that with injection
} else {
NoInternetView()
FullScreenErrorView(type: .noInternet)
}
} else {
EmptyView()
Expand All @@ -247,7 +247,7 @@ public struct CourseUnitView: View {
Spacer()
.frame(minHeight: 100)
} else {
NoInternetView()
FullScreenErrorView(type: .noInternet)
}
} else {
EmptyView()
Expand Down Expand Up @@ -275,7 +275,7 @@ public struct CourseUnitView: View {
//No need iPad paddings there bacause they were added
//to PostsView that placed inside DiscussionView
} else {
NoInternetView()
FullScreenErrorView(type: .noInternet)
}
} else {
EmptyView()
Expand Down Expand Up @@ -569,25 +569,3 @@ struct CourseUnitView_Previews: PreviewProvider {
}
//swiftlint:enable all
#endif

struct NoInternetView: View {

var body: some View {
VStack(spacing: 28) {
Spacer()
CoreAssets.noWifi.swiftUIImage
.renderingMode(.template)
.foregroundStyle(Color.primary)
.scaledToFit()
Text(CoreLocalization.Error.Internet.noInternetTitle)
.font(Theme.Fonts.titleLarge)
.foregroundColor(Theme.Colors.textPrimary)
Text(CoreLocalization.Error.Internet.noInternetDescription)
.font(Theme.Fonts.bodyLarge)
.foregroundColor(Theme.Colors.textPrimary)
.multilineTextAlignment(.center)
.padding(.horizontal, 50)
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Loading
Loading