|
|
|
import SessionUIKit
|
|
|
|
|
|
|
|
final class ShareVC : UIViewController, UITableViewDataSource, AppModeManagerDelegate {
|
|
|
|
@IBOutlet private var logoImageView: UIImageView!
|
|
|
|
private var areVersionMigrationsComplete = false
|
|
|
|
private var threads: YapDatabaseViewMappings!
|
|
|
|
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
|
|
|
|
|
|
|
|
private var threadCount: UInt {
|
|
|
|
threads.numberOfItems(inGroup: TSInboxGroup)
|
|
|
|
}
|
|
|
|
|
|
|
|
private lazy var dbConnection: YapDatabaseConnection = {
|
|
|
|
let result = OWSPrimaryStorage.shared().newDatabaseConnection()
|
|
|
|
result.objectCacheLimit = 500
|
|
|
|
return result
|
|
|
|
}()
|
|
|
|
|
|
|
|
private lazy var tableView: UITableView = {
|
|
|
|
let result = UITableView()
|
|
|
|
result.backgroundColor = .clear
|
|
|
|
result.separatorStyle = .none
|
|
|
|
result.register(SimplifiedConversationCell.self, forCellReuseIdentifier: SimplifiedConversationCell.reuseIdentifier)
|
|
|
|
result.showsVerticalScrollIndicator = false
|
|
|
|
return result
|
|
|
|
}()
|
|
|
|
|
|
|
|
// MARK: Lifecycle
|
|
|
|
override func loadView() {
|
|
|
|
super.loadView()
|
|
|
|
|
|
|
|
// This should be the first thing we do.
|
|
|
|
let appContext = ShareAppExtensionContext(rootViewController: self)
|
|
|
|
SetCurrentAppContext(appContext)
|
|
|
|
|
|
|
|
AppModeManager.configure(delegate: self)
|
|
|
|
|
|
|
|
DebugLogger.shared().enableTTYLogging()
|
|
|
|
if _isDebugAssertConfiguration() {
|
|
|
|
DebugLogger.shared().enableFileLogging()
|
|
|
|
} else if OWSPreferences.isLoggingEnabled() {
|
|
|
|
DebugLogger.shared().enableFileLogging()
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.info("")
|
|
|
|
|
|
|
|
_ = AppVersion.sharedInstance()
|
|
|
|
|
|
|
|
Cryptography.seedRandom()
|
|
|
|
|
|
|
|
// We don't need to use DeviceSleepManager in the SAE.
|
|
|
|
|
|
|
|
// We don't need to use applySignalAppearence in the SAE.
|
|
|
|
|
|
|
|
if CurrentAppContext().isRunningTests {
|
|
|
|
// TODO: Do we need to implement isRunningTests in the SAE context?
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
AppSetup.setupEnvironment(appSpecificSingletonBlock: {
|
|
|
|
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
|
|
|
|
}, migrationCompletion: { [weak self] in
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
guard let strongSelf = self else { return }
|
|
|
|
|
|
|
|
// performUpdateCheck must be invoked after Environment has been initialized because
|
|
|
|
// upgrade process may depend on Environment.
|
|
|
|
strongSelf.versionMigrationsDidComplete()
|
|
|
|
})
|
|
|
|
|
|
|
|
// We don't need to use "screen protection" in the SAE.
|
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(storageIsReady),
|
|
|
|
name: .StorageIsReady,
|
|
|
|
object: nil)
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
|
|
selector: #selector(applicationDidEnterBackground),
|
|
|
|
name: .OWSApplicationDidEnterBackground,
|
|
|
|
object: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
func versionMigrationsDidComplete() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
Logger.debug("")
|
|
|
|
|
|
|
|
areVersionMigrationsComplete = true
|
|
|
|
|
|
|
|
checkIsAppReady()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
func storageIsReady() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
Logger.debug("")
|
|
|
|
|
|
|
|
checkIsAppReady()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
func checkIsAppReady() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
// App isn't ready until storage is ready AND all version migrations are complete.
|
|
|
|
guard areVersionMigrationsComplete else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard OWSStorage.isStorageReady() else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
guard !AppReadiness.isAppReady() else {
|
|
|
|
// Only mark the app as ready once.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
SignalUtilitiesKit.Configuration.performMainSetup()
|
|
|
|
|
|
|
|
Logger.debug("")
|
|
|
|
|
|
|
|
// TODO: Once "app ready" logic is moved into AppSetup, move this line there.
|
|
|
|
OWSProfileManager.shared().ensureLocalProfileCached()
|
|
|
|
|
|
|
|
// Note that this does much more than set a flag;
|
|
|
|
// it will also run all deferred blocks.
|
|
|
|
AppReadiness.setAppIsReady()
|
|
|
|
|
|
|
|
// We don't need to use messageFetcherJob in the SAE.
|
|
|
|
|
|
|
|
// We don't need to use SyncPushTokensJob in the SAE.
|
|
|
|
|
|
|
|
// We don't need to use DeviceSleepManager in the SAE.
|
|
|
|
|
|
|
|
AppVersion.sharedInstance().saeLaunchDidComplete()
|
|
|
|
|
|
|
|
setUpViewHierarchy()
|
|
|
|
|
|
|
|
// We don't need to use OWSMessageReceiver in the SAE.
|
|
|
|
// We don't need to use OWSBatchMessageProcessor in the SAE.
|
|
|
|
|
|
|
|
OWSProfileManager.shared().ensureLocalProfileCached()
|
|
|
|
|
|
|
|
// We don't need to use OWSOrphanDataCleaner in the SAE.
|
|
|
|
|
|
|
|
// We don't need to fetch the local profile in the SAE
|
|
|
|
|
|
|
|
OWSReadReceiptManager.shared().prepareCachedValues()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func setUpViewHierarchy() {
|
|
|
|
// Threads
|
|
|
|
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
|
|
|
|
threads = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) // The extension should be registered at this point
|
|
|
|
threads.setIsReversed(true, forGroup: TSInboxGroup)
|
|
|
|
dbConnection.read { transaction in
|
|
|
|
self.threads.update(with: transaction) // Perform the initial update
|
|
|
|
}
|
|
|
|
// Logo
|
|
|
|
logoImageView.alpha = 0
|
|
|
|
// Table view
|
|
|
|
tableView.dataSource = self
|
|
|
|
view.addSubview(tableView)
|
|
|
|
tableView.pin(to: view)
|
|
|
|
// Reload
|
|
|
|
reload()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc
|
|
|
|
public func applicationDidEnterBackground() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
Logger.info("")
|
|
|
|
|
|
|
|
if OWSScreenLock.shared.isScreenLockEnabled() {
|
|
|
|
|
|
|
|
self.dismiss(animated: false) { [weak self] in
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
guard let strongSelf = self else { return }
|
|
|
|
strongSelf.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deinit {
|
|
|
|
NotificationCenter.default.removeObserver(self)
|
|
|
|
|
|
|
|
// Share extensions reside in a process that may be reused between usages.
|
|
|
|
// That isn't safe; the codebase is full of statics (e.g. singletons) which
|
|
|
|
// we can't easily clean up.
|
|
|
|
ExitShareExtension()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: App Mode
|
|
|
|
public func getCurrentAppMode() -> AppMode {
|
|
|
|
guard let window = self.view.window else { return .light }
|
|
|
|
let userInterfaceStyle = window.traitCollection.userInterfaceStyle
|
|
|
|
let isLightMode = (userInterfaceStyle == .light || userInterfaceStyle == .unspecified)
|
|
|
|
return isLightMode ? .light : .dark
|
|
|
|
}
|
|
|
|
|
|
|
|
public func setCurrentAppMode(to appMode: AppMode) {
|
|
|
|
return // Not applicable to share extensions
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Table View Data Source
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
|
|
return Int(threadCount)
|
|
|
|
}
|
|
|
|
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: SimplifiedConversationCell.reuseIdentifier) as! SimplifiedConversationCell
|
|
|
|
cell.threadViewModel = threadViewModel(at: indexPath.row)
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Updating
|
|
|
|
private func reload() {
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
|
|
|
|
dbConnection.read { transaction in
|
|
|
|
self.threads.update(with: transaction)
|
|
|
|
}
|
|
|
|
threadViewModelCache.removeAll()
|
|
|
|
tableView.reloadData()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Convenience
|
|
|
|
private func thread(at index: Int) -> TSThread? {
|
|
|
|
var thread: TSThread? = nil
|
|
|
|
dbConnection.read { transaction in
|
|
|
|
let ext = transaction.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewTransaction
|
|
|
|
thread = ext.object(atRow: UInt(index), inSection: 0, with: self.threads) as! TSThread?
|
|
|
|
}
|
|
|
|
return thread
|
|
|
|
}
|
|
|
|
|
|
|
|
private func threadViewModel(at index: Int) -> ThreadViewModel? {
|
|
|
|
guard let thread = thread(at: index) else { return nil }
|
|
|
|
if let cachedThreadViewModel = threadViewModelCache[thread.uniqueId!] {
|
|
|
|
return cachedThreadViewModel
|
|
|
|
} else {
|
|
|
|
var threadViewModel: ThreadViewModel? = nil
|
|
|
|
dbConnection.read { transaction in
|
|
|
|
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)
|
|
|
|
}
|
|
|
|
threadViewModelCache[thread.uniqueId!] = threadViewModel
|
|
|
|
return threadViewModel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|