session-ios/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift

589 lines
21 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
2017-09-26 19:40:37 +02:00
import UIKit
import Combine
import Reachability
2020-11-11 07:45:50 +01:00
import SignalUtilitiesKit
import SessionUIKit
import SignalCoreKit
2017-09-26 19:40:37 +02:00
class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegate, GifPickerLayoutDelegate {
2017-09-26 19:40:37 +02:00
// MARK: Properties
enum ViewMode {
case idle, searching, results, noResults, error
}
private var viewMode = ViewMode.idle {
didSet {
2018-08-24 18:40:16 +02:00
Logger.info("viewMode: \(viewMode)")
updateContents()
}
}
var lastQuery: String = ""
public weak var delegate: GifPickerViewControllerDelegate?
2019-12-13 05:02:05 +01:00
let searchBar: SearchBar
2017-09-26 19:40:37 +02:00
let layout: GifPickerLayout
let collectionView: UICollectionView
var noResultsView: UILabel?
var searchErrorView: UILabel?
var activityIndicator: UIActivityIndicatorView?
var hasSelectedCell: Bool = false
2017-09-28 20:09:11 +02:00
var imageInfos = [GiphyImageInfo]()
2017-09-28 20:09:11 +02:00
private let kCellReuseIdentifier = "kCellReuseIdentifier"
2017-09-26 19:40:37 +02:00
var progressiveSearchTimer: Timer?
private var disposables: Set<AnyCancellable> = Set()
// MARK: - Initialization
2017-09-26 19:40:37 +02:00
@available(*, unavailable, message:"use other constructor instead.")
2017-09-26 19:40:37 +02:00
required init?(coder aDecoder: NSCoder) {
2018-08-27 16:21:03 +02:00
notImplemented()
2017-09-26 19:40:37 +02:00
}
required init() {
2019-12-13 05:02:05 +01:00
self.searchBar = SearchBar()
2017-09-26 19:40:37 +02:00
self.layout = GifPickerLayout()
self.collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.layout)
2017-09-26 19:40:37 +02:00
super.init(nibName: nil, bundle: nil)
2017-09-29 00:15:18 +02:00
self.layout.delegate = self
2017-09-26 19:40:37 +02:00
}
deinit {
NotificationCenter.default.removeObserver(self)
progressiveSearchTimer?.invalidate()
}
2018-05-25 18:54:25 +02:00
@objc func didBecomeActive() {
AssertIsOnMainThread()
2018-08-23 16:37:34 +02:00
Logger.info("")
// Prod cells to try to load when app becomes active.
ensureCellState()
}
2018-05-25 18:54:25 +02:00
@objc func reachabilityChanged() {
AssertIsOnMainThread()
2018-08-23 16:37:34 +02:00
Logger.info("")
// Prod cells to try to load when connectivity changes.
ensureCellState()
}
func ensureCellState() {
for cell in self.collectionView.visibleCells {
guard let cell = cell as? GifPickerCell else {
2018-08-27 16:27:48 +02:00
owsFailDebug("unexpected cell.")
return
}
cell.ensureCellState()
}
}
2017-09-26 19:40:37 +02:00
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .cancel,
target: self,
action: #selector(donePressed)
)
2020-01-20 04:44:51 +01:00
// Loki: Customize title
let titleLabel: UILabel = UILabel()
2020-01-20 04:44:51 +01:00
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = "accessibility_gif_button".localized().uppercased()
titleLabel.themeTextColor = .textPrimary
2020-01-20 04:44:51 +01:00
navigationItem.titleView = titleLabel
2017-09-26 19:40:37 +02:00
createViews()
NotificationCenter.default.addObserver(
self,
selector: #selector(reachabilityChanged),
name: .reachabilityChanged,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(didBecomeActive),
name: .OWSApplicationDidBecomeActive,
object: nil
)
loadTrending()
2017-09-26 19:40:37 +02:00
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
2017-09-28 20:09:11 +02:00
self.searchBar.becomeFirstResponder()
2017-09-28 20:09:11 +02:00
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
progressiveSearchTimer?.invalidate()
progressiveSearchTimer = nil
}
2017-09-26 19:40:37 +02:00
// MARK: Views
private func createViews() {
self.view.themeBackgroundColor = .backgroundPrimary
2017-09-26 19:40:37 +02:00
// Search
searchBar.delegate = self
2017-10-02 21:24:57 +02:00
2017-09-26 19:40:37 +02:00
self.view.addSubview(searchBar)
searchBar.autoPinWidthToSuperview()
2020-06-05 05:43:06 +02:00
searchBar.autoPinEdge(.top, to: .top, of: view)
2017-09-26 19:40:37 +02:00
2017-09-28 20:09:11 +02:00
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.themeBackgroundColor = .backgroundPrimary
2017-09-28 20:09:11 +02:00
self.collectionView.register(GifPickerCell.self, forCellWithReuseIdentifier: kCellReuseIdentifier)
2017-10-20 20:19:32 +02:00
// Inserted below searchbar because we later occlude the collectionview
// by inserting a masking layer between the search bar and collectionview
self.view.insertSubview(self.collectionView, belowSubview: searchBar)
2019-01-08 17:18:05 +01:00
self.collectionView.autoPinEdge(toSuperviewSafeArea: .leading)
self.collectionView.autoPinEdge(toSuperviewSafeArea: .trailing)
self.collectionView.autoPinEdge(.top, to: .bottom, of: searchBar)
// Block UIKit from adjust insets of collection view which screws up
// min/max scroll positions
self.collectionView.contentInsetAdjustmentBehavior = .never
// for iPhoneX devices, extends the black background to the bottom edge of the view.
let bottomBannerContainer = UIView()
bottomBannerContainer.themeBackgroundColor = .backgroundPrimary
self.view.addSubview(bottomBannerContainer)
bottomBannerContainer.autoPinWidthToSuperview()
bottomBannerContainer.autoPinEdge(.top, to: .bottom, of: self.collectionView)
bottomBannerContainer.autoPinEdge(toSuperviewEdge: .bottom)
let bottomBanner = UIView()
bottomBannerContainer.addSubview(bottomBanner)
bottomBanner.autoPinEdge(toSuperviewEdge: .top)
bottomBanner.autoPinWidthToSuperview()
2018-08-08 21:49:22 +02:00
self.autoPinView(toBottomOfViewControllerOrKeyboard: bottomBanner, avoidNotch: true)
2017-09-26 19:40:37 +02:00
2017-10-01 02:26:29 +02:00
// The Giphy API requires us to "show their trademark prominently" in our GIF experience.
let logoImage = UIImage(named: "giphy_logo")
let logoImageView = UIImageView(image: logoImage)
bottomBanner.addSubview(logoImageView)
logoImageView.autoPinHeightToSuperview(withMargin: 3)
logoImageView.autoHCenterInSuperview()
2017-09-28 20:09:11 +02:00
let noResultsView = createErrorLabel(text: "GIF_VIEW_SEARCH_NO_RESULTS".localized())
self.noResultsView = noResultsView
self.view.addSubview(noResultsView)
noResultsView.autoPinWidthToSuperview(withMargin: 20)
noResultsView.autoAlignAxis(.horizontal, toSameAxisOf: self.collectionView)
let searchErrorView = createErrorLabel(text: "GIF_VIEW_SEARCH_ERROR".localized())
self.searchErrorView = searchErrorView
self.view.addSubview(searchErrorView)
searchErrorView.autoPinWidthToSuperview(withMargin: 20)
searchErrorView.autoAlignAxis(.horizontal, toSameAxisOf: self.collectionView)
searchErrorView.isUserInteractionEnabled = true
searchErrorView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(retryTapped)))
let activityIndicator = UIActivityIndicatorView(style: .large)
self.activityIndicator = activityIndicator
self.view.addSubview(activityIndicator)
activityIndicator.autoHCenterInSuperview()
activityIndicator.autoAlignAxis(.horizontal, toSameAxisOf: self.collectionView)
2019-12-13 05:02:05 +01:00
2017-09-26 19:40:37 +02:00
self.updateContents()
}
private func createErrorLabel(text: String) -> UILabel {
let label: UILabel = UILabel()
label.font = UIFont.systemFont(ofSize: 20, weight: .medium)
label.text = text
label.themeTextColor = .textPrimary
label.textAlignment = .center
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
return label
}
2017-09-26 19:40:37 +02:00
private func updateContents() {
guard let noResultsView = self.noResultsView else {
2018-08-27 16:27:48 +02:00
owsFailDebug("Missing noResultsView")
return
}
guard let searchErrorView = self.searchErrorView else {
2018-08-27 16:27:48 +02:00
owsFailDebug("Missing searchErrorView")
return
}
guard let activityIndicator = self.activityIndicator else {
2018-08-27 16:27:48 +02:00
owsFailDebug("Missing activityIndicator")
return
}
switch viewMode {
case .idle:
self.collectionView.isHidden = true
noResultsView.isHidden = true
searchErrorView.isHidden = true
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
case .searching:
self.collectionView.isHidden = true
noResultsView.isHidden = true
searchErrorView.isHidden = true
activityIndicator.isHidden = false
activityIndicator.startAnimating()
case .results:
self.collectionView.isHidden = false
noResultsView.isHidden = true
searchErrorView.isHidden = true
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.reloadData()
case .noResults:
self.collectionView.isHidden = true
noResultsView.isHidden = false
searchErrorView.isHidden = true
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
case .error:
self.collectionView.isHidden = true
noResultsView.isHidden = true
searchErrorView.isHidden = false
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
}
2017-09-26 19:40:37 +02:00
}
// MARK: - UIScrollViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.searchBar.resignFirstResponder()
}
2017-09-28 20:09:11 +02:00
// MARK: - UICollectionViewDataSource
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageInfos.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
2017-10-20 04:51:32 +02:00
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCellReuseIdentifier, for: indexPath)
guard indexPath.row < imageInfos.count else {
2018-08-24 18:40:16 +02:00
Logger.warn("indexPath: \(indexPath.row) out of range for imageInfo count: \(imageInfos.count) ")
2017-10-20 04:51:32 +02:00
return cell
}
2017-09-28 20:09:11 +02:00
let imageInfo = imageInfos[indexPath.row]
2017-10-01 20:54:39 +02:00
guard let gifCell = cell as? GifPickerCell else {
2018-08-27 16:27:48 +02:00
owsFailDebug("Unexpected cell type.")
2017-10-01 20:54:39 +02:00
return cell
}
gifCell.imageInfo = imageInfo
2017-09-28 20:09:11 +02:00
return cell
}
// MARK: - UICollectionViewDelegate
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? GifPickerCell else {
2018-08-27 16:27:48 +02:00
owsFailDebug("unexpected cell.")
return
}
guard cell.stillAsset != nil || cell.animatedAsset != nil else {
// we don't want to let the user blindly select a gray cell
2018-08-23 16:37:34 +02:00
Logger.debug("ignoring selection of cell with no preview")
return
}
guard self.hasSelectedCell == false else {
2018-08-27 16:27:48 +02:00
owsFailDebug("Already selected cell")
return
}
self.hasSelectedCell = true
// Fade out all cells except the selected one.
let maskingView = OWSBezierPathView()
// Selecting cell behind searchbar masks part of search bar.
// So we insert mask *behind* the searchbar.
self.view.insertSubview(maskingView, belowSubview: searchBar)
let cellRect = self.collectionView.convert(cell.frame, to: self.view)
maskingView.configureShapeLayerBlock = { layer, bounds in
let path = UIBezierPath(rect: bounds)
path.append(UIBezierPath(rect: cellRect))
layer.path = path.cgPath
layer.fillRule = .evenOdd
layer.themeFillColor = .black
layer.opacity = 0.7
}
maskingView.autoPinEdgesToSuperviewEdges()
cell.isCellSelected = true
self.collectionView.isUserInteractionEnabled = false
getFileForCell(cell)
}
public func getFileForCell(_ cell: GifPickerCell) {
2019-01-23 20:03:21 +01:00
GiphyDownloader.giphyDownloader.cancelAllRequests()
cell
.requestRenditionForSending()
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)
2023-06-23 09:54:29 +02:00
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] result in
switch result {
case .finished: break
case .failure(let error):
let modal: ConfirmationModal = ConfirmationModal(
targetView: self?.view,
info: ConfirmationModal.Info(
title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(),
Merge remote-tracking branch 'upstream/dev' into feature/updated-user-config-handling # Conflicts: # Session/Closed Groups/NewClosedGroupVC.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewModel.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/New Conversation/NewDMVC.swift # Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Settings/BlockedContactsViewController.swift # Session/Settings/NukeDataModal.swift # Session/Settings/SettingsViewModel.swift # SessionMessagingKit/Shared Models/SessionThreadViewModel.swift # SessionUIKit/Components/ConfirmationModal.swift
2023-05-18 09:34:25 +02:00
body: .text(error.localizedDescription),
confirmTitle: CommonStrings.retryButton,
cancelTitle: CommonStrings.dismissButton,
cancelStyle: .alert_text,
onConfirm: { _ in
self?.getFileForCell(cell)
}
)
)
self?.present(modal, animated: true)
}
},
receiveValue: { [weak self] asset in
guard let rendition = asset.assetDescription as? GiphyRendition else {
owsFailDebug("Invalid asset description.")
return
}
2019-01-23 20:03:21 +01:00
let filePath = asset.filePath
guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath,
shouldDeleteOnDeallocation: false) else {
owsFailDebug("couldn't load asset.")
return
}
let attachment = SignalAttachment.attachment(dataSource: dataSource, dataUTI: rendition.utiType, imageQuality: .medium)
self?.dismiss(animated: true) {
// Delegate presents view controllers, so it's important that *this* controller be dismissed before that occurs.
self?.delegate?.gifPickerDidSelect(attachment: attachment)
}
}
)
.store(in: &disposables)
2017-09-28 20:09:11 +02:00
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
2017-09-29 05:30:33 +02:00
guard let cell = cell as? GifPickerCell else {
2018-08-27 16:27:48 +02:00
owsFailDebug("unexpected cell.")
2017-09-29 05:30:33 +02:00
return
}
// We only want to load the cells which are on-screen.
cell.isCellVisible = true
2017-09-28 20:09:11 +02:00
}
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
2017-09-29 05:30:33 +02:00
guard let cell = cell as? GifPickerCell else {
2018-08-27 16:27:48 +02:00
owsFailDebug("unexpected cell.")
2017-09-29 05:30:33 +02:00
return
}
cell.isCellVisible = false
2017-09-28 20:09:11 +02:00
}
2017-09-26 19:40:37 +02:00
// MARK: - Event Handlers
2018-05-25 18:54:25 +02:00
@objc func donePressed(sender: UIButton) {
dismiss(animated: true, completion: nil)
2017-09-26 19:40:37 +02:00
}
2017-09-28 20:09:11 +02:00
// MARK: - UISearchBarDelegate
2017-09-28 20:09:11 +02:00
public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// Clear error messages immediately.
if viewMode == .error || viewMode == .noResults {
viewMode = .idle
}
// Do progressive search after a delay.
progressiveSearchTimer?.invalidate()
progressiveSearchTimer = nil
let kProgressiveSearchDelaySeconds = 1.0
progressiveSearchTimer = WeakTimer.scheduledTimer(timeInterval: kProgressiveSearchDelaySeconds, target: self, userInfo: nil, repeats: true) { [weak self] _ in
self?.tryToSearch()
}
2017-09-28 20:09:11 +02:00
}
public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
2017-10-18 17:32:28 +02:00
self.searchBar.resignFirstResponder()
tryToSearch()
}
2017-10-18 17:32:28 +02:00
public func tryToSearch() {
progressiveSearchTimer?.invalidate()
progressiveSearchTimer = nil
guard let text: String = searchBar.text else {
// Alert message shown when user tries to search for GIFs without entering any search terms
let modal: ConfirmationModal = ConfirmationModal(
targetView: self.view,
info: ConfirmationModal.Info(
title: CommonStrings.errorAlertTitle,
body: .text("GIF_PICKER_VIEW_MISSING_QUERY".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
)
)
self.present(modal, animated: true)
2017-09-28 20:09:11 +02:00
return
}
let query: String = text.trimmingCharacters(in: .whitespacesAndNewlines)
if (viewMode == .searching || viewMode == .results) && lastQuery == query {
2018-08-23 16:37:34 +02:00
Logger.info("ignoring duplicate search: \(query)")
return
}
guard !query.isEmpty else {
loadTrending()
return
}
2017-10-18 17:32:28 +02:00
search(query: query)
2017-09-28 20:09:11 +02:00
}
private func loadTrending() {
assert(progressiveSearchTimer == nil)
assert(searchBar.text == nil || searchBar.text?.count == 0)
GiphyAPI.trending()
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)
2023-06-23 09:54:29 +02:00
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { result in
switch result {
case .finished: break
case .failure(let error):
// Don't both showing error UI feedback for default "trending" results.
Logger.error("error: \(error)")
}
},
receiveValue: { [weak self] imageInfos in
Logger.info("showing trending")
if imageInfos.count > 0 {
self?.imageInfos = imageInfos
self?.viewMode = .results
}
else {
owsFailDebug("trending results was unexpectedly empty")
}
}
)
.store(in: &disposables)
}
2017-09-28 20:09:11 +02:00
2017-10-18 17:32:28 +02:00
private func search(query: String) {
2018-08-23 16:37:34 +02:00
Logger.info("searching: \(query)")
progressiveSearchTimer?.invalidate()
progressiveSearchTimer = nil
imageInfos = []
viewMode = .searching
lastQuery = query
self.collectionView.contentOffset = CGPoint.zero
GiphyAPI
.search(query: query)
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)
2023-06-23 09:54:29 +02:00
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] result in
switch result {
case .finished: break
case .failure:
Logger.info("search failed.")
// TODO: Present this error to the user.
self?.viewMode = .error
}
},
receiveValue: { [weak self] imageInfos in
Logger.info("search complete")
self?.imageInfos = imageInfos
if imageInfos.count > 0 {
self?.viewMode = .results
}
else {
self?.viewMode = .noResults
}
}
)
.store(in: &disposables)
2017-09-28 20:09:11 +02:00
}
2017-09-29 00:15:18 +02:00
// MARK: - GifPickerLayoutDelegate
func imageInfosForLayout() -> [GiphyImageInfo] {
return imageInfos
}
// MARK: - Event Handlers
2018-05-25 18:54:25 +02:00
@objc func retryTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else {
return
}
guard viewMode == .error else {
return
}
tryToSearch()
}
2019-01-10 15:37:33 +01:00
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
layout.invalidateLayout()
}
2017-09-26 19:40:37 +02:00
}
// MARK: - GifPickerViewControllerDelegate
protocol GifPickerViewControllerDelegate: AnyObject {
func gifPickerDidSelect(attachment: SignalAttachment)
}