mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
9844fd0eb4
commit
32ff62bcb5
BIN
Telegram/Telegram-iOS/Resources/ForumAvatarMask.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/ForumAvatarMask.tgs
Normal file
Binary file not shown.
@ -9090,3 +9090,5 @@ Sorry for the inconvenience.";
|
||||
"PeerInfo.CancelSelectionAlertNo" = "No";
|
||||
|
||||
"StickerPacksSettings.SuggestAnimatedEmojiInfo" = "Each time you enter an emoji you can replace it with an animated emoji.";
|
||||
|
||||
"DialogList.DeleteBotClearHistory" = "Clear Chat History";
|
||||
|
@ -252,6 +252,9 @@ public final class AuthorizationSequencePhoneEntryController: ViewController, MF
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
guard self.confirmationController == nil else {
|
||||
return
|
||||
}
|
||||
let (_, _, number) = self.controllerNode.codeAndNumber
|
||||
if !number.isEmpty {
|
||||
let logInNumber = cleanPhoneNumber(self.controllerNode.currentNumber, removePlus: true)
|
||||
|
@ -759,6 +759,7 @@ final class PhoneConfirmationController: ViewController {
|
||||
|
||||
private let codeTargetNode: ImmediateTextNode
|
||||
private let phoneTargetNode: ImmediateTextNode
|
||||
private let measureTargetNode: ImmediateTextNode
|
||||
|
||||
private let textNode: ImmediateTextNode
|
||||
private let textActivateAreaNode: AccessibilityAreaNode
|
||||
@ -824,6 +825,10 @@ final class PhoneConfirmationController: ViewController {
|
||||
self.phoneTargetNode = ImmediateTextNode()
|
||||
self.phoneTargetNode.displaysAsynchronously = false
|
||||
|
||||
self.measureTargetNode = ImmediateTextNode()
|
||||
self.measureTargetNode.displaysAsynchronously = false
|
||||
self.measureTargetNode.maximumNumberOfLines = 1
|
||||
|
||||
let targetString = NSMutableAttributedString(string: number, font: largeFont, textColor: theme.list.itemPrimaryTextColor)
|
||||
targetString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: sourceString.length))
|
||||
self.phoneTargetNode.attributedText = targetString
|
||||
@ -1015,6 +1020,12 @@ final class PhoneConfirmationController: ViewController {
|
||||
fontSize = 30.0
|
||||
}
|
||||
|
||||
self.measureTargetNode.attributedText = NSAttributedString(string: self.code + " " + self.number, font: Font.with(size: fontSize, design: .regular, weight: .bold, traits: [.monospacedNumbers]), textColor: self.theme.list.itemPrimaryTextColor)
|
||||
let measuredSize = self.measureTargetNode.updateLayout(CGSize(width: 1000.0, height: .greatestFiniteMagnitude))
|
||||
if measuredSize.width > maxWidth {
|
||||
fontSize = floor(0.8 * fontSize)
|
||||
}
|
||||
|
||||
let largeFont = Font.with(size: fontSize, design: .regular, weight: .bold, traits: [.monospacedNumbers])
|
||||
|
||||
self.codeTargetNode.attributedText = NSAttributedString(string: self.code, font: largeFont, textColor: self.theme.list.itemPrimaryTextColor)
|
||||
|
@ -3385,7 +3385,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
} else if case let .user(user) = chatPeer, user.botInfo != nil {
|
||||
canStop = !user.flags.contains(.isSupport)
|
||||
canClear = user.botInfo == nil
|
||||
deleteTitle = strongSelf.presentationData.strings.ChatList_DeleteChat
|
||||
} else if case .secretChat = chatPeer {
|
||||
canClear = true
|
||||
@ -3450,6 +3449,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
} else {
|
||||
items.append(DeleteChatPeerActionSheetItem(context: strongSelf.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder))
|
||||
|
||||
if canStop {
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_DeleteBotConversationConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.maybeAskForPeerChatRemoval(peer: peer, completion: { _ in
|
||||
}, removed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.peerId, isBlocked: true).start()
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
if canClear {
|
||||
let beginClear: (InteractiveHistoryClearingType) -> Void = { type in
|
||||
guard let strongSelf = self else {
|
||||
@ -3495,7 +3510,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}), in: .current)
|
||||
}
|
||||
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, color: .accent, action: { [weak actionSheet] in
|
||||
items.append(ActionSheetButtonItem(title: canStop ? strongSelf.presentationData.strings.DialogList_DeleteBotClearHistory : strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
@ -3558,7 +3573,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
strongSelf.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: true, completion: {
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
} else if !canStop {
|
||||
items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
@ -3630,23 +3645,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if canStop {
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_DeleteBotConversationConfirmation, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.maybeAskForPeerChatRemoval(peer: peer, completion: { _ in
|
||||
}, removed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.peerId, isBlocked: true).start()
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
|
@ -82,6 +82,7 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
public private(set) var isReady: Bool = false
|
||||
public var isReadyUpdated: (() -> Void)?
|
||||
public var controllerRemoved: (ViewController) -> Void
|
||||
public var requestFilterController: (ViewController) -> Void = { _ in }
|
||||
public var keyboardViewManager: KeyboardViewManager? {
|
||||
didSet {
|
||||
}
|
||||
@ -118,6 +119,8 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
var statusBarStyle: StatusBarStyle = .Ignore
|
||||
var statusBarStyleUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
|
||||
|
||||
private var panRecognizer: InteractiveTransitionGestureRecognizer?
|
||||
|
||||
public init(isFlat: Bool, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||
@ -211,7 +214,10 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
let topController = self.controllers[self.controllers.count - 1]
|
||||
let bottomController = self.controllers[self.controllers.count - 2]
|
||||
|
||||
if !topController.attemptNavigation({
|
||||
if !topController.attemptNavigation({ [weak self, weak topController] in
|
||||
if let self, let topController {
|
||||
self.requestFilterController(topController)
|
||||
}
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
@ -826,6 +826,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
let flatContainer = NavigationContainer(isFlat: self.isFlat, controllerRemoved: { [weak self] controller in
|
||||
self?.controllerRemoved(controller)
|
||||
})
|
||||
flatContainer.requestFilterController = { [weak self] controller in
|
||||
self?.filterController(controller, animated: true)
|
||||
}
|
||||
flatContainer.statusBarStyleUpdated = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -853,6 +856,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
let flatContainer = NavigationContainer(isFlat: self.isFlat, controllerRemoved: { [weak self] controller in
|
||||
self?.controllerRemoved(controller)
|
||||
})
|
||||
flatContainer.requestFilterController = { [weak self] controller in
|
||||
self?.filterController(controller, animated: true)
|
||||
}
|
||||
flatContainer.statusBarStyleUpdated = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -3,20 +3,28 @@ import UIKit
|
||||
import Photos
|
||||
import SwiftSignalKit
|
||||
|
||||
private let imageManager = PHCachingImageManager()
|
||||
private let imageManager: PHCachingImageManager = {
|
||||
let imageManager = PHCachingImageManager()
|
||||
imageManager.allowsCachingHighQualityImages = false
|
||||
return imageManager
|
||||
}()
|
||||
|
||||
|
||||
private let assetsQueue = Queue()
|
||||
|
||||
func assetImage(fetchResult: PHFetchResult<PHAsset>, index: Int, targetSize: CGSize, exact: Bool) -> Signal<UIImage?, NoError> {
|
||||
func assetImage(fetchResult: PHFetchResult<PHAsset>, index: Int, targetSize: CGSize, exact: Bool, deliveryMode: PHImageRequestOptionsDeliveryMode = .opportunistic, synchronous: Bool = false) -> Signal<UIImage?, NoError> {
|
||||
let asset = fetchResult[index]
|
||||
return assetImage(asset: asset, targetSize: targetSize, exact: exact)
|
||||
return assetImage(asset: asset, targetSize: targetSize, exact: exact, deliveryMode: deliveryMode, synchronous: synchronous)
|
||||
}
|
||||
|
||||
func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool) -> Signal<UIImage?, NoError> {
|
||||
func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool, deliveryMode: PHImageRequestOptionsDeliveryMode = .opportunistic, synchronous: Bool = false) -> Signal<UIImage?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let options = PHImageRequestOptions()
|
||||
options.deliveryMode = deliveryMode
|
||||
if exact {
|
||||
options.resizeMode = .exact
|
||||
}
|
||||
options.isSynchronous = synchronous
|
||||
let token = imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (image, info) in
|
||||
var degraded = false
|
||||
|
||||
@ -31,17 +39,15 @@ func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool) -> Signal<UIIma
|
||||
|
||||
if let image = image {
|
||||
subscriber.putNext(image)
|
||||
if !degraded {
|
||||
if !degraded || deliveryMode == .fastFormat {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ActionDisposable {
|
||||
assetsQueue.async {
|
||||
imageManager.cancelImageRequest(token)
|
||||
}
|
||||
imageManager.cancelImageRequest(token)
|
||||
}
|
||||
} |> runOn(assetsQueue)
|
||||
}
|
||||
}
|
||||
|
||||
func assetVideo(fetchResult: PHFetchResult<PHAsset>, index: Int) -> Signal<AVAsset?, NoError> {
|
||||
@ -49,7 +55,6 @@ func assetVideo(fetchResult: PHFetchResult<PHAsset>, index: Int) -> Signal<AVAss
|
||||
let asset = fetchResult[index]
|
||||
|
||||
let options = PHVideoRequestOptions()
|
||||
|
||||
let token = imageManager.requestAVAsset(forVideo: asset, options: options) { (avAsset, _, info) in
|
||||
if let avAsset = avAsset {
|
||||
subscriber.putNext(avAsset)
|
||||
|
@ -154,15 +154,20 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
var _cachedTag: Int32?
|
||||
var tag: Int32? {
|
||||
// if let tag = self._cachedTag {
|
||||
// return tag
|
||||
// } else if let asset = self.asset, let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
// let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
// self._cachedTag = tag
|
||||
// return tag
|
||||
// } else {
|
||||
if let tag = self._cachedTag {
|
||||
return tag
|
||||
} else if let (fetchResult, index) = self.currentState {
|
||||
let asset = fetchResult.object(at: index)
|
||||
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
self._cachedTag = tag
|
||||
return tag
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool = false) {
|
||||
@ -226,58 +231,6 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentState!.1 != index {
|
||||
// let editingContext = interaction.editingState
|
||||
// let asset = media.asset as? TGMediaEditableItem
|
||||
//
|
||||
// let editedSignal = Signal<UIImage?, NoError> { subscriber in
|
||||
// if let signal = editingContext.thumbnailImageSignal(forIdentifier: media.identifier) {
|
||||
// let disposable = signal.start(next: { next in
|
||||
// if let image = next as? UIImage {
|
||||
// subscriber.putNext(image)
|
||||
// } else {
|
||||
// subscriber.putNext(nil)
|
||||
// }
|
||||
// }, error: { _ in
|
||||
// }, completed: nil)!
|
||||
//
|
||||
// return ActionDisposable {
|
||||
// disposable.dispose()
|
||||
// }
|
||||
// } else {
|
||||
// return EmptyDisposable
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let originalImageSignal = Signal<UIImage?, NoError> { subscriber in
|
||||
// if let signal = asset?.thumbnailImageSignal?()
|
||||
// }
|
||||
//
|
||||
// let scale = min(2.0, UIScreenScale)
|
||||
// let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
|
||||
// let originalSignal: Signal<UIImage, NoError> = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
|
||||
// let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
||||
// |> mapToSignal { result in
|
||||
// if let result = result {
|
||||
// return .single(result)
|
||||
// } else {
|
||||
// return originalSignal
|
||||
// }
|
||||
// }
|
||||
// self.imageNode.setSignal(imageSignal)
|
||||
//
|
||||
// if case .video = media, let asset = media.asset as? TGCameraCapturedVideo {
|
||||
// self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo")
|
||||
//
|
||||
// if self.typeIconNode.supernode == nil {
|
||||
// self.durationNode.attributedText = NSAttributedString(string: stringForDuration(Int32(asset.videoDuration)), font: Font.semibold(12.0), textColor: .white)
|
||||
//
|
||||
// self.addSubnode(self.gradientNode)
|
||||
// self.addSubnode(self.typeIconNode)
|
||||
// self.addSubnode(self.durationNode)
|
||||
// self.setNeedsLayout()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
self.currentMediaState = (media.asset, index)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@ -319,10 +272,17 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
|
||||
let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
|
||||
|
||||
let assetImageSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, deliveryMode: .fastFormat, synchronous: true)
|
||||
|> then(
|
||||
assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, deliveryMode: .highQualityFormat, synchronous: false)
|
||||
|> delay(0.03, queue: Queue.concurrentDefaultQueue())
|
||||
)
|
||||
|
||||
let originalSignal = assetImageSignal //assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, synchronous: true)
|
||||
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
||||
|> mapToSignal { result in
|
||||
if let result = result {
|
||||
|
@ -196,6 +196,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||
private var manageNode: MediaPickerManageNode?
|
||||
private var scrollingArea: SparseItemGridScrollingArea
|
||||
private var isFastScrolling = false
|
||||
|
||||
private var selectionNode: MediaPickerSelectedListNode?
|
||||
|
||||
@ -213,12 +214,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
private let hiddenMediaId = Promise<String?>(nil)
|
||||
|
||||
private var selectionGesture: MediaPickerGridSelectionGesture<TGMediaSelectableItem>?
|
||||
|
||||
private var fastScrollContentOffset = ValuePromise<CGPoint>(ignoreRepeated: true)
|
||||
private var fastScrollDisposable: Disposable?
|
||||
|
||||
private var didSetReady = false
|
||||
private let _ready = Promise<Bool>()
|
||||
var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
fileprivate var isSuspended = false
|
||||
private var hasGallery = false
|
||||
private var isCameraPreviewVisible = true
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
init(controller: MediaPickerScreen) {
|
||||
@ -248,7 +258,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.gridNode)
|
||||
//self.containerNode.addSubnode(self.scrollingArea)
|
||||
self.containerNode.addSubnode(self.scrollingArea)
|
||||
|
||||
let preloadPromise = self.preloadPromise
|
||||
let updatedState: Signal<State, NoError>
|
||||
@ -357,9 +367,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.hiddenMediaDisposable?.dispose()
|
||||
self.selectionChangedDisposable?.dispose()
|
||||
self.itemsDimensionsUpdatedDisposable?.dispose()
|
||||
self.fastScrollDisposable?.dispose()
|
||||
}
|
||||
|
||||
private var selectionGesture: MediaPickerGridSelectionGesture<TGMediaSelectableItem>?
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -402,16 +412,44 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
guard let strongSelf = self else {
|
||||
return nil
|
||||
}
|
||||
strongSelf.controller?.requestAttachmentMenuExpansion()
|
||||
strongSelf.isFastScrolling = true
|
||||
return strongSelf.gridNode.scrollView
|
||||
}
|
||||
self.scrollingArea.finishedScrolling = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isFastScrolling = false
|
||||
}
|
||||
self.scrollingArea.setContentOffset = { [weak self] offset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
// strongSelf.isFastScrolling = true
|
||||
strongSelf.gridNode.scrollView.setContentOffset(offset, animated: false)
|
||||
// strongSelf.isFastScrolling = false
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
strongSelf.fastScrollContentOffset.set(offset)
|
||||
}
|
||||
}
|
||||
self.gridNode.visibleItemsUpdated = { [weak self] _ in
|
||||
self?.updateScrollingArea()
|
||||
|
||||
if let self, let cameraView = self.cameraView {
|
||||
self.isCameraPreviewVisible = self.gridNode.scrollView.bounds.intersects(cameraView.frame)
|
||||
self.updateIsCameraActive()
|
||||
}
|
||||
}
|
||||
self.updateScrollingArea()
|
||||
|
||||
let throttledContentOffsetSignal = self.fastScrollContentOffset.get()
|
||||
|> mapToThrottled { next -> Signal<CGPoint, NoError> in
|
||||
return .single(next) |> then(.complete() |> delay(0.02, queue: Queue.concurrentDefaultQueue()))
|
||||
}
|
||||
self.fastScrollDisposable = (throttledContentOffsetSignal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] contentOffset in
|
||||
if let self {
|
||||
self.gridNode.scrollView.setContentOffset(contentOffset, animated: false)
|
||||
}
|
||||
})
|
||||
|
||||
if let controller = self.controller, case .assets(nil) = controller.subject {
|
||||
let enableAnimations = self.controller?.context.sharedContext.energyUsageSettings.fullTranslucency ?? true
|
||||
@ -442,6 +480,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
func updateIsCameraActive() {
|
||||
let isCameraActive = !self.isSuspended && !self.hasGallery && self.isCameraPreviewVisible
|
||||
if isCameraActive {
|
||||
self.cameraView?.resumePreview()
|
||||
} else {
|
||||
self.cameraView?.pausePreview()
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if otherGestureRecognizer.view is UIScrollView || otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
return true
|
||||
@ -706,9 +753,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
}, finishedTransitionIn: { [weak self] in
|
||||
self?.openingMedia = false
|
||||
self?.cameraView?.pausePreview()
|
||||
self?.hasGallery = true
|
||||
self?.updateIsCameraActive()
|
||||
}, willTransitionOut: { [weak self] in
|
||||
self?.cameraView?.resumePreview()
|
||||
self?.hasGallery = false
|
||||
self?.updateIsCameraActive()
|
||||
}, dismissAll: { [weak self] in
|
||||
self?.controller?.dismissAll()
|
||||
})
|
||||
@ -742,9 +791,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||
}, finishedTransitionIn: { [weak self] in
|
||||
self?.openingMedia = false
|
||||
self?.cameraView?.pausePreview()
|
||||
self?.hasGallery = true
|
||||
self?.updateIsCameraActive()
|
||||
}, willTransitionOut: { [weak self] in
|
||||
self?.cameraView?.resumePreview()
|
||||
self?.hasGallery = false
|
||||
self?.updateIsCameraActive()
|
||||
}, dismissAll: { [weak self] in
|
||||
self?.controller?.dismissAll()
|
||||
})
|
||||
@ -1590,12 +1641,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
self.scrollToTop?()
|
||||
|
||||
self.controllerNode.cameraView?.pausePreview()
|
||||
self.controllerNode.isSuspended = true
|
||||
self.controllerNode.updateIsCameraActive()
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
self.controllerNode.cameraView?.resumePreview()
|
||||
|
||||
self.controllerNode.isSuspended = false
|
||||
self.controllerNode.updateIsCameraActive()
|
||||
self.controllerNode.updateNavigation(delayDisappear: true, transition: .immediate)
|
||||
}
|
||||
|
||||
|
@ -1350,6 +1350,12 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.currentViewport?.scrollView.isScrollEnabled = self.isScrollEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollWithDelta(_ delta: CGFloat) {
|
||||
if let scrollView = self.currentViewport?.scrollView {
|
||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentOffset.y + delta), animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
public init(theme: PresentationTheme, initialZoomLevel: ZoomLevel? = nil) {
|
||||
self.theme = theme
|
||||
|
@ -948,6 +948,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
private var activityTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public var beginScrolling: (() -> UIScrollView?)?
|
||||
public var finishedScrolling: (() -> Void)?
|
||||
public var setContentOffset: ((CGPoint) -> Void)?
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
|
||||
@ -1059,6 +1060,8 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
strongSelf.updateLineIndicator(transition: transition)
|
||||
|
||||
strongSelf.updateActivityTimer(isScrolling: false)
|
||||
|
||||
strongSelf.finishedScrolling?()
|
||||
},
|
||||
moved: { [weak self] relativeOffset in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -104,7 +104,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
components.append(.isHidden(hidden == .boolTrue))
|
||||
}
|
||||
return TelegramMediaAction(action: .topicEdited(components: components))
|
||||
case let.messageActionSuggestProfilePhoto(photo):
|
||||
case let .messageActionSuggestProfilePhoto(photo):
|
||||
return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo)))
|
||||
case let .messageActionRequestedPeer(buttonId, peer):
|
||||
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerId: peer.peerId))
|
||||
|
@ -2447,7 +2447,152 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
|
||||
private var gridSelectionGesture: MediaPickerGridSelectionGesture<EngineMessage.Id>?
|
||||
private var listSelectionGesture: MediaPickerGridSelectionGesture<EngineMessage.Id>?
|
||||
private var listSelectionGesture: MediaListSelectionRecognizer?
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let selectionRecognizer = MediaListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
|
||||
selectionRecognizer.shouldBegin = {
|
||||
return true
|
||||
}
|
||||
self.view.addGestureRecognizer(selectionRecognizer)
|
||||
}
|
||||
|
||||
private var selectionPanState: (selecting: Bool, initialMessageId: EngineMessage.Id, toggledMessageIds: [[EngineMessage.Id]])?
|
||||
private var selectionScrollActivationTimer: SwiftSignalKit.Timer?
|
||||
private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator?
|
||||
private var selectionScrollDelta: CGFloat?
|
||||
private var selectionLastLocation: CGPoint?
|
||||
|
||||
private func messageAtPoint(_ location: CGPoint) -> EngineMessage? {
|
||||
if let itemView = self.itemGrid.item(at: location)?.view as? ItemView, let message = itemView.item?.message {
|
||||
return EngineMessage(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc private func selectionPanGesture(_ recognizer: UIGestureRecognizer) -> Void {
|
||||
let location = recognizer.location(in: self.view)
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
if let message = self.messageAtPoint(location) {
|
||||
let selecting = !(self.chatControllerInteraction.selectionState?.selectedIds.contains(message.id) ?? false)
|
||||
self.selectionPanState = (selecting, message.id, [])
|
||||
self.chatControllerInteraction.toggleMessagesSelection([message.id], selecting)
|
||||
}
|
||||
case .changed:
|
||||
self.handlePanSelection(location: location)
|
||||
self.selectionLastLocation = location
|
||||
case .ended, .failed, .cancelled:
|
||||
self.selectionPanState = nil
|
||||
self.selectionScrollDisplayLink = nil
|
||||
self.selectionScrollActivationTimer?.invalidate()
|
||||
self.selectionScrollActivationTimer = nil
|
||||
self.selectionScrollDelta = nil
|
||||
self.selectionLastLocation = nil
|
||||
self.selectionScrollSkipUpdate = false
|
||||
case .possible:
|
||||
break
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private func handlePanSelection(location: CGPoint) {
|
||||
var location = location
|
||||
if location.y < 0.0 {
|
||||
location.y = 5.0
|
||||
} else if location.y > self.frame.height {
|
||||
location.y = self.frame.height - 5.0
|
||||
}
|
||||
|
||||
var hasState = false
|
||||
if let state = self.selectionPanState {
|
||||
hasState = true
|
||||
if let message = self.messageAtPoint(location) {
|
||||
if message.id == state.initialMessageId {
|
||||
if !state.toggledMessageIds.isEmpty {
|
||||
self.chatControllerInteraction.toggleMessagesSelection(state.toggledMessageIds.flatMap { $0.compactMap({ $0 }) }, !state.selecting)
|
||||
self.selectionPanState = (state.selecting, state.initialMessageId, [])
|
||||
}
|
||||
} else if state.toggledMessageIds.last?.first != message.id {
|
||||
var updatedToggledMessageIds: [[EngineMessage.Id]] = []
|
||||
var previouslyToggled = false
|
||||
for i in (0 ..< state.toggledMessageIds.count) {
|
||||
if let messageId = state.toggledMessageIds[i].first {
|
||||
if messageId == message.id {
|
||||
previouslyToggled = true
|
||||
updatedToggledMessageIds = Array(state.toggledMessageIds.prefix(i + 1))
|
||||
|
||||
let messageIdsToToggle = Array(state.toggledMessageIds.suffix(state.toggledMessageIds.count - i - 1)).flatMap { $0 }
|
||||
self.chatControllerInteraction.toggleMessagesSelection(messageIdsToToggle, !state.selecting)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !previouslyToggled {
|
||||
updatedToggledMessageIds = state.toggledMessageIds
|
||||
let isSelected = self.chatControllerInteraction.selectionState?.selectedIds.contains(message.id) ?? false
|
||||
if state.selecting != isSelected {
|
||||
updatedToggledMessageIds.append([message.id])
|
||||
self.chatControllerInteraction.toggleMessagesSelection([message.id], state.selecting)
|
||||
}
|
||||
}
|
||||
|
||||
self.selectionPanState = (state.selecting, state.initialMessageId, updatedToggledMessageIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
guard hasState else {
|
||||
return
|
||||
}
|
||||
let scrollingAreaHeight: CGFloat = 50.0
|
||||
if location.y < scrollingAreaHeight || location.y > self.frame.height - scrollingAreaHeight {
|
||||
if location.y < self.frame.height / 2.0 {
|
||||
self.selectionScrollDelta = (scrollingAreaHeight - location.y) / scrollingAreaHeight
|
||||
} else {
|
||||
self.selectionScrollDelta = -(scrollingAreaHeight - min(scrollingAreaHeight, max(0.0, (self.frame.height - location.y)))) / scrollingAreaHeight
|
||||
}
|
||||
if let displayLink = self.selectionScrollDisplayLink {
|
||||
displayLink.isPaused = false
|
||||
} else {
|
||||
if let _ = self.selectionScrollActivationTimer {
|
||||
} else {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.45, repeat: false, completion: { [weak self] in
|
||||
self?.setupSelectionScrolling()
|
||||
}, queue: .mainQueue())
|
||||
timer.start()
|
||||
self.selectionScrollActivationTimer = timer
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.selectionScrollDisplayLink?.isPaused = true
|
||||
self.selectionScrollActivationTimer?.invalidate()
|
||||
self.selectionScrollActivationTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
private var selectionScrollSkipUpdate = false
|
||||
private func setupSelectionScrolling() {
|
||||
self.selectionScrollDisplayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.selectionScrollActivationTimer = nil
|
||||
if let strongSelf = self, let delta = strongSelf.selectionScrollDelta {
|
||||
let distance: CGFloat = 15.0 * min(1.0, 0.15 + abs(delta * delta))
|
||||
let direction: ListViewScrollDirection = delta > 0.0 ? .up : .down
|
||||
let _ = strongSelf.itemGrid.scrollWithDelta(direction == .up ? -distance : distance)
|
||||
|
||||
if let location = strongSelf.selectionLastLocation {
|
||||
if !strongSelf.selectionScrollSkipUpdate {
|
||||
strongSelf.handlePanSelection(location: location)
|
||||
}
|
||||
strongSelf.selectionScrollSkipUpdate = !strongSelf.selectionScrollSkipUpdate
|
||||
}
|
||||
}
|
||||
})
|
||||
self.selectionScrollDisplayLink?.isPaused = false
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let location = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||
@ -2717,3 +2862,65 @@ func updateVisualMediaStoredState(engine: TelegramEngine, peerId: PeerId, messag
|
||||
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.visualMediaStoredState, id: key)
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaListSelectionRecognizer: UIPanGestureRecognizer {
|
||||
private let selectionGestureActivationThreshold: CGFloat = 5.0
|
||||
|
||||
var recognized: Bool? = nil
|
||||
var initialLocation: CGPoint = CGPoint()
|
||||
|
||||
public var shouldBegin: (() -> Bool)?
|
||||
|
||||
public override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.minimumNumberOfTouches = 2
|
||||
self.maximumNumberOfTouches = 2
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.recognized = nil
|
||||
}
|
||||
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let shouldBegin = self.shouldBegin, !shouldBegin() {
|
||||
self.state = .failed
|
||||
} else {
|
||||
let touch = touches.first!
|
||||
self.initialLocation = touch.location(in: self.view)
|
||||
}
|
||||
}
|
||||
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
let location = touches.first!.location(in: self.view)
|
||||
let translation = location.offsetBy(dx: -self.initialLocation.x, dy: -self.initialLocation.y)
|
||||
|
||||
let touchesArray = Array(touches)
|
||||
if self.recognized == nil, touchesArray.count == 2 {
|
||||
if let firstTouch = touchesArray.first, let secondTouch = touchesArray.last {
|
||||
let firstLocation = firstTouch.location(in: self.view)
|
||||
let secondLocation = secondTouch.location(in: self.view)
|
||||
|
||||
func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat {
|
||||
let dx = v1.x - v2.x
|
||||
let dy = v1.y - v2.y
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
if distance(firstLocation, secondLocation) > 200.0 {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
if self.state != .failed && (abs(translation.y) >= selectionGestureActivationThreshold) {
|
||||
self.recognized = true
|
||||
}
|
||||
}
|
||||
|
||||
if let recognized = self.recognized, recognized {
|
||||
super.touchesMoved(touches, with: event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1125,8 +1125,9 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
|
||||
func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, isForum: Bool, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
|
||||
self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded)
|
||||
self.maskNode.isForum = isForum
|
||||
self.pinchSourceNode.update(size: size, transition: transition)
|
||||
self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings)
|
||||
@ -2579,7 +2580,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.presentationData = presentationData
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let credibilityIcon: CredibilityIcon
|
||||
if let peer = peer {
|
||||
if peer.isFake {
|
||||
@ -2599,6 +2599,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
credibilityIcon = .none
|
||||
}
|
||||
|
||||
var isForum = false
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
if themeUpdated || self.currentCredibilityIcon != credibilityIcon {
|
||||
self.currentCredibilityIcon = credibilityIcon
|
||||
|
||||
@ -2742,7 +2747,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
transitionSourceAvatarFrame = avatarNavigationNode.avatarNode.view.convert(avatarNavigationNode.avatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view)
|
||||
}
|
||||
} else {
|
||||
transitionSourceAvatarFrame = avatarFrame.offsetBy(dx: 0.0, dy: -avatarFrame.maxY).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
|
||||
if deviceMetrics.hasDynamicIsland {
|
||||
transitionSourceAvatarFrame = CGRect(origin: CGPoint(x: avatarFrame.minX, y: -20.0), size: avatarFrame.size).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
|
||||
} else {
|
||||
transitionSourceAvatarFrame = avatarFrame.offsetBy(dx: 0.0, dy: -avatarFrame.maxY).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
|
||||
}
|
||||
}
|
||||
transitionSourceTitleFrame = navigationTransition.sourceTitleFrame
|
||||
transitionSourceSubtitleFrame = navigationTransition.sourceSubtitleFrame
|
||||
@ -3207,12 +3216,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: subtitleArrowNode, alpha: (1.0 - titleCollapseFraction))
|
||||
}
|
||||
}
|
||||
|
||||
var isForum = false
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
|
||||
let avatarCornerRadius: CGFloat = isForum ? floor(avatarSize * 0.25) : avatarSize / 2.0
|
||||
|
||||
if self.isAvatarExpanded {
|
||||
@ -3240,7 +3244,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition)
|
||||
self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, isForum: isForum, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition)
|
||||
self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
|
||||
self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
|
||||
if additive {
|
||||
@ -3305,7 +3309,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
|
||||
}
|
||||
|
||||
if deviceMetrics.hasDynamicIsland && !isForum && self.forumTopicThreadId == nil {
|
||||
if deviceMetrics.hasDynamicIsland && self.forumTopicThreadId == nil {
|
||||
self.avatarListNode.maskNode.frame = CGRect(origin: CGPoint(x: -85.5, y: -self.avatarListNode.frame.minY + 48.0), size: CGSize(width: 171.0, height: 171.0))
|
||||
self.avatarListNode.bottomCoverNode.frame = self.avatarListNode.maskNode.frame
|
||||
self.avatarListNode.topCoverNode.frame = self.avatarListNode.maskNode.frame
|
||||
@ -3677,15 +3681,27 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
private class DynamicIslandMaskNode: ManagedAnimationNode {
|
||||
var frameIndex: Int = 0
|
||||
|
||||
var isForum = false {
|
||||
didSet {
|
||||
if self.isForum != oldValue {
|
||||
self.update(frameIndex: self.frameIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(_ value: CGFloat) {
|
||||
let lowerBound = 0
|
||||
let upperBound = 180
|
||||
let frameIndex = lowerBound + Int(value * CGFloat(upperBound - lowerBound))
|
||||
if frameIndex != self.frameIndex {
|
||||
self.frameIndex = frameIndex
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local("UserAvatarMask"), frames: .range(startFrame: frameIndex, endFrame: frameIndex), duration: 0.001))
|
||||
self.update(frameIndex: frameIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func update(frameIndex: Int) {
|
||||
self.frameIndex = frameIndex
|
||||
self.trackTo(item: ManagedAnimationItem(source: .local(self.isForum ? "ForumAvatarMask" : "UserAvatarMask"), frames: .range(startFrame: frameIndex, endFrame: frameIndex), duration: 0.001))
|
||||
}
|
||||
}
|
||||
|
||||
private class DynamicIslandBlurNode: ASDisplayNode {
|
||||
|
@ -374,6 +374,9 @@ final class SharedMediaPlayer {
|
||||
strongSelf.forceAudioToSpeaker = forceAudioToSpeaker
|
||||
strongSelf.playbackItem?.setForceAudioToSpeaker(forceAudioToSpeaker)
|
||||
if !forceAudioToSpeaker {
|
||||
if let playbackStateValue = strongSelf._playbackStateValue, case let .item(item) = playbackStateValue, item.status.timestamp < 1.5 {
|
||||
strongSelf.control(.seek(0.0))
|
||||
}
|
||||
strongSelf.control(.playback(.play))
|
||||
} else {
|
||||
strongSelf.control(.playback(.pause))
|
||||
|
@ -122,6 +122,9 @@ public final class WebSearchController: ViewController {
|
||||
|
||||
public var attemptItemSelection: (ChatContextResult) -> Bool = { _ in return true }
|
||||
|
||||
private var searchQueryPromise = ValuePromise<String>()
|
||||
private var searchQueryDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: EngineConfiguration.SearchBots, mode: WebSearchControllerMode, activateOnDisplay: Bool = true) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
@ -195,7 +198,7 @@ public final class WebSearchController: ViewController {
|
||||
self.navigationContentNode = navigationContentNode
|
||||
navigationContentNode.setQueryUpdated { [weak self] query in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
strongSelf.updateSearchQuery(query)
|
||||
strongSelf.searchQueryPromise.set(query)
|
||||
strongSelf.searchingUpdated(!query.isEmpty)
|
||||
}
|
||||
}
|
||||
@ -288,6 +291,24 @@ public final class WebSearchController: ViewController {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let throttledSearchQuery = self.searchQueryPromise.get()
|
||||
|> mapToSignal { query -> Signal<String, NoError> in
|
||||
if !query.isEmpty {
|
||||
return (.complete() |> delay(0.6, queue: Queue.mainQueue()))
|
||||
|> then(.single(query))
|
||||
} else {
|
||||
return .single(query)
|
||||
}
|
||||
}
|
||||
|
||||
self.searchQueryDisposable = (throttledSearchQuery
|
||||
|> deliverOnMainQueue).start(next: { [weak self] query in
|
||||
if let self {
|
||||
self.updateSearchQuery(query)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -298,6 +319,7 @@ public final class WebSearchController: ViewController {
|
||||
self.disposable?.dispose()
|
||||
self.resultsDisposable.dispose()
|
||||
self.selectionDisposable?.dispose()
|
||||
self.searchQueryDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func cancel() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user