diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3d5b79b61a..356a456045 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1445,7 +1445,7 @@ public protocol SharedAccountContext: AnyObject { func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) - func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) + func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) @@ -1550,7 +1550,7 @@ public protocol SharedAccountContext: AnyObject { func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) - func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController + func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, firstName: String?, lastName: String?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0debd0179e..b605a693a8 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1789,7 +1789,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.requestAddContact = { [weak self] phoneNumber in if let strongSelf = self { strongSelf.view.endEditing(true) - strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in + strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in self?.present(controller, in: .window(.root), with: arguments) }, pushController: { [weak self] controller in (self?.navigationController as? NavigationController)?.pushViewController(controller) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index abbe656e9a..aef3f58aa0 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -1343,8 +1343,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } public override func searchTextClearTokens() { + self.folder = nil self.updateSearchOptions(nil) -// self.setQuery?(nil, [], self.searchQueryValue ?? "") } func deleteMessages(messageIds: Set?) { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index ec2a65f95a..76692de347 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1309,7 +1309,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } - var item: ChatListItem? + public private(set) var item: ChatListItem? private let backgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index be63c37d30..f8fbf6c94e 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -365,7 +365,7 @@ public class ContactsController: ViewController { self.contactsNode.requestAddContact = { [weak self] phoneNumber in if let strongSelf = self { strongSelf.view.endEditing(true) - strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in + strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in self?.present(controller, in: .window(.root), with: arguments) }, pushController: { [weak self] controller in (self?.navigationController as? NavigationController)?.pushViewController(controller) @@ -757,6 +757,8 @@ public class ContactsController: ViewController { let controller = strongSelf.context.sharedContext.makeNewContactScreen( context: strongSelf.context, peer: nil, + firstName: nil, + lastName: nil, phoneNumber: nil, shareViaException: false, completion: { [weak self] peer, stableId, contactData in diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index e6dab339fc..1b5551a902 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -310,8 +310,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } didSet { - if let scrubberView = self.scrubberView { + if let scrubberView = self.scrubberView { self.view.addSubview(scrubberView) + scrubberView.onRequestLayout = { [weak self] transition in + guard let self else { + return + } + if let requestLayout = self.requestLayout { + requestLayout(transition) + } else { + if let validLayout = self.validLayout { + let _ = self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, contentInset: validLayout.5, transition: transition) + } + } + } scrubberView.updateScrubbingVisual = { [weak self] value in guard let strongSelf = self else { return @@ -1296,7 +1308,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll panelHeight -= 44.0 } - let scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0)) + var scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0)) + if scrubberView.hasVisibleInfo { + let infoHeight: CGFloat = 16.0 + scrubberFrame.size.height += infoHeight + panelHeight += infoHeight + } + scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, isCollapsed: self.visibilityAlpha < 1.0, transition: transition) transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size)) transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY)) diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index af13b1fc83..58f740c35b 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -38,13 +38,11 @@ final class ChatVideoGalleryItemScrubberView: UIView { private var chapters: [MediaPlayerScrubbingChapter] = [] private var fetchStatusDisposable = MetaDisposable() - private var scrubbingDisposable = MetaDisposable() private var chapterDisposable = MetaDisposable() private var loadingDisposable = MetaDisposable() private var leftTimestampNodePushed = false private var rightTimestampNodePushed = false - private var infoNodePushed = false private var currentChapter: MediaPlayerScrubbingChapter? @@ -53,6 +51,14 @@ final class ChatVideoGalleryItemScrubberView: UIView { private var currentLeftString: String? private var currentRightString: String? + var hasVisibleInfo: Bool { + if let attributedText = self.infoNode.attributedText, !attributedText.string.isEmpty { + return true + } else { + return false + } + } + var hideWhenDurationIsUnknown = false { didSet { if self.hideWhenDurationIsUnknown { @@ -72,6 +78,8 @@ final class ChatVideoGalleryItemScrubberView: UIView { var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in } var seek: (Double) -> Void = { _ in } + var onRequestLayout: ((ContainedViewLayoutTransition) -> Void)? + init(chapters: [MediaPlayerScrubbingChapter]) { self.backgroundContainer = GlassBackgroundContainerView() self.backgroundView = GlassBackgroundView() @@ -131,7 +139,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.backgroundView.contentView.addSubview(self.leftTimestampNode.view) self.backgroundView.contentView.addSubview(self.rightTimestampNode.view) self.addSubview(self.scrubberNode.view) - //self.backgroundView.contentView.addSubview(self.infoNode.view) + self.backgroundView.contentView.addSubview(self.infoNode.view) } required init?(coder aDecoder: NSCoder) { @@ -139,7 +147,6 @@ final class ChatVideoGalleryItemScrubberView: UIView { } deinit { - self.scrubbingDisposable.dispose() self.fetchStatusDisposable.dispose() self.chapterDisposable.dispose() self.loadingDisposable.dispose() @@ -148,6 +155,16 @@ final class ChatVideoGalleryItemScrubberView: UIView { var isLoading = false var isCollapsed: Bool? + private func requestLayout(transition: ContainedViewLayoutTransition) { + if let onRequestLayout = self.onRequestLayout { + onRequestLayout(transition) + } else { + if let (size, leftInset, rightInset, isCollapsed) = self.containerLayout { + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate) + } + } + } + func updateTimestampsVisibility(animated: Bool) { if self.isAnimatedOut { return @@ -276,41 +293,11 @@ final class ChatVideoGalleryItemScrubberView: UIView { } strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: textFont, textColor: .white) - if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout { - strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate) - } + strongSelf.requestLayout(transition: .immediate) } } })) } - - self.scrubbingDisposable.set((self.scrubberNode.scrubbingPosition - |> deliverOnMainQueue).start(next: { [weak self] value in - guard let strongSelf = self else { - return - } - let leftTimestampNodePushed: Bool = false - let rightTimestampNodePushed: Bool = false - let infoNodePushed: Bool - if let value = value { - //leftTimestampNodePushed = value < 0.16 - //rightTimestampNodePushed = value > 0.84 - infoNodePushed = value >= 0.16 && value <= 0.84 - } else { - //leftTimestampNodePushed = false - //rightTimestampNodePushed = false - infoNodePushed = false - } - if leftTimestampNodePushed != strongSelf.leftTimestampNodePushed || rightTimestampNodePushed != strongSelf.rightTimestampNodePushed || infoNodePushed != strongSelf.infoNodePushed { - strongSelf.leftTimestampNodePushed = leftTimestampNodePushed - strongSelf.rightTimestampNodePushed = rightTimestampNodePushed - strongSelf.infoNodePushed = infoNodePushed - - if let layout = strongSelf.containerLayout { - strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, isCollapsed: layout.3, transition: .animated(duration: 0.35, curve: .spring)) - } - } - })) } func setBufferingStatusSignal(_ status: Signal<(RangeSet, Int64)?, NoError>?) { @@ -335,16 +322,16 @@ final class ChatVideoGalleryItemScrubberView: UIView { } strongSelf.infoNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .white) - if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout { - strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate) - } + strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } })) } else if self.chapters.isEmpty { self.infoNode.attributedText = NSAttributedString(string: dataSizeString(fileSize, forceDecimal: true, formatting: formatting), font: textFont, textColor: .white) + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } } else if self.chapters.isEmpty { self.infoNode.attributedText = nil + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } } @@ -376,27 +363,32 @@ final class ChatVideoGalleryItemScrubberView: UIView { } } + var yOffset: CGFloat = 0.0 + if !isCollapsed { + yOffset = size.height - 44.0 + } + leftTimestampOffset = 14.0 rightTimestampOffset = 14.0 - infoOffset = 0.0 + infoOffset = -8.0 if isCollapsed { scrubberLeftInset = 0.0 scrubberRightInset = 0.0 } - transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0))) - transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0))) + transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: yOffset + leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0))) + transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: yOffset + rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0))) var infoConstrainedSize = size - infoConstrainedSize.width = size.width - scrubberLeftInset - scrubberRightInset - 100.0 + infoConstrainedSize.width = size.width - 20.0 * 2.0 let infoSize = self.infoNode.measure(infoConstrainedSize) self.infoNode.bounds = CGRect(origin: CGPoint(), size: infoSize) - transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0)) - self.infoNode.alpha = size.width < size.height && self.isCollapsed == false ? 1.0 : 0.0 + transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: yOffset + infoOffset + infoSize.height / 2.0)) + self.infoNode.alpha = self.isCollapsed == false ? 1.0 : 0.0 - var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight)) + var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: yOffset + 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight)) if isCollapsed { scrubberFrame.origin.y = size.height - scrubberHeight } diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index b8cb27c13d..769144c9a8 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -927,8 +927,9 @@ public class GalleryController: ViewController, StandalonePresentableController, if case .custom = source { displayInfoOnTop = true } - + let syncResult = Atomic<(Bool, (() -> Void)?)>(value: (false, nil)) + var isFirstTime = true self.disposable.set(combineLatest( messageView, self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1), @@ -1015,14 +1016,22 @@ public class GalleryController: ViewController, StandalonePresentableController, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in }, generateStoreAfterDownload: strongSelf.generateStoreAfterDownload, sendSticker: strongSelf.actionInteraction?.sendSticker, - present: { [weak self] c, a in - if let strongSelf = self { + present: { [weak strongSelf] c, a in + if let strongSelf { strongSelf.presentInGlobalOverlay(c, with: a) } } ) { if isCentral { centralItemIndex = items.count + + if isFirstTime { + isFirstTime = false + if item is UniversalVideoGalleryItem { + } else { + strongSelf.galleryNode.areControlsHidden = false + } + } } items.append(item) } diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 6b83cecb5d..75985d1823 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1516,8 +1516,22 @@ public func pushContactContextOptionsController(context: AccountContext, context .action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_CreateNewContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.default) - push(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in - }), completed: nil, cancelled: nil)) + context.sharedContext.openAddContact( + context: context, + peer: peer, + firstName: contactData.basicData.firstName, + lastName: contactData.basicData.lastName, + phoneNumber: contactData.basicData.phoneNumbers.first?.value ?? "", + label: contactData.basicData.phoneNumbers.first?.label ?? "mobile", + present: { [weak parentController] c, a in + parentController?.present(c, in: .window(.root), with: a) + }, + pushController: { c in + push(c) + }, + completed: { + } + ) })) ) items.append( diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index be7a1488d0..66f16dbb5b 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -61,6 +61,8 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio let controller = context.sharedContext.makeNewContactScreen( context: context, peer: peer, + firstName: nil, + lastName: nil, phoneNumber: user.phone, shareViaException: shareViaException, completion: { peer, _, _ in diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index ab694dfc60..4c44f3ce28 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -228,6 +228,7 @@ public protocol ManagedAudioSession: AnyObject { func isActive() -> Signal func isPlaybackActive() -> Signal func isOtherAudioPlaying() -> Bool + func didActivateWithZeroVolume() -> Signal func push(params: ManagedAudioSessionClientParams) -> Disposable func dropAll() @@ -311,6 +312,11 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession { private var isActiveValue: Bool = false private var callKitAudioSessionIsActive: Bool = false + private let zeroVolumeEvents = ValuePipe() + public func didActivateWithZeroVolume() -> Signal { + return self.zeroVolumeEvents.signal() + } + override public init() { self.queue = Queue() @@ -1123,6 +1129,22 @@ public final class ManagedAudioSessionImpl: NSObject, ManagedAudioSession { if case .voiceCall = type { //try AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.005) } + + if AVAudioSession.sharedInstance().outputVolume <= 0.01 { + let queue = self.queue + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { [weak self] in + queue.async { + guard let self else { + return + } + if self.currentTypeAndOutputMode != nil { + if AVAudioSession.sharedInstance().outputVolume <= 0.01 { + self.zeroVolumeEvents.putNext(Void()) + } + } + } + }) + } } catch let error { managedAudioSessionLog("ManagedAudioSession activate error \(error)") } diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index f1cd0c2990..e81d670733 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -74,6 +74,16 @@ public final class ChatInlineSearchResultsListComponent: Component { case monoforumChats(query: String) } + public final class ScrollingState { + fileprivate let entryId: Entry.Id + fileprivate let entryOffset: CGFloat + + fileprivate init(entryId: Entry.Id, entryOffset: CGFloat) { + self.entryId = entryId + self.entryOffset = entryOffset + } + } + public let context: AccountContext public let presentation: Presentation public let peerId: EnginePeer.Id? @@ -81,6 +91,7 @@ public final class ChatInlineSearchResultsListComponent: Component { public let insets: UIEdgeInsets public let inputHeight: CGFloat public let showEmptyResults: Bool + public let initialScrollingState: ScrollingState? public let messageSelected: (EngineMessage) -> Void public let peerSelected: (EnginePeer) -> Void public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? @@ -97,6 +108,7 @@ public final class ChatInlineSearchResultsListComponent: Component { insets: UIEdgeInsets, inputHeight: CGFloat, showEmptyResults: Bool, + initialScrollingState: ScrollingState?, messageSelected: @escaping (EngineMessage) -> Void, peerSelected: @escaping (EnginePeer) -> Void, loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, @@ -112,6 +124,7 @@ public final class ChatInlineSearchResultsListComponent: Component { self.insets = insets self.inputHeight = inputHeight self.showEmptyResults = showEmptyResults + self.initialScrollingState = initialScrollingState self.messageSelected = messageSelected self.peerSelected = peerSelected self.loadTagMessages = loadTagMessages @@ -146,7 +159,7 @@ public final class ChatInlineSearchResultsListComponent: Component { return true } - private enum Entry: Equatable, Comparable { + fileprivate enum Entry: Equatable, Comparable { enum Id: Hashable { case peer(EnginePeer.Id) case message(EngineMessage.Id) @@ -373,6 +386,26 @@ public final class ChatInlineSearchResultsListComponent: Component { return result } + public func scrollingState() -> ScrollingState? { + var scrollingState: ScrollingState? + self.listNode.forEachVisibleItemNode { itemNode in + if scrollingState != nil { + return + } + if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { + switch item.content { + case let .peer(peerData): + if let message = peerData.messages.first { + scrollingState = ScrollingState(entryId: .message(message.id), entryOffset: itemNode.frame.minY - self.listNode.insets.top) + } + default: + break + } + } + } + return scrollingState + } + func update(component: ChatInlineSearchResultsListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -1127,6 +1160,22 @@ public final class ChatInlineSearchResultsListComponent: Component { } self.hintAnimateListTransition = false + if previousComponent == nil, let initialScrollingState = component.initialScrollingState { + var index = 0 + for entry in contentsState.entries { + if entry.id == initialScrollingState.entryId { + scrollToItem = ListViewScrollToItem( + index: index, + position: .top(initialScrollingState.entryOffset), + animated: false, + curve: .Default(duration: nil), + directionHint: .Up + ) + } + index += 1 + } + } + self.listNode.transaction( deleteIndices: deleteIndices.map { index in return ListViewDeleteItem(index: index, directionHint: nil) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 19fc2d759f..c36c5ab7d9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1366,8 +1366,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let action = strongSelf.gestureRecognized(gesture: .longTap, location: point, recognizer: recognizer) { switch action { case let .action(f): - f.action() - recognizer.cancel() + if let actionWithLongTapRecognizer = f.actionWithLongTapRecognizer { + actionWithLongTapRecognizer(recognizer) + } else { + f.action() + } case let .optionalAction(f): f() recognizer.cancel() @@ -5949,12 +5952,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI break case let .url(url): if tapAction.hasLongTapAction { - return .action(InternalBubbleTapAction.Action({ [weak self] in + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in let cleanUrl = url.url.replacingOccurrences(of: "mailto:", with: "") guard let self, let contentNode = self.contextContentNodeForLink(cleanUrl, rects: rects) else { return } - item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) + item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) }, contextMenuOnLongPress: false)) } else { disableDefaultPressAnimation = true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index 6fdb5b8347..efcd885e4c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -623,10 +623,12 @@ public final class ChatMessageAccessibilityData { public enum InternalBubbleTapAction { public struct Action { public var action: () -> Void + public var actionWithLongTapRecognizer: ((TapLongTapOrDoubleTapGestureRecognizer) -> Void)? public var contextMenuOnLongPress: Bool - public init(_ action: @escaping () -> Void, contextMenuOnLongPress: Bool = false) { + public init(_ action: @escaping () -> Void, actionWithLongTapRecognizer: ((TapLongTapOrDoubleTapGestureRecognizer) -> Void)? = nil, contextMenuOnLongPress: Bool = false) { self.action = action + self.actionWithLongTapRecognizer = actionWithLongTapRecognizer self.contextMenuOnLongPress = contextMenuOnLongPress } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 931ccd6ec2..af00e36bc7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -253,7 +253,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else { currentMaxGlyphCount = nil } - let previousGlyphCount = self.textNode.textNode.getGlyphCount() + let previousGlyphCount = self.textNode.textNode.getCharacterToGlyphMapping().count return { item, layoutConstants, _, _, _, _ in let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -828,7 +828,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var previousAnimateGlyphCount: Int? if hasDraft || hadDraft { - previousAnimateGlyphCount = strongSelf.textNode.textNode.getGlyphCount() + previousAnimateGlyphCount = strongSelf.textNode.textNode.getCharacterToGlyphMapping().count } strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview @@ -1065,7 +1065,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private func updateTextRevealAnimation(previousGlyphCount: Int) { var fromCount = previousGlyphCount - let toCount = self.textNode.textNode.getGlyphCount() + let toCount = self.textNode.textNode.getCharacterToGlyphMapping().count let timestamp = CACurrentMediaTime() if let textRevealAnimationState = self.textRevealAnimationState { if textRevealAnimationState.toCount == toCount { @@ -1114,7 +1114,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var requestUpdate = false let glyphCount = textRevealAnimationState.glyphCount(timestamp: timestamp) if let revealGlyphCount = self.textNode.textNode.revealGlyphCount, let cachedLayout = self.textNode.textNode.cachedLayout { - if cachedLayout.sizeForGlyphCount(glyphCount: revealGlyphCount).height != cachedLayout.sizeForGlyphCount(glyphCount: glyphCount).height { + let previousLayout = cachedLayout.layoutForGlyphCount(glyphCount: revealGlyphCount) + let updatedLayout = cachedLayout.layoutForGlyphCount(glyphCount: glyphCount) + + if updatedLayout.size.height != previousLayout.size.height || abs(updatedLayout.size.width - previousLayout.size.width) > 8.0 { if lineUpdateTimeout >= 0.0 { lastLineUpdateTimestamp = timestamp requestUpdate = true diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index d200d532ca..ff9b4d23e4 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -164,12 +164,14 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var contentNode: ContextExtractedContentContainingNode? public var messageNode: ASDisplayNode? public var progress: Promise? + public var gesture: TapLongTapOrDoubleTapGestureRecognizer? - public init(message: Message? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise? = nil) { + public init(message: Message? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise? = nil, gesture: TapLongTapOrDoubleTapGestureRecognizer? = nil) { self.message = message self.contentNode = contentNode self.messageNode = messageNode self.progress = progress + self.gesture = gesture } } diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift index f4dffd096e..34ae81f6c5 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -183,10 +183,10 @@ final class NewContactScreenComponent: Component { } - func updateCountryCode(code: Int32, name: String) { + func updateCountryCode(code: Int32, name: String, phoneNumber: String?) { if let view = self.phoneSection.findTaggedView(tag: self.phoneTag) as? ListItemComponentAdaptor.View { if let itemNode = view.itemNode as? PhoneInputItemNode { - itemNode.updateCountryCode(code: code, name: name) + itemNode.updateCountryCode(code: code, name: name, phoneNumber: phoneNumber) } } } @@ -256,6 +256,7 @@ final class NewContactScreenComponent: Component { let strings = environment.strings var initialCountryCode: Int32? + var initialPhoneNumberWithoutCountryCode: String? var updateFocusTag: Any? if self.component == nil { if let peer = component.initialData.peer { @@ -279,6 +280,9 @@ final class NewContactScreenComponent: Component { } else { updateFocusTag = self.firstNameTag } + if phone.hasPrefix("\(countryCode)") { + initialPhoneNumberWithoutCountryCode = String(phone[phone.index(phone.startIndex, offsetBy: "\(countryCode)".count)...]) + } } else { countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() updateFocusTag = self.phoneTag @@ -456,7 +460,7 @@ final class NewContactScreenComponent: Component { itemGenerator: PhoneInputItem( theme: theme, strings: strings, - value: (initialCountryCode, nil, ""), + value: (initialCountryCode, nil, initialPhoneNumberWithoutCountryCode ?? ""), accessory: phoneAccesory, selectCountryCode: { [weak self] in guard let self, let environment = self.environment, let controller = environment.controller() else { @@ -467,7 +471,7 @@ final class NewContactScreenComponent: Component { guard let self else { return } - self.updateCountryCode(code: Int32(code), name: name) + self.updateCountryCode(code: Int32(code), name: name, phoneNumber: nil) self.activateInput(tag: self.phoneTag) } self.deactivateInput() @@ -616,9 +620,8 @@ final class NewContactScreenComponent: Component { contentHeight += sectionSpacing if let initialCountryCode { - self.updateCountryCode(code: initialCountryCode, name: "") + self.updateCountryCode(code: initialCountryCode, name: "", phoneNumber: initialPhoneNumberWithoutCountryCode) } - var optionsSectionItems: [AnyComponentWithIdentity] = [ AnyComponentWithIdentity(id: "syncContact", component: AnyComponent(ListActionItemComponent( @@ -1033,22 +1036,24 @@ public class NewContactScreen: ViewControllerComponentContainer { public static func initialData( peer: EnginePeer? = nil, + firstName: String? = nil, + lastName: String? = nil, phoneNumber: String? = nil, shareViaException: Bool = false ) -> InitialData { if case let .user(user) = peer { return InitialData( peer: peer, - firstName: user.firstName, - lastName: user.lastName, + firstName: firstName ?? user.firstName, + lastName: lastName ?? user.lastName, phoneNumber: user.phone ?? phoneNumber, shareViaException: shareViaException ) } else { return InitialData( peer: nil, - firstName: nil, - lastName: nil, + firstName: firstName, + lastName: lastName, phoneNumber: phoneNumber, shareViaException: false ) diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift index b87ce20240..475a4d4f54 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift @@ -296,8 +296,8 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { self.phoneInputNode.codeAndNumber = self.phoneInputNode.codeAndNumber } - func updateCountryCode(code: Int32, name: String) { - self.phoneInputNode.codeAndNumber = (code, name, self.phoneInputNode.codeAndNumber.2) + func updateCountryCode(code: Int32, name: String, phoneNumber: String? = nil) { + self.phoneInputNode.codeAndNumber = (code, name, phoneNumber ?? self.phoneInputNode.codeAndNumber.2) let _ = self.processNumberChange(self.phoneInputNode.number) } diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift index 596a7eb76b..c4ffa9609a 100644 --- a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift @@ -418,6 +418,7 @@ public final class InteractiveTextNodeLayout: NSObject { fileprivate let textStroke: (UIColor, CGFloat)? public let displayContentsUnderSpoilers: Bool fileprivate let expandedBlocks: Set + fileprivate var characterToGlyphMapping: [Int]? fileprivate init( attributedString: NSAttributedString?, @@ -1045,9 +1046,22 @@ public final class InteractiveTextNodeLayout: NSObject { if !self.segments.isEmpty, let line = self.segments[0].lines.first { height = line.frame.maxY } + + var actualCount = 0 + var itemCount = 0 + for item in self.getCharacterToGlyphMapping() { + if itemCount >= glyphCount { + break + } + actualCount = item + itemCount += 1 + } + let glyphCount = actualCount + var width: CGFloat = 0.0 var count = 0 var trailingLineWidth: CGFloat = 0.0 + var lineCount = 0 for segment in self.segments { for line in segment.lines { if count >= glyphCount { @@ -1094,13 +1108,18 @@ public final class InteractiveTextNodeLayout: NSObject { } width = max(width, lineWidth) - trailingLineWidth = lineWidth + trailingLineWidth = lineWidth + self.insets.left + self.insets.right + 4.0 + lineCount += 1 } } - let _ = width + width = ceil(width) + self.insets.left + self.insets.right + if lineCount > 1 { + width = self.size.width + } + height += self.insets.top + self.insets.bottom + 2.0 return TextNodeLayout.LayoutInfo( - size: CGSize(width: self.size.width, height: ceil(height)), + size: CGSize(width: width, height: ceil(height)), trailingLineWidth: trailingLineWidth ) } @@ -1108,6 +1127,62 @@ public final class InteractiveTextNodeLayout: NSObject { public func sizeForGlyphCount(glyphCount: Int) -> CGSize { return self.layoutForGlyphCount(glyphCount: glyphCount).size } + + public func getCharacterToGlyphMapping() -> [Int] { + guard let attributedString = self.attributedString else { + return [] + } + if let value = self.characterToGlyphMapping { + return value + } + + let nsString = attributedString.string as NSString + let fullLength = nsString.length + + // Build a map from each composed character start index to its cumulative glyph count. + // Walk glyphs in reveal order and record the glyph count after processing each composed character. + var glyphCountByComposedStart: [Int: Int] = [:] + var cumulativeGlyphCount = 0 + + for segment in self.segments { + for line in segment.lines { + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + + var stringIndices = [CFIndex](repeating: 0, count: glyphCount) + CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), &stringIndices) + + for i in 0.. [Int] { + guard let cachedLayout = self.cachedLayout else { + return [] + } + return cachedLayout.getCharacterToGlyphMapping() + } + public func updateRevealGlyphCount(count: Int?) { guard let cachedLayout = self.cachedLayout else { return @@ -2187,7 +2269,18 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn self.revealGlyphCount = count - if let count { + if var count { + var actualCount = 0 + var itemCount = 0 + for item in self.getCharacterToGlyphMapping() { + if itemCount >= count { + break + } + actualCount = item + itemCount += 1 + } + count = actualCount + var nextItemId = 0 var currentCount = 0 for segment in cachedLayout.segments { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift index 2bb6b7f03e..ebaea8f357 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift @@ -76,7 +76,7 @@ extension PeerInfoScreenNode { }, openAd: { _ in }, addContact: { [weak self] phoneNumber in if let strongSelf = self { - strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in + strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in self?.controller?.present(controller, in: .window(.root), with: arguments) }, pushController: { [weak self] controller in if let strongSelf = self { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index 8831a82eb1..cc9acdf90f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -141,7 +141,7 @@ extension ChatControllerImpl { openText = self.presentationData.strings.Conversation_FileOpenIn } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture let source: ContextContentSource diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift index 511a8b1711..4441bc44f7 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift @@ -79,7 +79,7 @@ extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { ]) let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: nil, contactData: contactData, parentController: self, push: { [weak self] c in + pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: peer, contactData: contactData, parentController: self, push: { [weak self] c in self?.push(c) }) })) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 85839d3756..66d9817011 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1405,14 +1405,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if case .current = i { - c.presentationArguments = a - c.statusBar.alphaUpdated = { [weak self] transition in - guard let self else { - return + if c is UndoOverlayController { + self.present(c, in: .current) + } else { + c.presentationArguments = a + c.statusBar.alphaUpdated = { [weak self] transition in + guard let self else { + return + } + self.updateStatusBarPresentation(animated: transition.isAnimated) } - self.updateStatusBarPresentation(animated: transition.isAnimated) + self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {}) } - self.galleryPresentationContext.present(c, on: PresentationSurfaceLevel(rawValue: 0), blockInteraction: true, completion: {}) } else { self.present(c, in: .window(.root), with: a, blockInteraction: true) } @@ -3617,7 +3621,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, addContact: { [weak self] phoneNumber in if let strongSelf = self { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { - strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in + strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, peer: nil, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in self?.present(controller, in: .window(.root), with: arguments) }, pushController: { [weak self] controller in if let strongSelf = self { @@ -9105,6 +9109,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = self.context.sharedContext.makeNewContactScreen( context: self.context, peer: EnginePeer(peer), + firstName: nil, + lastName: nil, phoneNumber: nil, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, _, _ in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 3b4906d18d..fb25659d4f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -211,6 +211,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var inlineSearchResults: ComponentView? private var inlineSearchResultsReadyDisposable: Disposable? private var inlineSearchResultsReady: Bool = false + private var inlineSearchResultsScrollingState: (domain: ChatSearchDomain, query: String, state: ChatInlineSearchResultsListComponent.ScrollingState)? var isScrollingLockedAtTop: Bool = false @@ -3050,6 +3051,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { showNavigateButtons = false } + if let inlineSearchResultsScrollingState = self.inlineSearchResultsScrollingState { + if let search = self.chatPresentationInterfaceState.search { + if search.domain != inlineSearchResultsScrollingState.domain || search.query != inlineSearchResultsScrollingState.query { + self.inlineSearchResultsScrollingState = nil + } + } else { + self.inlineSearchResultsScrollingState = nil + } + } + if displayInlineSearch { let peerId = self.chatPresentationInterfaceState.chatLocation.peerId @@ -3107,6 +3118,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { insets: childContentInsets, inputHeight: layout.inputHeight ?? 0.0, showEmptyResults: self.showListEmptyResults, + initialScrollingState: self.inlineSearchResultsScrollingState?.state, messageSelected: { [weak self] message in guard let self else { return @@ -3385,6 +3397,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { environment: {}, containerSize: layout.size ) + self.inlineSearchResultsScrollingState = nil if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View { var animateIn = false if inlineSearchResultsView.superview == nil { @@ -3438,6 +3451,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let inlineSearchResults = self.inlineSearchResults { self.inlineSearchResults = nil if let inlineSearchResultsView = inlineSearchResults.view as? ChatInlineSearchResultsListComponent.View { + + if let search = self.chatPresentationInterfaceState.search, let scrollingState = inlineSearchResultsView.scrollingState() { + self.inlineSearchResultsScrollingState = (search.domain, search.query, scrollingState) + } + transition.updateAlpha(layer: inlineSearchResultsView.layer, alpha: 0.0, completion: { [weak inlineSearchResultsView] _ in inlineSearchResultsView?.removeFromSuperview() }) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 87181921ff..534654c109 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -408,7 +408,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { var tapButtonRightInset: CGFloat = rightInset let buttonsContainerSize = CGSize(width: 16.0, height: panelHeight) - self.buttonsContainer.frame = CGRect(origin: CGPoint(x: width - buttonsContainerSize.width - rightInset, y: 0.0), size: buttonsContainerSize) + self.buttonsContainer.frame = CGRect(origin: CGPoint(x: width - buttonsContainerSize.width - rightInset - 4.0, y: 0.0), size: buttonsContainerSize) let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) @@ -608,7 +608,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let previousMediaReference = self.previousMediaReference let context = self.context - let contentLeftInset: CGFloat = leftInset + 10.0 + let contentLeftInset: CGFloat = leftInset + 18.0 var textLineInset: CGFloat = 10.0 var rightInset: CGFloat = 14.0 + rightInset @@ -707,7 +707,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { var applyImage: (() -> Void)? if let imageDimensions = imageDimensions { let boundingSize = CGSize(width: 35.0, height: 35.0) - applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) textLineInset += 9.0 + 35.0 } @@ -885,8 +885,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { transition: animationTransition ) - strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0)) - strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0)) + strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 36.0, height: 36.0)) + strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 36.0, height: 36.0)) if let applyImage = applyImage { applyImage() diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index 0d1a5a16fd..e5788e4079 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -202,6 +202,8 @@ public class ComposeControllerImpl: ViewController, ComposeController { let controller = strongSelf.context.sharedContext.makeNewContactScreen( context: strongSelf.context, peer: nil, + firstName: nil, + lastName: nil, phoneNumber: nil, shareViaException: false, completion: { [weak self] peer, stableId, contactData in diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index 2a32e11323..d71ffc1abe 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -8,8 +8,9 @@ import ShareController import AlertUI import PresentationDataUtils import PeerInfoUI +import PhoneNumberFormat -func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) { +func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) { let _ = (DeviceAccess.authorizationStatus(subject: .contacts) |> take(1) |> deliverOnMainQueue).startStandalone(next: { value in @@ -17,8 +18,10 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam case .allowed: let controller = context.sharedContext.makeNewContactScreen( context: context, - peer: nil, - phoneNumber: phoneNumber, + peer: peer, + firstName: firstName.isEmpty ? nil : firstName, + lastName: lastName.isEmpty ? nil : lastName, + phoneNumber: cleanPhoneNumber(phoneNumber, removePlus: true), shareViaException: false, completion: { peer, stableId, contactData in if let peer = peer { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 968abcb521..a75cc7b7c3 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -1,4 +1,6 @@ import Foundation +import MediaPlayer +import AVFAudio import Display import AsyncDisplayKit import Postbox @@ -318,6 +320,45 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { playerType = (file.isVoice || file.isInstantVideo) ? .voice : .file } params.context.sharedContext.mediaManager.setPlaylist((params.context, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control) + + if case .voice = playerType { + let _ = (params.context.sharedContext.mediaManager.audioSession.didActivateWithZeroVolume() |> map { _ -> Bool in + return true + } |> take(1) |> timeout(1.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { value in + if value { + let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } + //TODO:localize + let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Device is muted.", timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return true + }) + params.present(toastController, nil, .current) + let isNotMuted: Signal = Signal { subscriber in + subscriber.putNext(AVAudioSession.sharedInstance().outputVolume > 0.01) + subscriber.putCompletion() + return EmptyDisposable + } + let _ = isNotMuted + + let volumeView = MPVolumeView() + if let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + let value = AVAudioSession.sharedInstance().outputVolume + slider.setValue(AVAudioSession.sharedInstance().outputVolume + 0.01, animated: false) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + slider.setValue(value, animated: false) + } + } + } + + let _ = (isNotMuted |> filter({ $0 }) |> delay(0.1, queue: .mainQueue()) |> restart |> take(1) |> timeout(5.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { [weak toastController] value in + if value { + toastController?.dismiss() + } + }) + } + }) + } + return true case let .story(storyController): params.dismissInput() diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index e3c040419d..2231f78cbd 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -765,6 +765,7 @@ func openResolvedUrlImpl( if case .new = section { context.sharedContext.openAddContact( context: context, + peer: nil, firstName: "", lastName: "", phoneNumber: "", @@ -841,6 +842,7 @@ func openResolvedUrlImpl( case .contact: context.sharedContext.openAddContact( context: context, + peer: nil, firstName: "", lastName: "", phoneNumber: "", diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 85a08a8197..dd5d28ed28 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2323,8 +2323,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return LocalizationListController(context: context) } - public func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) { - openAddContactImpl(context: context, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, label: label, present: present, pushController: pushController, completed: completed) + public func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) { + openAddContactImpl(context: context, peer: peer, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, label: label, present: present, pushController: pushController, completed: completed) } public func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) { @@ -4341,8 +4341,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return ChannelMembersSearchControllerImpl(params: params) } - public func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController { - return NewContactScreen(context: context, initialData: NewContactScreen.initialData(peer: peer, phoneNumber: phoneNumber, shareViaException: shareViaException), completion: completion) + public func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, firstName: String?, lastName: String?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController { + return NewContactScreen(context: context, initialData: NewContactScreen.initialData(peer: peer, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, shareViaException: shareViaException), completion: completion) } public func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController {