Skip to content

Commit

Permalink
v4.0.9
Browse files Browse the repository at this point in the history
Truncated Kudit API calls to prevent long URL issues.  Added public visibility for HTMLView.  Re-worked to allow opening URLs in app/external app.  Fixed testing navigation styles to .stack for iPad views.  Deleted WebView since we're not really using that for anything.  Can look through history if you ever need a WebView.  Added HTML cleaning for strings.
  • Loading branch information
kudit committed Oct 14, 2023
1 parent 0dfda4f commit dc10ffd
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 172 deletions.
4 changes: 2 additions & 2 deletions .swiftpm/playgrounds/CachedManifest.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
bU5hbWUiOiJpb3MiLCJ2ZXJzaW9uIjoiMTUuMiJ9XSwicHJvZHVjdHMiOlt7
Im5hbWUiOiJLdWRpdEZyYW1ld29ya3NBcHAiLCJzZXR0aW5ncyI6W3sidGVh
bUlkZW50aWZpZXIiOlsiM1FQVjg5NEMzMyJdfSx7ImRpc3BsYXlWZXJzaW9u
IjpbIjQuMC44Il19LHsiYnVuZGxlVmVyc2lvbiI6WyIxIl19LHsiaU9TQXBw
IjpbIjQuMC45Il19LHsiYnVuZGxlVmVyc2lvbiI6WyIxIl19LHsiaU9TQXBw
SW5mbyI6W3siYWNjZW50Q29sb3IiOnsicHJlc2V0Q29sb3IiOnsicHJlc2V0
Q29sb3IiOnsicmF3VmFsdWUiOiJ0ZWFsIn19fSwiYXBwQ2F0ZWdvcnkiOnsi
cmF3VmFsdWUiOiJwdWJsaWMuYXBwLWNhdGVnb3J5LmRldmVsb3Blci10b29s
Expand Down Expand Up @@ -50,7 +50,7 @@
</data>
<key>manifestHash</key>
<data>
75eR3mGQTZ+nN4y2w5zM8gj1MrAi3PggqnWHQgQ7ZTU=
YgkRIRDvj2ZuHeC/R9A+K/S1mP/QXh154bHoljXo7Hw=
</data>
<key>schemaVersion</key>
<integer>4</integer>
Expand Down
1 change: 1 addition & 0 deletions Resources/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Additional components, simpler, and convenience code for Kudit projects.

v4.0.9 10/12/2023 Truncated Kudit API calls to prevent long URL issues. Added public visibility for HTMLView. Re-worked to allow opening URLs in app/external app. Fixed testing navigation styles to .stack for iPad views. Deleted WebView since we're not really using that for anything. Can look through history if you ever need a WebView. Added HTML cleaning for strings.
v4.0.8 10/11/2023 Added encoding/decoding strategies to dictionary coding. Added Debug message to KuditConnect for reminder to set DebugLevel.current = .NOTICE (or higher for production). Re-worked FAQ rendering to use new HTMLView which works better with images and layout. Added LosslessStringConvertible and KuColor default value constructors to work with possibly empty values to guarantee a return so we don't have to do an optionals dance.
v4.0.7 10/11/2023 Fixed non-public access for KuditConnect.shared to support customizeMessageBody handler.
v4.0.6 10/11/2023 Fixed versioning numbering to support swift package updates.
Expand Down
2 changes: 1 addition & 1 deletion Resources/iPadPackage.txt
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
name: "KuditFrameworksApp",
targets: ["AppModule"],
teamIdentifier: "3QPV894C33",
displayVersion: "4.0.0",
displayVersion: "4.0.9",
bundleVersion: "1",
appIcon: .asset("AppIcon"),
accentColor: .presetColor(.teal),
Expand Down
33 changes: 33 additions & 0 deletions Sources/Foundation/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ public extension CharacterSet {
}
}

// MARK: - HTML
public typealias HTML = String
public extension HTML {
/// Cleans the HTML content to ensure this isn't just a snippet of HTML and includes the proper headers, etc.
var cleaned: HTML {
var cleaned = self
if !cleaned.contains("<body>") {
cleaned = """
<body>
\(cleaned)
</body>
"""
}
if !cleaned.contains("<html>") {
cleaned = """
<html>
\(cleaned)
</html>
"""
}
return cleaned
}
/// Generate an NSAttributedString from the HTML content enclosed
var attributedString: NSAttributedString {
let cleaned = self.cleaned
let data = Data(cleaned.utf8)
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
return attributedString
}
return NSAttributedString(string: cleaned)
}
}

extension String: Testable {}
public extension String {
static var INVALID_ENCODING = "INVALID_ENCODING"
Expand Down
63 changes: 17 additions & 46 deletions Sources/KuditConnect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,21 @@ import DeviceKit
// https://swiftuirecipes.com/blog/send-mail-in-swiftui

// MARK: - FAQs
typealias HTML = String
extension HTML {
/// Cleans the HTML content to ensure this isn't just a snippet of HTML and includes the proper headers, etc.
var cleaned: HTML {
var cleaned = self
if !cleaned.contains("<body>") {
cleaned = """
<body>
\(cleaned)
</body>
"""
}
if !cleaned.contains("<html>") {
cleaned = """
<html>
\(cleaned)
</html>
"""
}
return cleaned
}
/// Generate an NSAttributedString from the HTML content enclosed
var attributedString: NSAttributedString {
let cleaned = self.cleaned
let data = Data(cleaned.utf8)
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
return attributedString
}
return NSAttributedString(string: cleaned)
}
}
typealias MySQLDate = String // in the future, convert to actual date? Support conversion to Date object?
struct KuditFAQ: Codable, Identifiable {
var question: String
var answer: HTML
var category: String
var minversion: Version? // could be null
var maxversion: Version? // could be null
var updated: MySQLDate
var key: String { // TODO: Is this still needed?
public typealias MySQLDate = String // in the future, convert to actual date? Support conversion to Date object?
public struct KuditFAQ: Codable, Identifiable {
public var question: String
public var answer: HTML
public var category: String
public var minversion: Version? // could be null
public var maxversion: Version? // could be null
public var updated: MySQLDate
public var key: String { // TODO: Is this still needed?
return "kuditConnectAlertShown:\(question)"
}
var id: String {
public var id: String {
question
}
func visible(in version: Version) -> Bool {
public func visible(in version: Version) -> Bool {
if let minversion, minversion > version {
return false
}
Expand All @@ -72,7 +41,7 @@ struct KuditFAQ: Codable, Identifiable {
}
return true
}
func answerHTML(textColor: Color) -> HTML {
public func answerHTML(textColor: Color) -> HTML {
var debugHTML = """
<footer>(\(minversion?.rawValue ?? "n/a"),\(maxversion?.rawValue ?? "n/a")) \(updated) text: \(textColor.cssString)</footer>
"""
Expand All @@ -85,7 +54,7 @@ struct KuditFAQ: Codable, Identifiable {
"""
}
}
extension [KuditFAQ] {
public extension [KuditFAQ] {
var categories: [String] {
var categories = [String]()
for faq in self {
Expand Down Expand Up @@ -203,7 +172,9 @@ public class KuditConnect: ObservableObject {
identifier = "com.unknown.unknown" // for testing
}
let version = Application.main.version
let urlString = "\(Self.kuditAPIURL)/\(api).php?identifier=\(identifier.urlEncoded)&version=\(version)\(info)&kcVersion=\(Self.kuditConnectVersion)"
var urlString = "\(Self.kuditAPIURL)/\(api).php?identifier=\(identifier.urlEncoded)&version=\(version)&kcVersion=\(Self.kuditConnectVersion)\(info)"
// limit to 2000 characters to prevent URI overflow
urlString = String(urlString.prefix(2000))
debug("Kudit Connect: API URL: \(urlString)", level: .NOTICE)
return urlString
}
Expand Down Expand Up @@ -278,7 +249,7 @@ public class KuditConnect: ObservableObject {
}
}

@Published var faqs: [KuditFAQ] // TODO: have a way of loading on demand
@Published public var faqs: [KuditFAQ] // TODO: have a way of loading on demand

// MARK: - Support Email
@Published public var customizeMessageBody: (String) -> String = {$0} // TODO: Add ability to add files and photos?
Expand Down
146 changes: 93 additions & 53 deletions Sources/UI/HTMLView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,58 @@ import SwiftUI
import WebKit
// https://alexpaul.dev/2023/01/19/rendering-web-content-in-swiftui-using-uiviewrepresentable-html-and-css/

// 1
// Create a custom `UIViewRepresentable` that will render web content.
struct HTMLView: UIViewRepresentable {
typealias UIViewType = WKWebView

// 4
// Access the `homepage.html` file that is stored in the app bundle
var fileURL: URL {
guard let url = Bundle.main.url(forResource: "homepage", withExtension: "html") else {
fatalError("path does not exist")
}
return url
}

/// Accepts a user HTML string e.g <p>SwiftUI is <b>awesome</b></p>
var htmlString: String?

func makeUIView(context: Context) -> WKWebView {
// 5
// Configure the WKWebView
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.isOpaque = false
webView.backgroundColor = .clear
// 6
// Part of the configuration is to allow for back-and-forth navigation between web pages.
webView.allowsBackForwardNavigationGestures = true
return webView
}

func updateUIView(_ uiView: WKWebView, context: Context) {
guard let htmlString else {
// 7
// Load the `homepage.html` page (has CSS styling), refer to `styles.css`
uiView.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
class HTMLViewDelegate: NSObject, ObservableObject, WKNavigationDelegate {
var handleURL: (URL) -> Void = {_ in }
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
debug("WebView decision handler for navigation action: \(navigationAction)", level: .DEBUG)
guard let url = navigationAction.request.url else {
debug("No request url in navigation action: \(navigationAction)", level: .NOTICE)
return
}
// 8
// If the user passes an HTML string this page will be rendered
uiView.loadHTMLString(htmlString, baseURL: nil)
debug("WebView Decision Handler for \(url)", level: .DEBUG)
// don't actually change views for URL. This really is only used in KuditConnect FAQ views right now, so perhaps re-work this to be more flexible in the future as a true browser.
switch navigationAction.navigationType {
case .linkActivated:
// TODO: open in safari, or open in app
handleURL(url)
decisionHandler(.cancel)
default:
decisionHandler(.allow)
break
}
/*
switch navigationAction.navigationType {
case .linkActivated:
// TODO: open in safari, or open in app
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)// It will load that link in same WKWebView
}
default:
break
}
if let url = navigationAction.request.url {
print(url.absoluteString) // It will give the selected link URL
}
decisionHandler(.allow)*/
}
}

struct HTMLView_Previews: PreviewProvider {
static var previews: some View {
VStack {
Text("Regular SwiftUI View")
.font(.headline)
.padding(.bottom, 20)
// 2
// Embed the custom `UIViewRepresentable` View which has HTML content in your SwiftUI View.
HTMLView(htmlString: """
// Create a custom `UIViewRepresentable` that will render web content.
public struct HTMLView: UIViewRepresentable {
@Environment(\.openURL) var openURL
static var testHTML = """
<html>
<head>
<style type="text/css">
body {
margin-top: 40px;
background-color: black;
color: white;
background-color: blue;
color: lime;
text-align: center;
font-size: 1.8em;
font-family: 'Inter', sans-serif;
Expand All @@ -71,7 +64,8 @@ main {
}
a {
color: white;
color: red;
text-decoration: underline;
}
.image-container {
Expand All @@ -98,15 +92,59 @@ a {
Learn more about SwiftUI <a href="https://developer.apple.com/xcode/swiftui/">here.</a>
</p>
<p>
This page is designed using CSS.</a>
This page is designed using <a href="https://www.kudit.com" target="_blank">CSS (new window)</a>.
</p>
<p>
<a href="shoutit://?message=Foobar">ShoutIt special link</a>
</p>
<div class="image-container">
<img src="https://developer.apple.com/assets/elements/icons/swiftui/swiftui-96x96_2x.png" />
</div>
</main>
</body>
</html>
""")
"""
public typealias UIViewType = WKWebView

/// Accepts a user HTML string e.g <p>SwiftUI is <b>awesome</b></p>
public var htmlString: String

@StateObject var delegate = HTMLViewDelegate()

public func makeUIView(context: Context) -> WKWebView {
// Configure the WKWebView
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.isOpaque = false
webView.backgroundColor = .clear

delegate.handleURL = {
url in
openURL(url)
}
webView.navigationDelegate = delegate

// Part of the configuration is to allow for back-and-forth navigation between web pages.
webView.allowsBackForwardNavigationGestures = true
webView.allowsLinkPreview = true
return webView
}

public func updateUIView(_ uiView: WKWebView, context: Context) {
// If the user passes an HTML string this page will be rendered
uiView.loadHTMLString(htmlString, baseURL: nil)
}
}

struct HTMLView_Previews: PreviewProvider {
static var previews: some View {
VStack {
Text("Regular SwiftUI View")
.font(.headline)
.padding(.bottom, 20)
// 2
// Embed the custom `UIViewRepresentable` View which has HTML content in your SwiftUI View.
HTMLView(htmlString: HTMLView.testHTML)
.frame(height: 500)
// 3
// SwiftUI cannot style the rendered `HTMLView`, we have to style the HTML server-side
Expand All @@ -117,5 +155,7 @@ a {
.padding(.top, 20)
}
.frame(maxHeight: .infinity)

HTMLView(htmlString: KuditConnect.shared.faqs[safe: 1]?.answerHTML(textColor: .red) ?? HTMLView.testHTML)
}
}
1 change: 1 addition & 0 deletions Sources/UI/KuditLogo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct KuditLogo_Previews: PreviewProvider {
}
}
}
.navigationViewStyle(.stack)
//.previewLayout(.fixed(width: 500, height: 500 ))
}
}
Expand Down
Loading

0 comments on commit dc10ffd

Please sign in to comment.