diff --git a/Images.xcassets/Item List/AddItemIcon.imageset/Contents.json b/Images.xcassets/Item List/AddItemIcon.imageset/Contents.json index 2e2e9b52c0..ffe65a7ec0 100644 --- a/Images.xcassets/Item List/AddItemIcon.imageset/Contents.json +++ b/Images.xcassets/Item List/AddItemIcon.imageset/Contents.json @@ -2,7 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "add.pdf" + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_addoption@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_addoption@3x.png", + "scale" : "3x" } ], "info" : { diff --git a/Images.xcassets/Item List/AddItemIcon.imageset/add.pdf b/Images.xcassets/Item List/AddItemIcon.imageset/add.pdf deleted file mode 100644 index 931be96420..0000000000 Binary files a/Images.xcassets/Item List/AddItemIcon.imageset/add.pdf and /dev/null differ diff --git a/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@2x.png b/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@2x.png new file mode 100644 index 0000000000..4a17bc48eb Binary files /dev/null and b/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@2x.png differ diff --git a/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@3x.png b/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@3x.png new file mode 100644 index 0000000000..0c2b3becdf Binary files /dev/null and b/Images.xcassets/Item List/AddItemIcon.imageset/ic_addoption@3x.png differ diff --git a/Images.xcassets/Item List/RemoveItemIcon.imageset/Contents.json b/Images.xcassets/Item List/RemoveItemIcon.imageset/Contents.json index 8edcfb86ed..7a4287b94c 100644 --- a/Images.xcassets/Item List/RemoveItemIcon.imageset/Contents.json +++ b/Images.xcassets/Item List/RemoveItemIcon.imageset/Contents.json @@ -2,7 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "delete.pdf" + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "ic_deleteotion@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "ic_deleteotion@3x.png", + "scale" : "3x" } ], "info" : { diff --git a/Images.xcassets/Item List/RemoveItemIcon.imageset/delete.pdf b/Images.xcassets/Item List/RemoveItemIcon.imageset/delete.pdf deleted file mode 100644 index b73c6ec402..0000000000 Binary files a/Images.xcassets/Item List/RemoveItemIcon.imageset/delete.pdf and /dev/null differ diff --git a/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@2x.png b/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@2x.png new file mode 100644 index 0000000000..a52d7ebf3c Binary files /dev/null and b/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@2x.png differ diff --git a/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@3x.png b/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@3x.png new file mode 100644 index 0000000000..a4c5861aae Binary files /dev/null and b/Images.xcassets/Item List/RemoveItemIcon.imageset/ic_deleteotion@3x.png differ diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index cc6a3c00ce..3318f9e46d 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -8,6 +8,11 @@ import MtProtoKitDynamic import MessageUI import CoreTelephony +private enum InnerState: Equatable { + case state(UnauthorizedAccountStateContents) + case authorized +} + public final class AuthorizationSequenceController: NavigationController { static func navigationBarTheme(_ theme: AuthorizationTheme) -> NavigationBarTheme { return NavigationBarTheme(buttonColor: theme.accentColor, disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) @@ -34,11 +39,13 @@ public final class AuthorizationSequenceController: NavigationController { super.init(mode: .single, theme: NavigationControllerTheme(navigationBar: AuthorizationSequenceController.navigationBarTheme(theme), emptyAreaColor: .black, emptyDetailIcon: nil)) self.stateDisposable = (account.postbox.stateView() - |> map { view -> UnauthorizedAccountStateContents in - if let state = view.state as? UnauthorizedAccountState { - return state.contents + |> map { view -> InnerState in + if let _ = view.state as? AuthorizedAccountState { + return .authorized + } else if let state = view.state as? UnauthorizedAccountState { + return .state(state.contents) } else { - return .empty + return .state(.empty) } } |> distinctUntilChanged @@ -387,7 +394,7 @@ public final class AuthorizationSequenceController: NavigationController { return controller } - private func passwordEntryController(hint: String) -> AuthorizationSequencePasswordEntryController { + private func passwordEntryController(hint: String, suggestReset: Bool) -> AuthorizationSequencePasswordEntryController { var currentController: AuthorizationSequencePasswordEntryController? for c in self.viewControllers { if let c = c as? AuthorizationSequencePasswordEntryController { @@ -464,7 +471,7 @@ public final class AuthorizationSequenceController: NavigationController { } controller.reset = { [weak self, weak controller] in if let strongSelf = self, let strongController = controller { - strongController.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: strongSelf.theme), title: nil, text: strongSelf.strings.TwoStepAuth_RecoveryUnavailable, actions: [ + strongController.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: strongSelf.theme), title: nil, text: suggestReset ? strongSelf.strings.TwoStepAuth_RecoveryFailed : strongSelf.strings.TwoStepAuth_RecoveryUnavailable, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: strongSelf.strings.Login_ResetAccountProtected_Reset, action: { if let strongSelf = self, let strongController = controller { @@ -491,7 +498,7 @@ public final class AuthorizationSequenceController: NavigationController { })]), in: .window(.root)) } } - controller.updateData(hint: hint) + controller.updateData(hint: hint, suggestReset: suggestReset) return controller } @@ -548,7 +555,7 @@ public final class AuthorizationSequenceController: NavigationController { let account = strongSelf.account let _ = (strongSelf.account.postbox.transaction { transaction -> Void in if let state = transaction.getState() as? UnauthorizedAccountState, case let .passwordRecovery(hint, number, code, _) = state.contents { - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: false))) + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .passwordEntry(hint: hint, number: number, code: code, suggestReset: true))) } }).start() } @@ -679,25 +686,30 @@ public final class AuthorizationSequenceController: NavigationController { return controller } - private func updateState(state: UnauthorizedAccountStateContents) { + private func updateState(state: InnerState) { switch state { - case .empty: - if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController { - } else { - self.setViewControllers([self.splashController()], animated: !self.viewControllers.isEmpty) + case .authorized: + break + case let .state(state): + switch state { + case .empty: + if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController { + } else { + self.setViewControllers([self.splashController()], animated: !self.viewControllers.isEmpty) + } + case let .phoneEntry(countryCode, number): + self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: countryCode, number: number)], animated: !self.viewControllers.isEmpty) + case let .confirmationCodeEntry(number, type, _, timeout, nextType, termsOfService): + self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: defaultCountryCode(), number: ""), self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) + case let .passwordEntry(hint, _, _, suggestReset): + self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint, suggestReset: suggestReset)], animated: !self.viewControllers.isEmpty) + case let .passwordRecovery(_, _, _, emailPattern): + self.setViewControllers([self.splashController(), self.passwordRecoveryController(emailPattern: emailPattern)], animated: !self.viewControllers.isEmpty) + case let .awaitingAccountReset(protectedUntil, number): + self.setViewControllers([self.splashController(), self.awaitingAccountResetController(protectedUntil: protectedUntil, number: number)], animated: !self.viewControllers.isEmpty) + case let .signUp(_, _, _, firstName, lastName, termsOfService): + self.setViewControllers([self.splashController(), self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) } - case let .phoneEntry(countryCode, number): - self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: countryCode, number: number)], animated: !self.viewControllers.isEmpty) - case let .confirmationCodeEntry(number, type, _, timeout, nextType, termsOfService): - self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: defaultCountryCode(), number: ""), self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) - case let .passwordEntry(hint, _, _, _): - self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty) - case let .passwordRecovery(_, _, _, emailPattern): - self.setViewControllers([self.splashController(), self.passwordRecoveryController(emailPattern: emailPattern)], animated: !self.viewControllers.isEmpty) - case let .awaitingAccountReset(protectedUntil, number): - self.setViewControllers([self.splashController(), self.awaitingAccountResetController(protectedUntil: protectedUntil, number: number)], animated: !self.viewControllers.isEmpty) - case let .signUp(_, _, _, firstName, lastName, termsOfService): - self.setViewControllers([self.splashController(), self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService)], animated: !self.viewControllers.isEmpty) } } diff --git a/TelegramUI/AuthorizationSequencePasswordEntryController.swift b/TelegramUI/AuthorizationSequencePasswordEntryController.swift index 722ceba444..e7f531e044 100644 --- a/TelegramUI/AuthorizationSequencePasswordEntryController.swift +++ b/TelegramUI/AuthorizationSequencePasswordEntryController.swift @@ -19,12 +19,14 @@ final class AuthorizationSequencePasswordEntryController: ViewController { didSet { if self.didForgotWithNoRecovery != oldValue { if self.isNodeLoaded, let hint = self.hint { - self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: didForgotWithNoRecovery) + self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: didForgotWithNoRecovery, suggestReset: self.suggestReset) } } } } + var suggestReset: Bool = false + private let hapticFeedback = HapticFeedback() var inProgress: Bool = false { @@ -82,7 +84,7 @@ final class AuthorizationSequencePasswordEntryController: ViewController { } if let hint = self.hint { - self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: self.didForgotWithNoRecovery) + self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: self.didForgotWithNoRecovery, suggestReset: self.suggestReset) } } @@ -92,11 +94,12 @@ final class AuthorizationSequencePasswordEntryController: ViewController { self.controllerNode.activateInput() } - func updateData(hint: String) { - if self.hint != hint { + func updateData(hint: String, suggestReset: Bool) { + if self.hint != hint || self.suggestReset != suggestReset { self.hint = hint + self.suggestReset = suggestReset if self.isNodeLoaded { - self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: self.didForgotWithNoRecovery) + self.controllerNode.updateData(hint: hint, didForgotWithNoRecovery: self.didForgotWithNoRecovery, suggestReset: self.suggestReset) } } } @@ -123,7 +126,9 @@ final class AuthorizationSequencePasswordEntryController: ViewController { } func forgotPressed() { - if self.didForgotWithNoRecovery { + if self.suggestReset { + self.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: self.theme), title: nil, text: self.strings.TwoStepAuth_RecoveryFailed, actions: [TextAlertAction(type: .defaultAction, title: self.strings.Common_OK, action: {})]), in: .window(.root)) + } else if self.didForgotWithNoRecovery { self.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: self.theme), title: nil, text: self.strings.TwoStepAuth_RecoveryUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.strings.Common_OK, action: {})]), in: .window(.root)) } else { self.forgot?() diff --git a/TelegramUI/AuthorizationSequencePasswordEntryControllerNode.swift b/TelegramUI/AuthorizationSequencePasswordEntryControllerNode.swift index 515f23d415..5832f0def2 100644 --- a/TelegramUI/AuthorizationSequencePasswordEntryControllerNode.swift +++ b/TelegramUI/AuthorizationSequencePasswordEntryControllerNode.swift @@ -25,6 +25,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT var reset: (() -> Void)? var didForgotWithNoRecovery = false + var suggestReset = false private var clearOnce: Bool = false @@ -91,8 +92,9 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT self.resetNode.addTarget(self, action: #selector(self.resetPressed), forControlEvents: .touchUpInside) } - func updateData(hint: String, didForgotWithNoRecovery: Bool) { + func updateData(hint: String, didForgotWithNoRecovery: Bool, suggestReset: Bool) { self.didForgotWithNoRecovery = didForgotWithNoRecovery + self.suggestReset = suggestReset self.codeField.textField.attributedPlaceholder = NSAttributedString(string: hint, font: Font.regular(20.0), textColor: self.theme.textPlaceholderColor) if let (layout, navigationHeight) = self.layoutArguments { self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) @@ -126,7 +128,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT items.append(AuthorizationLayoutItem(node: self.forgotNode, size: forgotSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - if self.didForgotWithNoRecovery { + if self.didForgotWithNoRecovery || self.suggestReset { self.resetNode.isHidden = false items.append(AuthorizationLayoutItem(node: self.resetNode, size: resetSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) } else { diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index 3076efd1f5..a3d1a59b27 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -357,7 +357,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo |> afterCompleted { contactsController?.dismiss() } - }).start(error: { error in + }).start(error: { [weak contactsController] error in let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let text: String switch error { @@ -369,6 +369,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo text = presentationData.strings.Channel_ErrorAddBlocked } presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + contactsController?.dismiss() })) presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) diff --git a/TelegramUI/ChannelVisibilityController.swift b/TelegramUI/ChannelVisibilityController.swift index cf960ed214..051b19e691 100644 --- a/TelegramUI/ChannelVisibilityController.swift +++ b/TelegramUI/ChannelVisibilityController.swift @@ -871,7 +871,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: if let link = link { UIPasteboard.general.string = link let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Username_LinkCopied, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .genericSuccess(presentationData.strings.Username_LinkCopied)), nil) } }) }, revokePrivateLink: { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 1b5ce79f9b..adebbe495b 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -2373,7 +2373,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } if let updatedIndex = updatedIndex { navigateIndex = resultsState.messageIndices[updatedIndex] - return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: resultsState.messageIndices, currentId: resultsState.messageIndices[updatedIndex].id, totalCount: resultsState.totalCount, complete: resultsState.complete))) + return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: resultsState.messageIndices, currentId: resultsState.messageIndices[updatedIndex].id, state: resultsState.state, totalCount: resultsState.totalCount, completed: resultsState.completed))) } } } @@ -2382,7 +2382,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { case .peer: - strongSelf.navigateToMessage(from: nil, to: .id(navigateIndex.id)) + strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) case .group: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex)) } @@ -2603,11 +2603,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal strongSelf.recordingModeFeedback?.error() - let rect: CGRect? + var rect: CGRect? let isStickers: Bool = subject == .stickers switch subject { case .stickers: rect = strongSelf.chatDisplayNode.frameForStickersButton() + if var rectValue = rect, let actionRect = strongSelf.chatDisplayNode.frameForInputActionButton() { + rectValue.origin.y = actionRect.minY + rect = rectValue + } case .mediaRecording: rect = strongSelf.chatDisplayNode.frameForInputActionButton() } @@ -3281,6 +3285,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.silentPostTooltipController?.dismiss() self.mediaRecordingModeTooltipController?.dismiss() + + self.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) } private func saveInterfaceState(includeScrollState: Bool = true) { @@ -4558,13 +4568,13 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal var derivedSearchState: ChatSearchState? if let search = interfaceState.search { - func loadMoreIndexFromResultsState(_ resultsState: ChatSearchResultsState?) -> MessageIndex? { + func loadMoreStateFromResultsState(_ resultsState: ChatSearchResultsState?) -> SearchMessagesState? { guard let resultsState = resultsState, let currentId = resultsState.currentId else { return nil } if let index = resultsState.messageIndices.index(where: { $0.id == currentId }) { if index <= limit / 2 { - return resultsState.messageIndices.first + return resultsState.state } } return nil @@ -4573,16 +4583,16 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal case .everything: switch self.chatLocation { case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil), loadMoreIndex: loadMoreIndexFromResultsState(search.resultsState)) + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) case let .group(groupId): - derivedSearchState = ChatSearchState(query: search.query, location: .group(groupId), loadMoreIndex: loadMoreIndexFromResultsState(search.resultsState)) + derivedSearchState = ChatSearchState(query: search.query, location: .group(groupId), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) } case .members: derivedSearchState = nil case let .member(peer): switch self.chatLocation { case let .peer(peerId): - derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil), loadMoreIndex: loadMoreIndexFromResultsState(search.resultsState)) + derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) case .group: derivedSearchState = nil } @@ -4620,18 +4630,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal searchDisposable = MetaDisposable() self.searchDisposable = searchDisposable } - searchDisposable.set((searchMessages(account: self.account, location: searchState.location, query: searchState.query, limit: limit) - |> map { ($0.0, $0.2) } + searchDisposable.set((searchMessages(account: self.account, location: searchState.location, query: searchState.query, state: nil, limit: limit) |> delay(0.2, queue: Queue.mainQueue()) - |> deliverOnMainQueue).start(next: { [weak self] results, totalCount in + |> deliverOnMainQueue).start(next: { [weak self] results, updatedState in guard let strongSelf = self else { return } - let complete = results.count == 0 + let complete = results.completed var navigateIndex: MessageIndex? strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in if let data = current.search { - let messageIndices = results.map({ MessageIndex($0) }).sorted() + let messageIndices = results.messages.map({ MessageIndex($0) }).sorted() var currentIndex = messageIndices.last if let previousResultId = data.resultsState?.currentId { for index in messageIndices { @@ -4642,7 +4651,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } } navigateIndex = currentIndex - return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, totalCount: max(Int32(messageIndices.count), totalCount), complete: complete))) + return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: currentIndex?.id, state: updatedState, totalCount: results.totalCount, completed: results.completed))) } else { return current } @@ -4650,7 +4659,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal if let navigateIndex = navigateIndex { switch strongSelf.chatLocation { case .peer: - strongSelf.navigateToMessage(from: nil, to: .id(navigateIndex.id)) + strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex), forceInCurrentChat: true) case .group: strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex)) } @@ -4661,8 +4670,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } })) } - } else if previousSearchState?.loadMoreIndex != searchState.loadMoreIndex { - if let loadMoreIndex = searchState.loadMoreIndex { + } else if previousSearchState?.loadMoreState != searchState.loadMoreState { + if let loadMoreState = searchState.loadMoreState { self.searching.set(true) let searchDisposable: MetaDisposable if let current = self.searchDisposable { @@ -4671,22 +4680,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal searchDisposable = MetaDisposable() self.searchDisposable = searchDisposable } - searchDisposable.set((searchMessages(account: self.account, location: searchState.location, query: searchState.query, lowerBound: loadMoreIndex, limit: limit) - |> map { ($0.0, $0.2) } + searchDisposable.set((searchMessages(account: self.account, location: searchState.location, query: searchState.query, state: loadMoreState, limit: limit) |> delay(0.2, queue: Queue.mainQueue()) - |> deliverOnMainQueue).start(next: { [weak self] results, totalCount in + |> deliverOnMainQueue).start(next: { [weak self] results, updatedState in guard let strongSelf = self else { return } - let complete = results.count == 0 + let complete = results.completed strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in if let data = current.search, let previousResultsState = data.resultsState { - let previousSet = Set(previousResultsState.messageIndices) - let messageIndices = results.map({ MessageIndex($0) }).sorted() - var mergedIndices = messageIndices.filter({ !previousSet.contains($0) }) - mergedIndices.append(contentsOf: previousResultsState.messageIndices) - - return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: mergedIndices, currentId: previousResultsState.currentId, totalCount: max(totalCount, Int32(mergedIndices.count)), complete: complete))) + let messageIndices = results.messages.map({ MessageIndex($0) }).sorted() + return current.updatedSearch(data.withUpdatedResultsState(ChatSearchResultsState(messageIndices: messageIndices, currentId: previousResultsState.currentId, state: updatedState, totalCount: results.totalCount, completed: results.completed))) } else { return current } @@ -4717,11 +4721,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal self.chatDisplayNode.historyNode.scrollToEndOfHistory() } - public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, completion: (() -> Void)? = nil) { - self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, animated: animated, completion: completion) + public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, completion: (() -> Void)? = nil) { + self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion) } - private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, animated: Bool = true, completion: (() -> Void)? = nil) { + private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil) { if self.isNodeLoaded { var fromIndex: MessageIndex? @@ -4733,11 +4737,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } } - if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, messageId.peerId != peerId { + if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) { if let navigationController = self.navigationController as? NavigationController { navigateToChatController(navigationController: navigationController, account: self.account, chatLocation: .peer(messageId.peerId), messageId: messageId, keepStack: .always) } - } else if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId { + } else if case let .peer(peerId) = self.chatLocation, (messageLocation.peerId == peerId || forceInCurrentChat) { if let fromIndex = fromIndex { if let _ = fromId, rememberInStack { self.historyNavigationStack.add(fromIndex) diff --git a/TelegramUI/ChatEmptyNode.swift b/TelegramUI/ChatEmptyNode.swift index 8d863b3165..39ffcc8987 100644 --- a/TelegramUI/ChatEmptyNode.swift +++ b/TelegramUI/ChatEmptyNode.swift @@ -457,7 +457,7 @@ final class ChatEmptyNode: ASDisplayNode { contentType = .secret } else if let group = peer as? TelegramGroup, case .creator = group.role { contentType = .group - } else if let channel = peer as? TelegramChannel, channel.flags.contains(.isCreator) { + } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) { contentType = .group } else { contentType = .regular diff --git a/TelegramUI/ChatHistorySearchContainerNode.swift b/TelegramUI/ChatHistorySearchContainerNode.swift index 96296be76d..1154dc2ce4 100644 --- a/TelegramUI/ChatHistorySearchContainerNode.swift +++ b/TelegramUI/ChatHistorySearchContainerNode.swift @@ -174,18 +174,19 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let signal: Signal<[ChatHistorySearchEntry]?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query) |> map {$0.0} - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + let foundRemoteMessages: Signal<[Message], NoError> = searchMessages(account: account, location: .peer(peerId: peerId, fromId: nil, tags: tagMask), query: query, state: nil) + |> map { $0.0.messages } + |> delay(0.2, queue: Queue.concurrentDefaultQueue()) signal = combineLatest(foundRemoteMessages, themeAndStringsPromise.get()) - |> map { messages, themeAndStrings -> [ChatHistorySearchEntry]? in - if messages.isEmpty { - return [] - } else { - return messages.map { message -> ChatHistorySearchEntry in - return .message(message, themeAndStrings.0, themeAndStrings.1, themeAndStrings.2) - } + |> map { messages, themeAndStrings -> [ChatHistorySearchEntry]? in + if messages.isEmpty { + return [] + } else { + return messages.map { message -> ChatHistorySearchEntry in + return .message(message, themeAndStrings.0, themeAndStrings.1, themeAndStrings.2) } + } } strongSelf._isSearching.set(true) @@ -195,17 +196,17 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { } strongSelf.searchDisposable.set((signal - |> deliverOnMainQueue).start(next: { entries in - if let strongSelf = self { - let previousEntries = previousEntriesValue.swap(entries) - - let firstTime = previousEntries == nil - let transition = chatHistorySearchContainerPreparedTransition(from: previousEntries ?? [], to: entries ?? [], query: query ?? "", displayingResults: entries != nil, account: account, peerId: peerId, interaction: interfaceInteraction) - strongSelf.currentEntries = entries - strongSelf.enqueueTransition(transition, firstTime: firstTime) - strongSelf._isSearching.set(false) - } - })) + |> deliverOnMainQueue).start(next: { entries in + if let strongSelf = self { + let previousEntries = previousEntriesValue.swap(entries) + + let firstTime = previousEntries == nil + let transition = chatHistorySearchContainerPreparedTransition(from: previousEntries ?? [], to: entries ?? [], query: query ?? "", displayingResults: entries != nil, account: account, peerId: peerId, interaction: interfaceInteraction) + strongSelf.currentEntries = entries + strongSelf.enqueueTransition(transition, firstTime: firstTime) + strongSelf._isSearching.set(false) + } + })) } })) diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 7ed60f1bc0..f4458111ab 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -321,129 +321,150 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in - if let strongSelf = self { - let _ = (strongSelf.account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(peerId) - } |> deliverOnMainQueue).start(next: { peer in - if let strongSelf = self, let peer = peer { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) - var items: [ActionSheetItem] = [] - var canClear = true - var canStop = false - var delayedDelete = true - - var deleteTitle = strongSelf.presentationData.strings.Common_Delete - if let channel = peer as? TelegramChannel { - if case .broadcast = channel.info { - delayedDelete = false - canClear = false - deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel - } - if let addressName = channel.addressName, !addressName.isEmpty { - canClear = false - } - } else if let user = peer as? TelegramUser, user.botInfo != nil { - canStop = true - } - if canClear { - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - if let strongSelf = self { - let _ = clearHistoryInteractively(postbox: strongSelf.account.postbox, peerId: peerId).start() - } - })) - } - - items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - if delayedDelete { - let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) - var items: [ActionSheetItem] = [] - items.append(DeleteChatPeerActionSheetItem(account: strongSelf.account, peer: peer, strings: strongSelf.presentationData.strings)) - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.ChatList_DeleteChat, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in - var state = state - state.pendingRemovalPeerIds.insert(peer.id) - return state - }) - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) - strongSelf.present(UndoOverlayController(account: strongSelf.account, text: strongSelf.presentationData.strings.Undo_ChatDeleted, action: { shouldCommit in - guard let strongSelf = self else { - return - } - if shouldCommit { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: { - guard let strongSelf = self else { - return - } - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in - var state = state - state.pendingRemovalPeerIds.remove(peer.id) - return state - }) - self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) - }) - } else { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - strongSelf.chatListDisplayNode.chatListNode.updateState({ state in - var state = state - state.pendingRemovalPeerIds.remove(peer.id) - return state - }) - self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) - } - }), in: .window(.root)) - } - })) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - strongSelf.present(actionSheet, in: .window(.root)) - } else { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: { - self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) - }) - } - } - })) - - if canStop { - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_DeleteBotConversationConfirmation, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - if let strongSelf = self { - strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: { - self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) - }) - let _ = requestUpdatePeerIsBlocked(account: strongSelf.account, peerId: peer.id, isBlocked: true).start() - } - })) - } - - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - strongSelf.present(actionSheet, in: .window(.root)) - } - }) + guard let strongSelf = self else { + return } + let _ = (strongSelf.account.postbox.transaction { transaction -> RenderedPeer? in + guard let peer = transaction.getPeer(peerId) else { + return nil + } + if let associatedPeerId = peer.associatedPeerId { + if let associatedPeer = transaction.getPeer(associatedPeerId) { + return RenderedPeer(peerId: peerId, peers: SimpleDictionary([peer.id: peer, associatedPeer.id: associatedPeer])) + } else { + return nil + } + } else { + return RenderedPeer(peer: peer) + } + } + |> deliverOnMainQueue).start(next: { peer in + guard let strongSelf = self, let peer = peer, let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else { + return + } + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + var items: [ActionSheetItem] = [] + var canClear = true + var canStop = false + + var deleteTitle = strongSelf.presentationData.strings.Common_Delete + if let channel = chatPeer as? TelegramChannel { + if case .broadcast = channel.info { + canClear = false + deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel + } + if let addressName = channel.addressName, !addressName.isEmpty { + canClear = false + } + } else if let user = chatPeer as? TelegramUser, user.botInfo != nil { + canStop = true + deleteTitle = strongSelf.presentationData.strings.ChatList_DeleteChat + } else if let _ = chatPeer as? TelegramSecretChat { + deleteTitle = strongSelf.presentationData.strings.ChatList_DeleteChat + } + items.append(DeleteChatPeerActionSheetItem(account: strongSelf.account, peer: mainPeer, strings: strongSelf.presentationData.strings)) + if canClear { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingClearHistoryPeerIds.insert(peer.peerId) + return state + }) + strongSelf.present(UndoOverlayController(account: strongSelf.account, text: strongSelf.presentationData.strings.Undo_MessagesDeleted, action: { shouldCommit in + guard let strongSelf = self else { + return + } + if shouldCommit { + let _ = clearHistoryInteractively(postbox: strongSelf.account.postbox, peerId: peerId).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingClearHistoryPeerIds.remove(peer.peerId) + return state + }) + }) + } else { + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingClearHistoryPeerIds.remove(peer.peerId) + return state + }) + } + }), in: .window(.root)) + })) + } + + items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingRemovalPeerIds.insert(peer.peerId) + return state + }) + strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + strongSelf.present(UndoOverlayController(account: strongSelf.account, text: strongSelf.presentationData.strings.Undo_ChatDeleted, action: { shouldCommit in + guard let strongSelf = self else { + return + } + if shouldCommit { + strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: { + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingRemovalPeerIds.remove(peer.peerId) + return state + }) + self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + }) + } else { + strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + strongSelf.chatListDisplayNode.chatListNode.updateState({ state in + var state = state + state.pendingRemovalPeerIds.remove(peer.peerId) + return state + }) + self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil) + } + }), in: .window(.root)) + })) + + if canStop { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.DialogList_DeleteBotConversationConfirmation, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + if let strongSelf = self { + strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: { + self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId) + }) + let _ = requestUpdatePeerIsBlocked(account: strongSelf.account, peerId: peer.peerId, isBlocked: true).start() + } + })) + } + + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + strongSelf.present(actionSheet, in: .window(.root)) + }) } self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated, isAd in @@ -735,6 +756,16 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } } + override public func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + self.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitAction() + } + }) + } + override public func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index 761fe86321..02337c23e9 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -328,12 +328,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var peer: Peer? switch item.content { - case let .peer(message, peerValue, _, _, _, _, _, _, _): - if let message = message { - peer = messageMainPeer(message) - } else { - peer = peerValue.chatMainPeer - } + case let .peer(_, peerValue, _, _, _, _, _, _, _): + peer = peerValue.chatMainPeer case .groupReference: break } diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 10c82983cf..146aaee603 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -103,6 +103,7 @@ struct ChatListNodeState: Equatable { var selectedPeerIds: Set var peerInputActivities: ChatListNodePeerInputActivities? var pendingRemovalPeerIds: Set + var pendingClearHistoryPeerIds: Set static func ==(lhs: ChatListNodeState, rhs: ChatListNodeState) -> Bool { if lhs.presentationData !== rhs.presentationData { @@ -123,6 +124,9 @@ struct ChatListNodeState: Equatable { if lhs.pendingRemovalPeerIds != rhs.pendingRemovalPeerIds { return false } + if lhs.pendingClearHistoryPeerIds != rhs.pendingClearHistoryPeerIds { + return false + } return true } } @@ -363,7 +367,7 @@ final class ChatListNode: ListView { self.controlsHistoryPreload = controlsHistoryPreload self.mode = mode - self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set()) + self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set()) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) self.theme = theme diff --git a/TelegramUI/ChatListNodeEntries.swift b/TelegramUI/ChatListNodeEntries.swift index 00778d5375..0ba0c7a7be 100644 --- a/TelegramUI/ChatListNodeEntries.swift +++ b/TelegramUI/ChatListNodeEntries.swift @@ -233,7 +233,11 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState, if state.pendingRemovalPeerIds.contains(index.messageIndex.id.peerId) { continue loop } - result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: message, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false)) + var updatedMessage = message + if state.pendingClearHistoryPeerIds.contains(index.messageIndex.id.peerId) { + updatedMessage = nil + } + result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: combinedReadState, notificationSettings: notificationSettings, embeddedInterfaceState: embeddedState, peer: peer, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false)) case let .HoleEntry(hole): result.append(.HoleEntry(hole, theme: state.presentationData.theme)) case let .GroupReferenceEntry(groupId, index, message, topPeers, counters): diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index ac5a9bb3a2..62b35a41f6 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -527,19 +527,7 @@ private struct ChatListSearchMessagesResult { let messages: [Message] let readStates: [PeerId: CombinedPeerReadState] let hasMore: Bool - - func appending(_ other: ChatListSearchMessagesResult) -> ChatListSearchMessagesResult { - var messages: [Message] = self.messages - var ids = Set(self.messages.map { $0.id }) - for message in other.messages { - if !ids.contains(message.id) { - ids.insert(message.id) - messages.append(message) - } - } - messages.sort(by: { MessageIndex($0) > MessageIndex($1) }) - return ChatListSearchMessagesResult(query: query, messages: messages, readStates: self.readStates.merging(other.readStates, uniquingKeysWith: { left, _ in left }), hasMore: other.hasMore) - } + let state: SearchMessagesState } private struct ChatListSearchMessagesContext { @@ -700,22 +688,22 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { if filter.contains(.doNotSearchMessages) { foundRemoteMessages = .single((([], [:], 0), false)) } else { - let searchSignal = searchMessages(account: account, location: location, query: query, limit: 50) - |> map { result -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: query, messages: result.0.sorted(by: { MessageIndex($0) > MessageIndex($1) }), readStates: result.1, hasMore: result.2 != 0) + let searchSignal = searchMessages(account: account, location: location, query: query, state: nil, limit: 50) + |> map { result, updatedState -> ChatListSearchMessagesResult in + return ChatListSearchMessagesResult(query: query, messages: result.messages.sorted(by: { MessageIndex($0) > MessageIndex($1) }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) } let loadMore = searchContext.get() |> mapToSignal { searchContext -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in if let searchContext = searchContext { - if let loadMoreIndex = searchContext.loadMoreIndex { - return searchMessages(account: account, location: location, query: query, lowerBound: loadMoreIndex, limit: 80) - |> map { result -> ChatListSearchMessagesResult in - return ChatListSearchMessagesResult(query: query, messages: result.0.sorted(by: { MessageIndex($0) > MessageIndex($1) }), readStates: result.1, hasMore: result.2 != 0) + if let _ = searchContext.loadMoreIndex { + return searchMessages(account: account, location: location, query: query, state: searchContext.result.state, limit: 80) + |> map { result, updatedState -> ChatListSearchMessagesResult in + return ChatListSearchMessagesResult(query: query, messages: result.messages.sorted(by: { MessageIndex($0) > MessageIndex($1) }), readStates: result.readStates, hasMore: !result.completed, state: updatedState) } |> mapToSignal { foundMessages -> Signal<(([Message], [PeerId: CombinedPeerReadState], Int32), Bool), NoError> in updateSearchContext { previous in - let updated = ChatListSearchMessagesContext(result: previous?.result.appending(foundMessages) ?? foundMessages, loadMoreIndex: nil) + let updated = ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil) return (updated, true) } return .complete() diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index cbabf3c179..a0c195587a 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -1779,10 +1779,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { override func updateHiddenMedia() { var hasHiddenMosaicStatus = false + var hasHiddenBackground = false if let item = self.item { for contentNode in self.contentNodes { if let contentItem = contentNode.item { if contentNode.updateHiddenMedia(item.controllerInteraction.hiddenMedia[contentItem.message.id]) { + if self.contentNodes.count == 1 && self.nameNode == nil && self.adminBadgeNode == nil && self.forwardInfoNode == nil && self.replyInfoNode == nil { + hasHiddenBackground = true + } if let mosaicStatusNode = self.mosaicStatusNode, mosaicStatusNode.frame.intersects(contentNode.frame) { hasHiddenMosaicStatus = true } @@ -1801,6 +1805,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } } } + + //self.backgroundNode.isHidden = hasHiddenBackground } override func updateAutomaticMediaDownloadSettings() { diff --git a/TelegramUI/ChatPresentationInterfaceState.swift b/TelegramUI/ChatPresentationInterfaceState.swift index b0a2f967d4..00a4e8adf3 100644 --- a/TelegramUI/ChatPresentationInterfaceState.swift +++ b/TelegramUI/ChatPresentationInterfaceState.swift @@ -191,8 +191,9 @@ enum ChatTitlePanelContext: Equatable, Comparable { struct ChatSearchResultsState: Equatable { let messageIndices: [MessageIndex] let currentId: MessageId? + let state: SearchMessagesState let totalCount: Int32 - let complete: Bool + let completed: Bool } enum ChatSearchDomain: Equatable { diff --git a/TelegramUI/ChatRecentActionsHistoryTransition.swift b/TelegramUI/ChatRecentActionsHistoryTransition.swift index 5584240349..58927b8ff7 100644 --- a/TelegramUI/ChatRecentActionsHistoryTransition.swift +++ b/TelegramUI/ChatRecentActionsHistoryTransition.swift @@ -785,7 +785,6 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), (.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers), (.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages), - (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), (.banChangeInfo, self.presentationData.strings.Channel_AdminLog_ChangeInfo) ] diff --git a/TelegramUI/ChatSearchInputPanelNode.swift b/TelegramUI/ChatSearchInputPanelNode.swift index 0bc0360d06..1d855a4075 100644 --- a/TelegramUI/ChatSearchInputPanelNode.swift +++ b/TelegramUI/ChatSearchInputPanelNode.swift @@ -121,7 +121,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { var resultsText: NSAttributedString? if let results = interfaceState.search?.resultsState { resultCount = results.messageIndices.count - let displayTotalCount = results.complete ? results.messageIndices.count : Int(results.totalCount) + let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount) if let currentId = results.currentId, let index = results.messageIndices.index(where: { $0.id == currentId }) { let adjustedIndex = results.messageIndices.count - 1 - index resultIndex = index diff --git a/TelegramUI/ChatSearchState.swift b/TelegramUI/ChatSearchState.swift index 671715622c..bf886b8000 100644 --- a/TelegramUI/ChatSearchState.swift +++ b/TelegramUI/ChatSearchState.swift @@ -5,5 +5,5 @@ import TelegramCore struct ChatSearchState: Equatable { let query: String let location: SearchMessagesLocation - let loadMoreIndex: MessageIndex? + let loadMoreState: SearchMessagesState? } diff --git a/TelegramUI/FFMpegMediaFrameSource.swift b/TelegramUI/FFMpegMediaFrameSource.swift index 365a44d3cd..aa7c8da58a 100644 --- a/TelegramUI/FFMpegMediaFrameSource.swift +++ b/TelegramUI/FFMpegMediaFrameSource.swift @@ -89,6 +89,8 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource { localStorage["FFMpegMediaFrameSourceContext"] = context taskQueue.loop() + + Thread.current.threadDictionary.removeObject(forKey: "FFMpegMediaFrameSourceContext") } } diff --git a/TelegramUI/FFMpegMediaFrameSourceContext.swift b/TelegramUI/FFMpegMediaFrameSourceContext.swift index 9b7e75d745..0e94c4429a 100644 --- a/TelegramUI/FFMpegMediaFrameSourceContext.swift +++ b/TelegramUI/FFMpegMediaFrameSourceContext.swift @@ -55,6 +55,8 @@ struct FFMpegMediaFrameSourceContextInfo { let videoStream: FFMpegMediaFrameSourceStreamContextInfo? } +private var maxOffset: Int = 0 + private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: UnsafeMutablePointer?, bufferSize: Int32) -> Int32 { let context = Unmanaged.fromOpaque(userData!).takeUnretainedValue() guard let postbox = context.postbox, let resourceReference = context.resourceReference, let streamable = context.streamable else { @@ -65,6 +67,11 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa var fetchedData: Data? + #if DEBUG + maxOffset = max(maxOffset, context.readingOffset + Int(bufferSize)) + print("maxOffset \(maxOffset)") + #endif + if streamable { let data: Signal let resourceSize: Int = resourceReference.resource.size ?? Int(Int32.max - 1) @@ -217,7 +224,7 @@ final class FFMpegMediaFrameSourceContext: NSObject { fileprivate var streamable: Bool? fileprivate var statsCategory: MediaResourceStatsCategory? - private let ioBufferSize = 64 * 1024 + private let ioBufferSize = 1 * 1024 fileprivate var readingOffset = 0 fileprivate var requestedDataOffset: Int? diff --git a/TelegramUI/FetchManager.swift b/TelegramUI/FetchManager.swift index 4236993298..6c1617e6a9 100644 --- a/TelegramUI/FetchManager.swift +++ b/TelegramUI/FetchManager.swift @@ -185,10 +185,11 @@ private final class FetchManagerCategoryContext { let entryCompleted = self.entryCompleted let storeManager = self.storeManager activeContext.disposable = (fetchedMediaResource(postbox: self.postbox, reference: entry.resourceReference, statsCategory: entry.statsCategory, reportResultStatus: true, continueInBackground: entry.userInitiated) - |> mapToSignal { type -> Signal in + |> mapToSignal { type -> Signal in if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, let peerType = entry.storeToDownloadsPeerType { return storeDownloadedMedia(storeManager: storeManager, media: mediaReference, peerType: peerType) - |> mapToSignal { _ -> Signal in + |> introduceError(FetchResourceError.self) + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(type)) @@ -266,10 +267,11 @@ private final class FetchManagerCategoryContext { let entryCompleted = self.entryCompleted let storeManager = self.storeManager activeContext.disposable = (fetchedMediaResource(postbox: self.postbox, reference: entry.resourceReference, statsCategory: entry.statsCategory, reportResultStatus: true, continueInBackground: entry.userInitiated) - |> mapToSignal { type -> Signal in + |> mapToSignal { type -> Signal in if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, let peerType = entry.storeToDownloadsPeerType { return storeDownloadedMedia(storeManager: storeManager, media: mediaReference, peerType: peerType) - |> mapToSignal { _ -> Signal in + |> introduceError(FetchResourceError.self) + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(type)) diff --git a/TelegramUI/FetchMediaUtils.swift b/TelegramUI/FetchMediaUtils.swift index 6501f1f7ee..0fc55c2b56 100644 --- a/TelegramUI/FetchMediaUtils.swift +++ b/TelegramUI/FetchMediaUtils.swift @@ -3,11 +3,11 @@ import TelegramCore import Postbox import SwiftSignalKit -public func freeMediaFileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal { +public func freeMediaFileInteractiveFetched(account: Account, fileReference: FileMediaReference) -> Signal { return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)) } -func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal { +func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal { return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(resource)) } diff --git a/TelegramUI/FetchVideoMediaResource.swift b/TelegramUI/FetchVideoMediaResource.swift index c51e32e85e..a4311efefb 100644 --- a/TelegramUI/FetchVideoMediaResource.swift +++ b/TelegramUI/FetchVideoMediaResource.swift @@ -2,6 +2,7 @@ import Foundation import Postbox import SwiftSignalKit import LegacyComponents +import FFMpeg private final class AVURLAssetCopyItem: MediaResourceDataFetchCopyLocalItem { private let url: URL @@ -79,6 +80,8 @@ public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) if stat(asset.url.path, &value) == 0 { subscriber.putNext(.copyLocalItem(AVURLAssetCopyItem(url: asset.url))) subscriber.putCompletion() + } else { + subscriber.putError(.generic) } return } else { @@ -110,6 +113,14 @@ public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) if let result = next as? TGMediaVideoConversionResult { var value = stat() if stat(result.fileURL.path, &value) == 0 { + let tempFile = TempBox.shared.tempFile(fileName: "video.mp4") + if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) { + let _ = try? FileManager.default.removeItem(atPath: result.fileURL.path) + subscriber.putNext(.moveTempFile(file: tempFile)) + } else { + TempBox.shared.dispose(tempFile) + subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) + } /*if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) { var range: Range? let _ = updatedSize.modify { updatedSize in @@ -122,10 +133,13 @@ public func fetchVideoLibraryMediaResource(resource: VideoLibraryMediaResource) subscriber.putNext(.dataPart(resourceOffset: data.count, data: Data(), range: 0 ..< 0, complete: true)) }*/ subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) + } else { + subscriber.putError(.generic) } subscriber.putCompletion() } }, error: { _ in + subscriber.putError(.generic) }, completed: nil) disposable.set(ActionDisposable { signalDisposable?.dispose() diff --git a/TelegramUI/GroupStickerPackCurrentItem.swift b/TelegramUI/GroupStickerPackCurrentItem.swift index a6d0ddec17..b12393fd0a 100644 --- a/TelegramUI/GroupStickerPackCurrentItem.swift +++ b/TelegramUI/GroupStickerPackCurrentItem.swift @@ -223,7 +223,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { } var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchSignal: Signal? + var updatedFetchSignal: Signal? if fileUpdated { if let file = file { updatedImageSignal = chatMessageSticker(account: item.account, file: file, small: false) diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index e2c9507366..0e6ac12941 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -36,11 +36,11 @@ final class HashtagSearchController: TelegramController { let chatListPresentationData = 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) let location: SearchMessagesLocation = .general - let search = searchMessages(account: account, location: location, query: query) + let search = searchMessages(account: account, location: location, query: query, state: nil) let foundMessages: Signal<[ChatListSearchEntry], NoError> = search - |> map { result in - return result.0.map({ .message($0, result.1[$0.id.peerId], chatListPresentationData) }) - } + |> map { result, _ in + return result.messages.map({ .message($0, result.readStates[$0.id.peerId], chatListPresentationData) }) + } let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { peer in }, togglePeerSelected: { _ in diff --git a/TelegramUI/ItemListStickerPackItem.swift b/TelegramUI/ItemListStickerPackItem.swift index 7578e4dcd4..db9741d040 100644 --- a/TelegramUI/ItemListStickerPackItem.swift +++ b/TelegramUI/ItemListStickerPackItem.swift @@ -333,7 +333,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchSignal: Signal? + var updatedFetchSignal: Signal? if fileUpdated { if let file = file { updatedImageSignal = chatMessageSticker(account: item.account, file: file, small: false) diff --git a/TelegramUI/MergedItemListItem.swift b/TelegramUI/MergedItemListItem.swift index 9a30829567..8b13789179 100644 --- a/TelegramUI/MergedItemListItem.swift +++ b/TelegramUI/MergedItemListItem.swift @@ -1,3 +1 @@ -import Foundation - diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 21f1c825be..d4220a9798 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -28,7 +28,7 @@ func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference return .single((nil, loadedData, true)) } else { let decodedThumbnailData = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) - let fetchedThumbnail: Signal + let fetchedThumbnail: Signal if let _ = decodedThumbnailData { fetchedThumbnail = .complete() } else { @@ -138,7 +138,7 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe if maybeData.complete { return .single((nil, maybeData.path, true)) } else { - let fetchedThumbnail: Signal + let fetchedThumbnail: Signal if !fetched, let _ = decodedThumbnailData { fetchedThumbnail = .single(.local) } else if let thumbnailResource = thumbnailResource { @@ -201,7 +201,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: if let decodedThumbnailData = decodedThumbnailData { return .single((decodedThumbnailData, nil, false)) } else if let thumbnailResource = thumbnailResource { - let fetchedThumbnail: Signal = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource)) + let fetchedThumbnail: Signal = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource)) return Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in @@ -233,7 +233,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: if maybeData.complete { return .single((nil, maybeData.path, true)) } else { - let fetchedThumbnail: Signal + let fetchedThumbnail: Signal if let _ = fileReference.media.immediateThumbnailData { fetchedThumbnail = .complete() } else if let thumbnailResource = thumbnailResource { @@ -1690,13 +1690,14 @@ func chatMessagePhotoStatus(account: Account, messageId: MessageId, photoReferen } } -public func chatMessagePhotoInteractiveFetched(account: Account, photoReference: ImageMediaReference, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal { +public func chatMessagePhotoInteractiveFetched(account: Account, photoReference: ImageMediaReference, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal { if let largestRepresentation = largestRepresentationForPhoto(photoReference.media) { return fetchedMediaResource(postbox: account.postbox, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image, reportResultStatus: true) - |> mapToSignal { type -> Signal in + |> mapToSignal { type -> Signal in if case .remote = type, let peerType = storeToDownloadsPeerType { return storeDownloadedMedia(storeManager: account.telegramApplicationContext.mediaManager?.downloadedMediaStoreManager, media: photoReference.abstract, peerType: peerType) - |> mapToSignal { _ -> Signal in + |> introduceError(FetchResourceError.self) + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(type)) @@ -1714,7 +1715,7 @@ func chatMessagePhotoCancelInteractiveFetch(account: Account, photoReference: Im } } -func chatMessageWebFileInteractiveFetched(account: Account, image: TelegramMediaWebFile) -> Signal { +func chatMessageWebFileInteractiveFetched(account: Account, image: TelegramMediaWebFile) -> Signal { return fetchedMediaResource(postbox: account.postbox, reference: .standalone(resource: image.resource), statsCategory: .image) } @@ -2208,7 +2209,7 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((nil, loadedData, true)) } else { - let fetchedThumbnail: Signal + let fetchedThumbnail: Signal if let _ = decodedThumbnailData { fetchedThumbnail = .complete() } else { diff --git a/TelegramUI/PresentationResourcesItemList.swift b/TelegramUI/PresentationResourcesItemList.swift index ef402ea17e..67f3d6be48 100644 --- a/TelegramUI/PresentationResourcesItemList.swift +++ b/TelegramUI/PresentationResourcesItemList.swift @@ -73,7 +73,15 @@ struct PresentationResourcesItemList { static func itemListDeleteIndicatorIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListDeleteIndicatorIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Item List/RemoveItemIcon"), color: theme.list.itemDestructiveColor) + guard let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/RemoveItemIcon"), color: theme.list.itemDestructiveColor) else { + return nil + } + return generateImage(image.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 2, y: 2), size: CGSize(width: size.width - 4.0, height: size.height - 4.0))) + context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }) }) } @@ -104,7 +112,15 @@ struct PresentationResourcesItemList { static func addPhoneIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListAddPhoneIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Item List/AddItemIcon"), color: theme.list.itemAccentColor) + guard let image = generateTintedImage(image: UIImage(bundleImageName: "Item List/AddItemIcon"), color: theme.list.itemAccentColor) else { + return nil + } + return generateImage(image.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 2, y: 2), size: CGSize(width: size.width - 4.0, height: size.height - 4.0))) + context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }) }) } diff --git a/TelegramUI/SecureIdLocalResource.swift b/TelegramUI/SecureIdLocalResource.swift index 9d35536a92..0248b8d091 100644 --- a/TelegramUI/SecureIdLocalResource.swift +++ b/TelegramUI/SecureIdLocalResource.swift @@ -85,6 +85,13 @@ func fetchSecureIdLocalImageResource(postbox: Postbox, resource: SecureIdLocalIm } let _ = try? FileManager.default.removeItem(atPath: path) } + case let .moveTempFile(file): + if let data = try? Data(contentsOf: URL(fileURLWithPath: file.path)) { + let _ = buffer.with { buffer in + buffer.data = data + } + } + TempBox.shared.dispose(file) case .copyLocalItem: assertionFailure() break diff --git a/TelegramUI/SharedMediaPlayer.swift b/TelegramUI/SharedMediaPlayer.swift index d000f1a509..0331f313ee 100644 --- a/TelegramUI/SharedMediaPlayer.swift +++ b/TelegramUI/SharedMediaPlayer.swift @@ -729,6 +729,9 @@ final class SharedMediaPlayer { case let .telegramFile(file): fetchedNextSignal = fetchedMediaResource(postbox: self.postbox, reference: file.resourceReference(file.media.resource)) |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } } self.prefetchDisposable.set((fetchedCurrentSignal |> then(fetchedNextSignal)).start()) } else { diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index 5e31d86b2d..2c908fbb1c 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -197,14 +197,24 @@ private func storageUsageControllerEntries(presentationData: PresentationData, c var peerSizes: Int64 = 0 var statsByPeerId: [(PeerId, Int64)] = [] + var peerIndices: [PeerId: Int] = [:] for (peerId, categories) in stats.media { + var updatedPeerId = peerId + if let group = stats.peers[peerId] as? TelegramGroup, let migrationReference = group.migrationReference, let channel = stats.peers[migrationReference.peerId] { + updatedPeerId = channel.id + } var combinedSize: Int64 = 0 for (_, media) in categories { for (_, size) in media { combinedSize += size } } - statsByPeerId.append((peerId, combinedSize)) + if let index = peerIndices[updatedPeerId] { + statsByPeerId[index].1 += combinedSize + } else { + peerIndices[updatedPeerId] = statsByPeerId.count + statsByPeerId.append((updatedPeerId, combinedSize)) + } peerSizes += combinedSize } @@ -527,7 +537,23 @@ func storageUsageController(account: Account, isModal: Bool = false) -> ViewCont }, openPeerMedia: { peerId in let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in if let result = result, case let .result(stats) = result { - if let categories = stats.media[peerId] { + var additionalPeerId: PeerId? + if var categories = stats.media[peerId] { + if let channel = stats.peers[peerId] as? TelegramChannel, case .group = channel.info { + for (_, peer) in stats.peers { + if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference, migrationReference.peerId == peerId { + if let additionalCategories = stats.media[group.id] { + additionalPeerId = group.id + categories.merge(additionalCategories, uniquingKeysWith: { lhs, rhs in + return lhs.merging(rhs, uniquingKeysWith: { lhs, rhs in + return lhs + rhs + }) + }) + } + } + } + } + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in @@ -611,6 +637,20 @@ func storageUsageController(account: Account, isModal: Bool = false) -> ViewCont media[peerId] = categories } + if let additionalPeerId = additionalPeerId { + if var categories = media[additionalPeerId] { + for category in clearCategories { + if let contents = categories[category] { + for (mediaId, _) in contents { + clearMediaIds.insert(mediaId) + } + } + categories.removeValue(forKey: category) + } + + media[additionalPeerId] = categories + } + } var clearResourceIds = Set() for id in clearMediaIds { diff --git a/TelegramUI/UndoOverlayController.swift b/TelegramUI/UndoOverlayController.swift index 67a80cbcaf..ec621ac4c7 100644 --- a/TelegramUI/UndoOverlayController.swift +++ b/TelegramUI/UndoOverlayController.swift @@ -32,6 +32,11 @@ final class UndoOverlayController: ViewController { self.displayNodeDidLoad() } + func dismissWithCommitAction() { + self.action(true) + self.dismiss() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) diff --git a/TelegramUI/UndoOverlayControllerNode.swift b/TelegramUI/UndoOverlayControllerNode.swift index a157a85555..1d077e59c7 100644 --- a/TelegramUI/UndoOverlayControllerNode.swift +++ b/TelegramUI/UndoOverlayControllerNode.swift @@ -113,17 +113,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.panelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - rightInset - buttonTextSize.width, y: floor((panelHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize) + let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - rightInset - buttonTextSize.width, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize) transition.updateFrame(node: self.buttonTextNode, frame: buttonTextFrame) self.buttonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0, height: contentHeight)) - let textSize = self.textNode.updateLayout(CGSize(width: buttonTextFrame.minX - 8.0 - leftInset, height: .greatestFiniteMagnitude)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: floor((panelHeight - textSize.height) / 2.0)), size: textSize)) + let textSize = self.textNode.updateLayout(CGSize(width: buttonTextFrame.minX - 8.0 - leftInset - layout.safeInsets.left - layout.safeInsets.right, height: .greatestFiniteMagnitude)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + leftInset, y: floor((contentHeight - textSize.height) / 2.0)), size: textSize)) let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((leftInset - timerTextSize.width) / 2.0), y: floor((panelHeight - timerTextSize.height) / 2.0)), size: timerTextSize)) + transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize)) let statusSize: CGFloat = 30.0 - transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((leftInset - statusSize) / 2.0), y: floor((panelHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))) + transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize))) if firstLayout { self.statusNode.transitionToState(.secretTimeout(color: .white, icon: nil, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {}) } diff --git a/TelegramUI/WallpaperListPreviewControllerNode.swift b/TelegramUI/WallpaperListPreviewControllerNode.swift index ea4255c246..0ba9300f2f 100644 --- a/TelegramUI/WallpaperListPreviewControllerNode.swift +++ b/TelegramUI/WallpaperListPreviewControllerNode.swift @@ -59,7 +59,7 @@ private final class WallpaperBackgroundNode: ASDisplayNode { self.addSubnode(self.statusNode) let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> - let fetchSignal: Signal + let fetchSignal: Signal let statusSignal: Signal let displaySize: CGSize switch wallpaper {