Skip to content

Commit

Permalink
Merge pull request #219 from saeedbashir/saeed/web_discovery
Browse files Browse the repository at this point in the history
feat: webview based discovery implementation
  • Loading branch information
saeedbashir authored Jan 17, 2024
2 parents 6f3a985 + 574b74f commit 06f0993
Show file tree
Hide file tree
Showing 55 changed files with 1,303 additions and 543 deletions.
24 changes: 8 additions & 16 deletions Core/Core.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@
DBF6F2462B01DAFE0098414B /* AgreementConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */; };
DBF6F24A2B0380E00098414B /* FeaturesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF6F2492B0380E00098414B /* FeaturesConfig.swift */; };
E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; };
E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E055A5382B18DC95008D9E5E /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E09179FD2B0F204E002AB695 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09179FC2B0F204D002AB695 /* ConfigTests.swift */; };
E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */; };
E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */; };
E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */; };
/* End PBXBuildFile section */

Expand All @@ -165,20 +166,6 @@
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
E055A53B2B18DC95008D9E5E /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
E055A53A2B18DC95008D9E5E /* Theme.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
020306CB2932C0C4000949EA /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = "<group>"; };
02066B472906F73400F4307E /* PickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerMenu.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -331,6 +318,8 @@
DBF6F2492B0380E00098414B /* FeaturesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesConfig.swift; sourceTree = "<group>"; };
E055A5382B18DC95008D9E5E /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E09179FC2B0F204D002AB695 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = "<group>"; };
E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryConfig.swift; sourceTree = "<group>"; };
E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawStringExtactable.swift; sourceTree = "<group>"; };
E0D586352B314CD3009B4BA7 /* LogistrationBottomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogistrationBottomView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -446,6 +435,7 @@
BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */,
BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */,
02D400602B0678190029D168 /* SKStoreReviewControllerExtension.swift */,
E0D5861B2B2FF85B009B4BA7 /* RawStringExtactable.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -766,6 +756,7 @@
BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */,
BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */,
A53A32342B233DEC005FE38A /* ThemeConfig.swift */,
E0D586192B2FF74C009B4BA7 /* DiscoveryConfig.swift */,
);
path = Config;
sourceTree = "<group>";
Expand Down Expand Up @@ -837,7 +828,6 @@
0770DE0428D07831006D8A5D /* Sources */,
0770DE0528D07831006D8A5D /* Frameworks */,
0770DE0628D07831006D8A5D /* Resources */,
E055A53B2B18DC95008D9E5E /* Embed Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -1060,13 +1050,15 @@
BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */,
0284DBFE28D48C5300830893 /* CourseItem.swift in Sources */,
0248C92329C075EF00DC8402 /* CourseBlockModel.swift in Sources */,
E0D5861A2B2FF74C009B4BA7 /* DiscoveryConfig.swift in Sources */,
DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */,
072787B628D37A0E002E9142 /* Validator.swift in Sources */,
0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */,
02AFCC182AEFDB24000360F0 /* ThirdPartyMailClient.swift in Sources */,
0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */,
BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */,
02B2B594295C5C7A00914876 /* Thread.swift in Sources */,
E0D5861C2B2FF85B009B4BA7 /* RawStringExtactable.swift in Sources */,
027BD3BD2909478B00392132 /* UIView+EnclosingScrollView.swift in Sources */,
BA8FA6682AD59A5700EA029A /* SocialAuthButton.swift in Sources */,
02D400612B0678190029D168 /* SKStoreReviewControllerExtension.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Core/Core/Configuration/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public protocol ConfigProtocol {
var features: FeaturesConfig { get }
var theme: ThemeConfig { get }
var uiComponents: UIComponentsConfig { get }
var discovery: DiscoveryConfig { get }
}

public enum TokenType: String {
Expand Down
57 changes: 57 additions & 0 deletions Core/Core/Configuration/Config/DiscoveryConfig.swift
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] ?? [:])
}
}
1 change: 1 addition & 0 deletions Core/Core/Extensions/Notification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ 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")
}
27 changes: 27 additions & 0 deletions Core/Core/Extensions/RawStringExtactable.swift
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]
}
}
2 changes: 2 additions & 0 deletions Core/Core/SwiftGen/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ public enum CoreLocalization {
public enum Alert {
/// Cancel
public static let cancel = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.CANCEL", fallback: "Cancel")
/// Continue
public static let `continue` = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.CONTINUE", fallback: "Continue")
/// Ok
public static let ok = CoreLocalization.tr("Localizable", "WEBVIEW.ALERT.OK", fallback: "Ok")
}
Expand Down
19 changes: 12 additions & 7 deletions Core/Core/View/Base/AlertView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public enum AlertViewType: Equatable {
var contentPadding: CGFloat {
switch self {
case .`default`:
return 5
return 16
case .action, .logOut, .leaveProfile:
return 36
}
Expand Down Expand Up @@ -113,6 +113,7 @@ public struct AlertView: View {
Text(alertTitle)
.font(Theme.Fonts.titleLarge)
.padding(.horizontal, 40)
.padding(.top, 10)
Text(alertMessage)
.font(Theme.Fonts.bodyMedium)
.multilineTextAlignment(.center)
Expand Down Expand Up @@ -151,11 +152,15 @@ public struct AlertView: View {
HStack {
switch type {
case let .`default`(positiveAction):
StyledButton(positiveAction, action: { okTapped() })
.frame(maxWidth: 135)
StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() })
.frame(maxWidth: 135)
.saturation(0)
HStack {
StyledButton(positiveAction, action: { okTapped() })
.frame(maxWidth: 135)
StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() })
.frame(maxWidth: 135)
.saturation(0)
}
.padding(.leading, 10)
.padding(.trailing, 10)
case let .action(action, _):
if !isHorizontal {
VStack(spacing: 20) {
Expand Down Expand Up @@ -274,7 +279,7 @@ public struct AlertView: View {
}.padding(.trailing, isHorizontal ? 20 : 0)
}
}
.padding(.top, 5)
.padding(.top, 16)
.padding(.bottom, isHorizontal ? 16 : type.contentPadding)
}
Button(action: {
Expand Down
1 change: 1 addition & 0 deletions Core/Core/View/Base/LogistrationBottomView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum LogistrationSourceScreen: Equatable {
case startup
case discovery
case courseDetail(String, String)
case programDetails(String)
}

public enum LogistrationAction {
Expand Down
52 changes: 49 additions & 3 deletions Core/Core/View/Base/Webview/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import WebKit
import SwiftUI
import Theme

public protocol WebViewNavigationDelegate: AnyObject {
func webView(_ webView: WKWebView, shouldLoad request: URLRequest, navigationAction: WKNavigationAction) -> Bool
}

public struct WebView: UIViewRepresentable {

public class ViewModel: ObservableObject {
Expand All @@ -27,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, WKScriptMessageHandler {
var parent: WebView

init(_ parent: WebView) {
self.parent = parent
super.init()

addObserver()
}

public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
Expand Down Expand Up @@ -81,6 +96,17 @@ 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 {
Expand Down Expand Up @@ -119,6 +145,26 @@ public struct WebView: UIViewRepresentable {
}
return .allow
}

private func addObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(reload),
name: .webviewReloadNotification,
object: nil
)
}

fileprivate var webview: WKWebView?

@objc private func reload() {
parent.isLoading = true
webview?.reload()
}

deinit {
NotificationCenter.default.removeObserver(self)
}

public func userContentController(
_ userContentController: WKUserContentController,
Expand Down Expand Up @@ -147,6 +193,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
Expand All @@ -156,8 +204,6 @@ 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.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0)

for injection in viewModel.injections ?? [] {
Expand Down
1 change: 1 addition & 0 deletions Core/Core/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

"WEBVIEW.ALERT.OK" = "Ok";
"WEBVIEW.ALERT.CANCEL" = "Cancel";
"WEBVIEW.ALERT.CONTINUE" = "Continue";

"REVIEW.VOTE_TITLE" = "Enjoying Open edX?";
"REVIEW.VOTE_DESCRIPTION" = "Your feedback matters to us. Would you take a moment to rate the app by tapping a star below? Thanks for your support!";
Expand Down
1 change: 1 addition & 0 deletions Core/Core/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@

"WEBVIEW.ALERT.OK" = "Так";
"WEBVIEW.ALERT.CANCEL" = "Скасувати";
"WEBVIEW.ALERT.CONTINUE" = "Continue";


"REVIEW.VOTE_TITLE" = "Вам подобається Open edX?";
Expand Down
Loading

0 comments on commit 06f0993

Please sign in to comment.