diff --git a/ownCloud/Client/Actions/Action+UserInterface.swift b/ownCloud/Client/Actions/Action+UserInterface.swift index a04d44669..4bc1d35e7 100644 --- a/ownCloud/Client/Actions/Action+UserInterface.swift +++ b/ownCloud/Client/Actions/Action+UserInterface.swift @@ -32,6 +32,11 @@ extension Action { let moreViewController = FrameViewController(header: header, viewController: tableViewController) let actions = Action.sortedApplicableActions(for: context) + moreViewController.watermark( + username: core.bookmark.userName, + userMail: core.bookmark.user?.emailAddress + ) + actions.forEach({ $0.actionWillRunHandler = { [weak moreViewController] (_ donePreparing: @escaping () -> Void) in moreViewController?.dismiss(animated: true, completion: donePreparing) diff --git a/ownCloud/Client/Viewer/DisplayHostViewController.swift b/ownCloud/Client/Viewer/DisplayHostViewController.swift index 94b18e332..7d310ef3b 100644 --- a/ownCloud/Client/Viewer/DisplayHostViewController.swift +++ b/ownCloud/Client/Viewer/DisplayHostViewController.swift @@ -128,6 +128,12 @@ class DisplayHostViewController: UIPageViewController { override func viewDidLoad() { super.viewDidLoad() + watermark( + username: self.clientContext?.core?.bookmark.userName, + userMail: self.clientContext?.core?.bookmark.user?.emailAddress + ) + + self.dataSource = self self.delegate = self diff --git a/ownCloud/Resources/Localizable.xcstrings b/ownCloud/Resources/Localizable.xcstrings index 5b723c6fd..759ba3ca4 100644 --- a/ownCloud/Resources/Localizable.xcstrings +++ b/ownCloud/Resources/Localizable.xcstrings @@ -72450,6 +72450,75 @@ } } }, + "ScreenshotNotificationButton" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أنا أفهم" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verstanden" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "I understand" + } + } + } + }, + "ScreenshotNotificationMessage" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : " تم اكتشاف لقطة شاشة، ولا يسمح بلقطات الشاشة وتسجيلات الشاشة لهذا التطبيق" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Capture Detected, Screenshots und Bildschirmaufnahmen dieser Anwendung sind nicht erlaubt." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screen Capture Detected, Screenshots and screen recordings of this app are not allowed." + } + } + } + }, + "ScreenshotNotificationTitle" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screenshot taken" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bildschirmaufnahme gemacht" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screenshot taken" + } + } + } + }, "Scroll to Bottom": { "extractionState": "manual", "localizations": { diff --git a/ownCloudAppShared/Client/Sharing/ShareViewController.swift b/ownCloudAppShared/Client/Sharing/ShareViewController.swift index f663a80fb..384ffc11f 100644 --- a/ownCloudAppShared/Client/Sharing/ShareViewController.swift +++ b/ownCloudAppShared/Client/Sharing/ShareViewController.swift @@ -251,6 +251,7 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe expirationDate = Date(timeIntervalSinceNow: numberOfDays.doubleValue * (24 * 60 * 60)) } } + observeScreenshotEvent() } required public init?(coder: NSCoder) { @@ -335,6 +336,12 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe } updateState() + + watermark( + username: self.clientContext?.core?.bookmark.userName, + userMail: self.clientContext?.core?.bookmark.user?.emailAddress + ) + } // MARK: - Share Role & permissions @@ -1073,4 +1080,9 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe } } } + + deinit { + stopObserveScreenshotEvent() + } + } diff --git a/ownCloudAppShared/Client/Sharing/SharingViewController.swift b/ownCloudAppShared/Client/Sharing/SharingViewController.swift index 2588142ef..9b4767126 100644 --- a/ownCloudAppShared/Client/Sharing/SharingViewController.swift +++ b/ownCloudAppShared/Client/Sharing/SharingViewController.swift @@ -229,6 +229,16 @@ open class SharingViewController: CollectionViewController { } } + override open func viewDidLoad() { + super.viewDidLoad() + observeScreenshotEvent() + watermark( + username: self.clientContext?.core?.bookmark.userName, + userMail: self.clientContext?.core?.bookmark.user?.emailAddress + ) + } + + deinit { if let core = clientContext?.core, let itemSharesQuery { core.stop(itemSharesQuery) diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift index 2bba79f2d..96363d4a8 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -334,6 +334,11 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, public override func viewDidLoad() { super.viewDidLoad() + watermark( + username: self.clientContext?.accountConnection?.core?.connection.loggedInUser?.userName, + userMail: self.clientContext?.accountConnection?.core?.connection.loggedInUser?.emailAddress + ) + // Add navigation bar button items updateNavigationBarButtonItems() diff --git a/ownCloudAppShared/Client/View Controllers/ClientSharedByMeViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientSharedByMeViewController.swift index cdd029987..f82a9be0a 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientSharedByMeViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientSharedByMeViewController.swift @@ -49,6 +49,12 @@ class ClientSharedByMeViewController: CollectionViewController { override func viewDidLoad() { super.viewDidLoad() + observeScreenshotEvent() + watermark( + username: self.clientContext?.core?.bookmark.userName, + userMail: self.clientContext?.core?.bookmark.user?.emailAddress + ) + // Disable dragging of items, so keyboard control does // not include "Drag Item" in the accessibility actions // invoked with Tab + Z @@ -120,4 +126,8 @@ class ClientSharedByMeViewController: CollectionViewController { setCoverView(coverView, layout: .top) } + deinit { + stopObserveScreenshotEvent() + } + } diff --git a/ownCloudAppShared/Client/View Controllers/ClientSharedWithMeViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientSharedWithMeViewController.swift index 4fca7bd4a..3f77102cf 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientSharedWithMeViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientSharedWithMeViewController.swift @@ -45,6 +45,12 @@ class ClientSharedWithMeViewController: CollectionViewController { override func viewDidLoad() { super.viewDidLoad() + observeScreenshotEvent() + watermark( + username: self.clientContext?.core?.bookmark.userName, + userMail: self.clientContext?.core?.bookmark.user?.emailAddress + ) + // Disable dragging of items, so keyboard control does // not include "Drag Item" in the accessibility actions // invoked with Tab + Z @@ -123,4 +129,8 @@ class ClientSharedWithMeViewController: CollectionViewController { setCoverView(coverView, layout: .top) } + + deinit { + stopObserveScreenshotEvent() + } } diff --git a/ownCloudAppShared/Tools/VendorServices.swift b/ownCloudAppShared/Tools/VendorServices.swift index 69be0a1a6..1bb1e2582 100644 --- a/ownCloudAppShared/Tools/VendorServices.swift +++ b/ownCloudAppShared/Tools/VendorServices.swift @@ -103,6 +103,58 @@ public class VendorServices : NSObject { return false } + public var watermarkEnabled: Bool { + if let watermarkEnabled = self.classSetting(forOCClassSettingsKey: .watermarkEnabled) as? Bool { + return watermarkEnabled + } + + return false + } + + public var watermarkOpacity: Int { + if let watermarkOpacity = self.classSetting(forOCClassSettingsKey: .watermarkOpacity) as? Int { + return watermarkOpacity + } + + return 100 + } + + public var watermarkFontSize: Int { + if let watermarkFontSize = self.classSetting(forOCClassSettingsKey: .watermarkFontSize) as? Int { + return watermarkFontSize + } + + return 15 + } + + public var watermarkText: String? { + return self.classSetting(forOCClassSettingsKey: .watermarkText) as? String + } + + public var watermarkShowMail: Bool { + if let watermarkShowMail = self.classSetting(forOCClassSettingsKey: .watermarkShowMail) as? Bool { + return watermarkShowMail + } + + return false + } + + public var watermarkShowDate: Bool { + if let watermarkShowDate = self.classSetting(forOCClassSettingsKey: .watermarkShowDate) as? Bool { + return watermarkShowDate + } + + return false + } + + public var showScreenshotNotification: Bool { + if let showScreenshotNotification = self.classSetting(forOCClassSettingsKey: .showScreenshotNotification) as? Bool { + return showScreenshotNotification + } + + return false + } + static public var shared : VendorServices = { return VendorServices() }() @@ -151,6 +203,14 @@ public extension OCClassSettingsKey { static let appStoreLink = OCClassSettingsKey("app-store-link") static let recommendToFriendEnabled = OCClassSettingsKey("recommend-to-friend-enabled") + + static let watermarkEnabled = OCClassSettingsKey("watermark-enabled") + static let watermarkOpacity = OCClassSettingsKey("watermark-opacity") + static let watermarkFontSize = OCClassSettingsKey("watermark-font-size") + static let watermarkText = OCClassSettingsKey("watermark-text") + static let watermarkShowMail = OCClassSettingsKey("watermark-show-mail") + static let watermarkShowDate = OCClassSettingsKey("watermark-show-date") + static let showScreenshotNotification = OCClassSettingsKey("show-screenshot-notification") } extension VendorServices : OCClassSettingsSupport { @@ -165,7 +225,16 @@ extension VendorServices : OCClassSettingsSupport { .enableReviewPrompt: VendorServices.shared.enableReviewPrompt, .appStoreLink : "https://itunes.apple.com/app/id1359583808?mt=8", - .recommendToFriendEnabled: !VendorServices.shared.isBranded + .recommendToFriendEnabled: !VendorServices.shared.isBranded, + + .watermarkEnabled: false, + .watermarkOpacity: 80, + .watermarkFontSize: 20, + .watermarkText: "", + .watermarkShowMail: true, + .watermarkShowDate: true, + .showScreenshotNotification: true, + ] } diff --git a/ownCloudAppShared/UIKit Extension/UIView+Extension.swift b/ownCloudAppShared/UIKit Extension/UIView+Extension.swift index b281fd66f..b3b86d26c 100644 --- a/ownCloudAppShared/UIKit Extension/UIView+Extension.swift +++ b/ownCloudAppShared/UIKit Extension/UIView+Extension.swift @@ -17,6 +17,8 @@ */ import UIKit +import ownCloudApp + public extension UIView { // MARK: - Animation diff --git a/ownCloudAppShared/UIKit Extension/UIViewController+Extension.swift b/ownCloudAppShared/UIKit Extension/UIViewController+Extension.swift index adbadb5cd..2e5469bb1 100644 --- a/ownCloudAppShared/UIKit Extension/UIViewController+Extension.swift +++ b/ownCloudAppShared/UIKit Extension/UIViewController+Extension.swift @@ -17,6 +17,7 @@ */ import UIKit +import ownCloudApp public extension UIViewController { @@ -48,3 +49,273 @@ public extension UIViewController { return true } } + +public extension UIViewController { + func observeScreenshotEvent() { + NotificationCenter.default.addObserver( + self, + selector: #selector(userDidTakeScreenshot), + name: UIApplication.userDidTakeScreenshotNotification, + object: nil + ) + } + + func stopObserveScreenshotEvent() { + NotificationCenter.default.removeObserver( + self, + name: UIApplication.userDidTakeScreenshotNotification, + object: nil + ) + } + + @objc private func userDidTakeScreenshot() { + if VendorServices.shared.showScreenshotNotification { + showScreenshotAlert() + } + } + + private func showScreenshotAlert() { + let alert = UIAlertController( + title: OCLocalizedString("ScreenshotNotificationTitle", nil), + message: OCLocalizedString("ScreenshotNotificationMessage", nil), + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction(title: OCLocalizedString("ScreenshotNotificationButton", nil), style: .default, handler: nil)) + + // Present the alert + self.present(alert, animated: true, completion: nil) + } +} + +public extension UIViewController { + func watermark( + isWatermarkEnabled: Bool = VendorServices.shared.watermarkEnabled, + watermarkOpacity: Int = VendorServices.shared.watermarkOpacity, + watermarkFontSize: Int = VendorServices.shared.watermarkFontSize, + watermarkText: String? = VendorServices.shared.watermarkText, + watermarkShowMail: Bool = VendorServices.shared.watermarkShowMail, + watermarkShowDate: Bool = VendorServices.shared.watermarkShowDate, + username: String?, + userMail: String? + ){ + let watermark = Watermark( + isWatermarkEnabled: isWatermarkEnabled, + watermarkOpacity: watermarkOpacity, + watermarkFontSize: watermarkFontSize, + watermarkText: watermarkText, + watermarkShowMail: watermarkShowMail, + watermarkShowDate: watermarkShowDate, + username: username, + userMail: userMail + ) + view.addSubview(watermark) + watermark.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + watermark.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + watermark.leadingAnchor.constraint(equalTo: view.leadingAnchor), + watermark.trailingAnchor.constraint(equalTo: view.trailingAnchor), + watermark.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } +} + +class Watermark: UIView { + + var angle: CGFloat = 45.0 { + didSet { updateView() } + } + + var isWatermarkEnabled: Bool { + didSet { updateView() } + } + + var watermarkOpacity: Int { + didSet { updateView() } + } + + var watermarkFontSize: Int { + didSet { updateView() } + } + + var watermarkText: String? { + didSet { updateView() } + } + + var watermarkShowMail: Bool { + didSet { updateView() } + } + + var watermarkShowDate: Bool { + didSet { updateView() } + } + + var username: String { + didSet { updateView() } + } + + var userMail: String? { + didSet { updateView() } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init( + isWatermarkEnabled: Bool, + watermarkOpacity: Int, + watermarkFontSize: Int , + watermarkText: String?, + watermarkShowMail: Bool, + watermarkShowDate: Bool , + username: String?, + userMail: String? + ) { + self.isWatermarkEnabled = isWatermarkEnabled + self.watermarkOpacity = watermarkOpacity + self.watermarkFontSize = watermarkFontSize + self.watermarkText = watermarkText + self.watermarkShowMail = watermarkShowMail + self.watermarkShowDate = watermarkShowDate + self.username = username ?? "Unknown User" + self.userMail = userMail + super.init(frame: .zero) + self.translatesAutoresizingMaskIntoConstraints = false + self.isUserInteractionEnabled = false + } + + private var justUpdated = false + override func updateConstraints() { + // Prevent infinite loop. Not ideal, but working + if !justUpdated { + + updateView() + } + justUpdated = false + + super.updateConstraints() + } + + private func updateView() { + self.subviews.forEach { $0.removeFromSuperview() } + + + if isWatermarkEnabled == false { + return + } + + let watermarkString = createWatermarkString() + + let rotatedRect = calculateRotatedLabelDimensions(watermarkString: watermarkString, angle: angle) + + if rotatedRect.size.width == 0 || rotatedRect.size.height == 0 { + return + } + + // Calculate how many labels fit horizontally and vertically, applying mins and max + let columns = min(max(Int(self.bounds.width / rotatedRect.size.width), 1), 10) + let rows = min(max(Int(self.bounds.height / rotatedRect.size.height), 1), 10) + + // Calculate the total width and height occupied by the grid of labels + let totalWidth = CGFloat(columns) * rotatedRect.size.width + let totalHeight = CGFloat(rows) * rotatedRect.size.height + + + // Calculate the offsets to evenly space out the grid of labels + let horizontalOffset = (self.bounds.width - totalWidth) / CGFloat(columns + 1) + let verticalOffset = (self.bounds.height - totalHeight) / CGFloat(rows + 1) + + let angleInRadiands = CGFloat.pi * (360 - angle) / 180 + + for row in 0.. String { + let currentDateTime = Date() + let formatter = DateFormatter() + formatter.timeStyle = .short + formatter.dateStyle = .short + let dateString = formatter.string(from: currentDateTime) + + var watermarkString: String + if watermarkText?.isEmpty == false { + watermarkString = watermarkText! + } else if watermarkShowMail == false || userMail == nil { + watermarkString = username + } else { + watermarkString = username + ", " + userMail! + } + if watermarkShowDate == true { + watermarkString = watermarkString + ", " + dateString + } + return watermarkString + } + + private func calculateRotatedLabelDimensions( + watermarkString: String, + angle: CGFloat + ) -> CGRect { + let angleInRadiands = CGFloat.pi * (360 - angle) / 180 + + let labelForSize = _watermarkLabel( + watermarkString: watermarkString, + watermarkFontSize: watermarkFontSize, + watermarkOpacity: watermarkOpacity + ) + + labelForSize.sizeToFit() + + let originalSize = labelForSize.bounds.size + + let rotatedRect = CGRect(origin: .zero, size: originalSize).applying(CGAffineTransform(rotationAngle: angleInRadiands)) + return rotatedRect + } + + private func _watermarkLabel( + watermarkString: String, + watermarkFontSize: Int, + watermarkOpacity: Int + ) -> UILabel { + let watermarkLabel = UILabel() + watermarkLabel.text = watermarkString + watermarkLabel.font = UIFont.boldSystemFont(ofSize: CGFloat(watermarkFontSize)) + watermarkLabel.textColor = UIColor.white.withAlphaComponent(CGFloat(Double(watermarkOpacity) / 100.0)) + watermarkLabel.textAlignment = .center + watermarkLabel.translatesAutoresizingMaskIntoConstraints = false + watermarkLabel.numberOfLines = 1; + watermarkLabel.lineBreakMode = .byClipping + watermarkLabel.layer.shadowColor = UIColor.black.cgColor + watermarkLabel.layer.shadowOffset = CGSize(width: 1, height: 1) + watermarkLabel.layer.shadowOpacity = 0.5 + watermarkLabel.layer.shadowRadius = 5.0 + return watermarkLabel + } +} diff --git a/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift b/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift index 71cb0dda5..fcd5f538e 100644 --- a/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift +++ b/ownCloudAppShared/User Interface/More/MoreStaticTableViewController.swift @@ -27,10 +27,12 @@ open class MoreStaticTableViewController: StaticTableViewController { super.init(style: style) cssSelectors = [.more] + observeScreenshotEvent() } deinit { themeApplierTokens.forEach({ token in Theme.shared.remove(applierForToken: token) }) + stopObserveScreenshotEvent() } required public init?(coder aDecoder: NSCoder) {