mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
53a5db0ea5
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)
205 lines
7 KiB
Swift
205 lines
7 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)
|
|
|
|
child.willMove(toParent: self)
|
|
addChild(child)
|
|
contentStackView.addArrangedSubview(child.view)
|
|
child.didMove(toParent: self)
|
|
|
|
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 static func show(warning: Warning, inWindowFor view: UIView? = nil) {
|
|
guard Thread.isMainThread else {
|
|
DispatchQueue.main.async {
|
|
TopBannerController.show(warning: warning, 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
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
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() }
|
|
}
|
|
}
|