-
Notifications
You must be signed in to change notification settings - Fork 18
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: webview based discovery implementation #219
Changes from 7 commits
d656376
c542202
634b73b
6666cb8
cec1608
f19b0e6
4779971
80c7799
370d2c7
8d28140
4cc33df
4c9ab03
86205d5
acec545
574b74f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// DiscoveryConfig.swift | ||
// Core | ||
// | ||
// Created by SaeedBashir on 12/18/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public enum DiscoveryConfigType: String { | ||
case native | ||
case webview | ||
case none | ||
} | ||
|
||
private enum DiscoveryKeys: String, RawStringExtractable { | ||
case discoveryType = "TYPE" | ||
case webview = "WEBVIEW" | ||
case baseURL = "BASE_URL" | ||
case courseDetailTemplate = "COURSE_DETAIL_TEMPLATE" | ||
case programDetailTemplate = "PROGRAM_DETAIL_TEMPLATE" | ||
} | ||
|
||
public class DiscoveryWebviewConfig: NSObject { | ||
public let baseURL: String? | ||
public let courseDetailTemplate: String? | ||
public let programDetailTemplate: String? | ||
|
||
init(dictionary: [String: AnyObject]) { | ||
baseURL = dictionary[DiscoveryKeys.baseURL] as? String | ||
courseDetailTemplate = dictionary[DiscoveryKeys.courseDetailTemplate] as? String | ||
programDetailTemplate = dictionary[DiscoveryKeys.programDetailTemplate] as? String | ||
} | ||
} | ||
|
||
public class DiscoveryConfig: NSObject { | ||
public let type: DiscoveryConfigType | ||
public let webview: DiscoveryWebviewConfig | ||
|
||
init(dictionary: [String: AnyObject]) { | ||
type = (dictionary[DiscoveryKeys.discoveryType] as? String).flatMap { | ||
DiscoveryConfigType(rawValue: $0) | ||
} ?? .none | ||
webview = DiscoveryWebviewConfig(dictionary: dictionary[DiscoveryKeys.webview] as? [String: AnyObject] ?? [:]) | ||
} | ||
|
||
public var enabled: Bool { | ||
return type != .none | ||
} | ||
} | ||
|
||
private let key = "DISCOVERY" | ||
extension Config { | ||
public var discovery: DiscoveryConfig { | ||
DiscoveryConfig(dictionary: self[key] as? [String: AnyObject] ?? [:]) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// RawStringExtactable.swift | ||
// Core | ||
// | ||
// Created by SaeedBashir on 12/18/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol RawStringExtractable { | ||
var rawValue: String { get } | ||
} | ||
|
||
public protocol DictionaryExtractionExtension { | ||
associatedtype Key | ||
associatedtype Value | ||
subscript(key: Key) -> Value? { get } | ||
} | ||
|
||
extension Dictionary: DictionaryExtractionExtension {} | ||
|
||
public extension DictionaryExtractionExtension where Self.Key == String { | ||
|
||
subscript(key :RawStringExtractable) -> Value? { | ||
return self[key.rawValue] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,12 @@ import WebKit | |
import SwiftUI | ||
import Theme | ||
|
||
public let WebviewReloadNotification = "webviewReloadNotification" | ||
|
||
public protocol WebViewNavigationDelegate: AnyObject { | ||
func webView(_ webView: WKWebView, shouldLoad request: URLRequest, navigationAction: WKNavigationAction) -> Bool | ||
} | ||
|
||
public struct WebView: UIViewRepresentable { | ||
|
||
public class ViewModel: ObservableObject { | ||
|
@@ -25,19 +31,30 @@ public struct WebView: UIViewRepresentable { | |
|
||
@ObservedObject var viewModel: ViewModel | ||
@Binding public var isLoading: Bool | ||
var webViewNavDelegate: WebViewNavigationDelegate? | ||
|
||
var refreshCookies: () async -> Void | ||
|
||
public init(viewModel: ViewModel, isLoading: Binding<Bool>, refreshCookies: @escaping () async -> Void) { | ||
public init( | ||
viewModel: ViewModel, | ||
isLoading: Binding<Bool>, | ||
refreshCookies: @escaping () async -> Void, | ||
navigationDelegate: WebViewNavigationDelegate? = nil | ||
) { | ||
self.viewModel = viewModel | ||
self._isLoading = isLoading | ||
self.refreshCookies = refreshCookies | ||
self.webViewNavDelegate = navigationDelegate | ||
} | ||
|
||
public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { | ||
var parent: WebView | ||
|
||
init(_ parent: WebView) { | ||
self.parent = parent | ||
super.init() | ||
|
||
addObserver() | ||
} | ||
|
||
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | ||
|
@@ -79,6 +96,16 @@ public struct WebView: UIViewRepresentable { | |
|
||
guard let url = navigationAction.request.url else { return .cancel } | ||
|
||
let isWebViewDelegateHandled = await ( | ||
parent.webViewNavDelegate?.webView( | ||
webView, | ||
shouldLoad: navigationAction.request, navigationAction: navigationAction) ?? false | ||
) | ||
|
||
if isWebViewDelegateHandled { | ||
return .cancel | ||
} | ||
|
||
let baseURL = await parent.viewModel.baseURL | ||
if !baseURL.isEmpty, !url.absoluteString.starts(with: baseURL) { | ||
if navigationAction.navigationType == .other { | ||
|
@@ -117,6 +144,26 @@ public struct WebView: UIViewRepresentable { | |
} | ||
return .allow | ||
} | ||
|
||
private func addObserver() { | ||
NotificationCenter.default.addObserver( | ||
self, | ||
selector: #selector(reload), | ||
name: Notification.Name(WebviewReloadNotification), | ||
object: nil | ||
) | ||
} | ||
|
||
fileprivate var webview: WKWebView? | ||
|
||
@objc private func reload() { | ||
parent.isLoading = true | ||
webview?.reload() | ||
} | ||
|
||
deinit { | ||
NotificationCenter.default.removeObserver(self) | ||
} | ||
} | ||
|
||
public func makeCoordinator() -> Coordinator { | ||
|
@@ -130,6 +177,8 @@ public struct WebView: UIViewRepresentable { | |
webView.navigationDelegate = context.coordinator | ||
webView.uiDelegate = context.coordinator | ||
|
||
context.coordinator.webview = webView | ||
|
||
webView.scrollView.bounces = false | ||
webView.scrollView.alwaysBounceHorizontal = false | ||
webView.scrollView.showsHorizontalScrollIndicator = false | ||
|
@@ -139,8 +188,8 @@ public struct WebView: UIViewRepresentable { | |
webView.backgroundColor = .clear | ||
webView.scrollView.backgroundColor = Theme.Colors.white.uiColor() | ||
webView.scrollView.alwaysBounceVertical = false | ||
webView.scrollView.layer.cornerRadius = 24 | ||
webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] | ||
// webView.scrollView.layer.cornerRadius = 24 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @saeedbashir do we need this commented out code? |
||
// webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] | ||
webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0) | ||
|
||
return webView | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,7 @@ | |
|
||
"WEBVIEW.ALERT.OK" = "Так"; | ||
"WEBVIEW.ALERT.CANCEL" = "Скасувати"; | ||
"WEBVIEW.ALERT.CONTINUE" = "Continue"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary to have a localized version of the string at this moment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't add translations on the go. Files will be translated later. |
||
|
||
|
||
"REVIEW.VOTE_TITLE" = "Вам подобається Open edX?"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you follow style 'each argument on its own line' here please