mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-30 18:35:49 +00:00
ios: view conditions as markdown (#5248)
* ios: view conditions as markdown * changes * removed Down * refactor * unused * react on theme change
This commit is contained in:
committed by
GitHub
parent
345e0acdec
commit
25893177d0
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// ConditionsWebView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Stanislav Dmitrenko on 26.11.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
|
||||
struct ConditionsWebView: UIViewRepresentable {
|
||||
@State var html: String
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State var pageLoaded = false
|
||||
|
||||
func makeUIView(context: Context) -> WKWebView {
|
||||
let view = WKWebView()
|
||||
view.backgroundColor = .clear
|
||||
view.isOpaque = false
|
||||
view.navigationDelegate = context.coordinator
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
// just to make sure that even if updateUIView will not be called for any reason, the page
|
||||
// will be rendered anyway
|
||||
if !pageLoaded {
|
||||
loadPage(view)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ view: WKWebView, context: Context) {
|
||||
loadPage(view)
|
||||
}
|
||||
|
||||
private func loadPage(_ webView: WKWebView) {
|
||||
let styles = """
|
||||
<style>
|
||||
body {
|
||||
color: \(theme.colors.onBackground.toHTMLHex());
|
||||
font-family: Helvetica;
|
||||
}
|
||||
a {
|
||||
color: \(theme.colors.primary.toHTMLHex());
|
||||
}
|
||||
code, pre {
|
||||
font-family: Menlo;
|
||||
background: \(theme.colors.secondary.opacity(theme.colors.isLight ? 0.2 : 0.3).toHTMLHex());
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
let head = "<head><meta name='viewport' content='width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=no'>\(styles)</head>"
|
||||
webView.loadHTMLString(head + html, baseURL: nil)
|
||||
DispatchQueue.main.async {
|
||||
pageLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Cordinator {
|
||||
Cordinator()
|
||||
}
|
||||
|
||||
class Cordinator: NSObject, WKNavigationDelegate {
|
||||
func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
|
||||
guard let url = navigationAction.request.url else { return decisionHandler(.allow) }
|
||||
|
||||
switch navigationAction.navigationType {
|
||||
case .linkActivated:
|
||||
decisionHandler(.cancel)
|
||||
if url.absoluteString.starts(with: "https://simplex.chat/contact#") {
|
||||
ChatModel.shared.appOpenUrl = url
|
||||
} else {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
default:
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
import Ink
|
||||
|
||||
struct OperatorView: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@@ -342,6 +343,7 @@ struct OperatorInfoView: View {
|
||||
struct ConditionsTextView: View {
|
||||
@State private var conditionsData: (UsageConditions, String?, UsageConditions?)?
|
||||
@State private var failedToLoad: Bool = false
|
||||
@State private var conditionsHTML: String? = nil
|
||||
|
||||
let defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
|
||||
|
||||
@@ -350,7 +352,18 @@ struct ConditionsTextView: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.task {
|
||||
do {
|
||||
conditionsData = try await getUsageConditions()
|
||||
let conditions = try await getUsageConditions()
|
||||
let conditionsText = conditions.1
|
||||
let parentLink = "https://github.com/simplex-chat/simplex-chat/blob/\(conditions.0.conditionsCommit)"
|
||||
let preparedText: String?
|
||||
if let conditionsText {
|
||||
let prepared = prepareMarkdown(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines), parentLink)
|
||||
conditionsHTML = MarkdownParser().html(from: prepared)
|
||||
preparedText = prepared
|
||||
} else {
|
||||
preparedText = nil
|
||||
}
|
||||
conditionsData = (conditions.0, preparedText, conditions.2)
|
||||
} catch let error {
|
||||
logger.error("ConditionsTextView getUsageConditions error: \(responseError(error))")
|
||||
failedToLoad = true
|
||||
@@ -358,18 +371,16 @@ struct ConditionsTextView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Markdown & diff rendering
|
||||
// TODO Diff rendering
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if let (usageConditions, conditionsText, acceptedConditions) = conditionsData {
|
||||
if let conditionsText = conditionsText {
|
||||
ScrollView {
|
||||
Text(conditionsText.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
.padding()
|
||||
}
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
)
|
||||
if let (usageConditions, _, _) = conditionsData {
|
||||
if let conditionsHTML {
|
||||
ConditionsWebView(html: conditionsHTML)
|
||||
.padding(6)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
)
|
||||
} else {
|
||||
let conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/\(usageConditions.conditionsCommit)/PRIVACY.md"
|
||||
conditionsLinkView(conditionsLink)
|
||||
@@ -391,6 +402,16 @@ struct ConditionsTextView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func prepareMarkdown(_ text: String, _ parentLink: String) -> String {
|
||||
let localLinkRegex = try! NSRegularExpression(pattern: "\\[([^\\(]*)\\]\\(#.*\\)")
|
||||
let h1Regex = try! NSRegularExpression(pattern: "^# ")
|
||||
var text = localLinkRegex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "$1")
|
||||
text = h1Regex.stringByReplacingMatches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count), withTemplate: "")
|
||||
return text
|
||||
.replacingOccurrences(of: "](/", with: "](\(parentLink)/")
|
||||
.replacingOccurrences(of: "](./", with: "](\(parentLink)/")
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleOperatorUsageConditionsView: View {
|
||||
|
||||
@@ -199,6 +199,8 @@
|
||||
8C8118722C220B5B00E6FC94 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 8C8118712C220B5B00E6FC94 /* Yams */; };
|
||||
8C81482C2BD91CD4002CBEC3 /* AudioDevicePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C81482B2BD91CD4002CBEC3 /* AudioDevicePicker.swift */; };
|
||||
8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; };
|
||||
8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 8CB3476B2CF5CFFA006787A5 /* Ink */; };
|
||||
8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */; };
|
||||
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; };
|
||||
8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; };
|
||||
8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; };
|
||||
@@ -547,6 +549,7 @@
|
||||
8C852B072C1086D100BA61E8 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
|
||||
8C86EBE42C0DAE4F00E12243 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
|
||||
8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeModeEditor.swift; sourceTree = "<group>"; };
|
||||
8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsWebView.swift; sourceTree = "<group>"; };
|
||||
8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallAudioDeviceManager.swift; sourceTree = "<group>"; };
|
||||
8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = "<group>"; };
|
||||
8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = "<group>"; };
|
||||
@@ -636,6 +639,7 @@
|
||||
files = (
|
||||
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */,
|
||||
8C8118722C220B5B00E6FC94 /* Yams in Frameworks */,
|
||||
8CB3476C2CF5CFFA006787A5 /* Ink in Frameworks */,
|
||||
D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */,
|
||||
D7197A1829AE89660055C05A /* WebRTC in Frameworks */,
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */,
|
||||
@@ -1072,6 +1076,7 @@
|
||||
643B3B4D2CCFD6400083A2CF /* OperatorView.swift */,
|
||||
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
|
||||
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
|
||||
8CB3476D2CF5F58B006787A5 /* ConditionsWebView.swift */,
|
||||
);
|
||||
path = NetworkAndServers;
|
||||
sourceTree = "<group>";
|
||||
@@ -1183,6 +1188,7 @@
|
||||
D7F0E33829964E7E0068AF69 /* LZString */,
|
||||
D7197A1729AE89660055C05A /* WebRTC */,
|
||||
8C8118712C220B5B00E6FC94 /* Yams */,
|
||||
8CB3476B2CF5CFFA006787A5 /* Ink */,
|
||||
);
|
||||
productName = "SimpleX (iOS)";
|
||||
productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */;
|
||||
@@ -1326,6 +1332,7 @@
|
||||
D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */,
|
||||
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */,
|
||||
8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */,
|
||||
8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */,
|
||||
);
|
||||
productRefGroup = 5CA059CB279559F40002BEB4 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -1516,6 +1523,7 @@
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5C971E1D27AEBEF600C8A3CE /* ChatInfoView.swift in Sources */,
|
||||
5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */,
|
||||
8CB3476E2CF5F58B006787A5 /* ConditionsWebView.swift in Sources */,
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
|
||||
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */,
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
@@ -2375,6 +2383,14 @@
|
||||
version = 5.1.2;
|
||||
};
|
||||
};
|
||||
8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/johnsundell/ink";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 0.6.0;
|
||||
};
|
||||
};
|
||||
D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/simplex-chat/WebRTC.git";
|
||||
@@ -2412,6 +2428,11 @@
|
||||
package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
productName = Yams;
|
||||
};
|
||||
8CB3476B2CF5CFFA006787A5 /* Ink */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 8CB3476A2CF5CFFA006787A5 /* XCRemoteSwiftPackageReference "ink" */;
|
||||
productName = Ink;
|
||||
};
|
||||
CE38A29B2C3FCD72005ED185 /* SwiftyGif */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115",
|
||||
"originHash" : "33afc44be5f4225325b3cb940ed71b6cbf3ef97290d348d7b6803697bcd0637d",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "codescanner",
|
||||
@@ -10,6 +10,15 @@
|
||||
"version" : "2.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "ink",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/johnsundell/ink",
|
||||
"state" : {
|
||||
"revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b",
|
||||
"version" : "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lzstring-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@@ -63,6 +63,23 @@ extension Color {
|
||||
)
|
||||
}
|
||||
|
||||
public func toHTMLHex() -> String {
|
||||
let uiColor: UIColor = .init(self)
|
||||
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
|
||||
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
// Can be negative values and more than 1. Extended color range, making it normal
|
||||
r = min(1, max(0, r))
|
||||
g = min(1, max(0, g))
|
||||
b = min(1, max(0, b))
|
||||
a = min(1, max(0, a))
|
||||
return String(format: "#%02x%02x%02x%02x",
|
||||
Int((r * 255).rounded()),
|
||||
Int((g * 255).rounded()),
|
||||
Int((b * 255).rounded()),
|
||||
Int((a * 255).rounded())
|
||||
)
|
||||
}
|
||||
|
||||
public func darker(_ factor: CGFloat = 0.1) -> Color {
|
||||
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
|
||||
UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
|
||||
Reference in New Issue
Block a user