|
| 1 | +// |
| 2 | +// ReleaseNotesHostViewController.swift |
| 3 | +// ownCloud |
| 4 | +// |
| 5 | +// Created by Matthias Hühne on 04.12.19. |
| 6 | +// Copyright © 2019 ownCloud GmbH. All rights reserved. |
| 7 | +// |
| 8 | + |
| 9 | +/* |
| 10 | + * Copyright (C) 2019, ownCloud GmbH. |
| 11 | + * |
| 12 | + * This code is covered by the GNU Public License Version 3. |
| 13 | + * |
| 14 | + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ |
| 15 | + * You should have received a copy of this license along with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.en.html>. |
| 16 | + * |
| 17 | + */ |
| 18 | + |
| 19 | +import UIKit |
| 20 | +import ownCloudSDK |
| 21 | +import StoreKit |
| 22 | + |
| 23 | +class ReleaseNotesHostViewController: UIViewController { |
| 24 | + |
| 25 | + // MARK: - Constants |
| 26 | + private let cornerRadius : CGFloat = 8.0 |
| 27 | + private let padding : CGFloat = 20.0 |
| 28 | + private let smallPadding : CGFloat = 10.0 |
| 29 | + private let buttonHeight : CGFloat = 44.0 |
| 30 | + private let headerHeight : CGFloat = 60.0 |
| 31 | + |
| 32 | + // MARK: - Instance Variables |
| 33 | + var titleLabel = UILabel() |
| 34 | + var proceedButton = ThemeButton() |
| 35 | + var footerButton = UIButton() |
| 36 | + |
| 37 | + override func viewDidLoad() { |
| 38 | + super.viewDidLoad() |
| 39 | + |
| 40 | + Theme.shared.register(client: self) |
| 41 | + |
| 42 | + ReleaseNotesDatasource.setUserPreferenceValue(NSString(utf8String: VendorServices.shared.appVersion), forClassSettingsKey: .lastSeenReleaseNotesVersion) |
| 43 | + |
| 44 | + let headerView = UIView() |
| 45 | + headerView.backgroundColor = .clear |
| 46 | + headerView.translatesAutoresizingMaskIntoConstraints = false |
| 47 | + view.addSubview(headerView) |
| 48 | + NSLayoutConstraint.activate([ |
| 49 | + headerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), |
| 50 | + headerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), |
| 51 | + headerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), |
| 52 | + headerView.heightAnchor.constraint(equalToConstant: headerHeight) |
| 53 | + ]) |
| 54 | + |
| 55 | + titleLabel.translatesAutoresizingMaskIntoConstraints = false |
| 56 | + titleLabel.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal) |
| 57 | + |
| 58 | + titleLabel.text = "New in ownCloud".localized |
| 59 | + titleLabel.textAlignment = .center |
| 60 | + titleLabel.numberOfLines = 0 |
| 61 | + titleLabel.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline) |
| 62 | + titleLabel.adjustsFontForContentSizeCategory = true |
| 63 | + headerView.addSubview(titleLabel) |
| 64 | + |
| 65 | + NSLayoutConstraint.activate([ |
| 66 | + titleLabel.leftAnchor.constraint(greaterThanOrEqualTo: headerView.safeAreaLayoutGuide.leftAnchor, constant: padding), |
| 67 | + titleLabel.rightAnchor.constraint(lessThanOrEqualTo: headerView.safeAreaLayoutGuide.rightAnchor, constant: padding * -1), |
| 68 | + titleLabel.centerXAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.centerXAnchor), |
| 69 | + |
| 70 | + titleLabel.topAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.topAnchor, constant: padding) |
| 71 | + ]) |
| 72 | + |
| 73 | + let releaseNotesController = ReleaseNotesTableViewController(style: .plain) |
| 74 | + if let containerView = releaseNotesController.view { |
| 75 | + containerView.backgroundColor = .clear |
| 76 | + containerView.translatesAutoresizingMaskIntoConstraints = false |
| 77 | + view.addSubview(containerView) |
| 78 | + |
| 79 | + let bottomView = UIView() |
| 80 | + bottomView.backgroundColor = .clear |
| 81 | + bottomView.translatesAutoresizingMaskIntoConstraints = false |
| 82 | + view.addSubview(bottomView) |
| 83 | + NSLayoutConstraint.activate([ |
| 84 | + bottomView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), |
| 85 | + bottomView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), |
| 86 | + bottomView.topAnchor.constraint(equalTo: containerView.bottomAnchor), |
| 87 | + bottomView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) |
| 88 | + ]) |
| 89 | + |
| 90 | + proceedButton.setTitle("Proceed".localized, for: .normal) |
| 91 | + proceedButton.translatesAutoresizingMaskIntoConstraints = false |
| 92 | + proceedButton.addTarget(self, action: #selector(dismissView), for: .touchUpInside) |
| 93 | + bottomView.addSubview(proceedButton) |
| 94 | + |
| 95 | + let appName = OCAppIdentity.shared.appName ?? "ownCloud" |
| 96 | + footerButton.setTitle(String(format:"Thank you for using %@.\nIf you like our App, please leave an AppStore review.\n❤️".localized, appName), for: .normal) |
| 97 | + footerButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.footnote) |
| 98 | + footerButton.titleLabel?.adjustsFontForContentSizeCategory = true |
| 99 | + footerButton.titleLabel?.numberOfLines = 0 |
| 100 | + footerButton.titleLabel?.textAlignment = .center |
| 101 | + footerButton.translatesAutoresizingMaskIntoConstraints = false |
| 102 | + footerButton.addTarget(self, action: #selector(rateApp), for: .touchUpInside) |
| 103 | + bottomView.addSubview(footerButton) |
| 104 | + |
| 105 | + NSLayoutConstraint.activate([ |
| 106 | + footerButton.leadingAnchor.constraint(equalTo: bottomView.leadingAnchor, constant: padding), |
| 107 | + footerButton.trailingAnchor.constraint(equalTo: bottomView.trailingAnchor, constant: padding * -1), |
| 108 | + footerButton.topAnchor.constraint(equalTo: bottomView.topAnchor, constant: smallPadding), |
| 109 | + footerButton.bottomAnchor.constraint(equalTo: proceedButton.topAnchor, constant: padding * -1) |
| 110 | + ]) |
| 111 | + |
| 112 | + NSLayoutConstraint.activate([ |
| 113 | + proceedButton.leadingAnchor.constraint(equalTo: bottomView.leadingAnchor, constant: padding), |
| 114 | + proceedButton.trailingAnchor.constraint(equalTo: bottomView.trailingAnchor, constant: padding * -1), |
| 115 | + proceedButton.heightAnchor.constraint(equalToConstant: buttonHeight), |
| 116 | + proceedButton.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor, constant: smallPadding * -1) |
| 117 | + ]) |
| 118 | + |
| 119 | + NSLayoutConstraint.activate([ |
| 120 | + containerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), |
| 121 | + containerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), |
| 122 | + containerView.topAnchor.constraint(equalTo: headerView.bottomAnchor), |
| 123 | + containerView.bottomAnchor.constraint(equalTo: bottomView.topAnchor) |
| 124 | + ]) |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + deinit { |
| 129 | + Theme.shared.unregister(client: self) |
| 130 | + } |
| 131 | + |
| 132 | + @objc func dismissView() { |
| 133 | + self.dismiss(animated: true, completion: nil) |
| 134 | + } |
| 135 | + |
| 136 | + @objc func rateApp() { |
| 137 | + SKStoreReviewController.requestReview() |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +// MARK: - Themeable implementation |
| 142 | +extension ReleaseNotesHostViewController : Themeable { |
| 143 | + func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { |
| 144 | + |
| 145 | + self.view.backgroundColor = collection.tableBackgroundColor |
| 146 | + titleLabel.applyThemeCollection(collection, itemStyle: .logo) |
| 147 | + proceedButton.backgroundColor = collection.neutralColors.normal.background |
| 148 | + proceedButton.setTitleColor(collection.neutralColors.normal.foreground, for: .normal) |
| 149 | + footerButton.titleLabel?.textColor = collection.tableRowColors.labelColor |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +class ReleaseNotesDatasource : NSObject, OCClassSettingsUserPreferencesSupport { |
| 154 | + |
| 155 | + var shouldShowReleaseNotes: Bool { |
| 156 | + if let lastSeenReleaseNotesVersion = self.classSetting(forOCClassSettingsKey: .lastSeenReleaseNotesVersion) as? String { |
| 157 | + |
| 158 | + if lastSeenReleaseNotesVersion.compare(VendorServices.shared.appVersion, options: .numeric) == .orderedDescending || lastSeenReleaseNotesVersion.compare(VendorServices.shared.appVersion, options: .numeric) == .orderedSame { |
| 159 | + return false |
| 160 | + } |
| 161 | + |
| 162 | + if let path = Bundle.main.path(forResource: "ReleaseNotes", ofType: "plist"), let releaseNotesValues = NSDictionary(contentsOfFile: path), let versionsValues = releaseNotesValues["Versions"] as? NSArray { |
| 163 | + |
| 164 | + let relevantReleaseNotes = versionsValues.filter { |
| 165 | + if let version = ($0 as AnyObject)["Version"] as? String, version.compare(VendorServices.shared.appVersion, options: .numeric) == .orderedDescending { |
| 166 | + return false |
| 167 | + } |
| 168 | + |
| 169 | + return true |
| 170 | + } |
| 171 | + |
| 172 | + if relevantReleaseNotes.count > 0 { |
| 173 | + return true |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + return false |
| 178 | + } else if self.classSetting(forOCClassSettingsKey: .lastSeenAppVersion) != nil { |
| 179 | + if self.classSetting(forOCClassSettingsKey: .lastSeenAppVersion) as? String != VendorServices.shared.appVersion { |
| 180 | + return true |
| 181 | + } |
| 182 | + return false |
| 183 | + } else if OCBookmarkManager.shared.bookmarks.count > 0 { |
| 184 | + // Fallback, if app was previously installed, because we cannot check for an user defaults key, we have to check if accounts was previously configured |
| 185 | + return true |
| 186 | + } |
| 187 | + |
| 188 | + return false |
| 189 | + } |
| 190 | + |
| 191 | + func releaseNotes(for version: String) -> [[String:Any]]? { |
| 192 | + if let path = Bundle.main.path(forResource: "ReleaseNotes", ofType: "plist") { |
| 193 | + if let releaseNotesValues = NSDictionary(contentsOfFile: path), let versionsValues = releaseNotesValues["Versions"] as? NSArray { |
| 194 | + |
| 195 | + let relevantReleaseNotes = versionsValues.filter { |
| 196 | + if let version = ($0 as AnyObject)["Version"] as? String, version.compare(VendorServices.shared.appVersion, options: .numeric) == .orderedAscending { |
| 197 | + return false |
| 198 | + } |
| 199 | + |
| 200 | + return true |
| 201 | + } |
| 202 | + |
| 203 | + return relevantReleaseNotes as? [[String:Any]] |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + return nil |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +extension OCClassSettingsKey { |
| 212 | + // Available since version 1.3.0 |
| 213 | + static let lastSeenReleaseNotesVersion = OCClassSettingsKey("lastSeenReleaseNotesVersion") |
| 214 | + static let lastSeenAppVersion = OCClassSettingsKey("lastSeenAppVersion") |
| 215 | +} |
| 216 | + |
| 217 | +extension ReleaseNotesDatasource : OCClassSettingsSupport { |
| 218 | + static let classSettingsIdentifier : OCClassSettingsIdentifier = .app |
| 219 | + |
| 220 | + static func defaultSettings(forIdentifier identifier: OCClassSettingsIdentifier) -> [OCClassSettingsKey : Any]? { |
| 221 | + if identifier == .app { |
| 222 | + return nil |
| 223 | + } |
| 224 | + |
| 225 | + return nil |
| 226 | + } |
| 227 | +} |
0 commit comments