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/survey and dd #218

Merged
merged 12 commits into from
Jan 16, 2024
88 changes: 67 additions & 21 deletions Core/Core.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Core/Core/Domain/Model/CourseDetailBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ public enum BlockType: String {
case chapter
case video
case problem
case survey
case unknown
case dragAndDropV2 = "drag-and-drop-v2"

public var image: Image {
switch self {
Expand Down
14 changes: 10 additions & 4 deletions Core/Core/View/Base/WebUnitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import Theme
public struct WebUnitView: View {

private var url: String
@ObservedObject private var viewModel: WebUnitViewModel
private var injections: [WebviewInjection]?
@StateObject private var viewModel: WebUnitViewModel
@State private var isWebViewLoading = false

public init(url: String, viewModel: WebUnitViewModel) {
self.viewModel = viewModel
public init(url: String, viewModel: WebUnitViewModel, injections: [WebviewInjection]?) {
self._viewModel = .init(wrappedValue: viewModel)
self.url = url
self.injections = injections
}

@ViewBuilder
Expand Down Expand Up @@ -55,7 +57,11 @@ public struct WebUnitView: View {
ScrollView {
if viewModel.cookiesReady {
WebView(
viewModel: .init(url: url, baseURL: viewModel.config.baseURL.absoluteString),
viewModel: .init(
url: url,
baseURL: viewModel.config.baseURL.absoluteString,
injections: injections
),
isLoading: $isWebViewLoading, refreshCookies: {
await viewModel.updateCookies(force: true)
})
Expand Down
34 changes: 34 additions & 0 deletions Core/Core/View/Base/Webview/Models/DragAndDropCssInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// DragAndDropCssInjection.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import WebKit

public struct DragAndDropCssInjection: WebViewScriptInjectionProtocol, CSSInjectionProtocol {
public var id: String = "DragAndDropCSSInjection"
public var messages: [WebviewMessage]?
public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart

public var script: String {
cssScript(with: css)
}

var css: String {
"""
.xblock--drag-and-drop .drag-container {
-webkit-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
"""
}

public init() {}

public static func == (lhs: DragAndDropCssInjection, rhs: DragAndDropCssInjection) -> Bool {
lhs.script == rhs.script
}
}
36 changes: 36 additions & 0 deletions Core/Core/View/Base/Webview/Models/SurveyCssInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// SurveyCssInjection.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import WebKit

public struct SurveyCssInjection: WebViewScriptInjectionProtocol, CSSInjectionProtocol {
public var id: String = "SurveyCSSInjection"
public var messages: [WebviewMessage]?
public var injectionTime: WKUserScriptInjectionTime = .atDocumentStart

public var script: String {
cssScript(with: css)
}

var css: String {
"""
.survey-table:not(.poll-results) .survey-option .visible-mobile-only {
width: calc(100% - 21px) !important;
}

.survey-percentage .percentage {
width: 54px !important;
}
"""
}

public init() {}

public static func == (lhs: SurveyCssInjection, rhs: SurveyCssInjection) -> Bool {
lhs.script == rhs.script
}
}
46 changes: 46 additions & 0 deletions Core/Core/View/Base/Webview/Models/WebviewInjection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// WebviewInjection.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import WebKit
public struct WebviewInjection: WebViewScriptInjectionProtocol {
public var id: String
public var script: String
public var messages: [WebviewMessage]?
public var injectionTime: WKUserScriptInjectionTime
init(
id: String,
script: String,
messages: [WebviewMessage]? = nil,
injectionTime: WKUserScriptInjectionTime = .atDocumentEnd
) {
self.id = id
self.script = script
self.messages = messages
self.injectionTime = injectionTime
}

public static func == (lhs: WebviewInjection, rhs: WebviewInjection) -> Bool {
lhs.id == rhs.id &&
lhs.script == rhs.script &&
lhs.injectionTime == rhs.injectionTime &&
lhs.messages == rhs.messages
}
}

public extension WebviewInjection {

static var surveyCSS: WebviewInjection {
SurveyCssInjection()
.webviewInjection()
}

static var dragAndDropCss: WebviewInjection {
DragAndDropCssInjection()
.webviewInjection()
}

}
16 changes: 16 additions & 0 deletions Core/Core/View/Base/Webview/Models/WebviewMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// WebviewMessage.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import WebKit
public struct WebviewMessage: Equatable {
var name: String
var handler: (Any, WKWebView?) -> Void

public static func == (lhs: WebviewMessage, rhs: WebviewMessage) -> Bool {
lhs.name == rhs.name
}
}
31 changes: 31 additions & 0 deletions Core/Core/View/Base/Webview/Protocols/CSSInjectionProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// CSSInjectionProtocol.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import Foundation

public protocol CSSInjectionProtocol {
func cssScript(with css: String) -> String
}

extension CSSInjectionProtocol {
public func cssScript(with css: String) -> String {
"""
window.addEventListener("load", () => {
var css = `\(css)`,
head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style');
head.appendChild(style);
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
})
"""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// WebViewScriptInjectionProtocol.swift
// Core
//
// Created by Vadim Kuznetsov on 4.01.24.
//

import WebKit
public protocol WebViewScriptInjectionProtocol: Equatable, Identifiable {
var id: String { get }
var script: String { get }
var messages: [WebviewMessage]? { get }
var injectionTime: WKUserScriptInjectionTime { get }
}

extension WebViewScriptInjectionProtocol {
public func webviewInjection() -> WebviewInjection {
WebviewInjection(
id: self.id,
script: self.script,
messages: self.messages,
injectionTime: self.injectionTime
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ public struct WebView: UIViewRepresentable {

@Published var url: String
let baseURL: String
let injections: [WebviewInjection]?

public init(url: String, baseURL: String) {
public init(url: String, baseURL: String, injections: [WebviewInjection]? = nil) {
self.url = url
self.baseURL = baseURL
self.injections = injections
}
}

Expand All @@ -33,7 +35,7 @@ public struct WebView: UIViewRepresentable {
self.refreshCookies = refreshCookies
}

public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler {
var parent: WebView

init(_ parent: WebView) {
Expand Down Expand Up @@ -117,6 +119,16 @@ public struct WebView: UIViewRepresentable {
}
return .allow
}

public func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
let messages = parent.viewModel.injections?.compactMap({$0.messages}).flatMap({$0}) ?? []
if let currentMessage = messages.first(where: { $0.name == message.name }) {
currentMessage.handler(message.body, message.webView)
}
}
}

public func makeCoordinator() -> Coordinator {
Expand All @@ -127,6 +139,11 @@ public struct WebView: UIViewRepresentable {
let webViewConfig = WKWebViewConfiguration()

let webView = WKWebView(frame: .zero, configuration: webViewConfig)
#if DEBUG
if #available(iOS 16.4, *) {
webView.isInspectable = true
}
#endif
webView.navigationDelegate = context.coordinator
webView.uiDelegate = context.coordinator

Expand All @@ -143,6 +160,19 @@ public struct WebView: UIViewRepresentable {
webView.scrollView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right: 0)

for injection in viewModel.injections ?? [] {
let script = WKUserScript(
source: injection.script,
injectionTime: injection.injectionTime,
forMainFrameOnly: true
)
webView.configuration.userContentController.addUserScript(script)

for message in injection.messages ?? [] {
webView.configuration.userContentController.add(context.coordinator, name: message.name)
}
}

return webView
}

Expand All @@ -157,4 +187,9 @@ public struct WebView: UIViewRepresentable {
}
}
}

public static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
uiView.configuration.userContentController.removeAllUserScripts()
uiView.configuration.userContentController.removeAllScriptMessageHandlers()
}
}
17 changes: 10 additions & 7 deletions Course/Course/Presentation/Unit/CourseUnitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,19 @@ public struct CourseUnitView: View {
}
}
// MARK: Web
case .web(let url):
case let .web(url, injections):
if index >= viewModel.index - 1 && index <= viewModel.index + 1 {
if viewModel.connectivity.isInternetAvaliable {
WebView(url: url, viewModel: viewModel)
if viewModel.connectivity.isInternetAvaliable {
WebView(
url: url,
injections: injections
)
} else {
NoInternetView(playerStateSubject: playerStateSubject)
}
} else {
NoInternetView(playerStateSubject: playerStateSubject)
EmptyView()
}
} else {
EmptyView()
}
// MARK: Unknown
case .unknown(let url):
if index >= viewModel.index - 1 && index <= viewModel.index + 1 {
Expand Down
Loading