From c8a943a017f12d77d292a5471aaae469eacbb6fb Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 1 Dec 2018 02:42:58 +0400 Subject: [PATCH] Various fixes --- ...uthorizationSequenceSignUpController.swift | 2 +- TelegramUI/CallController.swift | 16 +-- TelegramUI/CallKitIntergation.swift | 111 ++++++++++-------- TelegramUI/ChatController.swift | 4 +- TelegramUI/ChatControllerNode.swift | 7 ++ TelegramUI/ChatListController.swift | 2 +- TelegramUI/ChatListControllerNode.swift | 8 ++ TelegramUI/ChatListSearchContainerNode.swift | 12 ++ TelegramUI/ChatListViewTransition.swift | 18 ++- .../ChatMessageBubbleMosaicLayout.swift | 55 ++++++--- .../ChatMessageInteractiveFileNode.swift | 6 +- .../ChatMessageInteractiveMediaNode.swift | 10 +- TelegramUI/ChatMessageItemView.swift | 2 +- TelegramUI/ContactListNode.swift | 2 +- TelegramUI/EditSettingsController.swift | 23 +++- TelegramUI/FileMediaResourceStatus.swift | 2 +- TelegramUI/LegacyAvatarPicker.swift | 7 +- TelegramUI/PresentationCallManager.swift | 85 +++++++++----- TelegramUI/PresentationSurfaceLevels.swift | 1 + .../SearchDisplayControllerContentNode.swift | 3 + TelegramUI/TelegramApplicationContext.swift | 3 + TelegramUI/TransformImageNode.swift | 17 ++- 22 files changed, 271 insertions(+), 125 deletions(-) diff --git a/TelegramUI/AuthorizationSequenceSignUpController.swift b/TelegramUI/AuthorizationSequenceSignUpController.swift index b9e5dd158b..55ff399240 100644 --- a/TelegramUI/AuthorizationSequenceSignUpController.swift +++ b/TelegramUI/AuthorizationSequenceSignUpController.swift @@ -68,7 +68,7 @@ final class AuthorizationSequenceSignUpController: ViewController { presentLegacyAvatarPicker(holder: currentAvatarMixin, signup: true, theme: defaultPresentationTheme, present: { c, a in self?.view.endEditing(true) self?.present(c, in: .window(.root), with: a) - }, completion: { image in + }, openCurrent: nil, completion: { image in self?.controllerNode.currentPhoto = image }) }) diff --git a/TelegramUI/CallController.swift b/TelegramUI/CallController.swift index a274dd6aec..4c0e2f067d 100644 --- a/TelegramUI/CallController.swift +++ b/TelegramUI/CallController.swift @@ -168,15 +168,15 @@ public final class CallController: ViewController { } self.peerDisposable = (account.postbox.peerView(id: self.call.peerId) - |> deliverOnMainQueue).start(next: { [weak self] view in - if let strongSelf = self { - if let peer = view.peers[view.peerId] { - strongSelf.peer = peer - strongSelf.controllerNode.updatePeer(peer: peer) - strongSelf._ready.set(.single(true)) - } + |> deliverOnMainQueue).start(next: { [weak self] view in + if let strongSelf = self { + if let peer = view.peers[view.peerId] { + strongSelf.peer = peer + strongSelf.controllerNode.updatePeer(peer: peer) + strongSelf._ready.set(.single(true)) } - }) + } + }) self.controllerNode.isMuted = self.isMuted diff --git a/TelegramUI/CallKitIntergation.swift b/TelegramUI/CallKitIntergation.swift index d16df31bb8..d11abfb86f 100644 --- a/TelegramUI/CallKitIntergation.swift +++ b/TelegramUI/CallKitIntergation.swift @@ -4,9 +4,9 @@ import AVFoundation import Postbox import SwiftSignalKit +private var sharedProviderDelegate: AnyObject? + public final class CallKitIntegration { - private let providerDelegate: AnyObject - public static var isAvailable: Bool { #if targetEnvironment(simulator) return false @@ -34,7 +34,10 @@ public final class CallKitIntegration { #else if #available(iOSApplicationExtension 10.0, *) { - self.providerDelegate = CallKitProviderDelegate(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged) + if sharedProviderDelegate == nil { + sharedProviderDelegate = CallKitProviderDelegate() + } + (sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged) } else { return nil } @@ -43,31 +46,31 @@ public final class CallKitIntegration { func startCall(peerId: PeerId, displayTitle: String) { if #available(iOSApplicationExtension 10.0, *) { - (self.providerDelegate as! CallKitProviderDelegate).startCall(peerId: peerId, displayTitle: displayTitle) + (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(peerId: peerId, displayTitle: displayTitle) } } func answerCall(uuid: UUID) { if #available(iOSApplicationExtension 10.0, *) { - (self.providerDelegate as! CallKitProviderDelegate).answerCall(uuid: uuid) + (sharedProviderDelegate as? CallKitProviderDelegate)?.answerCall(uuid: uuid) } } func dropCall(uuid: UUID) { if #available(iOSApplicationExtension 10.0, *) { - (self.providerDelegate as! CallKitProviderDelegate).dropCall(uuid: uuid) + (sharedProviderDelegate as? CallKitProviderDelegate)?.dropCall(uuid: uuid) } } func reportIncomingCall(uuid: UUID, handle: String, displayTitle: String, completion: ((NSError?) -> Void)?) { if #available(iOSApplicationExtension 10.0, *) { - (self.providerDelegate as! CallKitProviderDelegate).reportIncomingCall(uuid: uuid, handle: handle, displayTitle: displayTitle, completion: completion) + (sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, handle: handle, displayTitle: displayTitle, completion: completion) } } func reportOutgoingCallConnected(uuid: UUID, at date: Date) { if #available(iOSApplicationExtension 10.0, *) { - (self.providerDelegate as! CallKitProviderDelegate).reportOutgoingCallConnected(uuid: uuid, at: date) + (sharedProviderDelegate as? CallKitProviderDelegate)?.reportOutgoingCallConnected(uuid: uuid, at: date) } } } @@ -77,29 +80,31 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { private let provider: CXProvider private let callController = CXCallController() - private let startCall: (UUID, String) -> Signal - private let answerCall: (UUID) -> Void - private let endCall: (UUID) -> Signal - private let setCallMuted: (UUID, Bool) -> Void - private let audioSessionActivationChanged: (Bool) -> Void + private var startCall: ((UUID, String) -> Signal)? + private var answerCall: ((UUID) -> Void)? + private var endCall: ((UUID) -> Signal)? + private var setCallMuted: ((UUID, Bool) -> Void)? + private var audioSessionActivationChanged: ((Bool) -> Void)? private let disposableSet = DisposableSet() - fileprivate let audioSessionActivePromise: ValuePromise + fileprivate var audioSessionActivePromise: ValuePromise? - init(audioSessionActivePromise: ValuePromise, startCall: @escaping (UUID, String) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { + override init() { + self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration) + + super.init() + + self.provider.setDelegate(self, queue: nil) + } + + func setup(audioSessionActivePromise: ValuePromise, startCall: @escaping (UUID, String) -> Signal, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal, setCallMuted: @escaping (UUID, Bool) -> Void, audioSessionActivationChanged: @escaping (Bool) -> Void) { self.audioSessionActivePromise = audioSessionActivePromise self.startCall = startCall self.answerCall = answerCall self.endCall = endCall self.setCallMuted = setCallMuted self.audioSessionActivationChanged = audioSessionActivationChanged - - self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration) - - super.init() - - self.provider.setDelegate(self, queue: nil) } static var providerConfiguration: CXProviderConfiguration { @@ -184,19 +189,16 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { } func providerDidReset(_ provider: CXProvider) { - /*stopAudio() - - for call in callManager.calls { - call.end() - } - - callManager.removeAllCalls()*/ } func provider(_ provider: CXProvider, perform action: CXStartCallAction) { + guard let startCall = self.startCall else { + action.fail() + return + } let disposable = MetaDisposable() self.disposableSet.add(disposable) - disposable.set((self.startCall(action.callUUID, action.handle.value) + disposable.set((startCall(action.callUUID, action.handle.value) |> deliverOnMainQueue |> afterDisposed { [weak self, weak disposable] in if let strongSelf = self, let disposable = disposable { @@ -212,42 +214,53 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { } func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { - self.answerCall(action.callUUID) - + guard let answerCall = self.answerCall else { + action.fail() + return + } + answerCall(action.callUUID) action.fulfill() } func provider(_ provider: CXProvider, perform action: CXEndCallAction) { + guard let endCall = self.endCall else { + action.fail() + return + } let disposable = MetaDisposable() self.disposableSet.add(disposable) - disposable.set((self.endCall(action.callUUID) - |> deliverOnMainQueue - |> afterDisposed { [weak self, weak disposable] in - if let strongSelf = self, let disposable = disposable { - strongSelf.disposableSet.remove(disposable) - } - }).start(next: { result in - if result { - action.fulfill(withDateEnded: Date()) - } else { - action.fail() - } - })) + disposable.set((endCall(action.callUUID) + |> deliverOnMainQueue + |> afterDisposed { [weak self, weak disposable] in + if let strongSelf = self, let disposable = disposable { + strongSelf.disposableSet.remove(disposable) + } + }).start(next: { result in + if result { + action.fulfill(withDateEnded: Date()) + } else { + action.fail() + } + })) } func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { - self.setCallMuted(action.uuid, action.isMuted) + guard let setCallMuted = self.setCallMuted else { + action.fail() + return + } + setCallMuted(action.uuid, action.isMuted) action.fulfill() } func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { - self.audioSessionActivationChanged(true) - self.audioSessionActivePromise.set(true) + self.audioSessionActivationChanged?(true) + self.audioSessionActivePromise?.set(true) } func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { - self.audioSessionActivationChanged(false) - self.audioSessionActivePromise.set(false) + self.audioSessionActivationChanged?(false) + self.audioSessionActivePromise?.set(false) } } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index e461bd0199..1bf473ccb5 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -1727,7 +1727,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID pinnedMessageUpdated = true } - if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || strongSelf.presentationInterfaceState.canReportPeer != canReport || pinnedMessageUpdated { + let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate + + if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || strongSelf.presentationInterfaceState.canReportPeer != canReport || pinnedMessageUpdated || callsDataUpdated { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state.updatedPinnedMessageId(pinnedMessageId).updatedPinnedMessage(pinnedMessage).updatedPeerIsBlocked(peerIsBlocked).updatedCanReportPeer(canReport).updatedCallsAvailable(callsAvailable).updatedCallsPrivate(callsPrivate).updatedTitlePanelContext({ context in if pinnedMessageId != nil { diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index 55ac2739d0..1fba62cc14 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -260,6 +260,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { strongSelf.requestLayout(.animated(duration: 0.1, curve: .easeInOut)) } } + var lastSendTimestamp = 0.0 self.textInputPanelNode?.sendMessage = { [weak self] in if let strongSelf = self, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode { if textInputPanelNode.textInputNode?.isFirstResponder() ?? false { @@ -274,6 +275,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let _ = effectivePresentationInterfaceState.interfaceState.editMessage { strongSelf.interfaceInteraction?.editMessage() } else { + let timestamp = CACurrentMediaTime() + if lastSendTimestamp + 0.15 > timestamp { + return + } + lastSendTimestamp = timestamp + strongSelf.updateTypingActivity(false) var messages: [EnqueueMessage] = [] diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 69c0b7fc5a..ef50c5b8d8 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -78,7 +78,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) self.scrollToTop = { [weak self] in - self?.chatListDisplayNode.chatListNode.scrollToPosition(.top) + self?.chatListDisplayNode.scrollToTop() } self.scrollToTopWithTabBar = { [weak self] in guard let strongSelf = self else { diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index d2a98dc783..8639ab6c05 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -197,4 +197,12 @@ class ChatListControllerNode: ASDisplayNode { self.searchDisplayController = nil } } + + func scrollToTop() { + if let searchDisplayController = self.searchDisplayController { + searchDisplayController.contentNode.scrollToTop() + } else { + self.chatListNode.scrollToPosition(.top) + } + } } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index fb2584e30a..68930afec4 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -586,7 +586,9 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)) self.recentListNode = ListView() + self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.listNode = ListView() + self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.statePromise = ValuePromise(self.stateValue, ignoreRepeated: true) @@ -1015,6 +1017,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { private func updateTheme(theme: PresentationTheme) { self.backgroundColor = theme.chatList.backgroundColor + self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor + self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor } private func updateState(_ f: (ChatListSearchContainerNodeState) -> ChatListSearchContainerNodeState) { @@ -1188,4 +1192,12 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } } } + + override func scrollToTop() { + if !self.listNode.isHidden { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } else { + self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } } diff --git a/TelegramUI/ChatListViewTransition.swift b/TelegramUI/ChatListViewTransition.swift index 5ca0474eba..fbd2199d59 100644 --- a/TelegramUI/ChatListViewTransition.swift +++ b/TelegramUI/ChatListViewTransition.swift @@ -183,9 +183,21 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV } } - if let fromView = fromView, fromView.filteredEntries.isEmpty { - options.remove(.AnimateInsertion) - options.remove(.AnimateAlpha) + var fromEmptyView = false + if let fromView = fromView { + if fromView.filteredEntries.isEmpty { + options.remove(.AnimateInsertion) + options.remove(.AnimateAlpha) + fromEmptyView = true + } + } else { + fromEmptyView = true + } + + if fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 { + if case .SearchEntry = toView.filteredEntries[toView.filteredEntries.count - 1] { + scrollToItem = ListViewScrollToItem(index: 1, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) + } } subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange)) diff --git a/TelegramUI/ChatMessageBubbleMosaicLayout.swift b/TelegramUI/ChatMessageBubbleMosaicLayout.swift index 8694f4a219..76d3e8c5fb 100644 --- a/TelegramUI/ChatMessageBubbleMosaicLayout.swift +++ b/TelegramUI/ChatMessageBubbleMosaicLayout.swift @@ -58,7 +58,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C } averageAspectRatio += aspectRatio - return MosaicItemInfo(index: index, imageSize: itemSize, aspectRatio: aspectRatio , layoutFrame: CGRect(), position: []) + return MosaicItemInfo(index: index, imageSize: itemSize, aspectRatio: aspectRatio, layoutFrame: CGRect(), position: []) } let minWidth: CGFloat = 68.0 @@ -71,7 +71,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C if itemInfos.count == 2 { if proportions == "ww" && averageAspectRatio > 1.4 * maxAspectRatio && itemInfos[1].aspectRatio - itemInfos[0].aspectRatio < 0.2 { let width = maxSize.width - let height = floorToScreenPixels(min(width / itemInfos[0].aspectRatio, min(width / itemInfos[1].aspectRatio, (maxSize.height - spacing) / 2.0))) + let height = floor(min(width / itemInfos[0].aspectRatio, min(width / itemInfos[1].aspectRatio, (maxSize.height - spacing) / 2.0))) itemInfos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: width, height: height) itemInfos[0].position = [.top, .left, .right] @@ -80,7 +80,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C itemInfos[1].position = [.bottom, .left, .right] } else if proportions == "ww" || proportions == "qq" { let width = (maxSize.width - spacing) / 2.0 - let height = floorToScreenPixels(min(width / itemInfos[0].aspectRatio, min(width / itemInfos[1].aspectRatio, maxSize.height))) + let height = floor(min(width / itemInfos[0].aspectRatio, min(width / itemInfos[1].aspectRatio, maxSize.height))) itemInfos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: width, height: height) itemInfos[0].position = [.top, .left, .bottom] @@ -88,9 +88,9 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C itemInfos[1].layoutFrame = CGRect(x: width + spacing, y: 0.0, width: width, height: height) itemInfos[1].position = [.top, .right, .bottom] } else { - let secondWidth = floorToScreenPixels(min(0.5 * (maxSize.width - spacing), round((maxSize.width - spacing) / itemInfos[0].aspectRatio / (1.0 / itemInfos[0].aspectRatio + 1.0 / itemInfos[1].aspectRatio)))) + let secondWidth = floor(min(0.5 * (maxSize.width - spacing), round((maxSize.width - spacing) / itemInfos[0].aspectRatio / (1.0 / itemInfos[0].aspectRatio + 1.0 / itemInfos[1].aspectRatio)))) let firstWidth = maxSize.width - secondWidth - spacing - let height = floorToScreenPixels(min(maxSize.height, round(min(firstWidth / itemInfos[0].aspectRatio, secondWidth / itemInfos[1].aspectRatio)))) + let height = floor(min(maxSize.height, round(min(firstWidth / itemInfos[0].aspectRatio, secondWidth / itemInfos[1].aspectRatio)))) itemInfos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: firstWidth, height: height) itemInfos[0].position = [.top, .left, .bottom] @@ -117,7 +117,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C itemInfos[2].position = [.right, .bottom] } else { var width = maxSize.width - let firstHeight = floorToScreenPixels(min(width / itemInfos[0].aspectRatio, (maxSize.height - spacing) * 0.66)) + let firstHeight = floor(min(width / itemInfos[0].aspectRatio, (maxSize.height - spacing) * 0.66)) itemInfos[0].layoutFrame = CGRect(x: 0.0, y: 0.0, width: width, height: firstHeight) itemInfos[0].position = [.top, .left, .right] @@ -156,8 +156,8 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C itemInfos[0].position = [.top, .left, .bottom] var w = round((maxSize.height - 2 * spacing) / (1.0 / itemInfos[1].aspectRatio + 1.0 / itemInfos[2].aspectRatio + 1.0 / itemInfos[3].aspectRatio)) - let h0 = floorToScreenPixels(w / itemInfos[1].aspectRatio) - let h1 = floorToScreenPixels(w / itemInfos[2].aspectRatio) + let h0 = floor(w / itemInfos[1].aspectRatio) + let h1 = floor(w / itemInfos[2].aspectRatio) let h2 = h - h0 - h1 - 2.0 * spacing w = max(minWidth, min(maxSize.width - w0 - spacing, w)) itemInfos[1].layoutFrame = CGRect(x: w0 + spacing, y: 0.0, width: w, height: h0) @@ -207,8 +207,6 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C } addAttempt([firstLine, croppedRatios.count - firstLine], [multiHeight(Array(croppedRatios[0.. ([(C } addAttempt([firstLine, secondLine, thirdLine], [multiHeight(Array(croppedRatios[0 ..< firstLine])), multiHeight(Array(croppedRatios[firstLine ..< croppedRatios.count - thirdLine])), multiHeight(Array(croppedRatios[firstLine + secondLine ..< croppedRatios.count]))], &attempts) - - //addAttempt(@[@(firstLine), @(secondLine), @(thirdLine)], @[multiHeight([croppedRatios subarrayWithRange:NSMakeRange(0, firstLine)]), multiHeight([croppedRatios subarrayWithRange:NSMakeRange(firstLine, croppedRatios.count - firstLine - thirdLine)]), multiHeight([croppedRatios subarrayWithRange:NSMakeRange(firstLine + secondLine, croppedRatios.count - firstLine - secondLine)])]) } } @@ -237,14 +233,12 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C } addAttempt([firstLine, secondLine, thirdLine, fourthLine], [multiHeight(Array(croppedRatios[0 ..< firstLine])), multiHeight(Array(croppedRatios[firstLine ..< croppedRatios.count - thirdLine - fourthLine])), multiHeight(Array(croppedRatios[firstLine + secondLine ..< croppedRatios.count - fourthLine])), multiHeight(Array(croppedRatios[firstLine + secondLine + thirdLine ..< croppedRatios.count]))], &attempts) - - //addAttempt(@[@(firstLine), @(secondLine), @(thirdLine), @(fourthLine)], @[multiHeight([croppedRatios subarrayWithRange:NSMakeRange(0, firstLine)]), multiHeight([croppedRatios subarrayWithRange:NSMakeRange(firstLine, croppedRatios.count - firstLine - thirdLine - fourthLine)]), multiHeight([croppedRatios subarrayWithRange:NSMakeRange(firstLine + secondLine, croppedRatios.count - firstLine - secondLine - fourthLine)]), multiHeight([croppedRatios subarrayWithRange:NSMakeRange(firstLine + secondLine + thirdLine, croppedRatios.count - firstLine - secondLine - thirdLine)])]) } } } } - let maxHeight = maxSize.width / 3.0 * 4.0 + let maxHeight = floor(maxSize.width / 3.0 * 4.0) var optimal: MosaicLayoutAttempt? = nil var optimalDiff: CGFloat = 0.0 for attempt in attempts { @@ -252,7 +246,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C var minLineHeight: CGFloat = .greatestFiniteMagnitude var maxLineHeight: CGFloat = 0.0 for h in attempt.heights { - totalHeight += h + totalHeight += floor(h) if totalHeight < minLineHeight { minLineHeight = totalHeight } @@ -284,7 +278,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C if let optimal = optimal { for i in 0 ..< optimal.lineCounts.count { let count = optimal.lineCounts[i] - let lineHeight = optimal.heights[i] + let lineHeight = ceil(optimal.heights[i]) var x: CGFloat = 0.0 var positionFlags: MosaicItemPosition = [] @@ -310,7 +304,7 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C } let ratio = croppedRatios[index] - let width = ratio * lineHeight + let width = ceil(ratio * lineHeight) itemInfos[index].layoutFrame = CGRect(x: x, y: y, width: width, height: lineHeight) itemInfos[index].position = innerPositionFlags @@ -320,6 +314,31 @@ func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize]) -> ([(C y += lineHeight + spacing } + + index = 0 + var maxWidth: CGFloat = 0.0 + for i in 0 ..< optimal.lineCounts.count { + let count = optimal.lineCounts[i] + for k in 0 ..< count { + if k == count - 1 { + maxWidth = max(maxWidth, itemInfos[index].layoutFrame.maxX) + } + index += 1 + } + } + + index = 0 + for i in 0 ..< optimal.lineCounts.count { + let count = optimal.lineCounts[i] + for k in 0 ..< count { + if k == count - 1 { + var frame = itemInfos[index].layoutFrame + frame.size.width = max(frame.width, maxWidth - frame.minX) + itemInfos[index].layoutFrame = frame + } + index += 1 + } + } } } diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index afa58d89bc..b82385f691 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -258,7 +258,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { |> map { status in switch status.mediaStatus { case let .fetchStatus(fetchStatus): - if !voice { + if !voice && !message.flags.isSending { return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus) } else { return FileMediaResourceStatus(mediaStatus: .fetchStatus(fetchStatus), fetchStatus: status.fetchStatus) @@ -619,7 +619,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let state: RadialStatusNodeState var streamingState: RadialStatusNodeState = .none - if isAudio && !isVoice { + let isSending = message.flags.isSending + + if isAudio && !isVoice && !isSending { let streamingStatusForegroundColor: UIColor = incoming ? bubbleTheme.incomingAccentControlColor : bubbleTheme.outgoingAccentControlColor let streamingStatusBackgroundColor: UIColor = incoming ? bubbleTheme.incomingMediaInactiveControlColor : bubbleTheme.outgoingMediaInactiveControlColor switch resourceStatus.fetchStatus { diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 91feaaf21c..1ef208a3bc 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -429,7 +429,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { strongSelf.currentImageArguments = arguments imageApply() - strongSelf.statusNode?.position = CGPoint(x: imageFrame.midX, y: imageFrame.midY) + if let statusNode = strongSelf.statusNode { + var statusFrame = statusNode.frame + statusFrame.origin.x = floor(imageFrame.midX - statusFrame.width / 2.0) + statusFrame.origin.y = floor(imageFrame.midY - statusFrame.height / 2.0) + } if let replaceVideoNode = replaceVideoNode { if let videoNode = strongSelf.videoNode { @@ -581,8 +585,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if progressRequired { if self.statusNode == nil { let statusNode = RadialStatusNode(backgroundNodeColor: theme.chat.bubble.mediaOverlayControlBackgroundColor) - statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: radialStatusSize, height: radialStatusSize)) - statusNode.position = self.imageNode.position + let imagePosition = self.imageNode.position + statusNode.frame = CGRect(origin: CGPoint(x: floor(imagePosition.x - radialStatusSize / 2.0), y: floor(imagePosition.y - radialStatusSize / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize)) self.statusNode = statusNode self.addSubnode(statusNode) } diff --git a/TelegramUI/ChatMessageItemView.swift b/TelegramUI/ChatMessageItemView.swift index e90f1e201d..f58a87d4b2 100644 --- a/TelegramUI/ChatMessageItemView.swift +++ b/TelegramUI/ChatMessageItemView.swift @@ -66,7 +66,7 @@ struct ChatMessageItemLayoutConstants { self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 40.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0)) self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) - self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.5, left: 1.5, bottom: 1.5, right: 1.5), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 74.0, height: 74.0)) + self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 74.0, height: 74.0)) self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0)) self.instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0)) } diff --git a/TelegramUI/ContactListNode.swift b/TelegramUI/ContactListNode.swift index 3ad676a015..9f3b52290f 100644 --- a/TelegramUI/ContactListNode.swift +++ b/TelegramUI/ContactListNode.swift @@ -587,7 +587,7 @@ final class ContactListNode: ASDisplayNode { super.init() self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - //self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.selectionStateValue = selectionState self.selectionStatePromise.set(.single(selectionState)) diff --git a/TelegramUI/EditSettingsController.swift b/TelegramUI/EditSettingsController.swift index 351d3f5953..33b4498c14 100644 --- a/TelegramUI/EditSettingsController.swift +++ b/TelegramUI/EditSettingsController.swift @@ -444,7 +444,7 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName hasPhotos = true } - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)! + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let _ = currentAvatarMixin.swap(mixin) mixin.didFinishWithImage = { image in if let image = image { @@ -488,6 +488,27 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName } })) } + mixin.didFinishWithView = { + let _ = currentAvatarMixin.swap(nil) + + let _ = (account.postbox.loadedPeerWithId(account.peerId) + |> take(1) + |> deliverOnMainQueue).start(next: { peer in + if peer.smallProfileImage != nil { + let galleryController = AvatarGalleryController(account: account, peer: peer, replaceRootController: { controller, ready in + }) + /*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in + avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first + updateHiddenAvatarImpl?() + }))*/ + presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in + return nil + })) + } else { + changeProfilePhotoImpl?() + } + }) + } mixin.didDismiss = { [weak legacyController] in let _ = currentAvatarMixin.swap(nil) legacyController?.dismiss() diff --git a/TelegramUI/FileMediaResourceStatus.swift b/TelegramUI/FileMediaResourceStatus.swift index 273f728a64..21b6688883 100644 --- a/TelegramUI/FileMediaResourceStatus.swift +++ b/TelegramUI/FileMediaResourceStatus.swift @@ -53,7 +53,7 @@ func messageFileMediaResourceStatus(account: Account, file: TelegramMediaFile, m return status?.status } - if message.flags.isSending { + if message.flags.isSending { return combineLatest(messageMediaFileStatus(account: account, messageId: message.id, file: file), account.pendingMessageManager.pendingMessageStatus(message.id), playbackStatus) |> map { resourceStatus, pendingStatus, playbackStatus -> FileMediaResourceStatus in let mediaStatus: FileMediaResourceMediaStatus diff --git a/TelegramUI/LegacyAvatarPicker.swift b/TelegramUI/LegacyAvatarPicker.swift index e225c42816..3ba161c40e 100644 --- a/TelegramUI/LegacyAvatarPicker.swift +++ b/TelegramUI/LegacyAvatarPicker.swift @@ -3,7 +3,7 @@ import Display import SwiftSignalKit import LegacyComponents -func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, theme: PresentationTheme, present: (ViewController, Any?) -> Void, completion: @escaping (UIImage) -> Void) { +func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, theme: PresentationTheme, present: (ViewController, Any?) -> Void, openCurrent: (() -> Void)?, completion: @escaping (UIImage) -> Void) { let legacyController = LegacyController(presentation: .custom, theme: theme) legacyController.statusBar.statusBarStyle = .Ignore @@ -16,7 +16,7 @@ func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, theme: P present(legacyController, nil) - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)! + let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)! let _ = holder.swap(mixin) mixin.didFinishWithImage = { image in guard let image = image else { @@ -24,6 +24,9 @@ func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, theme: P } completion(image) } + mixin.didFinishWithView = { [weak legacyController] in + openCurrent?() + } mixin.didDismiss = { [weak legacyController] in let _ = holder.swap(nil) legacyController?.dismiss() diff --git a/TelegramUI/PresentationCallManager.swift b/TelegramUI/PresentationCallManager.swift index fa6118b07b..0e805f79be 100644 --- a/TelegramUI/PresentationCallManager.swift +++ b/TelegramUI/PresentationCallManager.swift @@ -261,38 +261,65 @@ public final class PresentationCallManager { return .alreadyInProgress(call.peerId) } if let _ = callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings?.0) { - let (presentationData, present, openSettings) = self.getDeviceAccessData() - - let accessEnabledSignal: Signal = Signal { subscriber in - DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in - present(c, a) - }, openSettings: { - openSettings() - }, { value in - subscriber.putNext(value) - subscriber.putCompletion() - }) - return EmptyDisposable - } - |> runOn(Queue.mainQueue()) - let postbox = self.account.postbox - self.startCallDisposable.set((accessEnabledSignal - |> mapToSignal { accessEnabled -> Signal in - if !accessEnabled { - return .single(nil) - } - return postbox.loadedPeerWithId(peerId) - |> take(1) - |> map(Optional.init) - } - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let strongSelf = self, let peer = peer else { + let begin: () -> Void = { [weak self] in + guard let strongSelf = self else { return } - strongSelf.callKitIntegration?.startCall(peerId: peerId, displayTitle: peer.displayTitle) - })) + let (presentationData, present, openSettings) = strongSelf.getDeviceAccessData() + + let accessEnabledSignal: Signal = Signal { subscriber in + DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in + present(c, a) + }, openSettings: { + openSettings() + }, { value in + subscriber.putNext(value) + subscriber.putCompletion() + }) + return EmptyDisposable + } + |> runOn(Queue.mainQueue()) + let postbox = strongSelf.account.postbox + strongSelf.startCallDisposable.set((accessEnabledSignal + |> mapToSignal { accessEnabled -> Signal in + if !accessEnabled { + return .single(nil) + } + return postbox.loadedPeerWithId(peerId) + |> take(1) + |> map(Optional.init) + } + |> deliverOnMainQueue).start(next: { peer in + guard let strongSelf = self, let peer = peer else { + return + } + strongSelf.callKitIntegration?.startCall(peerId: peerId, displayTitle: peer.displayTitle) + })) + } + if let currentCall = self.currentCall { + self.callKitIntegration?.dropCall(uuid: currentCall.internalId) + self.startCallDisposable.set((currentCall.hangUp() + |> deliverOnMainQueue).start(next: { _ in + begin() + })) + } else { + begin() + } } else { - let _ = self.startCall(peerId: peerId).start() + let begin: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + let _ = strongSelf.startCall(peerId: peerId).start() + } + if let currentCall = self.currentCall { + self.startCallDisposable.set((currentCall.hangUp() + |> deliverOnMainQueue).start(next: { _ in + begin() + })) + } else { + begin() + } } return .requested } diff --git a/TelegramUI/PresentationSurfaceLevels.swift b/TelegramUI/PresentationSurfaceLevels.swift index a0b56a461d..db5665f424 100644 --- a/TelegramUI/PresentationSurfaceLevels.swift +++ b/TelegramUI/PresentationSurfaceLevels.swift @@ -6,4 +6,5 @@ public extension PresentationSurfaceLevel { static let calls = PresentationSurfaceLevel(rawValue: 1) static let overlayMedia = PresentationSurfaceLevel(rawValue: 2) static let notifications = PresentationSurfaceLevel(rawValue: 3) + static let passcode = PresentationSurfaceLevel(rawValue: 4) } diff --git a/TelegramUI/SearchDisplayControllerContentNode.swift b/TelegramUI/SearchDisplayControllerContentNode.swift index 39003bd8d3..db743a6139 100644 --- a/TelegramUI/SearchDisplayControllerContentNode.swift +++ b/TelegramUI/SearchDisplayControllerContentNode.swift @@ -30,4 +30,7 @@ class SearchDisplayControllerContentNode: ASDisplayNode { func previewViewAndActionAtLocation(_ location: CGPoint) -> (UIView, Any)? { return nil } + + func scrollToTop() { + } } diff --git a/TelegramUI/TelegramApplicationContext.swift b/TelegramUI/TelegramApplicationContext.swift index ad5eda4f69..ed31040c5d 100644 --- a/TelegramUI/TelegramApplicationContext.swift +++ b/TelegramUI/TelegramApplicationContext.swift @@ -114,6 +114,9 @@ public final class TelegramApplicationContext { public var isCurrent: Bool = false { didSet { self.mediaManager?.isCurrent = self.isCurrent + if !self.isCurrent { + self.callManager = nil + } } } diff --git a/TelegramUI/TransformImageNode.swift b/TelegramUI/TransformImageNode.swift index 122ae2b5f4..a4f5623705 100644 --- a/TelegramUI/TransformImageNode.swift +++ b/TelegramUI/TransformImageNode.swift @@ -50,9 +50,8 @@ public class TransformImageNode: ASDisplayNode { public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) { let argumentsPromise = self.argumentsPromise - var shouldAttemptSynchronously = attemptSynchronously - let result = combineLatest(signal, argumentsPromise.get()) - |> mapToSignal { transform, arguments -> Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), NoError> in + let data = combineLatest(signal, argumentsPromise.get()) + /*|> mapToSignal { transform, arguments -> Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), NoError> in let result: Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), NoError> = .single((transform, arguments)) if shouldAttemptSynchronously { shouldAttemptSynchronously = false @@ -61,7 +60,17 @@ public class TransformImageNode: ASDisplayNode { return result |> deliverOn(Queue.concurrentDefaultQueue()) } + }*/ + + let resultData: Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), NoError> + if attemptSynchronously { + resultData = data + } else { + resultData = data + |> deliverOn(Queue.concurrentDefaultQueue()) } + + let result = resultData |> mapToThrottled { transform, arguments -> Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments, UIImage?)?, NoError> in return deferred { if let context = transform(arguments) { @@ -102,7 +111,7 @@ public class TransformImageNode: ASDisplayNode { } } } - if dispatchOnDisplayLink { + if dispatchOnDisplayLink && !attemptSynchronously { displayLinkDispatcher.dispatch { apply() }