Преглед изворни кода

Merge pull request #1058 from deltachat/message_web_view

HTML Message view
cyBerta пре 4 година
родитељ
комит
d00db7c690

+ 11 - 0
DcCore/DcCore/DC/Wrapper.swift

@@ -263,6 +263,13 @@ public class DcContext {
         return "ErrGetMsgInfo"
     }
 
+    public func getMsgHtml(msgId: Int) -> String {
+        guard let cString = dc_get_msg_html(self.contextPointer, UInt32(msgId)) else { return "" }
+        let swiftString = String(cString: cString)
+        dc_str_unref(cString)
+        return swiftString
+    }
+
     public func deleteMessage(msgId: Int) {
         dc_delete_msgs(contextPointer, [UInt32(msgId)], 1)
     }
@@ -1015,6 +1022,10 @@ public class DcMsg {
         return dc_msg_is_setupmessage(messagePointer) == 1
     }
 
+    public var hasHtml: Bool {
+        return dc_msg_has_html(messagePointer) == 1
+    }
+
     public var setupCodeBegin: String {
         guard let cString = dc_msg_get_setupcodebegin(messagePointer) else { return "" }
         let swiftString = String(cString: cString)

+ 12 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -35,6 +35,8 @@
 		304219D3243F588500516852 /* DcCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 304219D1243F588500516852 /* DcCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		304219D92440734A00516852 /* DcMsg+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304219D82440734A00516852 /* DcMsg+Extension.swift */; };
 		304F5E44244F571C00462538 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */; };
+		304F769525DD237B0094B5E2 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304F769425DD237B0094B5E2 /* WebViewViewController.swift */; };
+		304F769925DD23D70094B5E2 /* FullMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304F769825DD23D70094B5E2 /* FullMessageViewController.swift */; };
 		3052C60A253F082E007D13EA /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3052C609253F082E007D13EA /* MessageLabelDelegate.swift */; };
 		3052C60E253F088E007D13EA /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3052C60D253F088E007D13EA /* DetectorType.swift */; };
 		3057027F24C5B2F800D84EFC /* ChatListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */; };
@@ -50,6 +52,7 @@
 		305961D22346125100C80F33 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961882346125000C80F33 /* CGRect+Extensions.swift */; };
 		3059620E234614E700C80F33 /* DcContact+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059620D234614E700C80F33 /* DcContact+Extension.swift */; };
 		305962102346154D00C80F33 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059620F2346154D00C80F33 /* String+Extension.swift */; };
+		305DDD8725DD97BF00974489 /* DynamicFontButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305DDD8625DD97BF00974489 /* DynamicFontButton.swift */; };
 		3060119C22DDE24000C1CE6F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; };
 		306011B622E5E7FB00C1CE6F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
 		30653081254358B10093E196 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30653080254358B10093E196 /* QuoteView.swift */; };
@@ -237,6 +240,8 @@
 		303492CE2587C2DC00A523D0 /* ChatInputBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInputBar.swift; sourceTree = "<group>"; };
 		304219D1243F588500516852 /* DcCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DcCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		304219D82440734A00516852 /* DcMsg+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DcMsg+Extension.swift"; sourceTree = "<group>"; };
+		304F769425DD237B0094B5E2 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = "<group>"; };
+		304F769825DD23D70094B5E2 /* FullMessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullMessageViewController.swift; sourceTree = "<group>"; };
 		3052C609253F082E007D13EA /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = "<group>"; };
 		3052C60D253F088E007D13EA /* DetectorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = "<group>"; };
 		3057027E24C5B2F800D84EFC /* ChatListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListViewModel.swift; sourceTree = "<group>"; };
@@ -248,6 +253,7 @@
 		305961882346125000C80F33 /* CGRect+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Extensions.swift"; sourceTree = "<group>"; };
 		3059620D234614E700C80F33 /* DcContact+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DcContact+Extension.swift"; sourceTree = "<group>"; };
 		3059620F2346154D00C80F33 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
+		305DDD8625DD97BF00974489 /* DynamicFontButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFontButton.swift; sourceTree = "<group>"; };
 		305FE03523A81B4C0053BE90 /* EmptyStateLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateLabel.swift; sourceTree = "<group>"; };
 		3060119D22DDE24000C1CE6F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 		3060119F22DDE24500C1CE6F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -759,6 +765,8 @@
 				AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.swift */,
 				AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */,
 				AE19887423EB264000B4CD5F /* HelpViewController.swift */,
+				304F769425DD237B0094B5E2 /* WebViewViewController.swift */,
+				304F769825DD23D70094B5E2 /* FullMessageViewController.swift */,
 				AEE6EC402282DF5700EDC689 /* MailboxViewController.swift */,
 				785BE16721E247F1003BE98C /* MessageInfoViewController.swift */,
 				7AE0A5481FC42F65005ECB4B /* NewChatViewController.swift */,
@@ -832,6 +840,7 @@
 				AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */,
 				AED62BCD247687E6009E220D /* LocationStreamingIndicator.swift */,
 				30F4BFED252E3E020006B9B3 /* PaddingTextView.swift */,
+				305DDD8625DD97BF00974489 /* DynamicFontButton.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1311,8 +1320,10 @@
 				305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */,
 				30EF7324252FF15F00E2C54A /* MessageLabel.swift in Sources */,
 				30C0D49D237C4908008E2A0E /* CertificateCheckController.swift in Sources */,
+				304F769525DD237B0094B5E2 /* WebViewViewController.swift in Sources */,
 				7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */,
 				30F9B9EC235F2116006E7ACF /* MessageCounter.swift in Sources */,
+				304F769925DD23D70094B5E2 /* FullMessageViewController.swift in Sources */,
 				AE0AA952247800E700D42A7F /* GalleryCell.swift in Sources */,
 				AE0AA958247834A400D42A7F /* Date+Extension.swift in Sources */,
 				307D822E241669C7006D2490 /* LocationManager.swift in Sources */,
@@ -1321,6 +1332,7 @@
 				7A451DB01FB1F84900177250 /* AppCoordinator.swift in Sources */,
 				AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */,
 				AE9DAF0D22C1215D004C9591 /* EditContactController.swift in Sources */,
+				305DDD8725DD97BF00974489 /* DynamicFontButton.swift in Sources */,
 				785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */,
 				AED423D3249F578B00B6B2BB /* AddGroupMembersViewController.swift in Sources */,
 				AE851AC5227C755A00ED86F0 /* Protocols.swift in Sources */,

+ 9 - 0
deltachat-ios/Chat/ChatViewController.swift

@@ -1269,6 +1269,15 @@ class ChatViewController: UITableViewController {
 // MARK: - BaseMessageCellDelegate
 extension ChatViewController: BaseMessageCellDelegate {
 
+    @objc func fullMessageTapped(indexPath: IndexPath) {
+        if handleUIMenu() || handleSelection(indexPath: indexPath) {
+            return
+        }
+        let msg = DcMsg(id: messageIds[indexPath.row])
+        let fullMessageViewController = FullMessageViewController(dcContext: dcContext, messageId: msg.id)
+        navigationController?.pushViewController(fullMessageViewController, animated: true)
+    }
+
     @objc func quoteTapped(indexPath: IndexPath) {
         if handleSelection(indexPath: indexPath) { return }
         _ = handleUIMenu()

+ 50 - 6
deltachat-ios/Chat/Views/Cells/BaseMessageCell.swift

@@ -10,10 +10,11 @@ public class BaseMessageCell: UITableViewCell {
     private var trailingConstraintCurrentSender: NSLayoutConstraint?
     private var mainContentBelowTopLabelConstraint: NSLayoutConstraint?
     private var mainContentUnderTopLabelConstraint: NSLayoutConstraint?
-    private var mainContentAboveBottomLabelConstraint: NSLayoutConstraint?
+    private var mainContentAboveFullMessageBtnConstraint: NSLayoutConstraint?
     private var mainContentUnderBottomLabelConstraint: NSLayoutConstraint?
     private var mainContentViewLeadingConstraint: NSLayoutConstraint?
     private var mainContentViewTrailingConstraint: NSLayoutConstraint?
+    private var fullMessageZeroHeightConstraint: NSLayoutConstraint?
 
     public var mainContentViewHorizontalPadding: CGFloat {
         set {
@@ -42,7 +43,7 @@ public class BaseMessageCell: UITableViewCell {
     // if set to true bottomLabel overlaps the main content
     public var bottomCompactView: Bool {
         set {
-            mainContentAboveBottomLabelConstraint?.isActive = !newValue
+            mainContentAboveFullMessageBtnConstraint?.isActive = !newValue
             mainContentUnderBottomLabelConstraint?.isActive = newValue
             bottomLabel.backgroundColor = newValue ?
                 UIColor(alpha: 200, red: 50, green: 50, blue: 50) :
@@ -53,6 +54,18 @@ public class BaseMessageCell: UITableViewCell {
         }
     }
 
+    public var isFullMessageButtonHidden: Bool {
+        set {
+            mainContentAboveFullMessageBtnConstraint?.constant = newValue ? -2 : 8
+            fullMessageButton.setTitle(newValue ? "" : String.localized("show_full_message"), for: .normal)
+            fullMessageZeroHeightConstraint?.isActive = newValue
+            fullMessageButton.isHidden = newValue
+        }
+        get {
+            return fullMessageButton.isHidden
+        }
+    }
+
     public weak var baseDelegate: BaseMessageCellDelegate?
 
     public lazy var quoteView: QuoteView = {
@@ -113,6 +126,21 @@ public class BaseMessageCell: UITableViewCell {
         return view
     }()
 
+    lazy var fullMessageButton: DynamicFontButton = {
+        let button = DynamicFontButton()
+        button.translatesAutoresizingMaskIntoConstraints = false
+        button.setTitleColor(.systemBlue, for: .normal)
+        button.setTitleColor(.gray, for: .highlighted)
+        button.titleLabel?.lineBreakMode = .byWordWrapping
+        button.titleLabel?.textAlignment = .left
+        button.addTarget(self, action: #selector(onFullMessageButtonTapped), for: .touchUpInside)
+        button.titleLabel?.font = UIFont.preferredFont(for: .body, weight: .regular)
+        button.titleLabel?.adjustsFontForContentSizeCategory = true
+        button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0)
+        button.accessibilityLabel = String.localized("show_full_message")
+        return button
+    }()
+
     lazy var bottomLabel: PaddingTextView = {
         let label = PaddingTextView()
         label.translatesAutoresizingMaskIntoConstraints = false
@@ -154,6 +182,7 @@ public class BaseMessageCell: UITableViewCell {
         contentView.addSubview(messageBackgroundContainer)
         messageBackgroundContainer.addSubview(mainContentView)
         messageBackgroundContainer.addSubview(topLabel)
+        messageBackgroundContainer.addSubview(fullMessageButton)
         messageBackgroundContainer.addSubview(bottomLabel)
         contentView.addSubview(avatarView)
 
@@ -165,11 +194,14 @@ public class BaseMessageCell: UITableViewCell {
             topLabel.constraintAlignTopTo(messageBackgroundContainer, paddingTop: 6),
             topLabel.constraintAlignLeadingTo(messageBackgroundContainer, paddingLeading: 8),
             topLabel.constraintAlignTrailingMaxTo(messageBackgroundContainer, paddingTrailing: 8),
-            bottomLabel.constraintAlignBottomTo(messageBackgroundContainer, paddingBottom: 6),
             messageBackgroundContainer.constraintAlignTopTo(contentView, paddingTop: 3),
             messageBackgroundContainer.constraintAlignBottomTo(contentView, paddingBottom: 3),
+            fullMessageButton.constraintAlignLeadingTo(messageBackgroundContainer, paddingLeading: 12),
+            fullMessageButton.constraintAlignTrailingMaxTo(messageBackgroundContainer, paddingTrailing: 12),
             bottomLabel.constraintAlignLeadingMaxTo(messageBackgroundContainer, paddingLeading: 8),
-            bottomLabel.constraintAlignTrailingTo(messageBackgroundContainer, paddingTrailing: 8)
+            bottomLabel.constraintAlignTrailingTo(messageBackgroundContainer, paddingTrailing: 8),
+            bottomLabel.constraintToBottomOf(fullMessageButton, priority: .defaultHigh),
+            bottomLabel.constraintAlignBottomTo(messageBackgroundContainer, paddingBottom: 6)
         ])
 
         leadingConstraint = messageBackgroundContainer.constraintAlignLeadingTo(contentView, paddingLeading: 6)
@@ -185,11 +217,14 @@ public class BaseMessageCell: UITableViewCell {
 
         mainContentBelowTopLabelConstraint = mainContentView.constraintToBottomOf(topLabel, paddingTop: 6)
         mainContentUnderTopLabelConstraint = mainContentView.constraintAlignTopTo(messageBackgroundContainer)
-        mainContentAboveBottomLabelConstraint = bottomLabel.constraintToBottomOf(mainContentView, paddingTop: -2, priority: .defaultHigh)
+        mainContentAboveFullMessageBtnConstraint = fullMessageButton.constraintToBottomOf(mainContentView, paddingTop: 8, priority: .defaultHigh)
         mainContentUnderBottomLabelConstraint = mainContentView.constraintAlignBottomTo(messageBackgroundContainer, paddingBottom: 0, priority: .defaultHigh)
 
+        fullMessageZeroHeightConstraint = fullMessageButton.constraintHeightTo(0)
+
         topCompactView = false
         bottomCompactView = false
+        isFullMessageButtonHidden = true
         
 
         let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onAvatarTapped))
@@ -228,6 +263,12 @@ public class BaseMessageCell: UITableViewCell {
         }
     }
 
+    @objc func onFullMessageButtonTapped() {
+        if let tableView = self.superview as? UITableView, let indexPath = tableView.indexPath(for: self) {
+            baseDelegate?.fullMessageTapped(indexPath: indexPath)
+        }
+    }
+
     // update classes inheriting BaseMessageCell first before calling super.update(...)
     func update(msg: DcMsg, messageStyle: UIRectCorner, isAvatarVisible: Bool, isGroup: Bool) {
         if msg.isFromCurrentSender {
@@ -267,6 +308,8 @@ public class BaseMessageCell: UITableViewCell {
             avatarView.isHidden = true
         }
 
+        isFullMessageButtonHidden = !msg.hasHtml
+
         messageBackgroundContainer.update(rectCorners: messageStyle,
                                           color: msg.isFromCurrentSender ? DcColors.messagePrimaryColor : DcColors.messageSecondaryColor)
 
@@ -279,7 +322,7 @@ public class BaseMessageCell: UITableViewCell {
             quoteView.quote.text = quoteText
 
             if let quoteMsg = msg.quoteMessage {
-                quoteView.imagePreview.image = quoteMsg.image
+                quoteView.setImagePreview(quoteMsg.image)
                 if quoteMsg.isForwarded {
                     quoteView.senderTitle.text = String.localized("forwarded_message")
                     quoteView.senderTitle.textColor = DcColors.grayDateColor
@@ -518,4 +561,5 @@ public protocol BaseMessageCellDelegate: class {
     func avatarTapped(indexPath: IndexPath)
     func textTapped(indexPath: IndexPath)
     func quoteTapped(indexPath: IndexPath)
+    func fullMessageTapped(indexPath: IndexPath)
 }

+ 1 - 1
deltachat-ios/Chat/Views/QuotePreview.swift

@@ -33,7 +33,7 @@ public class QuotePreview: DraftPreview {
             compactView = draft.attachment != nil
             calculateQuoteHeight(compactView: compactView)
             if let quoteMessage = draft.quoteMessage {
-                quoteView.imagePreview.image = quoteMessage.image
+                quoteView.setImagePreview(quoteMessage.image)
                 if quoteMessage.isForwarded {
                     quoteView.senderTitle.text = String.localized("forwarded_message")
                     quoteView.senderTitle.textColor = DcColors.grayDateColor

+ 16 - 2
deltachat-ios/Chat/Views/QuoteView.swift

@@ -30,7 +30,7 @@ public class QuoteView: UIView {
         return view
     }()
 
-    public lazy var imagePreview: UIImageView = {
+    private lazy var imagePreview: UIImageView = {
         let view = UIImageView()
         view.translatesAutoresizingMaskIntoConstraints = false
         view.contentMode = .scaleAspectFill
@@ -39,6 +39,8 @@ public class QuoteView: UIView {
         return view
     }()
 
+    private var imageWidthConstraint: NSLayoutConstraint?
+
     init() {
         super.init(frame: .zero)
         setupSubviews()
@@ -58,7 +60,6 @@ public class QuoteView: UIView {
         addConstraints([
             imagePreview.constraintAlignTrailingTo(self, paddingTrailing: 16),
             imagePreview.constraintHeightTo(36),
-            imagePreview.constraintWidthTo(36),
             imagePreview.constraintCenterYTo(citeBar),
             imagePreview.constraintAlignTopMaxTo(self),
             senderTitle.constraintAlignTopTo(self),
@@ -73,6 +74,8 @@ public class QuoteView: UIView {
             citeBar.constraintAlignBottomTo(quote, paddingBottom: 2),
             citeBar.constraintWidthTo(3),
         ])
+        imageWidthConstraint = imagePreview.constraintWidthTo(0)
+        imageWidthConstraint?.isActive = true
     }
 
     public func configureAccessibilityLabel() -> String {
@@ -98,5 +101,16 @@ public class QuoteView: UIView {
         senderTitle.attributedText = nil
         citeBar.backgroundColor = DcColors.grayDateColor
         imagePreview.image = nil
+        imageWidthConstraint?.constant = 0
+    }
+
+    public func setImagePreview(_ image: UIImage?) {
+        if let image = image {
+            imageWidthConstraint?.constant = 36
+            imagePreview.image = image
+        } else {
+            imageWidthConstraint?.constant = 0
+        }
+
     }
 }

+ 137 - 0
deltachat-ios/Controller/FullMessageViewController.swift

@@ -0,0 +1,137 @@
+import UIKit
+import WebKit
+import DcCore
+
+class FullMessageViewController: WebViewViewController {
+
+    var loadButton: UIBarButtonItem {
+        let button = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.refresh, target: self, action: #selector(showLoadOptions))
+        button.accessibilityLabel = String.localized("load_remote_content")
+        button.tintColor = DcColors.primary
+        return button
+    }
+
+    var messageId: Int
+    var dcContext: DcContext
+    private var loadContentOnce = false
+
+    // Block just everything :)
+    let blockRules = """
+    [
+        {
+            "trigger": {
+                "url-filter": ".*"
+            },
+            "action": {
+                "type": "block"
+            }
+        }
+    ]
+    """
+    
+
+    init(dcContext: DcContext, messageId: Int) {
+        self.dcContext = dcContext
+        self.messageId = messageId
+        super.init()
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.title = String.localized("chat_input_placeholder")
+        self.navigationItem.rightBarButtonItem = loadButton
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        if UserDefaults.standard.bool(forKey: "html_load_remote_content") {
+            loadHtml()
+        } else {
+            loadRestrictedHtml()
+        }
+    }
+
+    @objc private func showLoadOptions() {
+        let checkmark = "✔︎ "
+        var onceCheckmark = ""
+        var neverCheckmark = ""
+        var alwaysCheckmark = ""
+        if loadContentOnce {
+            onceCheckmark = checkmark
+        } else if UserDefaults.standard.bool(forKey: "html_load_remote_content") {
+            alwaysCheckmark = checkmark
+        } else {
+            neverCheckmark = checkmark
+        }
+
+        let alert = UIAlertController(title: String.localized("load_remote_content"),
+                                      message: String.localized("load_remote_content_ask"),
+                                      preferredStyle: .safeActionSheet)
+        let alwaysAction = UIAlertAction(title: "\(alwaysCheckmark)\(String.localized("always"))", style: .default, handler: alwaysActionPressed(_:))
+        let neverAction = UIAlertAction(title: "\(neverCheckmark)\(String.localized("never"))", style: .default, handler: neverActionPressed(_:))
+        let onceAction = UIAlertAction(title: "\(onceCheckmark)\(String.localized("once"))", style: .default, handler: onceActionPressed(_:))
+
+
+        alert.addAction(onceAction)
+        alert.addAction(alwaysAction)
+        alert.addAction(neverAction)
+        alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
+        self.present(alert, animated: true, completion: nil)
+    }
+
+    @objc func alwaysActionPressed(_ action: UIAlertAction) {
+        UserDefaults.standard.set(true, forKey: "html_load_remote_content")
+        UserDefaults.standard.synchronize()
+        loadContentOnce = false
+        loadUnrestricedHtml()
+    }
+
+    @objc func onceActionPressed(_ action: UIAlertAction) {
+        UserDefaults.standard.set(false, forKey: "html_load_remote_content")
+        UserDefaults.standard.synchronize()
+        loadContentOnce = true
+        loadUnrestricedHtml()
+    }
+
+    @objc func neverActionPressed(_ action: UIAlertAction) {
+        UserDefaults.standard.set(false, forKey: "html_load_remote_content")
+        UserDefaults.standard.synchronize()
+        loadContentOnce = false
+        loadRestrictedHtml()
+    }
+
+    private func loadUnrestricedHtml() {
+        let configuration = self.webView.configuration
+        configuration.userContentController.removeAllContentRuleLists()
+        loadHtml()
+    }
+
+    private func loadRestrictedHtml() {
+        WKContentRuleListStore.default().compileContentRuleList(
+                forIdentifier: "ContentBlockingRules",
+                encodedContentRuleList: blockRules) { (contentRuleList, error) in
+
+            guard let contentRuleList = contentRuleList, error == nil else {
+                return
+            }
+
+            let configuration = self.webView.configuration
+            configuration.userContentController.add(contentRuleList)
+            self.loadHtml()
+        }
+    }
+
+    private func loadHtml() {
+        DispatchQueue.global(qos: .background).async { [weak self] in
+            guard let self = self else { return }
+            let html = self.dcContext.getMsgHtml(msgId: self.messageId)
+            DispatchQueue.main.async {
+                self.webView.loadHTMLString(html, baseURL: nil)
+            }
+        }
+    }
+}

+ 3 - 53
deltachat-ios/Controller/HelpViewController.swift

@@ -1,45 +1,16 @@
 import UIKit
 import WebKit
 
-class HelpViewController: UIViewController, WKNavigationDelegate {
+class HelpViewController: WebViewViewController {
 
-    private lazy var webView: WKWebView = {
-        let view = WKWebView()
-        view.navigationDelegate = self
-        return view
-    }()
-
-    init() {
-        super.init(nibName: nil, bundle: nil)
-        hidesBottomBarWhenPushed = true
-    }
-
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
-        if navigationAction.navigationType == .linkActivated {
-            if let url = navigationAction.request.url,
-                url.host != nil,
-                UIApplication.shared.canOpenURL(url) {
-                UIApplication.shared.open(url)
-                decisionHandler(.cancel)
-                return
-            }
-        }
-        decisionHandler(.allow)
-    }
-
-    // MARK: - lifecycle
     override func viewDidLoad() {
         super.viewDidLoad()
-        view.backgroundColor = .white //DcColors.defaultBackgroundColor
+        view.backgroundColor = .white
         self.title = String.localized("menu_help")
-        setupSubviews()
     }
 
     override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
         loadHtmlContent { [weak self] url in
             // return to main thread
             DispatchQueue.main.async {
@@ -48,27 +19,6 @@ class HelpViewController: UIViewController, WKNavigationDelegate {
         }
     }
 
-    // MARK: - setup + configuration
-    private func setupSubviews() {
-        view.addSubview(webView)
-        webView.translatesAutoresizingMaskIntoConstraints = false
-        webView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
-
-        if #available(iOS 11, *) {
-            webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
-            webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
-
-            //webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
-        } else {
-            webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
-            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
-
-           // webView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
-        }
-        webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
-        webView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
-    }
-
     private func loadHtmlContent(completionHandler: ((URL) -> Void)?) {
         // execute in background thread because file loading would blockui for a few milliseconds
         DispatchQueue.global(qos: .background).async {

+ 55 - 0
deltachat-ios/Controller/WebViewViewController.swift

@@ -0,0 +1,55 @@
+import UIKit
+import WebKit
+
+class WebViewViewController: UIViewController, WKNavigationDelegate {
+
+    public lazy var webView: WKWebView = {
+        let preferences = WKPreferences()
+        preferences.javaScriptEnabled = false
+
+        let configuration = WKWebViewConfiguration()
+        configuration.preferences = preferences
+
+        let view = WKWebView(frame: .zero, configuration: configuration)
+        view.navigationDelegate = self
+        return view
+    }()
+
+    init() {
+        super.init(nibName: nil, bundle: nil)
+        hidesBottomBarWhenPushed = true
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+        if navigationAction.navigationType == .linkActivated,
+           let url = navigationAction.request.url,
+            url.host != nil,
+            UIApplication.shared.canOpenURL(url) {
+            UIApplication.shared.open(url)
+            decisionHandler(.cancel)
+            return
+        }
+        decisionHandler(.allow)
+    }
+
+    // MARK: - lifecycle
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        setupSubviews()
+    }
+
+
+    // MARK: - setup + configuration
+    private func setupSubviews() {
+        view.addSubview(webView)
+        webView.translatesAutoresizingMaskIntoConstraints = false
+        webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
+        webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
+        webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
+        webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
+    }
+}

+ 18 - 0
deltachat-ios/View/DynamicFontButton.swift

@@ -0,0 +1,18 @@
+import UIKit
+
+public class DynamicFontButton: UIButton {
+
+    override public var intrinsicContentSize: CGSize {
+        if let size = self.titleLabel?.intrinsicContentSize {
+            return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right,
+                          height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
+        }
+
+        return super.intrinsicContentSize
+    }
+
+    override public func layoutSubviews() {
+        super.layoutSubviews()
+        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
+    }
+}