2022-04-01 08:22:45 +02:00
|
|
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
|
|
|
|
import UIKit
|
2022-04-06 07:43:26 +02:00
|
|
|
import GRDB
|
2022-04-21 08:42:35 +02:00
|
|
|
import DifferenceKit
|
2022-09-28 09:30:31 +02:00
|
|
|
import SessionUIKit
|
2022-04-01 08:22:45 +02:00
|
|
|
import SessionMessagingKit
|
|
|
|
import SessionUtilitiesKit
|
2022-04-21 08:42:35 +02:00
|
|
|
import SignalUtilitiesKit
|
2019-11-28 06:42:07 +01:00
|
|
|
|
2022-08-22 07:48:52 +02:00
|
|
|
final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, SeedReminderViewDelegate {
|
2022-09-07 09:37:01 +02:00
|
|
|
private static let loadingHeaderHeight: CGFloat = 40
|
2022-08-23 07:41:51 +02:00
|
|
|
public static let newConversationButtonSize: CGFloat = 60
|
2022-05-06 10:07:57 +02:00
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
private let viewModel: HomeViewModel = HomeViewModel()
|
|
|
|
private var dataChangeObservable: DatabaseCancellable?
|
2022-06-24 10:29:45 +02:00
|
|
|
private var hasLoadedInitialStateData: Bool = false
|
|
|
|
private var hasLoadedInitialThreadData: Bool = false
|
|
|
|
private var isLoadingMore: Bool = false
|
2022-06-27 09:57:46 +02:00
|
|
|
private var isAutoLoadingNextPage: Bool = false
|
2022-07-18 04:32:46 +02:00
|
|
|
private var viewHasAppeared: Bool = false
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-05-15 06:39:21 +02:00
|
|
|
// MARK: - Intialization
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
init() {
|
2022-07-01 05:08:45 +02:00
|
|
|
Storage.shared.addObserver(viewModel.pagedDataObserver)
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
preconditionFailure("Use init() instead.")
|
|
|
|
}
|
|
|
|
|
2022-05-15 06:39:21 +02:00
|
|
|
deinit {
|
|
|
|
NotificationCenter.default.removeObserver(self)
|
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// MARK: - UI
|
2019-11-28 06:42:07 +01:00
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
private var tableViewTopConstraint: NSLayoutConstraint!
|
2022-02-17 05:31:27 +01:00
|
|
|
|
2019-12-12 01:10:26 +01:00
|
|
|
private lazy var seedReminderView: SeedReminderView = {
|
2019-12-12 03:17:14 +01:00
|
|
|
let result = SeedReminderView(hasContinueButton: true)
|
2022-10-19 07:13:21 +02:00
|
|
|
result.accessibilityLabel = "Recovery phrase reminder"
|
2019-12-12 01:10:26 +01:00
|
|
|
let title = "You're almost finished! 80%"
|
2022-08-12 09:28:00 +02:00
|
|
|
result.subtitle = "view_seed_reminder_subtitle_1".localized()
|
2019-12-12 01:10:26 +01:00
|
|
|
result.setProgress(0.8, animated: false)
|
|
|
|
result.delegate = self
|
2022-06-01 08:50:21 +02:00
|
|
|
result.isHidden = !self.viewModel.state.showViewedSeedBanner
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2022-08-12 09:28:00 +02:00
|
|
|
ThemeManager.onThemeChange(observer: result) { [weak result] _, primaryColor in
|
|
|
|
let attributedTitle = NSMutableAttributedString(string: title)
|
|
|
|
attributedTitle.addAttribute(
|
|
|
|
.foregroundColor,
|
|
|
|
value: primaryColor.color,
|
|
|
|
range: (title as NSString).range(of: "80%")
|
|
|
|
)
|
|
|
|
result?.title = attributedTitle
|
|
|
|
}
|
|
|
|
|
2019-12-12 01:10:26 +01:00
|
|
|
return result
|
|
|
|
}()
|
2022-05-31 09:41:02 +02:00
|
|
|
|
|
|
|
private lazy var loadingConversationsLabel: UILabel = {
|
|
|
|
let result: UILabel = UILabel()
|
2022-09-28 09:30:31 +02:00
|
|
|
result.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
result.font = .systemFont(ofSize: Values.smallFontSize)
|
2022-05-31 09:41:02 +02:00
|
|
|
result.text = "LOADING_CONVERSATIONS".localized()
|
2022-09-28 09:30:31 +02:00
|
|
|
result.themeTextColor = .textSecondary
|
2022-05-31 09:41:02 +02:00
|
|
|
result.textAlignment = .center
|
|
|
|
result.numberOfLines = 0
|
|
|
|
|
|
|
|
return result
|
|
|
|
}()
|
2021-01-20 06:28:36 +01:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
private lazy var tableView: UITableView = {
|
2019-11-28 06:42:07 +01:00
|
|
|
let result = UITableView()
|
|
|
|
result.separatorStyle = .none
|
2022-08-26 08:23:39 +02:00
|
|
|
result.themeBackgroundColor = .clear
|
2022-04-21 08:42:35 +02:00
|
|
|
result.contentInset = UIEdgeInsets(
|
|
|
|
top: 0,
|
|
|
|
left: 0,
|
|
|
|
bottom: (
|
|
|
|
Values.largeSpacing +
|
2022-09-26 03:16:47 +02:00
|
|
|
HomeVC.newConversationButtonSize +
|
|
|
|
Values.smallSpacing +
|
|
|
|
(UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0)
|
2022-04-21 08:42:35 +02:00
|
|
|
),
|
|
|
|
right: 0
|
|
|
|
)
|
|
|
|
result.showsVerticalScrollIndicator = false
|
2022-05-09 06:45:14 +02:00
|
|
|
result.register(view: MessageRequestsCell.self)
|
2022-05-29 11:26:06 +02:00
|
|
|
result.register(view: FullConversationCell.self)
|
2022-04-21 08:42:35 +02:00
|
|
|
result.dataSource = self
|
|
|
|
result.delegate = self
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
if #available(iOS 15.0, *) {
|
|
|
|
result.sectionHeaderTopPadding = 0
|
|
|
|
}
|
|
|
|
|
2019-11-28 06:42:07 +01:00
|
|
|
return result
|
|
|
|
}()
|
2022-08-22 07:48:52 +02:00
|
|
|
|
2022-10-14 08:09:38 +02:00
|
|
|
private lazy var newConversationButton: UIView = {
|
|
|
|
let result: UIView = UIView()
|
|
|
|
result.set(.width, to: HomeVC.newConversationButtonSize)
|
|
|
|
result.set(.height, to: HomeVC.newConversationButtonSize)
|
|
|
|
|
|
|
|
let button = UIButton()
|
2022-10-19 07:13:21 +02:00
|
|
|
button.accessibilityLabel = "New conversation button"
|
2022-11-15 00:54:33 +01:00
|
|
|
button.isAccessibilityElement = true
|
2022-10-14 08:09:38 +02:00
|
|
|
button.clipsToBounds = true
|
|
|
|
button.setImage(
|
2022-09-26 03:16:47 +02:00
|
|
|
UIImage(named: "Plus")?
|
|
|
|
.withRenderingMode(.alwaysTemplate),
|
|
|
|
for: .normal
|
2022-08-22 07:48:52 +02:00
|
|
|
)
|
2022-10-14 08:09:38 +02:00
|
|
|
button.contentMode = .center
|
|
|
|
button.adjustsImageWhenHighlighted = false
|
|
|
|
button.themeTintColor = .menuButton_icon
|
|
|
|
button.setThemeBackgroundColor(.menuButton_background, for: .normal)
|
|
|
|
button.setThemeBackgroundColor(
|
|
|
|
.highlighted(.menuButton_background, alwaysDarken: true),
|
|
|
|
for: .highlighted
|
|
|
|
)
|
|
|
|
button.contentEdgeInsets = UIEdgeInsets(
|
2022-09-26 03:16:47 +02:00
|
|
|
top: ((HomeVC.newConversationButtonSize - 24) / 2),
|
|
|
|
leading: ((HomeVC.newConversationButtonSize - 24) / 2),
|
|
|
|
bottom: ((HomeVC.newConversationButtonSize - 24) / 2),
|
|
|
|
trailing: ((HomeVC.newConversationButtonSize - 24) / 2)
|
|
|
|
)
|
2022-10-14 08:09:38 +02:00
|
|
|
button.layer.cornerRadius = (HomeVC.newConversationButtonSize / 2)
|
|
|
|
button.addTarget(self, action: #selector(createNewConversation), for: .touchUpInside)
|
|
|
|
result.addSubview(button)
|
|
|
|
button.pin(to: result)
|
2022-09-26 03:16:47 +02:00
|
|
|
|
|
|
|
// Add the outer shadow
|
|
|
|
result.themeShadowColor = .menuButton_outerShadow
|
|
|
|
result.layer.shadowRadius = 15
|
|
|
|
result.layer.shadowOpacity = 0.3
|
|
|
|
result.layer.shadowOffset = .zero
|
|
|
|
result.layer.cornerRadius = (HomeVC.newConversationButtonSize / 2)
|
|
|
|
result.layer.shadowPath = UIBezierPath(
|
|
|
|
ovalIn: CGRect(
|
|
|
|
origin: CGPoint.zero,
|
|
|
|
size: CGSize(
|
|
|
|
width: HomeVC.newConversationButtonSize,
|
|
|
|
height: HomeVC.newConversationButtonSize
|
|
|
|
)
|
|
|
|
)
|
|
|
|
).cgPath
|
|
|
|
|
|
|
|
// Add the inner shadow
|
|
|
|
let innerShadowLayer: CALayer = CALayer()
|
|
|
|
innerShadowLayer.masksToBounds = true
|
|
|
|
innerShadowLayer.themeShadowColor = .menuButton_innerShadow
|
|
|
|
innerShadowLayer.position = CGPoint(
|
|
|
|
x: (HomeVC.newConversationButtonSize / 2),
|
|
|
|
y: (HomeVC.newConversationButtonSize / 2)
|
|
|
|
)
|
|
|
|
innerShadowLayer.bounds = CGRect(
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: HomeVC.newConversationButtonSize,
|
|
|
|
height: HomeVC.newConversationButtonSize
|
|
|
|
)
|
|
|
|
innerShadowLayer.cornerRadius = (HomeVC.newConversationButtonSize / 2)
|
|
|
|
innerShadowLayer.shadowOffset = .zero
|
|
|
|
innerShadowLayer.shadowOpacity = 0.4
|
|
|
|
innerShadowLayer.shadowRadius = 2
|
|
|
|
|
|
|
|
let cutout: UIBezierPath = UIBezierPath(
|
|
|
|
roundedRect: innerShadowLayer.bounds
|
|
|
|
.insetBy(dx: innerShadowLayer.shadowRadius, dy: innerShadowLayer.shadowRadius),
|
|
|
|
cornerRadius: (HomeVC.newConversationButtonSize / 2)
|
|
|
|
).reversing()
|
|
|
|
let path: UIBezierPath = UIBezierPath(
|
|
|
|
roundedRect: innerShadowLayer.bounds,
|
|
|
|
cornerRadius: (HomeVC.newConversationButtonSize / 2)
|
|
|
|
)
|
|
|
|
path.append(cutout)
|
|
|
|
innerShadowLayer.shadowPath = path.cgPath
|
|
|
|
result.layer.addSublayer(innerShadowLayer)
|
2020-03-17 04:25:53 +01:00
|
|
|
|
2019-12-02 01:58:15 +01:00
|
|
|
return result
|
|
|
|
}()
|
2022-08-12 09:28:00 +02:00
|
|
|
|
2020-04-20 02:47:38 +02:00
|
|
|
private lazy var emptyStateView: UIView = {
|
|
|
|
let explanationLabel = UILabel()
|
|
|
|
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
2022-08-12 05:35:17 +02:00
|
|
|
explanationLabel.text = "vc_home_empty_state_message".localized()
|
2022-08-12 09:28:00 +02:00
|
|
|
explanationLabel.themeTextColor = .textPrimary
|
2020-04-20 02:47:38 +02:00
|
|
|
explanationLabel.textAlignment = .center
|
2022-08-12 05:35:17 +02:00
|
|
|
explanationLabel.lineBreakMode = .byWordWrapping
|
|
|
|
explanationLabel.numberOfLines = 0
|
|
|
|
|
2022-09-28 09:30:31 +02:00
|
|
|
let createNewPrivateChatButton = SessionButton(style: .bordered, size: .large)
|
2022-08-12 05:35:17 +02:00
|
|
|
createNewPrivateChatButton.setTitle("vc_home_empty_state_button_title".localized(), for: .normal)
|
2022-10-07 06:35:17 +02:00
|
|
|
createNewPrivateChatButton.addTarget(self, action: #selector(createNewConversation), for: .touchUpInside)
|
2022-03-03 03:35:27 +01:00
|
|
|
createNewPrivateChatButton.set(.width, to: Values.iPadButtonWidth)
|
2022-08-12 05:35:17 +02:00
|
|
|
|
2020-04-20 02:47:38 +02:00
|
|
|
let result = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ])
|
|
|
|
result.axis = .vertical
|
|
|
|
result.spacing = Values.mediumSpacing
|
|
|
|
result.alignment = .center
|
2021-03-04 03:50:13 +01:00
|
|
|
result.isHidden = true
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2020-04-20 02:47:38 +02:00
|
|
|
return result
|
|
|
|
}()
|
2019-12-02 01:58:15 +01:00
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// MARK: - Lifecycle
|
|
|
|
|
2019-11-29 06:30:01 +01:00
|
|
|
override func viewDidLoad() {
|
2020-02-20 04:37:17 +01:00
|
|
|
super.viewDidLoad()
|
2022-03-21 07:18:12 +01:00
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// Note: This is a hack to ensure `isRTL` is initially gets run on the main thread so the value
|
|
|
|
// is cached (it gets called on background threads and if it hasn't cached the value then it can
|
|
|
|
// cause odd performance issues since it accesses UIKit)
|
2022-03-21 07:18:12 +01:00
|
|
|
_ = CurrentAppContext().isRTL
|
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
// Preparation
|
2022-05-03 09:14:56 +02:00
|
|
|
SessionApp.homeViewController.mutate { $0 = self }
|
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
updateNavBarButtons()
|
2022-01-13 01:45:05 +01:00
|
|
|
setUpNavBarSessionHeading()
|
2022-05-29 11:26:06 +02:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
// Recovery phrase reminder
|
2022-06-01 08:50:21 +02:00
|
|
|
view.addSubview(seedReminderView)
|
|
|
|
seedReminderView.pin(.leading, to: .leading, of: view)
|
|
|
|
seedReminderView.pin(.top, to: .top, of: view)
|
|
|
|
seedReminderView.pin(.trailing, to: .trailing, of: view)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2022-05-31 09:41:02 +02:00
|
|
|
// Loading conversations label
|
|
|
|
view.addSubview(loadingConversationsLabel)
|
|
|
|
|
|
|
|
loadingConversationsLabel.pin(.top, to: .top, of: view, withInset: Values.veryLargeSpacing)
|
|
|
|
loadingConversationsLabel.pin(.leading, to: .leading, of: view, withInset: 50)
|
|
|
|
loadingConversationsLabel.pin(.trailing, to: .trailing, of: view, withInset: -50)
|
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
// Table view
|
2019-11-28 06:42:07 +01:00
|
|
|
view.addSubview(tableView)
|
2019-12-12 01:10:26 +01:00
|
|
|
tableView.pin(.leading, to: .leading, of: view)
|
2022-06-01 08:50:21 +02:00
|
|
|
if self.viewModel.state.showViewedSeedBanner {
|
2022-01-24 23:46:22 +01:00
|
|
|
tableViewTopConstraint = tableView.pin(.top, to: .bottom, of: seedReminderView)
|
2022-06-01 08:50:21 +02:00
|
|
|
}
|
|
|
|
else {
|
2022-06-24 10:29:45 +02:00
|
|
|
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view)
|
2022-01-24 23:46:22 +01:00
|
|
|
}
|
2019-12-12 01:10:26 +01:00
|
|
|
tableView.pin(.trailing, to: .trailing, of: view)
|
|
|
|
tableView.pin(.bottom, to: .bottom, of: view)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
// Empty state view
|
2020-04-20 02:47:38 +02:00
|
|
|
view.addSubview(emptyStateView)
|
|
|
|
emptyStateView.center(.horizontal, in: view)
|
|
|
|
let verticalCenteringConstraint = emptyStateView.center(.vertical, in: view)
|
|
|
|
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2022-08-22 07:48:52 +02:00
|
|
|
// New conversation button
|
|
|
|
view.addSubview(newConversationButton)
|
|
|
|
newConversationButton.center(.horizontal, in: view)
|
2022-09-26 03:16:47 +02:00
|
|
|
newConversationButton.pin(.bottom, to: .bottom, of: view.safeAreaLayoutGuide, withInset: -Values.smallSpacing)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
// Notifications
|
2022-05-06 10:07:57 +02:00
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationDidBecomeActive(_:)),
|
|
|
|
name: UIApplication.didBecomeActiveNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
|
|
|
NotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(applicationDidResignActive(_:)),
|
|
|
|
name: UIApplication.didEnterBackgroundNotification, object: nil
|
|
|
|
)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2021-03-04 23:18:45 +01:00
|
|
|
// Start polling if needed (i.e. if the user just created or restored their Session ID)
|
2022-04-21 08:42:35 +02:00
|
|
|
if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate {
|
|
|
|
appDelegate.startPollersIfNeeded()
|
|
|
|
|
2021-02-24 05:30:20 +01:00
|
|
|
// Do this only if we created a new Session ID, or if we already received the initial configuration message
|
|
|
|
if UserDefaults.standard[.hasSyncedInitialConfiguration] {
|
|
|
|
appDelegate.syncConfigurationIfNeeded()
|
|
|
|
}
|
2019-11-29 06:30:01 +01:00
|
|
|
}
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
// Onion request path countries cache
|
2022-08-19 08:58:47 +02:00
|
|
|
IP2Country.shared.populateCacheIfNeededAsync()
|
2019-11-29 06:30:01 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
|
|
super.viewWillAppear(animated)
|
|
|
|
|
|
|
|
startObservingChanges()
|
2020-12-14 00:20:10 +01:00
|
|
|
}
|
|
|
|
|
2022-07-18 04:32:46 +02:00
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
|
|
super.viewDidAppear(animated)
|
|
|
|
|
|
|
|
self.viewHasAppeared = true
|
|
|
|
self.autoLoadNextPageIfNeeded()
|
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
|
|
super.viewWillDisappear(animated)
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
stopObservingChanges()
|
2022-02-17 05:31:27 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
2022-07-25 09:03:09 +02:00
|
|
|
startObservingChanges(didReturnFromBackground: true)
|
2021-01-20 06:28:36 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
@objc func applicationDidResignActive(_ notification: Notification) {
|
2022-06-24 10:29:45 +02:00
|
|
|
stopObservingChanges()
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// MARK: - Updating
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-07-25 09:03:09 +02:00
|
|
|
private func startObservingChanges(didReturnFromBackground: Bool = false) {
|
2022-04-21 08:42:35 +02:00
|
|
|
// Start observing for data changes
|
2022-07-01 05:08:45 +02:00
|
|
|
dataChangeObservable = Storage.shared.start(
|
2022-06-01 08:50:21 +02:00
|
|
|
viewModel.observableState,
|
|
|
|
// If we haven't done the initial load the trigger it immediately (blocking the main
|
|
|
|
// thread so we remain on the launch screen until it completes to be consistent with
|
|
|
|
// the old behaviour)
|
2022-06-24 10:29:45 +02:00
|
|
|
scheduling: (hasLoadedInitialStateData ?
|
2022-06-01 08:50:21 +02:00
|
|
|
.async(onQueue: .main) :
|
|
|
|
.immediate
|
|
|
|
),
|
2022-05-31 09:41:02 +02:00
|
|
|
onError: { _ in },
|
2022-06-01 08:50:21 +02:00
|
|
|
onChange: { [weak self] state in
|
2022-05-31 09:41:02 +02:00
|
|
|
// The default scheduler emits changes on the main thread
|
2022-06-01 08:50:21 +02:00
|
|
|
self?.handleUpdates(state)
|
2022-04-21 08:42:35 +02:00
|
|
|
}
|
|
|
|
)
|
2022-06-24 10:29:45 +02:00
|
|
|
|
2022-10-14 08:09:38 +02:00
|
|
|
self.viewModel.onThreadChange = { [weak self] updatedThreadData, changeset in
|
|
|
|
self?.handleThreadUpdates(updatedThreadData, changeset: changeset)
|
2022-06-24 10:29:45 +02:00
|
|
|
}
|
2022-07-25 09:03:09 +02:00
|
|
|
|
|
|
|
// Note: When returning from the background we could have received notifications but the
|
|
|
|
// PagedDatabaseObserver won't have them so we need to force a re-fetch of the current
|
|
|
|
// data to ensure everything is up to date
|
|
|
|
if didReturnFromBackground {
|
2022-10-14 08:09:38 +02:00
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
2022-09-30 06:31:05 +02:00
|
|
|
self?.viewModel.pagedDataObserver?.reload()
|
|
|
|
}
|
2022-07-25 09:03:09 +02:00
|
|
|
}
|
2022-06-24 10:29:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private func stopObservingChanges() {
|
|
|
|
// Stop observing database changes
|
|
|
|
dataChangeObservable?.cancel()
|
|
|
|
self.viewModel.onThreadChange = nil
|
2019-11-29 06:30:01 +01:00
|
|
|
}
|
2021-01-20 06:28:36 +01:00
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
private func handleUpdates(_ updatedState: HomeViewModel.State, initialLoad: Bool = false) {
|
2022-04-21 08:42:35 +02:00
|
|
|
// Ensure the first load runs without animations (if we don't do this the cells will animate
|
|
|
|
// in from a frame of CGRect.zero)
|
2022-06-24 10:29:45 +02:00
|
|
|
guard hasLoadedInitialStateData else {
|
|
|
|
hasLoadedInitialStateData = true
|
|
|
|
UIView.performWithoutAnimation { handleUpdates(updatedState, initialLoad: true) }
|
2022-04-21 08:42:35 +02:00
|
|
|
return
|
2020-07-28 02:26:56 +02:00
|
|
|
}
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-06-23 09:30:18 +02:00
|
|
|
if updatedState.userProfile != self.viewModel.state.userProfile {
|
|
|
|
updateNavBarButtons()
|
|
|
|
}
|
|
|
|
|
2022-06-01 08:50:21 +02:00
|
|
|
// Update the 'view seed' UI
|
|
|
|
if updatedState.showViewedSeedBanner != self.viewModel.state.showViewedSeedBanner {
|
|
|
|
tableViewTopConstraint.isActive = false
|
|
|
|
seedReminderView.isHidden = !updatedState.showViewedSeedBanner
|
|
|
|
|
|
|
|
if updatedState.showViewedSeedBanner {
|
|
|
|
tableViewTopConstraint = tableView.pin(.top, to: .bottom, of: seedReminderView)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
self.viewModel.updateState(updatedState)
|
|
|
|
}
|
|
|
|
|
2022-10-14 08:09:38 +02:00
|
|
|
private func handleThreadUpdates(
|
|
|
|
_ updatedData: [HomeViewModel.SectionModel],
|
|
|
|
changeset: StagedChangeset<[HomeViewModel.SectionModel]>,
|
|
|
|
initialLoad: Bool = false
|
|
|
|
) {
|
2022-06-24 10:29:45 +02:00
|
|
|
// Ensure the first load runs without animations (if we don't do this the cells will animate
|
|
|
|
// in from a frame of CGRect.zero)
|
|
|
|
guard hasLoadedInitialThreadData else {
|
|
|
|
hasLoadedInitialThreadData = true
|
2022-10-14 08:09:38 +02:00
|
|
|
UIView.performWithoutAnimation {
|
|
|
|
handleThreadUpdates(updatedData, changeset: changeset, initialLoad: true)
|
|
|
|
}
|
2022-06-24 10:29:45 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hide the 'loading conversations' label (now that we have received conversation data)
|
|
|
|
loadingConversationsLabel.isHidden = true
|
|
|
|
|
|
|
|
// Show the empty state if there is no data
|
|
|
|
emptyStateView.isHidden = (
|
|
|
|
!updatedData.isEmpty &&
|
|
|
|
updatedData.contains(where: { !$0.elements.isEmpty })
|
|
|
|
)
|
|
|
|
|
|
|
|
CATransaction.begin()
|
|
|
|
CATransaction.setCompletionBlock { [weak self] in
|
|
|
|
// Complete page loading
|
|
|
|
self?.isLoadingMore = false
|
2022-06-27 09:57:46 +02:00
|
|
|
self?.autoLoadNextPageIfNeeded()
|
2022-06-24 10:29:45 +02:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// Reload the table content (animate changes after the first load)
|
|
|
|
tableView.reload(
|
2022-10-14 08:09:38 +02:00
|
|
|
using: changeset,
|
2022-05-23 09:16:14 +02:00
|
|
|
deleteSectionsAnimation: .none,
|
|
|
|
insertSectionsAnimation: .none,
|
|
|
|
reloadSectionsAnimation: .none,
|
|
|
|
deleteRowsAnimation: .bottom,
|
2022-06-27 09:57:46 +02:00
|
|
|
insertRowsAnimation: .none,
|
2022-05-23 09:16:14 +02:00
|
|
|
reloadRowsAnimation: .none,
|
2022-06-01 08:50:21 +02:00
|
|
|
interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues
|
2022-06-24 10:29:45 +02:00
|
|
|
) { [weak self] updatedData in
|
|
|
|
self?.viewModel.updateThreadData(updatedData)
|
2022-04-06 07:43:26 +02:00
|
|
|
}
|
2022-06-01 08:50:21 +02:00
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
CATransaction.commit()
|
2020-07-21 06:18:49 +02:00
|
|
|
}
|
2019-12-12 03:17:14 +01:00
|
|
|
|
2022-06-27 09:57:46 +02:00
|
|
|
private func autoLoadNextPageIfNeeded() {
|
|
|
|
guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return }
|
|
|
|
|
|
|
|
self.isAutoLoadingNextPage = true
|
|
|
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + PagedData.autoLoadNextPageDelay) { [weak self] in
|
|
|
|
self?.isAutoLoadingNextPage = false
|
|
|
|
|
|
|
|
// Note: We sort the headers as we want to prioritise loading newer pages over older ones
|
|
|
|
let sections: [(HomeViewModel.Section, CGRect)] = (self?.viewModel.threadData
|
|
|
|
.enumerated()
|
|
|
|
.map { index, section in (section.model, (self?.tableView.rectForHeader(inSection: index) ?? .zero)) })
|
|
|
|
.defaulting(to: [])
|
|
|
|
let shouldLoadMore: Bool = sections
|
|
|
|
.contains { section, headerRect in
|
|
|
|
section == .loadMore &&
|
|
|
|
headerRect != .zero &&
|
|
|
|
(self?.tableView.bounds.contains(headerRect) == true)
|
|
|
|
}
|
|
|
|
|
|
|
|
guard shouldLoadMore else { return }
|
|
|
|
|
|
|
|
self?.isLoadingMore = true
|
|
|
|
|
2022-10-14 08:09:38 +02:00
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
2022-06-27 09:57:46 +02:00
|
|
|
self?.viewModel.pagedDataObserver?.load(.pageAfter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 06:28:36 +01:00
|
|
|
private func updateNavBarButtons() {
|
2022-01-24 23:46:22 +01:00
|
|
|
// Profile picture view
|
2019-11-29 06:30:01 +01:00
|
|
|
let profilePictureSize = Values.verySmallProfilePictureSize
|
|
|
|
let profilePictureView = ProfilePictureView()
|
2022-12-05 05:27:27 +01:00
|
|
|
profilePictureView.accessibilityIdentifier = "User settings"
|
2022-10-19 07:13:21 +02:00
|
|
|
profilePictureView.accessibilityLabel = "User settings"
|
2022-11-15 00:54:33 +01:00
|
|
|
profilePictureView.isAccessibilityElement = true
|
2019-11-29 06:30:01 +01:00
|
|
|
profilePictureView.size = profilePictureSize
|
2022-05-06 10:07:57 +02:00
|
|
|
profilePictureView.update(
|
|
|
|
publicKey: getUserHexEncodedPublicKey(),
|
|
|
|
profile: Profile.fetchOrCreateCurrentUser(),
|
|
|
|
threadVariant: .contact
|
|
|
|
)
|
2019-11-29 06:30:01 +01:00
|
|
|
profilePictureView.set(.width, to: profilePictureSize)
|
|
|
|
profilePictureView.set(.height, to: profilePictureSize)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2019-12-03 03:21:42 +01:00
|
|
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
|
|
|
|
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
// Path status indicator
|
|
|
|
let pathStatusView = PathStatusView()
|
|
|
|
pathStatusView.accessibilityLabel = "Current onion routing path indicator"
|
2022-04-21 08:42:35 +02:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
// Container view
|
2020-03-06 04:32:46 +01:00
|
|
|
let profilePictureViewContainer = UIView()
|
|
|
|
profilePictureViewContainer.addSubview(profilePictureView)
|
2022-01-24 23:46:22 +01:00
|
|
|
profilePictureView.autoPinEdgesToSuperviewEdges()
|
|
|
|
profilePictureViewContainer.addSubview(pathStatusView)
|
|
|
|
pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
|
|
|
pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
2022-02-28 07:23:34 +01:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
// Left bar button item
|
2020-12-17 01:37:53 +01:00
|
|
|
let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
|
|
|
leftBarButtonItem.isAccessibilityElement = true
|
|
|
|
navigationItem.leftBarButtonItem = leftBarButtonItem
|
2022-02-28 07:23:34 +01:00
|
|
|
|
2022-01-24 23:46:22 +01:00
|
|
|
// Right bar button item - search button
|
2022-01-25 05:04:11 +01:00
|
|
|
let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI))
|
2022-01-24 23:46:22 +01:00
|
|
|
rightBarButtonItem.accessibilityLabel = "Search button"
|
2020-12-17 01:37:53 +01:00
|
|
|
rightBarButtonItem.isAccessibilityElement = true
|
|
|
|
navigationItem.rightBarButtonItem = rightBarButtonItem
|
2019-11-29 06:30:01 +01:00
|
|
|
}
|
|
|
|
|
2022-04-21 08:42:35 +02:00
|
|
|
// MARK: - UITableViewDataSource
|
|
|
|
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
2022-06-24 10:29:45 +02:00
|
|
|
return viewModel.threadData.count
|
2022-04-21 08:42:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
2022-06-24 10:29:45 +02:00
|
|
|
let section: HomeViewModel.SectionModel = viewModel.threadData[section]
|
|
|
|
|
|
|
|
return section.elements.count
|
2022-04-21 08:42:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
2022-06-24 10:29:45 +02:00
|
|
|
let section: HomeViewModel.SectionModel = viewModel.threadData[indexPath.section]
|
2022-05-06 10:07:57 +02:00
|
|
|
|
|
|
|
switch section.model {
|
2022-04-21 08:42:35 +02:00
|
|
|
case .messageRequests:
|
2022-06-24 10:29:45 +02:00
|
|
|
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
2022-05-06 10:07:57 +02:00
|
|
|
let cell: MessageRequestsCell = tableView.dequeue(type: MessageRequestsCell.self, for: indexPath)
|
2022-11-15 00:54:33 +01:00
|
|
|
cell.accessibilityIdentifier = "Message requests banner"
|
|
|
|
cell.isAccessibilityElement = true
|
2022-06-24 10:29:45 +02:00
|
|
|
cell.update(with: Int(threadViewModel.threadUnreadCount ?? 0))
|
2022-04-21 08:42:35 +02:00
|
|
|
return cell
|
|
|
|
|
|
|
|
case .threads:
|
2022-06-24 10:29:45 +02:00
|
|
|
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
2022-05-29 11:26:06 +02:00
|
|
|
let cell: FullConversationCell = tableView.dequeue(type: FullConversationCell.self, for: indexPath)
|
2022-06-24 10:29:45 +02:00
|
|
|
cell.update(with: threadViewModel)
|
2022-12-05 05:27:27 +01:00
|
|
|
cell.accessibilityIdentifier = "Conversation list item"
|
|
|
|
cell.accessibilityLabel = threadViewModel.displayName
|
2022-04-21 08:42:35 +02:00
|
|
|
return cell
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
default: preconditionFailure("Other sections should have no content")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
|
|
let section: HomeViewModel.SectionModel = viewModel.threadData[section]
|
|
|
|
|
|
|
|
switch section.model {
|
|
|
|
case .loadMore:
|
|
|
|
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium)
|
2022-08-12 09:28:00 +02:00
|
|
|
loadingIndicator.themeTintColor = .textPrimary
|
2022-06-24 10:29:45 +02:00
|
|
|
loadingIndicator.alpha = 0.5
|
|
|
|
loadingIndicator.startAnimating()
|
|
|
|
|
|
|
|
let view: UIView = UIView()
|
|
|
|
view.addSubview(loadingIndicator)
|
|
|
|
loadingIndicator.center(in: view)
|
|
|
|
|
|
|
|
return view
|
|
|
|
|
|
|
|
default: return nil
|
2022-04-21 08:42:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-02 06:59:56 +01:00
|
|
|
// MARK: - UITableViewDelegate
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
|
|
let section: HomeViewModel.SectionModel = viewModel.threadData[section]
|
|
|
|
|
|
|
|
switch section.model {
|
|
|
|
case .loadMore: return HomeVC.loadingHeaderHeight
|
|
|
|
default: return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
|
2022-07-18 04:32:46 +02:00
|
|
|
guard self.hasLoadedInitialThreadData && self.viewHasAppeared && !self.isLoadingMore else { return }
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
let section: HomeViewModel.SectionModel = self.viewModel.threadData[section]
|
|
|
|
|
|
|
|
switch section.model {
|
|
|
|
case .loadMore:
|
|
|
|
self.isLoadingMore = true
|
|
|
|
|
2022-10-14 08:09:38 +02:00
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
2022-06-24 10:29:45 +02:00
|
|
|
self?.viewModel.pagedDataObserver?.load(.pageAfter)
|
|
|
|
}
|
|
|
|
|
|
|
|
default: break
|
|
|
|
}
|
|
|
|
}
|
2022-02-02 06:59:56 +01:00
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section]
|
2022-05-06 10:07:57 +02:00
|
|
|
|
|
|
|
switch section.model {
|
|
|
|
case .messageRequests:
|
2022-02-02 06:59:56 +01:00
|
|
|
let viewController: MessageRequestsViewController = MessageRequestsViewController()
|
|
|
|
self.navigationController?.pushViewController(viewController, animated: true)
|
|
|
|
|
2022-05-06 10:07:57 +02:00
|
|
|
case .threads:
|
2022-06-24 10:29:45 +02:00
|
|
|
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
2022-06-09 10:37:44 +02:00
|
|
|
show(
|
2022-06-24 10:29:45 +02:00
|
|
|
threadViewModel.threadId,
|
|
|
|
variant: threadViewModel.threadVariant,
|
2022-07-08 09:53:48 +02:00
|
|
|
isMessageRequest: (threadViewModel.threadIsMessageRequest == true),
|
2022-06-09 10:37:44 +02:00
|
|
|
with: .none,
|
|
|
|
focusedInteractionId: nil,
|
|
|
|
animated: true
|
|
|
|
)
|
2022-06-24 10:29:45 +02:00
|
|
|
|
|
|
|
default: break
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-02-24 07:03:45 +01:00
|
|
|
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
|
|
|
let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section]
|
|
|
|
let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500)
|
|
|
|
|
|
|
|
switch section.model {
|
|
|
|
case .messageRequests:
|
|
|
|
return nil
|
|
|
|
case .threads:
|
|
|
|
|
|
|
|
return UISwipeActionsConfiguration(actions: [ ])
|
|
|
|
default: return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
2022-06-24 10:29:45 +02:00
|
|
|
let section: HomeViewModel.SectionModel = self.viewModel.threadData[indexPath.section]
|
2022-08-26 06:09:41 +02:00
|
|
|
let unswipeAnimationDelay: DispatchTimeInterval = .milliseconds(500)
|
2022-05-06 10:07:57 +02:00
|
|
|
|
|
|
|
switch section.model {
|
2022-04-21 08:42:35 +02:00
|
|
|
case .messageRequests:
|
2022-08-26 06:09:41 +02:00
|
|
|
let hide: UIContextualAction = UIContextualAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _, completionHandler in
|
2022-07-01 05:08:45 +02:00
|
|
|
Storage.shared.write { db in db[.hasHiddenMessageRequests] = true }
|
2022-08-26 06:09:41 +02:00
|
|
|
completionHandler(true)
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
2022-08-24 09:33:10 +02:00
|
|
|
hide.themeBackgroundColor = .conversationButton_swipeDestructive
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
return UISwipeActionsConfiguration(actions: [hide])
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-05-06 10:07:57 +02:00
|
|
|
case .threads:
|
2022-06-24 10:29:45 +02:00
|
|
|
let threadViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
2022-08-26 06:09:41 +02:00
|
|
|
let delete: UIContextualAction = UIContextualAction(
|
2022-05-06 10:07:57 +02:00
|
|
|
style: .destructive,
|
|
|
|
title: "TXT_DELETE_TITLE".localized()
|
2022-08-26 06:09:41 +02:00
|
|
|
) { [weak self] _, _, completionHandler in
|
2022-09-02 09:32:13 +02:00
|
|
|
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
|
|
|
info: ConfirmationModal.Info(
|
|
|
|
title: "CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE".localized(),
|
|
|
|
explanation: (threadViewModel.currentUserIsClosedGroupAdmin == true ?
|
|
|
|
"admin_group_leave_warning".localized() :
|
|
|
|
"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE".localized()
|
|
|
|
),
|
|
|
|
confirmTitle: "TXT_DELETE_TITLE".localized(),
|
|
|
|
confirmStyle: .danger,
|
2022-09-28 10:26:02 +02:00
|
|
|
cancelStyle: .alert_text,
|
2022-09-02 09:32:13 +02:00
|
|
|
dismissOnConfirm: true,
|
|
|
|
onConfirm: { [weak self] _ in
|
|
|
|
self?.viewModel.delete(
|
|
|
|
threadId: threadViewModel.threadId,
|
|
|
|
threadVariant: threadViewModel.threadVariant
|
|
|
|
)
|
|
|
|
self?.dismiss(animated: true, completion: nil)
|
|
|
|
|
|
|
|
completionHandler(true)
|
|
|
|
},
|
|
|
|
afterClosed: { completionHandler(false) }
|
|
|
|
)
|
2022-05-06 10:07:57 +02:00
|
|
|
)
|
|
|
|
|
2022-09-02 09:32:13 +02:00
|
|
|
self?.present(confirmationModal, animated: true, completion: nil)
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
2022-08-24 09:33:10 +02:00
|
|
|
delete.themeBackgroundColor = .conversationButton_swipeDestructive
|
2023-02-24 07:03:45 +01:00
|
|
|
delete.setupSessionStyle(with: UIImage(systemName: "trash"))
|
2022-05-06 10:07:57 +02:00
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
let pin: UIContextualAction = UIContextualAction(
|
2022-05-06 10:07:57 +02:00
|
|
|
style: .normal,
|
2022-06-24 10:29:45 +02:00
|
|
|
title: (threadViewModel.threadIsPinned ?
|
2022-05-17 09:47:56 +02:00
|
|
|
"UNPIN_BUTTON_TEXT".localized() :
|
|
|
|
"PIN_BUTTON_TEXT".localized()
|
2022-05-06 10:07:57 +02:00
|
|
|
)
|
2022-08-26 06:09:41 +02:00
|
|
|
) { _, _, completionHandler in
|
|
|
|
(tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate(
|
|
|
|
isPinned: !threadViewModel.threadIsPinned
|
|
|
|
)
|
|
|
|
completionHandler(true)
|
|
|
|
|
|
|
|
// Delay the change to give the cell "unswipe" animation some time to complete
|
|
|
|
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
|
|
|
|
Storage.shared.writeAsync { db in
|
|
|
|
try SessionThread
|
|
|
|
.filter(id: threadViewModel.threadId)
|
|
|
|
.updateAll(db, SessionThread.Columns.isPinned.set(to: !threadViewModel.threadIsPinned))
|
|
|
|
}
|
2022-05-06 10:07:57 +02:00
|
|
|
}
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
2022-08-24 09:33:10 +02:00
|
|
|
pin.themeBackgroundColor = .conversationButton_swipeTertiary
|
2023-02-24 07:03:45 +01:00
|
|
|
pin.setupSessionStyle(with: UIImage(systemName: "pin"))
|
2022-02-02 06:59:56 +01:00
|
|
|
|
2022-06-24 10:29:45 +02:00
|
|
|
guard threadViewModel.threadVariant == .contact && !threadViewModel.threadIsNoteToSelf else {
|
2022-08-26 06:09:41 +02:00
|
|
|
return UISwipeActionsConfiguration(actions: [ delete, pin ])
|
2022-05-06 10:07:57 +02:00
|
|
|
}
|
2022-03-22 23:59:38 +01:00
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
let block: UIContextualAction = UIContextualAction(
|
2022-05-06 10:07:57 +02:00
|
|
|
style: .normal,
|
2022-06-24 10:29:45 +02:00
|
|
|
title: (threadViewModel.threadIsBlocked == true ?
|
2022-05-06 10:07:57 +02:00
|
|
|
"BLOCK_LIST_UNBLOCK_BUTTON".localized() :
|
|
|
|
"BLOCK_LIST_BLOCK_BUTTON".localized()
|
|
|
|
)
|
2022-08-26 06:09:41 +02:00
|
|
|
) { _, _, completionHandler in
|
|
|
|
(tableView.cellForRow(at: indexPath) as? FullConversationCell)?.optimisticUpdate(
|
|
|
|
isBlocked: (threadViewModel.threadIsBlocked == false)
|
|
|
|
)
|
|
|
|
completionHandler(true)
|
|
|
|
|
|
|
|
// Delay the change to give the cell "unswipe" animation some time to complete
|
|
|
|
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + unswipeAnimationDelay) {
|
|
|
|
Storage.shared.writeAsync { db in
|
|
|
|
try Contact
|
|
|
|
.filter(id: threadViewModel.threadId)
|
|
|
|
.updateAll(
|
|
|
|
db,
|
|
|
|
Contact.Columns.isBlocked.set(
|
|
|
|
to: (threadViewModel.threadIsBlocked == false ?
|
|
|
|
true:
|
|
|
|
false
|
|
|
|
)
|
2022-05-17 09:47:56 +02:00
|
|
|
)
|
|
|
|
)
|
2022-08-26 06:09:41 +02:00
|
|
|
|
|
|
|
try MessageSender.syncConfiguration(db, forceSyncNow: true)
|
|
|
|
.retainUntilComplete()
|
|
|
|
}
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
|
|
|
}
|
2022-08-24 09:33:10 +02:00
|
|
|
block.themeBackgroundColor = .conversationButton_swipeSecondary
|
2022-05-06 10:07:57 +02:00
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
return UISwipeActionsConfiguration(actions: [ delete, block, pin ])
|
2022-06-24 10:29:45 +02:00
|
|
|
|
2022-08-26 06:09:41 +02:00
|
|
|
default: return nil
|
2022-02-02 06:59:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Interaction
|
|
|
|
|
2019-12-12 01:10:26 +01:00
|
|
|
func handleContinueButtonTapped(from seedReminderView: SeedReminderView) {
|
2020-01-30 10:09:02 +01:00
|
|
|
let seedVC = SeedVC()
|
2022-09-30 10:22:28 +02:00
|
|
|
let navigationController = StyledNavigationController(rootViewController: seedVC)
|
2019-12-12 03:17:14 +01:00
|
|
|
present(navigationController, animated: true, completion: nil)
|
2019-12-12 01:10:26 +01:00
|
|
|
}
|
|
|
|
|
2022-05-06 10:07:57 +02:00
|
|
|
func show(
|
|
|
|
_ threadId: String,
|
2022-06-09 10:37:44 +02:00
|
|
|
variant: SessionThread.Variant,
|
2022-07-08 09:53:48 +02:00
|
|
|
isMessageRequest: Bool,
|
2022-05-06 10:07:57 +02:00
|
|
|
with action: ConversationViewModel.Action,
|
2022-05-27 10:37:59 +02:00
|
|
|
focusedInteractionId: Int64?,
|
2022-05-06 10:07:57 +02:00
|
|
|
animated: Bool
|
|
|
|
) {
|
|
|
|
if let presentedVC = self.presentedViewController {
|
|
|
|
presentedVC.dismiss(animated: false, completion: nil)
|
2021-01-13 06:10:06 +01:00
|
|
|
}
|
2022-06-08 06:29:51 +02:00
|
|
|
|
2022-07-08 09:53:48 +02:00
|
|
|
let finalViewControllers: [UIViewController] = [
|
|
|
|
self,
|
|
|
|
(isMessageRequest ? MessageRequestsViewController() : nil),
|
|
|
|
ConversationVC(
|
|
|
|
threadId: threadId,
|
|
|
|
threadVariant: variant,
|
|
|
|
focusedInteractionId: focusedInteractionId
|
|
|
|
)
|
|
|
|
].compactMap { $0 }
|
|
|
|
|
|
|
|
self.navigationController?.setViewControllers(finalViewControllers, animated: animated)
|
2021-01-13 06:10:06 +01:00
|
|
|
}
|
|
|
|
|
2019-11-29 06:30:01 +01:00
|
|
|
@objc private func openSettings() {
|
2022-09-28 09:30:31 +02:00
|
|
|
let settingsViewController: SessionTableViewController = SessionTableViewController(
|
2022-09-27 07:36:47 +02:00
|
|
|
viewModel: SettingsViewModel()
|
|
|
|
)
|
2022-09-30 10:22:28 +02:00
|
|
|
let navigationController = StyledNavigationController(rootViewController: settingsViewController)
|
2022-03-02 00:50:15 +01:00
|
|
|
navigationController.modalPresentationStyle = .fullScreen
|
2019-11-29 06:30:01 +01:00
|
|
|
present(navigationController, animated: true, completion: nil)
|
|
|
|
}
|
|
|
|
|
2022-01-25 05:04:11 +01:00
|
|
|
@objc private func showSearchUI() {
|
2022-01-25 05:25:48 +01:00
|
|
|
if let presentedVC = self.presentedViewController {
|
|
|
|
presentedVC.dismiss(animated: false, completion: nil)
|
|
|
|
}
|
2022-01-25 05:04:11 +01:00
|
|
|
let searchController = GlobalSearchViewController()
|
2022-01-25 05:25:48 +01:00
|
|
|
self.navigationController?.setViewControllers([ self, searchController ], animated: true)
|
2022-01-25 05:04:11 +01:00
|
|
|
}
|
|
|
|
|
2022-08-24 07:37:43 +02:00
|
|
|
@objc func createNewConversation() {
|
|
|
|
let newConversationVC = NewConversationVC()
|
2022-09-30 10:22:28 +02:00
|
|
|
let navigationController = StyledNavigationController(rootViewController: newConversationVC)
|
2022-02-24 04:03:50 +01:00
|
|
|
if UIDevice.current.isIPad {
|
|
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
|
|
}
|
2022-08-24 07:37:43 +02:00
|
|
|
navigationController.modalPresentationCapturesStatusBarAppearance = true
|
2019-11-29 06:30:01 +01:00
|
|
|
present(navigationController, animated: true, completion: nil)
|
|
|
|
}
|
|
|
|
|
2022-09-26 03:16:47 +02:00
|
|
|
func createNewDMFromDeepLink(sessionId: String) {
|
|
|
|
let newDMVC = NewDMVC(sessionId: sessionId, shouldShowBackButton: false)
|
2022-09-30 10:22:28 +02:00
|
|
|
let navigationController = StyledNavigationController(rootViewController: newDMVC)
|
2022-02-24 04:03:50 +01:00
|
|
|
if UIDevice.current.isIPad {
|
|
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
|
|
}
|
2022-08-23 06:38:35 +02:00
|
|
|
navigationController.modalPresentationCapturesStatusBarAppearance = true
|
2019-12-02 01:58:15 +01:00
|
|
|
present(navigationController, animated: true, completion: nil)
|
|
|
|
}
|
2019-11-28 06:42:07 +01:00
|
|
|
}
|