You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUIKit/Components/TopBannerController.swift

208 lines
7.1 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SessionUtilitiesKit
public class TopBannerController: UIViewController {
public enum Warning: String, Codable {
case outdatedUserConfig
var text: String {
switch self {
case .outdatedUserConfig: return "USER_CONFIG_OUTDATED_WARNING".localized()
}
}
}
private static var lastInstance: TopBannerController?
private let child: UIViewController
private var initialCachedWarning: Warning?
// MARK: - UI
private lazy var bottomConstraint: NSLayoutConstraint = bannerLabel
.pin(.bottom, to: .bottom, of: bannerContainer, withInset: -Values.verySmallSpacing)
private let contentStackView: UIStackView = {
let result: UIStackView = UIStackView()
result.translatesAutoresizingMaskIntoConstraints = false
result.axis = .vertical
result.distribution = .fill
result.alignment = .fill
return result
}()
private let bannerContainer: UIView = {
let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.themeBackgroundColor = .primary
result.isHidden = true
return result
}()
private let bannerLabel: UILabel = {
let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.setContentHuggingPriority(.required, for: .vertical)
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.textAlignment = .center
result.themeTextColor = .black
result.numberOfLines = 0
return result
}()
private lazy var closeButton: UIButton = {
let result: UIButton = UIButton()
result.translatesAutoresizingMaskIntoConstraints = false
result.setImage(
UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .bold))?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.contentMode = .center
result.themeTintColor = .black
result.addTarget(self, action: #selector(dismissBanner), for: .touchUpInside)
return result
}()
// MARK: - Initialization
public init(
child: UIViewController,
cachedWarning: Warning? = nil
) {
self.child = child
self.initialCachedWarning = cachedWarning
super.init(nibName: nil, bundle: nil)
TopBannerController.lastInstance = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
public override func loadView() {
super.loadView()
view.addSubview(contentStackView)
contentStackView.addArrangedSubview(bannerContainer)
attachChild()
bannerContainer.addSubview(bannerLabel)
bannerContainer.addSubview(closeButton)
setupLayout()
// If we had an initial warning then show it
if let warning: Warning = self.initialCachedWarning {
UIView.performWithoutAnimation {
TopBannerController.show(warning: warning)
}
self.initialCachedWarning = nil
}
}
private func setupLayout() {
contentStackView.pin(.top, to: .top, of: view.safeAreaLayoutGuide)
contentStackView.pin(.leading, to: .leading, of: view)
contentStackView.pin(.trailing, to: .trailing, of: view)
contentStackView.pin(.bottom, to: .bottom, of: view)
bannerLabel.pin(.top, to: .top, of: view.safeAreaLayoutGuide, withInset: Values.verySmallSpacing)
bannerLabel.pin(.leading, to: .leading, of: bannerContainer, withInset: Values.veryLargeSpacing)
bannerLabel.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.veryLargeSpacing)
bottomConstraint.isActive = false
let buttonSize: CGFloat = (12 + (Values.smallSpacing * 2))
closeButton.center(.vertical, in: bannerLabel)
closeButton.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.smallSpacing)
closeButton.set(.width, to: buttonSize)
closeButton.set(.height, to: buttonSize)
}
// MARK: - Actions
@objc private func dismissBanner() {
// Remove the cached warning
UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = nil
UIView.animate(
withDuration: 0.3,
animations: { [weak self] in
self?.bottomConstraint.isActive = false
self?.contentStackView.setNeedsLayout()
self?.contentStackView.layoutIfNeeded()
},
completion: { [weak self] _ in
self?.bannerContainer.isHidden = true
}
)
}
// MARK: - Functions
public func attachChild() {
child.willMove(toParent: self)
addChild(child)
contentStackView.addArrangedSubview(child.view)
child.didMove(toParent: self)
}
public static func show(warning: Warning, inWindowFor view: UIView? = nil) {
guard Thread.isMainThread else {
DispatchQueue.main.async {
TopBannerController.show(warning: warning, inWindowFor: view)
}
return
}
Fixed a number of issues found during internal testing Added copy for an unrecoverable startup case Added some additional logs to better debug ValueObservation query errors Increased the pageSize to 20 on iPad devices (to prevent it immediately loading a second page) Cleaned up a bunch of threading logic (try to avoid overriding subscribe/receive threads specified at subscription) Consolidated the 'sendMessage' and 'sendAttachments' functions Updated the various frameworks to use 'DAWRF with DSYM' to allow for better debugging during debug mode (at the cost of a longer build time) Updated the logic to optimistically insert messages when sending to avoid any database write delays Updated the logic to avoid sending notifications for messages which are already marked as read by the config Fixed an issue where multiple paths could incorrectly get built at the same time in some cases Fixed an issue where other job queues could be started before the blockingQueue finishes Fixed a potential bug with the snode version comparison (was just a string comparison which would fail when getting to double-digit values) Fixed a bug where you couldn't remove the last reaction on a message Fixed the broken media message zoom animations Fixed a bug where the last message read in a conversation wouldn't be correctly detected as already read Fixed a bug where the QuoteView had no line limits (resulting in the '@You' mention background highlight being incorrectly positioned in the quote preview) Fixed a bug where a large number of configSyncJobs could be scheduled (only one would run at a time but this could result in performance impacts)
1 year ago
// Not an ideal approach but should allow us to have a single banner
guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else {
return
}
// Cache the banner to show (so we can show it on re-launch)
UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = warning.rawValue
UIView.performWithoutAnimation {
instance.bannerLabel.text = warning.text
instance.bannerLabel.setNeedsLayout()
instance.bannerLabel.layoutIfNeeded()
instance.bottomConstraint.isActive = false
instance.bannerContainer.isHidden = false
}
UIView.animate(withDuration: 0.3) { [weak instance] in
instance?.bottomConstraint.isActive = true
instance?.contentStackView.setNeedsLayout()
instance?.contentStackView.layoutIfNeeded()
}
}
Fixed a number of issues found during internal testing Added copy for an unrecoverable startup case Added some additional logs to better debug ValueObservation query errors Increased the pageSize to 20 on iPad devices (to prevent it immediately loading a second page) Cleaned up a bunch of threading logic (try to avoid overriding subscribe/receive threads specified at subscription) Consolidated the 'sendMessage' and 'sendAttachments' functions Updated the various frameworks to use 'DAWRF with DSYM' to allow for better debugging during debug mode (at the cost of a longer build time) Updated the logic to optimistically insert messages when sending to avoid any database write delays Updated the logic to avoid sending notifications for messages which are already marked as read by the config Fixed an issue where multiple paths could incorrectly get built at the same time in some cases Fixed an issue where other job queues could be started before the blockingQueue finishes Fixed a potential bug with the snode version comparison (was just a string comparison which would fail when getting to double-digit values) Fixed a bug where you couldn't remove the last reaction on a message Fixed the broken media message zoom animations Fixed a bug where the last message read in a conversation wouldn't be correctly detected as already read Fixed a bug where the QuoteView had no line limits (resulting in the '@You' mention background highlight being incorrectly positioned in the quote preview) Fixed a bug where a large number of configSyncJobs could be scheduled (only one would run at a time but this could result in performance impacts)
1 year ago
public static func hide(inWindowFor view: UIView? = nil) {
guard Thread.isMainThread else {
DispatchQueue.main.async {
TopBannerController.hide(inWindowFor: view)
}
return
}
// Not an ideal approach but should allow us to have a single banner
guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else {
return
}
UIView.performWithoutAnimation { instance.dismissBanner() }
}
}