diff --git a/submodules/TelegramCore/Sources/AuthTransfer.swift b/submodules/TelegramCore/Sources/AuthTransfer.swift index 4480abb041..232dd17f4f 100644 --- a/submodules/TelegramCore/Sources/AuthTransfer.swift +++ b/submodules/TelegramCore/Sources/AuthTransfer.swift @@ -24,20 +24,49 @@ public struct AuthTransferTokenInfo { public enum ExportAuthTransferTokenError { case generic + case limitExceeded } public enum ExportAuthTransferTokenResult { case displayToken(AuthTransferExportedToken) case changeAccountAndRetry(UnauthorizedAccount) case loggedIn + case passwordRequested } public func exportAuthTransferToken(accountManager: AccountManager, account: UnauthorizedAccount, otherAccountUserIds: [Int32], syncContacts: Bool) -> Signal { return account.network.request(Api.functions.auth.exportLoginToken(apiId: account.networkArguments.apiId, apiHash: account.networkArguments.apiHash, exceptIds: otherAccountUserIds)) - |> mapError { _ -> ExportAuthTransferTokenError in - return .generic + |> map(Optional.init) + |> `catch` { error -> Signal in + if error.errorDescription == "SESSION_PASSWORD_NEEDED" { + return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false) + |> mapError { error -> ExportAuthTransferTokenError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + switch result { + case let .password(password): + return account.postbox.transaction { transaction -> Api.auth.LoginToken? in + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: password.hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts))) + return nil + } + |> castError(ExportAuthTransferTokenError.self) + + return .single(nil) + } + } + } else { + return .fail(.generic) + } } |> mapToSignal { result -> Signal in + guard let result = result else { + return .single(.passwordRequested) + } switch result { case let .loginToken(expires, token): return .single(.displayToken(AuthTransferExportedToken(value: token.makeData(), validUntil: expires))) @@ -47,8 +76,32 @@ public func exportAuthTransferToken(accountManager: AccountManager, account: Una |> castError(ExportAuthTransferTokenError.self) |> mapToSignal { updatedAccount -> Signal in return updatedAccount.network.request(Api.functions.auth.importLoginToken(token: token)) - |> mapError { _ -> ExportAuthTransferTokenError in - return .generic + |> map(Optional.init) + |> `catch` { error -> Signal in + if error.errorDescription == "SESSION_PASSWORD_NEEDED" { + return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false) + |> mapError { error -> ExportAuthTransferTokenError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .limitExceeded + } else { + return .generic + } + } + |> mapToSignal { result -> Signal in + switch result { + case let .password(password): + return account.postbox.transaction { transaction -> Api.auth.LoginToken? in + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: password.hint ?? "", number: nil, code: nil, suggestReset: false, syncContacts: syncContacts))) + return nil + } + |> castError(ExportAuthTransferTokenError.self) + + return .single(nil) + } + } + } else { + return .fail(.generic) + } } |> mapToSignal { result -> Signal in switch result { diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift index da12563150..c887a828af 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift @@ -315,7 +315,9 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { super.didLoad() self.titleNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugTap(_:)))) - //self.noticeNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugQrTap(_:)))) + #if DEBUG + self.noticeNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugQrTap(_:)))) + #endif } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -452,7 +454,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self?.refreshQrToken() })) strongSelf.refreshQrToken() - case .loggedIn: + case .loggedIn, .passwordRequested: strongSelf.exportTokenDisposable.set(nil) } })) diff --git a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift index 08e87ea73b..38464bbd4d 100644 --- a/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -112,7 +112,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont self.listView.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in if let strongSelf = self, let state = opaqueTransactionState as? HorizontalListContextResultsOpaqueState { if let visible = displayedRange.visibleRange { - if state.hasMore && visible.lastIndex <= state.entryCount - 10 { + if state.hasMore && visible.lastIndex >= state.entryCount - 10 { strongSelf.loadMore() } } diff --git a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift index db08657b50..eae5612e39 100644 --- a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import TelegramUIPreferences import MergeLists import AccountContext +import SwiftSignalKit private enum VerticalChatContextResultsEntryStableId: Hashable { case action @@ -122,12 +123,16 @@ private func preparedTransition(from fromEntries: [VerticalListContextResultsCha final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { private let listView: ListView - private var currentResults: ChatContextResultCollection? + private var currentExternalResults: ChatContextResultCollection? + private var currentProcessedResults: ChatContextResultCollection? private var currentEntries: [VerticalListContextResultsChatInputContextPanelEntry]? private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = [] private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? + private let loadMoreDisposable = MetaDisposable() + private var isLoadingMore: Bool = false + override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) { self.listView = ListView() self.listView.isOpaque = false @@ -143,10 +148,33 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex self.clipsToBounds = true self.addSubnode(self.listView) + + self.listView.visibleBottomContentOffsetChanged = { [weak self] offset in + guard let strongSelf = self, !strongSelf.isLoadingMore, case let .known(value) = offset, value < 40.0 else { + return + } + strongSelf.loadMore() + } + } + + deinit { + self.loadMoreDisposable.dispose() } func updateResults(_ results: ChatContextResultCollection) { - self.currentResults = results + if self.currentExternalResults == results { + return + } + self.currentExternalResults = results + self.currentProcessedResults = results + + self.isLoadingMore = false + self.loadMoreDisposable.set(nil) + + self.updateInternalResults(results) + } + + private func updateInternalResults(_ results: ChatContextResultCollection) { var entries: [VerticalListContextResultsChatInputContextPanelEntry] = [] var index = 0 var resultIds = Set() @@ -203,15 +231,13 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex var options = ListViewDeleteAndInsertOptions() if firstTime { - //options.insert(.Synchronous) - //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) options.insert(.AnimateCrossfade) } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0, hasSwitchPeer: self.currentResults?.switchPeer != nil) + insets.top = topInsetForLayout(size: validLayout.0, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil) insets.left = validLayout.1 insets.right = validLayout.2 @@ -251,7 +277,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex self.validLayout = (size, leftInset, rightInset, bottomInset) var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size, hasSwitchPeer: self.currentResults?.switchPeer != nil) + insets.top = self.topInsetForLayout(size: size, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil) insets.left = leftInset insets.right = rightInset @@ -268,12 +294,12 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex } } - if self.theme !== interfaceState.theme, let currentResults = currentResults { + if self.theme !== interfaceState.theme, let currentProcessedResults = self.currentProcessedResults { self.theme = interfaceState.theme self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] - prepareTransition(from: self.currentEntries, to: new, results: currentResults) + prepareTransition(from: self.currentEntries, to: new, results: currentProcessedResults) } } @@ -299,4 +325,33 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex let listViewFrame = self.listView.frame return self.listView.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event) } + + private func loadMore() { + guard !self.isLoadingMore, let currentProcessedResults = self.currentProcessedResults, let nextOffset = currentProcessedResults.nextOffset else { + return + } + self.isLoadingMore = true + self.loadMoreDisposable.set((requestChatContextResults(account: self.context.account, botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(currentProcessedResults.geoPoint), offset: nextOffset) + |> deliverOnMainQueue).start(next: { [weak self] nextResults in + guard let strongSelf = self, let nextResults = nextResults else { + return + } + strongSelf.isLoadingMore = false + var results: [ChatContextResult] = [] + var existingIds = Set() + for result in currentProcessedResults.results { + results.append(result) + existingIds.insert(result.id) + } + for result in nextResults.results { + if !existingIds.contains(result.id) { + results.append(result) + existingIds.insert(result.id) + } + } + let mergedResults = ChatContextResultCollection(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, geoPoint: currentProcessedResults.geoPoint, queryId: nextResults.queryId, nextOffset: nextResults.nextOffset, presentation: currentProcessedResults.presentation, switchPeer: currentProcessedResults.switchPeer, results: results, cacheTimeout: currentProcessedResults.cacheTimeout) + strongSelf.currentProcessedResults = mergedResults + strongSelf.updateInternalResults(mergedResults) + })) + } }