diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 259b9cfa7e..bd126a0a72 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -85,6 +85,7 @@ D0185E882089ED5F005E1A6C /* ProxyListSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0185E872089ED5F005E1A6C /* ProxyListSettingsController.swift */; }; D0185E8A208A01AF005E1A6C /* ProxySettingsActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0185E89208A01AF005E1A6C /* ProxySettingsActionItem.swift */; }; D0185E8C208A025A005E1A6C /* ProxySettingsServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0185E8B208A025A005E1A6C /* ProxySettingsServerItem.swift */; }; + D018BE58218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018BE57218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift */; }; D0192D3C210A44D00005FA10 /* DeviceContactData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0192D3B210A44D00005FA10 /* DeviceContactData.swift */; }; D0192D44210A5AA50005FA10 /* DeviceContactDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0192D43210A5AA50005FA10 /* DeviceContactDataManager.swift */; }; D0192D46210F4F950005FA10 /* FixSearchableListNodeScrolling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0192D45210F4F940005FA10 /* FixSearchableListNodeScrolling.swift */; }; @@ -1168,6 +1169,7 @@ D0185E872089ED5F005E1A6C /* ProxyListSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyListSettingsController.swift; sourceTree = ""; }; D0185E89208A01AF005E1A6C /* ProxySettingsActionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxySettingsActionItem.swift; sourceTree = ""; }; D0185E8B208A025A005E1A6C /* ProxySettingsServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxySettingsServerItem.swift; sourceTree = ""; }; + D018BE57218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageDeliveryFailedNode.swift; sourceTree = ""; }; D018D3311E6460B300C5E089 /* ChatUnblockInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatUnblockInputPanelNode.swift; sourceTree = ""; }; D018D3341E6489EC00C5E089 /* CreateChannelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateChannelController.swift; sourceTree = ""; }; D0192D3B210A44D00005FA10 /* DeviceContactData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactData.swift; sourceTree = ""; }; @@ -4202,6 +4204,7 @@ D0E8174F2012027900B82BBB /* ChatMessageEventLogPreviousLinkContentNode.swift */, D0B69C3820EBB397003632C7 /* ChatMessageInteractiveInstantVideoNode.swift */, D0380DB7204EE0A5000414AB /* ChatInstantVideoMessageDurationNode.swift */, + D018BE57218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift */, ); name = Items; sourceTree = ""; @@ -5137,6 +5140,7 @@ D0CE6F6E213EDF8800BCD44B /* SecureIdAuthPasswordSetupContentNode.swift in Sources */, D07E413D208A494D00FCA8F0 /* ProxyServerActionSheetController.swift in Sources */, D02C81732177AC5900CD1006 /* NotificationSearchItem.swift in Sources */, + D018BE58218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift in Sources */, D0EC6D681EB9F58800EBF1C3 /* AuthorizationSequenceController.swift in Sources */, D0EC6D691EB9F58800EBF1C3 /* AuthorizationSequenceSplashController.swift in Sources */, D0EC6D6A1EB9F58800EBF1C3 /* AuthorizationSequenceSplashControllerNode.swift in Sources */, diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index 26e449c86d..9cdae7418c 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -258,7 +258,7 @@ public final class AuthorizationSequenceController: NavigationController { let _ = (strongSelf.account.postbox.transaction { transaction -> Void in transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) }).start() - })]), on: .root) + })]), on: .root, blockInteraction: false) }) ], actionLayout: .vertical) contentNode.textAttributeAction = (NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), { value in @@ -271,7 +271,7 @@ public final class AuthorizationSequenceController: NavigationController { controller?.dismissAnimated() } strongSelf.view.endEditing(true) - strongSelf.currentWindow?.present(controller, on: .root) + strongSelf.currentWindow?.present(controller, on: .root, blockInteraction: false) } presentAlertAgainImpl = { presentAlertImpl() diff --git a/TelegramUI/ChannelAdminsController.swift b/TelegramUI/ChannelAdminsController.swift index 22098b12ea..e53c2fa1dd 100644 --- a/TelegramUI/ChannelAdminsController.swift +++ b/TelegramUI/ChannelAdminsController.swift @@ -598,7 +598,7 @@ public func channelAdminsController(account: Account, peerId: PeerId) -> ViewCon let peerView = account.viewTracker.peerView(peerId) |> deliverOnMainQueue - let (membersDisposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, peerId: peerId) { membersState in + let (membersDisposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { adminsPromise.set(.single(nil)) } else { diff --git a/TelegramUI/ChannelBlacklistController.swift b/TelegramUI/ChannelBlacklistController.swift index 7bdb6af047..afa60eaa34 100644 --- a/TelegramUI/ChannelBlacklistController.swift +++ b/TelegramUI/ChannelBlacklistController.swift @@ -386,7 +386,7 @@ public func channelBlacklistController(account: Account, peerId: PeerId) -> View let peerView = account.viewTracker.peerView(peerId) - let (listDisposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, peerId: peerId, updated: { listState in + let (listDisposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, updated: { listState in if case .loading = listState.loadingState, listState.list.isEmpty { blacklistPromise.set(.single(nil)) } else { diff --git a/TelegramUI/ChannelMemberCategoryListContext.swift b/TelegramUI/ChannelMemberCategoryListContext.swift index d0b1ea3854..7e6df18ec8 100644 --- a/TelegramUI/ChannelMemberCategoryListContext.swift +++ b/TelegramUI/ChannelMemberCategoryListContext.swift @@ -74,6 +74,7 @@ private func isParticipantMember(_ participant: ChannelParticipant) -> Bool { private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategoryListContext { private let postbox: Postbox private let network: Network + private let accountPeerId: PeerId private let peerId: PeerId private let category: ChannelMemberListCategory @@ -99,9 +100,10 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor private var headUpdateTimer: SwiftSignalKit.Timer? - init(postbox: Postbox, network: Network, peerId: PeerId, category: ChannelMemberListCategory) { + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMemberListCategory) { self.postbox = postbox self.network = network + self.accountPeerId = accountPeerId self.peerId = peerId self.category = category @@ -168,7 +170,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor case let .banned(query): requestCategory = .banned(query != nil ? .search(query!) : .all) } - return channelMembers(postbox: self.postbox, network: self.network, peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash) |> map { members in + return channelMembers(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash) |> map { members in switch requestCategory { case .admins: if let query = adminQuery { @@ -440,9 +442,9 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory } } - init(postbox: Postbox, network: Network, peerId: PeerId, categories: [ChannelMemberListCategory]) { + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, categories: [ChannelMemberListCategory]) { self.contexts = categories.map { category in - return ChannelMemberSingleCategoryListContext(postbox: postbox, network: network, peerId: peerId, category: category) + return ChannelMemberSingleCategoryListContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, category: category) } } @@ -548,14 +550,16 @@ private final class PeerChannelMemberContextWithSubscribers { final class PeerChannelMemberCategoriesContext { private let postbox: Postbox private let network: Network + private let accountPeerId: PeerId private let peerId: PeerId private var becameEmpty: (Bool) -> Void private var contexts: [PeerChannelMemberContextKey: PeerChannelMemberContextWithSubscribers] = [:] - init(postbox: Postbox, network: Network, peerId: PeerId, becameEmpty: @escaping (Bool) -> Void) { + init(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, becameEmpty: @escaping (Bool) -> Void) { self.postbox = postbox self.network = network + self.accountPeerId = accountPeerId self.peerId = peerId self.becameEmpty = becameEmpty } @@ -588,9 +592,9 @@ final class PeerChannelMemberCategoriesContext { default: mappedCategory = .recent } - context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, category: mappedCategory) + context = ChannelMemberSingleCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: mappedCategory) case let .restrictedAndBanned(query): - context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, peerId: self.peerId, categories: [.restricted(query), .banned(query)]) + context = ChannelMemberMultiCategoryListContext(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, categories: [.restricted(query), .banned(query)]) } let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, becameEmpty: { [weak self] in assert(Queue.mainQueue().isCurrent()) diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index 9e364a8b58..ae7d2eae32 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -347,7 +347,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo return addMembers(contacts) |> `catch` { error -> Signal in return .single(Void()) } |> mapToSignal { _ in - return channelMembers(postbox: account.postbox, network: account.network, peerId: peerId) + return channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId) } |> deliverOnMainQueue |> afterNext { _ in contactsController?.dismiss() } @@ -412,7 +412,7 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo let peerView = account.viewTracker.peerView(peerId) - let (disposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, updated: { state in + let (disposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, updated: { state in peersPromise.set(.single(state.list)) }) actionsDisposable.add(disposable) diff --git a/TelegramUI/ChannelMembersSearchContainerNode.swift b/TelegramUI/ChannelMembersSearchContainerNode.swift index 742228c46e..9860d79cb1 100644 --- a/TelegramUI/ChannelMembersSearchContainerNode.swift +++ b/TelegramUI/ChannelMembersSearchContainerNode.swift @@ -177,7 +177,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod switch mode { case .searchMembers, .banAndPromoteActions: foundGroupMembers = Signal { subscriber in - let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) subscriber.putCompletion() @@ -188,11 +188,11 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod foundMembers = .single([]) case .inviteActions: foundGroupMembers = .single([]) - foundMembers = channelMembers(postbox: account.postbox, network: account.network, peerId: peerId, category: .recent(.search(query))) + foundMembers = channelMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, category: .recent(.search(query))) |> map { $0 ?? [] } case .searchAdmins: foundGroupMembers = Signal { subscriber in - let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) subscriber.putCompletion() @@ -203,7 +203,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod foundMembers = .single([]) case .searchBanned: foundGroupMembers = Signal { subscriber in - let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, listControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.restrictedAndBanned(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) subscriber.putCompletion() diff --git a/TelegramUI/ChannelMembersSearchControllerNode.swift b/TelegramUI/ChannelMembersSearchControllerNode.swift index 692a1d5607..9a39e7a9a5 100644 --- a/TelegramUI/ChannelMembersSearchControllerNode.swift +++ b/TelegramUI/ChannelMembersSearchControllerNode.swift @@ -152,7 +152,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { }) let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil) - let (disposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, updated: { [weak self] state in + let (disposable, loadMoreControl) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, updated: { [weak self] state in guard let strongSelf = self else { return } diff --git a/TelegramUI/ChatAvatarNavigationNode.swift b/TelegramUI/ChatAvatarNavigationNode.swift index a718be145b..479f462191 100644 --- a/TelegramUI/ChatAvatarNavigationNode.swift +++ b/TelegramUI/ChatAvatarNavigationNode.swift @@ -6,9 +6,12 @@ private let normalFont = UIFont(name: ".SFCompactRounded-Semibold", size: 16.0)! private let smallFont = UIFont(name: ".SFCompactRounded-Semibold", size: 12.0)! final class ChatAvatarNavigationNodeView: UIView, PreviewingHostView { - @available(iOSApplicationExtension 9.0, *) - var previewingDelegate: UIViewControllerPreviewingDelegate? { - return self.chatController + var previewingDelegate: PreviewingHostViewDelegate? { + return PreviewingHostViewDelegate(controllerForLocation: { [weak self] sourceView, point in + return self?.chatController?.avatarPreviewingController(from: sourceView) + }, commitController: { [weak self] controller in + self?.chatController?.previewingCommit(controller) + }) } weak var chatController: ChatController? @@ -45,6 +48,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode { override func didLoad() { super.didLoad() + self.view.isOpaque = false (self.view as? ChatAvatarNavigationNodeView)?.targetNode = self (self.view as? ChatAvatarNavigationNodeView)?.chatController = self.chatController } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 700bc17488..fbb7bac4f7 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -68,27 +68,7 @@ private func isTopmostChatController(_ controller: ChatController) -> Bool { let ChatControllerCount = Atomic(value: 0) -@available(iOSApplicationExtension 9.0, *) -private final class ChatControllerPreviewingDelegate: NSObject, UIViewControllerPreviewingDelegate { - private weak var target: (NSObject & UIViewControllerPreviewingDelegate)? - - init(target: NSObject & UIViewControllerPreviewingDelegate) { - self.target = target - - super.init() - } - - public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { - return self.target?.previewingContext(previewingContext, viewControllerForLocation: location) - } - - public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { - self.target?.previewingContext(previewingContext, commit: viewControllerToCommit) - } -} - -public final class ChatController: TelegramController, KeyShortcutResponder, UIDropInteractionDelegate, UIViewControllerPreviewingDelegate { - private var previewingDelegate: AnyObject? +public final class ChatController: TelegramController, KeyShortcutResponder, UIDropInteractionDelegate { private var validLayout: ContainerViewLayout? public var peekActions: ChatControllerPeekActions = .standard @@ -322,7 +302,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID return openChatMessage(account: account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in - self?.present(c, in: .window(.root), with: a) + self?.present(c, in: .window(.root), with: a, blockInteraction: true) }, transitionNode: { messageId, media in var selectedNode: (ASDisplayNode, () -> UIView?)? if let strongSelf = self { @@ -1000,6 +980,49 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID default: break } + }, requestRedeliveryOfFailedMessages: { [weak self] id in + guard let strongSelf = self else { + return + } + let _ = (strongSelf.account.postbox.transaction { transaction -> [Message] in + return transaction.getMessageFailedGroup(id) ?? [] + } |> deliverOnMainQueue).start(next: { messages in + guard let strongSelf = self, !messages.isEmpty else { + return + } + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_MessageDialogRetry, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + let _ = resendMessages(account: strongSelf.account, messageIds: [id]).start() + })) + if messages.count != 1 { + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_MessageDialogRetryAll(messages.count).0, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + let _ = resendMessages(account: strongSelf.account, messageIds: messages.map({ $0.id })).start() + })) + } + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_MessageDialogDelete, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: messages.map({ $0.id }), type: .forLocalPeer).start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.chatDisplayNode.dismissInput() + strongSelf.present(actionSheet, in: .window(.root)) + }) }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -1069,7 +1092,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID peerView.set(account.viewTracker.peerView(peerId)) var onlineMemberCount: Signal = .single(nil) if peerId.namespace == Namespaces.Peer.CloudChannel { - onlineMemberCount = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: account.postbox, network: account.network, peerId: peerId) + onlineMemberCount = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recentOnline(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId) |> map(Optional.init) } self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount) @@ -1101,8 +1124,8 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID strongSelf.peerView = peerView if wasGroupChannel != isGroupChannel { if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = strongSelf.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.account.postbox, network: strongSelf.account.network, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = strongSelf.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.account.postbox, network: strongSelf.account.network, peerId: peerView.peerId, updated: { _ in }) + let (recentDisposable, _) = strongSelf.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: strongSelf.account.postbox, network: strongSelf.account.network, accountPeerId: account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = strongSelf.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: strongSelf.account.postbox, network: strongSelf.account.network, accountPeerId: account.peerId, peerId: peerView.peerId, updated: { _ in }) let disposable = DisposableSet() disposable.add(recentDisposable) disposable.add(adminsDisposable) @@ -1874,7 +1897,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in if let strongSelf = self, strongSelf.isNodeLoaded, case let .peer(peerId) = strongSelf.chatLocation { - let signal = earliestUnseenPersonalMentionMessage(postbox: strongSelf.account.postbox, network: strongSelf.account.network, peerId: peerId) + let signal = earliestUnseenPersonalMentionMessage(postbox: strongSelf.account.postbox, network: strongSelf.account.network, accountPeerId: strongSelf.account.peerId, peerId: peerId) strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { @@ -2863,19 +2886,10 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID if !self.didSetup3dTouch { self.didSetup3dTouch = true if #available(iOSApplicationExtension 9.0, *) { - let previewingDelegate: ChatControllerPreviewingDelegate - if let current = self.previewingDelegate as? ChatControllerPreviewingDelegate { - previewingDelegate = current - } else { - previewingDelegate = ChatControllerPreviewingDelegate(target: self) - self.previewingDelegate = previewingDelegate - } - - //self.registerForPreviewing(with: previewingDelegate, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + //self.registerForPreviewing(with: self, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) if case .peer = self.chatLocation, let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view { - //self.registerForPreviewing(with: previewingDelegate, sourceView: buttonView, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + //self.registerForPreviewing(with: self, sourceView: buttonView, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) } - //self.registerForPreviewing(with: previewingDelegate, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) } if #available(iOSApplicationExtension 11.0, *) { @@ -4676,76 +4690,79 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID } } + func avatarPreviewingController(from sourceView: UIView) -> (UIViewController, CGRect)? { + guard let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view else { + return nil + } + if let peer = self.presentationInterfaceState.renderedPeer?.peer, peer.smallProfileImage != nil { + let galleryController = AvatarGalleryController(account: self.account, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + }, synchronousLoad: true) + galleryController.setHintWillBePresentedInPreviewingContext(true) + galleryController.containerLayoutUpdated(ContainerViewLayout(size: CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height), metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) + return (galleryController, buttonView.convert(buttonView.bounds, to: sourceView)) + } + return nil + } - - @available(iOSApplicationExtension 9.0, *) - public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { - if previewingContext.sourceView === (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view { - if let peer = self.presentationInterfaceState.renderedPeer?.peer, peer.smallProfileImage != nil { - let galleryController = AvatarGalleryController(account: self.account, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in - }, synchronousLoad: true) - galleryController.setHintWillBePresentedInPreviewingContext(true) - galleryController.containerLayoutUpdated(ContainerViewLayout(size: CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height), metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) - return galleryController - } - } else { - let historyPoint = previewingContext.sourceView.convert(location, to: self.chatDisplayNode.historyNode.view) - var result: (Message, ChatMessagePeekPreviewContent)? - self.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if itemNode.frame.contains(historyPoint) { - if let value = itemNode.peekPreviewContent(at: self.chatDisplayNode.historyNode.view.convert(historyPoint, to: itemNode.view)) { - result = value - } + func previewingController(from sourceView: UIView, for location: CGPoint) -> (UIViewController, CGRect)? { + let historyPoint = sourceView.convert(location, to: self.chatDisplayNode.historyNode.view) + var result: (Message, ChatMessagePeekPreviewContent)? + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if itemNode.frame.contains(historyPoint) { + if let value = itemNode.peekPreviewContent(at: self.chatDisplayNode.historyNode.view.convert(historyPoint, to: itemNode.view)) { + result = value } } } - if let (message, content) = result { - switch content { - case let .media(media): - var selectedTransitionNode: (ASDisplayNode, () -> UIView?)? - self.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: message.id, media: media) { - selectedTransitionNode = result - } + } + if let (message, content) = result { + switch content { + case let .media(media): + var selectedTransitionNode: (ASDisplayNode, () -> UIView?)? + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + if let result = itemNode.transitionNode(id: message.id, media: media) { + selectedTransitionNode = result } } - - if let selectedTransitionNode = selectedTransitionNode { - if let previewData = chatMessagePreviewControllerData(account: self.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController as? NavigationController) { - switch previewData { - case let .gallery(gallery): - gallery.setHintWillBePresentedInPreviewingContext(true) - let rect = selectedTransitionNode.0.view.convert(selectedTransitionNode.0.bounds, to: previewingContext.sourceView) - previewingContext.sourceRect = rect.insetBy(dx: -2.0, dy: -2.0) - gallery.containerLayoutUpdated(ContainerViewLayout(size: CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height), metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) - return gallery - case let .instantPage(gallery, centralIndex, galleryMedia): - break - } + } + + if let selectedTransitionNode = selectedTransitionNode { + if let previewData = chatMessagePreviewControllerData(account: self.account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController as? NavigationController) { + switch previewData { + case let .gallery(gallery): + gallery.setHintWillBePresentedInPreviewingContext(true) + let rect = selectedTransitionNode.0.view.convert(selectedTransitionNode.0.bounds, to: sourceView) + let sourceRect = rect.insetBy(dx: -2.0, dy: -2.0) + gallery.containerLayoutUpdated(ContainerViewLayout(size: CGSize(width: self.view.bounds.size.width, height: self.view.bounds.size.height), metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) + return (gallery, sourceRect) + case let .instantPage(gallery, centralIndex, galleryMedia): + break } } - case let .url(node, rect, string): - let targetRect = node.view.convert(rect, to: previewingContext.sourceView) - previewingContext.sourceRect = CGRect(origin: CGPoint(x: floor(targetRect.midX), y: floor(targetRect.midY)), size: CGSize(width: 1.0, height: 1.0)) - if let parsedUrl = URL(string: string) { - if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" { + } + case let .url(node, rect, string): + let targetRect = node.view.convert(rect, to: sourceView) + let sourceRect = CGRect(origin: CGPoint(x: floor(targetRect.midX), y: floor(targetRect.midY)), size: CGSize(width: 1.0, height: 1.0)) + if let parsedUrl = URL(string: string) { + if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" { + if #available(iOSApplicationExtension 9.0, *) { let controller = SFSafariViewController(url: parsedUrl) if #available(iOSApplicationExtension 10.0, *) { controller.preferredBarTintColor = self.presentationData.theme.rootController.navigationBar.backgroundColor controller.preferredControlTintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor } - return controller + return (controller, sourceRect) } } - } + } } } return nil } - public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { + func previewingCommit(_ viewControllerToCommit: UIViewController) { if let gallery = viewControllerToCommit as? AvatarGalleryController { self.chatDisplayNode.dismissInput() gallery.setHintWillBePresentedInPreviewingContext(false) diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 24d65859d4..87ebd2ab7e 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -74,6 +74,7 @@ public final class ChatControllerInteraction { let setupReply: (MessageId) -> Void let canSetupReply: (Message) -> Bool let navigateToFirstDateMessage: (Int32) -> Void + let requestRedeliveryOfFailedMessages: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -84,7 +85,7 @@ public final class ChatControllerInteraction { var contextHighlightedState: ChatInterfaceHighlightedState? var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32)->Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -116,6 +117,7 @@ public final class ChatControllerInteraction { self.setupReply = setupReply self.canSetupReply = canSetupReply self.navigateToFirstDateMessage = navigateToFirstDateMessage + self.requestRedeliveryOfFailedMessages = requestRedeliveryOfFailedMessages self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index c5ee81bf47..326cab01b5 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -8,9 +8,13 @@ import TelegramCore private final class ChatControllerNodeView: UITracingLayerView, WindowInputAccessoryHeightProvider, PreviewingHostView { var inputAccessoryHeight: (() -> CGFloat)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? - @available(iOSApplicationExtension 9.0, *) - var previewingDelegate: UIViewControllerPreviewingDelegate? { - return self.controller + + var previewingDelegate: PreviewingHostViewDelegate? { + return PreviewingHostViewDelegate(controllerForLocation: { [weak self] sourceView, point in + return self?.controller?.previewingController(from: sourceView, for: point) + }, commitController: { [weak self] controller in + self?.controller?.previewingCommit(controller) + }) } weak var controller: ChatController? diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 4bb6228610..a43aa025cb 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -5,6 +5,8 @@ import Display import AsyncDisplayKit import TelegramCore +private let historyMessageCount: Int = 200 + public enum ChatHistoryListMode: Equatable { case bubbles case list(search: Bool, reversed: Bool) @@ -659,9 +661,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last { if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: 140)) + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount)) } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: 140)) + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount)) } } } @@ -726,7 +728,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } |> distinctUntilChanged(isEqual: { $0 == $1 }) |> mapToSignal { messageId -> Signal in if let messageId = messageId { - return getMessagesLoadIfNecessary([messageId], postbox: account.postbox, network: account.network) |> map { _ -> Void in return Void() } + return getMessagesLoadIfNecessary([messageId], postbox: account.postbox, network: account.network, accountPeerId: account.peerId) |> map { _ -> Void in return Void() } } else { return .complete() } diff --git a/TelegramUI/ChatHistoryViewForLocation.swift b/TelegramUI/ChatHistoryViewForLocation.swift index 3c15d88322..fb9d201c77 100644 --- a/TelegramUI/ChatHistoryViewForLocation.swift +++ b/TelegramUI/ChatHistoryViewForLocation.swift @@ -147,7 +147,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 140, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in + return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 200, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) let genericType: ViewUpdateType diff --git a/TelegramUI/ChatInterfaceStateContextQueries.swift b/TelegramUI/ChatInterfaceStateContextQueries.swift index dbc5911239..3047b6dad5 100644 --- a/TelegramUI/ChatInterfaceStateContextQueries.swift +++ b/TelegramUI/ChatInterfaceStateContextQueries.swift @@ -308,7 +308,7 @@ func searchQuerySuggestionResultStateForChatInterfacePresentationState(_ chatPre } } - let participants = searchGroupMembers(postbox: account.postbox, network: account.network, peerId: peer.id, query: query) + let participants = searchGroupMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peer.id, query: query) |> map { peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let filteredPeers = peers var sortedPeers: [Peer] = [] diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 854d06dd63..3dcd657aba 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -373,19 +373,24 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie } } - self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated in + self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated, isAd in if let strongSelf = self { if let navigationController = strongSelf.navigationController as? NavigationController { - /*let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(postbox: strongSelf.account.postbox) - |> deliverOnMainQueue).start(next: { value in - if !value { - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: "The proxy you are using displays a sponsored channel in your chat list.", actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { - if let strongSelf = self { - let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(postbox: strongSelf.account.postbox).start() - } - })]), in: .window(.root)) - } - })*/ + if isAd { + let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(postbox: strongSelf.account.postbox) + |> deliverOnMainQueue).start(next: { value in + guard let strongSelf = self else { + return + } + if !value { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { + if let strongSelf = self { + let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(postbox: strongSelf.account.postbox).start() + } + })]), in: .window(.root)) + } + }) + } navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), animated: animated, completion: { [weak self] in self?.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index e7f4f803f5..4316fe1fcf 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -97,9 +97,9 @@ class ChatListItem: ListViewItem { func selected(listView: ListView) { switch self.content { - case let .peer(message, peer, _, _, _, _, _, _, _): + case let .peer(message, peer, _, _, _, _, _, isAd, _): if let message = message { - self.interaction.messageSelected(message) + self.interaction.messageSelected(message, isAd) } else if let peer = peer.peers[peer.peerId] { self.interaction.peerSelected(peer) } diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 20732cf167..c70d6e6998 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -52,7 +52,7 @@ final class ChatListHighlightedLocation { final class ChatListNodeInteraction { let activateSearch: () -> Void let peerSelected: (Peer) -> Void - let messageSelected: (Message) -> Void + let messageSelected: (Message, Bool) -> Void let groupSelected: (PeerGroupId) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setItemPinned: (PinnedItemId, Bool) -> Void @@ -63,7 +63,7 @@ final class ChatListNodeInteraction { var highlightedChatLocation: ChatListHighlightedLocation? - init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) { + init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer) -> Void, messageSelected: @escaping (Message, Bool) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void) { self.activateSearch = activateSearch self.peerSelected = peerSelected self.messageSelected = messageSelected @@ -291,7 +291,7 @@ final class ChatListNode: ListView { return _ready.get() } - var peerSelected: ((PeerId, Bool) -> Void)? + var peerSelected: ((PeerId, Bool, Bool) -> Void)? var groupSelected: ((PeerGroupId) -> Void)? var activateSearch: (() -> Void)? var deletePeerChat: ((PeerId) -> Void)? @@ -361,11 +361,11 @@ final class ChatListNode: ListView { } }, peerSelected: { [weak self] peer in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { - peerSelected(peer.id, true) + peerSelected(peer.id, true, false) } - }, messageSelected: { [weak self] message in + }, messageSelected: { [weak self] message, isAd in if let strongSelf = self, let peerSelected = strongSelf.peerSelected { - peerSelected(message.id.peerId, true) + peerSelected(message.id.peerId, true, isAd) } }, groupSelected: { [weak self] groupId in if let strongSelf = self, let groupSelected = strongSelf.groupSelected { @@ -1054,7 +1054,7 @@ final class ChatListNode: ListView { , scrollPosition: .center(.top), animated: true) strongSelf.currentLocation = location strongSelf.chatListLocation.set(location) - strongSelf.peerSelected?(index.messageIndex.id.peerId, false) + strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false) }) break case .previous(unread: false), .next(unread: false): @@ -1082,7 +1082,7 @@ final class ChatListNode: ListView { , scrollPosition: .center(.top), animated: true) self.currentLocation = location self.chatListLocation.set(location) - self.peerSelected?(target.1, false) + self.peerSelected?(target.1, false, false) } break } diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index a274f40fa2..7551e10d7a 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -530,7 +530,8 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { let accountPeer = account.postbox.loadedPeerWithId(account.peerId) |> take(1) - let foundLocalPeers = account.postbox.searchPeers(query: query.lowercased(), groupId: groupId) |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in + let foundLocalPeers = account.postbox.searchPeers(query: query.lowercased(), groupId: groupId) + |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in return combineLatest(local.map {account.postbox.peerView(id: $0.peerId)}) |> map { views in return (views, local) } @@ -643,7 +644,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { openPeer(peer, false) let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start() self?.listNode.clearHighlightAnimated(true) - }, messageSelected: { [weak self] message in + }, messageSelected: { [weak self] message, _ in if let peer = message.peers[message.id.peerId] { openMessage(peer, message.id) } @@ -721,7 +722,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { return .single((transition, previousEntries == nil)) } - self.updatedRecentPeersDisposable.set(managedUpdatedRecentPeers(postbox: account.postbox, network: account.network).start()) + self.updatedRecentPeersDisposable.set(managedUpdatedRecentPeers(accountPeerId: account.peerId, postbox: account.postbox, network: account.network).start()) self.recentDisposable.set((recentItemsTransition |> deliverOnMainQueue).start(next: { [weak self] (transition, firstTime) in if let strongSelf = self { diff --git a/TelegramUI/ChatListSearchRecentPeersNode.swift b/TelegramUI/ChatListSearchRecentPeersNode.swift index bcd6e25bcf..5208186ece 100644 --- a/TelegramUI/ChatListSearchRecentPeersNode.swift +++ b/TelegramUI/ChatListSearchRecentPeersNode.swift @@ -202,7 +202,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { } })) if case .actionSheet = mode { - peersDisposable.add(managedUpdatedRecentPeers(postbox: account.postbox, network: account.network).start()) + peersDisposable.add(managedUpdatedRecentPeers(accountPeerId: account.peerId, postbox: account.postbox, network: account.network).start()) } self.disposable.set(peersDisposable) } diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 6b01901fd1..769a63bc4d 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -461,7 +461,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { var skipStandardStatus = false if let count = webpageGalleryMediaCount { - additionalImageBadgeContent = .text(backgroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusTextColor, shape: .corners(2.0), text: NSAttributedString(string: "1 \(presentationData.strings.Common_of) \(count)")) + additionalImageBadgeContent = .text(inset: 0.0, backgroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusFillColor, foregroundColor: presentationData.theme.theme.chat.bubble.mediaDateAndStatusTextColor, shape: .corners(2.0), text: NSAttributedString(string: "1 \(presentationData.strings.Common_of) \(count)")) skipStandardStatus = imageMode } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 6ea8edab4e..5c2359a806 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -118,6 +118,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { private var transitionClippingNode: ASDisplayNode? private var selectionNode: ChatMessageSelectionNode? + private var deliveryFailedNode: ChatMessageDeliveryFailedNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -1048,12 +1049,17 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { contentVerticalOffset = floorToScreenPixels((minimalContentSize.height - calculatedBubbleHeight) / 2.0) } + var deliveryFailedInset: CGFloat = 0.0 + if item.content.firstMessage.flags.contains(.Failed) { + deliveryFailedInset += 24.0 + } + let backgroundFrame: CGRect let contentOrigin: CGPoint let contentUpperRightCorner: CGPoint switch alignment { case .none: - backgroundFrame = CGRect(origin: CGPoint(x: incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset) : (params.width - params.rightInset - layoutBubbleSize.width - layoutConstants.bubble.edgeInset), y: 0.0), size: layoutBubbleSize) + backgroundFrame = CGRect(origin: CGPoint(x: incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset) : (params.width - params.rightInset - layoutBubbleSize.width - layoutConstants.bubble.edgeInset - deliveryFailedInset), y: 0.0), size: layoutBubbleSize) contentOrigin = CGPoint(x: backgroundFrame.origin.x + (incoming ? layoutConstants.bubble.contentInsets.left : layoutConstants.bubble.contentInsets.right), y: backgroundFrame.origin.y + layoutConstants.bubble.contentInsets.top + headerSize.height + contentVerticalOffset) contentUpperRightCorner = CGPoint(x: backgroundFrame.maxX - (incoming ? layoutConstants.bubble.contentInsets.right : layoutConstants.bubble.contentInsets.left), y: backgroundFrame.origin.y + layoutConstants.bubble.contentInsets.top + headerSize.height) case .center: @@ -1145,6 +1151,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { strongSelf.backgroundType = backgroundType + if item.content.firstMessage.flags.contains(.Failed) { + let deliveryFailedNode: ChatMessageDeliveryFailedNode + var isAppearing = false + if let current = strongSelf.deliveryFailedNode { + deliveryFailedNode = current + } else { + isAppearing = true + deliveryFailedNode = ChatMessageDeliveryFailedNode(tapped: { + if let item = self?.item { + item.controllerInteraction.requestRedeliveryOfFailedMessages(item.content.firstMessage.id) + } + }) + strongSelf.deliveryFailedNode = deliveryFailedNode + strongSelf.addSubnode(deliveryFailedNode) + } + let deliveryFailedSize = deliveryFailedNode.updateLayout(theme: item.presentationData.theme.theme) + let deliveryFailedFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + deliveryFailedInset - deliveryFailedSize.width, y: backgroundFrame.maxY - deliveryFailedSize.height), size: deliveryFailedSize) + if isAppearing { + deliveryFailedNode.frame = deliveryFailedFrame + transition.animatePositionAdditive(node: deliveryFailedNode, offset: CGPoint(x: deliveryFailedInset, y: 0.0)) + } else { + transition.updateFrame(node: deliveryFailedNode, frame: deliveryFailedFrame) + } + } else if let deliveryFailedNode = strongSelf.deliveryFailedNode { + strongSelf.deliveryFailedNode = nil + transition.updateAlpha(node: deliveryFailedNode, alpha: 0.0) + transition.updateFrame(node: deliveryFailedNode, frame: deliveryFailedNode.frame.offsetBy(dx: 24.0, dy: 0.0), completion: { [weak deliveryFailedNode] _ in + deliveryFailedNode?.removeFromSupernode() + }) + } + if let nameNode = nameNodeSizeApply.1() { strongSelf.nameNode = nameNode if nameNode.supernode == nil { diff --git a/TelegramUI/ChatMessageDeliveryFailedNode.swift b/TelegramUI/ChatMessageDeliveryFailedNode.swift new file mode 100644 index 0000000000..4875f91e6d --- /dev/null +++ b/TelegramUI/ChatMessageDeliveryFailedNode.swift @@ -0,0 +1,38 @@ +import Foundation +import AsyncDisplayKit +import Display + +final class ChatMessageDeliveryFailedNode: ASImageNode { + private let tapped: () -> Void + private var theme: PresentationTheme? + + init(tapped: @escaping () -> Void) { + self.tapped = tapped + + super.init() + + self.displaysAsynchronously = false + self.displayWithoutProcessing = true + self.isUserInteractionEnabled = true + } + + override func didLoad() { + super.didLoad() + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.tapped() + } + } + + func updateLayout(theme: PresentationTheme) -> CGSize { + if self.theme !== theme { + self.theme = theme + self.image = PresentationResourcesChat.chatBubbleDeliveryFailedIcon(theme) + } + + return CGSize(width: 22.0, height: 22.0) + } +} diff --git a/TelegramUI/ChatMessageInteractiveMediaBadge.swift b/TelegramUI/ChatMessageInteractiveMediaBadge.swift index 6d9d1d805d..8c91b3fabf 100644 --- a/TelegramUI/ChatMessageInteractiveMediaBadge.swift +++ b/TelegramUI/ChatMessageInteractiveMediaBadge.swift @@ -10,16 +10,18 @@ enum ChatMessageInteractiveMediaBadgeShape: Equatable { enum ChatMessageInteractiveMediaDownloadState: Equatable { case remote case fetching(progress: Float) + case compactRemote + case compactFetching(progress: Float) } enum ChatMessageInteractiveMediaBadgeContent: Equatable { - case text(backgroundColor: UIColor, foregroundColor: UIColor, shape: ChatMessageInteractiveMediaBadgeShape, text: NSAttributedString) + case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, shape: ChatMessageInteractiveMediaBadgeShape, text: NSAttributedString) case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String) static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool { switch lhs { - case let .text(lhsBackgroundColor, lhsForegroundColor, lhsShape, lhsText): - if case let .text(rhsBackgroundColor, rhsForegroundColor, rhsShape, rhsText) = rhs, lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsShape == rhsShape, lhsText.isEqual(to: rhsText) { + case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsShape, lhsText): + if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsShape, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsShape == rhsShape, lhsText.isEqual(to: rhsText) { return true } else { return false @@ -96,10 +98,10 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } else { mediaDownloadStatusNode = RadialStatusNode(backgroundNodeColor: .clear) self.mediaDownloadStatusNode = mediaDownloadStatusNode - mediaDownloadStatusNode.frame = CGRect(origin: CGPoint(x: 7.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)) self.addSubnode(mediaDownloadStatusNode) } let state: RadialStatusNodeState + var isCompact = false switch mediaDownloadState { case .remote: if let image = PresentationResourcesChat.chatBubbleFileCloudFetchMediaIcon(theme) { @@ -109,7 +111,20 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } case let .fetching(progress): state = .cloudProgress(color: .white, strokeBackgroundColor: UIColor(white: 1.0, alpha: 0.3), lineWidth: 2.0, value: CGFloat(progress)) + case .compactRemote: + state = .download(.white) + isCompact = true + case .compactFetching: + state = .progress(color: .white, lineWidth: nil, value: 0.0, cancelEnabled: true) + isCompact = true } + let mediaStatusFrame: CGRect + if isCompact { + mediaStatusFrame = CGRect(origin: CGPoint(x: 1.0, y: -1.0), size: CGSize(width: 20.0, height: 20.0)) + } else { + mediaStatusFrame = CGRect(origin: CGPoint(x: 7.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)) + } + mediaDownloadStatusNode.frame = mediaStatusFrame mediaDownloadStatusNode.transitionToState(state, animated: true, completion: {}) } else if let mediaDownloadStatusNode = self.mediaDownloadStatusNode { self.mediaDownloadStatusNode = nil @@ -125,7 +140,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { @objc override public class func display(withParameters: Any?, isCancelled: () -> Bool) -> UIImage? { if let content = (withParameters as? ChatMessageInteractiveMediaBadgeParams)?.content { switch content { - case let .text(backgroundColor, foregroundColor, shape, text): + case let .text(inset, backgroundColor, foregroundColor, shape, text): let convertedText = NSMutableAttributedString(string: text.string, attributes: [.font: font, .foregroundColor: foregroundColor]) text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: []) { attributes, range, _ in if let _ = attributes[ChatTextInputAttributes.bold] { @@ -133,7 +148,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } } let textRect = convertedText.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - let imageSize = CGSize(width: ceil(textRect.size.width) + 10.0, height: 18.0) + let imageSize = CGSize(width: inset + ceil(textRect.size.width) + 10.0, height: 18.0) return generateImage(imageSize, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) context.setBlendMode(.copy) @@ -155,7 +170,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { } context.setBlendMode(.normal) UIGraphicsPushContext(context) - convertedText.draw(at: CGPoint(x: floor((size.width - textRect.size.width) / 2.0) + textRect.origin.x, y: 2.0 + textRect.origin.y)) + convertedText.draw(at: CGPoint(x: inset + floor((size.width - inset - textRect.size.width) / 2.0) + textRect.origin.x, y: 2.0 + textRect.origin.y)) UIGraphicsPopContext() }) case let .mediaDownload(backgroundColor, foregroundColor, duration, size): diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 81d96256a1..d913547880 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -571,7 +571,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } string.append(NSAttributedString(string: title)) } - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: string) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: string) } if let fetchStatus = self.fetchStatus { switch fetchStatus { @@ -590,8 +590,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) } - if case .constrained = sizeCalculation { - if let file = media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { + if let file = media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { + if case .constrained = sizeCalculation { if let size = file.size { if let duration = file.duration, !message.flags.contains(.Unsent) { if isMediaStreamable(message: message, media: file) { @@ -601,13 +601,21 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { mediaDownloadState = .fetching(progress: progress) state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } else { - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))")) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))")) } } else { - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))")) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))")) } } else if let _ = file.duration { - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: strings.Conversation_Processing)) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: strings.Conversation_Processing)) + } + } else { + if let _ = file.size { + if !message.flags.contains(.Unsent) { + let progressString = String(format: "%d%%", Int(progress * 100.0)) + badgeContent = .text(inset: 16.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: progressString)) + mediaDownloadState = .compactFetching(progress: progress) + } } } } @@ -637,13 +645,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if case .constrained = sizeCalculation { if let file = media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { let durationString = String(format: "%d:%02d", duration / 60, duration % 60) - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) } } case .Remote: state = .download(bubbleTheme.mediaOverlayControlForegroundColor) - if case .constrained = sizeCalculation { - if let file = self.media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { + if let file = self.media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { + if case .constrained = sizeCalculation { if isMediaStreamable(message: message, media: file) { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) @@ -653,7 +661,16 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { mediaDownloadState = .remote } else { let durationString = String(format: "%d:%02d", duration / 60, duration % 60) - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) + } + } else { + let durationString = String(format: "%d:%02d", duration / 60, duration % 60) + if isMediaStreamable(message: message, media: file) { + state = .play(bubbleTheme.mediaOverlayControlForegroundColor) + badgeContent = .text(inset: 16.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) + mediaDownloadState = .compactRemote + } else { + badgeContent = .text(inset: 16.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString)) } } } @@ -669,7 +686,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { remainingTime = Int32(timeout) } - badgeContent = .text(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: strings.MessageTimer_ShortSeconds(Int32(remainingTime)))) + badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: strings.MessageTimer_ShortSeconds(Int32(remainingTime)))) } if let statusNode = self.statusNode { diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 957e9e4468..393a61d596 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -115,7 +115,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.panelButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside) - let (adminsDisposable, _) = self.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: self.account.postbox, network: self.account.network, peerId: self.peer.id, searchQuery: nil, updated: { [weak self] state in + let (adminsDisposable, _) = self.account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: self.account.postbox, network: self.account.network, accountPeerId: account.peerId, peerId: self.peer.id, searchQuery: nil, updated: { [weak self] state in self?.adminsState = state }) self.adminsDisposable = adminsDisposable @@ -348,6 +348,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { return false }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings) diff --git a/TelegramUI/ChatRecentActionsFilterController.swift b/TelegramUI/ChatRecentActionsFilterController.swift index 5734e29376..c3780ffbe5 100644 --- a/TelegramUI/ChatRecentActionsFilterController.swift +++ b/TelegramUI/ChatRecentActionsFilterController.swift @@ -430,7 +430,7 @@ public func channelRecentActionsFilterController(account: Account, peer: Peer, e adminsPromise.set(.single(nil)) - let (membersDisposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, peerId: peer.id) { membersState in + let (membersDisposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.admins(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peer.id) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { adminsPromise.set(.single(nil)) } else { diff --git a/TelegramUI/DataPrivacySettingsController.swift b/TelegramUI/DataPrivacySettingsController.swift index c3d00cd7b8..ea5c2567f6 100644 --- a/TelegramUI/DataPrivacySettingsController.swift +++ b/TelegramUI/DataPrivacySettingsController.swift @@ -453,7 +453,7 @@ public func dataPrivacyController(account: Account) -> ViewController { return state } if clear { - clearPaymentInfoDisposable.set((clearCloudDraftsInteractively(postbox: account.postbox, network: account.network) + clearPaymentInfoDisposable.set((clearCloudDraftsInteractively(postbox: account.postbox, network: account.network, accountPeerId: account.peerId) |> deliverOnMainQueue).start(completed: { updateState { state in var state = state @@ -475,7 +475,7 @@ public func dataPrivacyController(account: Account) -> ViewController { let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings])) - actionsDisposable.add(managedUpdatedRecentPeers(postbox: account.postbox, network: account.network).start()) + actionsDisposable.add(managedUpdatedRecentPeers(accountPeerId: account.peerId, postbox: account.postbox, network: account.network).start()) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, account.postbox.combinedView(keys: [.noticeEntry(ApplicationSpecificNotice.secretChatLinkPreviewsKey()), preferencesKey]), recentPeers(account: account)) |> map { presentationData, state, combined, recentPeers -> (ItemListControllerState, (ItemListNodeState, PrivacyAndSecurityEntry.ItemGenerationArguments)) in diff --git a/TelegramUI/DefaultDarkAccentPresentationTheme.swift b/TelegramUI/DefaultDarkAccentPresentationTheme.swift index 531e935c10..ff0e5d1097 100644 --- a/TelegramUI/DefaultDarkAccentPresentationTheme.swift +++ b/TelegramUI/DefaultDarkAccentPresentationTheme.swift @@ -180,7 +180,9 @@ private let bubble = PresentationThemeChatBubble( selectionControlBorderColor: .white, selectionControlFillColor: accentColor, selectionControlForegroundColor: .white, - mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6) + mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), + deliveryFailedFillColor: destructiveColor, + deliveryFailedForegroundColor: .white ) private let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index fc3bfd3d9c..c605c8f9cb 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -180,7 +180,9 @@ private let bubble = PresentationThemeChatBubble( selectionControlBorderColor: .white, selectionControlFillColor: accentColor, selectionControlForegroundColor: .black, - mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6) + mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), + deliveryFailedFillColor: destructiveColor, + deliveryFailedForegroundColor: .white ) private let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 7fcca2b583..5ed44b19c8 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -210,7 +210,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), selectionControlFillColor: accentColor, selectionControlForegroundColor: .white, - mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6) + mediaHighlightOverlayColor: UIColor(white: 1.0, alpha: 0.6), + deliveryFailedFillColor: destructiveColor, + deliveryFailedForegroundColor: .white ) let bubbleDay = PresentationThemeChatBubble( @@ -260,7 +262,9 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr selectionControlBorderColor: UIColor(rgb: 0xC7C7CC), selectionControlFillColor: accentColor, selectionControlForegroundColor: .white, - mediaHighlightOverlayColor: UIColor(rgb: 0xffffff, alpha: 0.6) + mediaHighlightOverlayColor: UIColor(rgb: 0xffffff, alpha: 0.6), + deliveryFailedFillColor: destructiveColor, + deliveryFailedForegroundColor: .white ) let serviceMessage = PresentationThemeServiceMessage( diff --git a/TelegramUI/DocumentPreviewController.swift b/TelegramUI/DocumentPreviewController.swift index d5f1f6f9b7..537ff2c3bd 100644 --- a/TelegramUI/DocumentPreviewController.swift +++ b/TelegramUI/DocumentPreviewController.swift @@ -203,6 +203,11 @@ func presentDocumentPreviewController(rootController: UIViewController, theme: P if #available(iOSApplicationExtension 9.0, *) { let navigationBar = UINavigationBar.appearance(whenContainedInInstancesOf: [QLPreviewController.self]) navigationBar.barTintColor = theme.rootController.navigationBar.backgroundColor + navigationBar.setBackgroundImage(generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in + context.setFillColor(theme.rootController.navigationBar.backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + }), for: .default) + navigationBar.isTranslucent = true navigationBar.tintColor = theme.rootController.navigationBar.accentTextColor navigationBar.shadowImage = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index 6a4795eb13..6316a84e0a 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -1328,7 +1328,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl let members: Promise<[PeerId]> = Promise() if peerId.namespace == Namespaces.Peer.CloudChannel { var membersDisposable: Disposable? - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, updated: { listState in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, updated: { listState in members.set(.single(listState.list.map {$0.peer.id})) membersDisposable?.dispose() }) @@ -1650,7 +1650,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl let channelMembersPromise = Promise<[RenderedChannelParticipant]>() if peerId.namespace == Namespaces.Peer.CloudChannel { - let (disposable, control) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, updated: { state in + let (disposable, control) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, updated: { state in channelMembersPromise.set(.single(state.list)) }) loadMoreControl = control diff --git a/TelegramUI/HashtagSearchController.swift b/TelegramUI/HashtagSearchController.swift index 57b6cf7bcf..d894790f9b 100644 --- a/TelegramUI/HashtagSearchController.swift +++ b/TelegramUI/HashtagSearchController.swift @@ -44,7 +44,7 @@ final class HashtagSearchController: TelegramController { let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { peer in - }, messageSelected: { [weak self] message in + }, messageSelected: { [weak self] message, _ in if let strongSelf = self { if let peer = message.peers[message.id.peerId] { strongSelf.openMessageFromSearchDisposable.set((storedMessageFromSearchPeer(account: strongSelf.account, peer: peer) |> deliverOnMainQueue).start(completed: { diff --git a/TelegramUI/IsMediaStreamable.swift b/TelegramUI/IsMediaStreamable.swift index dcb79577ed..9a1a46e31a 100644 --- a/TelegramUI/IsMediaStreamable.swift +++ b/TelegramUI/IsMediaStreamable.swift @@ -15,7 +15,7 @@ func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Bool { guard let size = media.size else { return false } - if size < 500 * 1024 { + if size < 1 * 1024 * 1024 { return false } return false diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index f2f5f2c1f1..0e0223c682 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -148,12 +148,15 @@ final class ListMessageFileItemNode: ListMessageNode { private let extensionIconNode: ASImageNode private let extensionIconText: TextNode private let iconImageNode: TransformImageNode + private let statusButtonNode: HighlightTrackingButtonNode + private let statusNode: RadialStatusNode private var currentIconImage: FileIconImage? private var currentMedia: Media? private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) + private var fetchStatus: MediaResourceStatus? private var resourceStatus: FileMediaResourceMediaStatus? private let fetchDisposable = MetaDisposable() @@ -200,6 +203,10 @@ final class ListMessageFileItemNode: ListMessageNode { self.iconImageNode.displaysAsynchronously = false self.iconImageNode.contentAnimations = .subsequentUpdates + self.statusButtonNode = HighlightTrackingButtonNode() + self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) + self.statusNode.isUserInteractionEnabled = false + self.downloadStatusIconNode = ASImageNode() self.downloadStatusIconNode.isLayerBacked = true self.downloadStatusIconNode.displaysAsynchronously = false @@ -218,6 +225,21 @@ final class ListMessageFileItemNode: ListMessageNode { self.addSubnode(self.descriptionNode) self.addSubnode(self.extensionIconNode) self.addSubnode(self.extensionIconText) + self.addSubnode(self.statusNode) + self.addSubnode(self.statusButtonNode) + + self.statusButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.statusNode.layer.removeAnimation(forKey: "opacity") + strongSelf.statusNode.alpha = 0.4 + } else { + strongSelf.statusNode.alpha = 1.0 + strongSelf.statusNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.statusButtonNode.addTarget(self, action: #selector(self.statusPressed), forControlEvents: .touchUpInside) } deinit { @@ -249,7 +271,6 @@ final class ListMessageFileItemNode: ListMessageNode { self.transitionOffset = self.bounds.size.height * 1.6 self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp) - //self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height * 1.4, to: 0.0, duration: duration) } override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { @@ -273,7 +294,7 @@ final class ListMessageFileItemNode: ListMessageNode { updatedTheme = item.theme } - var leftInset: CGFloat = 65.0 + params.leftInset + var leftInset: CGFloat = 60.0 + params.leftInset let rightInset: CGFloat = 8.0 + params.rightInset var leftOffset: CGFloat = 0.0 @@ -410,9 +431,9 @@ final class ListMessageFileItemNode: ListMessageNode { } } - let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0 - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -525,10 +546,10 @@ final class ListMessageFileItemNode: ListMessageNode { let iconFrame: CGRect if isAudio { let iconSize = CGSize(width: 48.0, height: 48.0) - iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 20.0, y: 5.0), size: iconSize) + iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 5.0), size: iconSize) } else { let iconSize = CGSize(width: 42.0, height: 42.0) - iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 9.0, y: 5.0), size: iconSize) + iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 5.0), size: iconSize) } transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame) strongSelf.extensionIconNode.image = extensionIconImage @@ -571,20 +592,43 @@ final class ListMessageFileItemNode: ListMessageNode { transition.updateFrame(node: playbackOverlayNode, frame: iconFrame) } + let statusSize = CGSize(width: 28.0, height: 28.0) + transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - statusSize.width, y: floor((nodeLayout.contentSize.height - statusSize.height) / 2.0)), size: statusSize)) + + strongSelf.statusButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - 40.0, y: 0.0), size: CGSize(width: 40.0, height: nodeLayout.contentSize.height)) + if let updatedStatusSignal = updatedStatusSignal { strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] fileStatus in let status = fileStatus.mediaStatus displayLinkDispatcher.dispatch { if let strongSelf = strongSelf { + strongSelf.fetchStatus = fileStatus.fetchStatus strongSelf.resourceStatus = status var musicIsPlaying: Bool? - + var statusState: RadialStatusNodeState = .none if !isAudio { if let layoutParams = strongSelf.layoutParams { strongSelf.updateProgressFrame(size: nodeLayout.contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate) } } else { + switch fileStatus.fetchStatus { + case let .Fetching(isActive, progress): + var adjustedProgress = progress + if isActive { + adjustedProgress = max(adjustedProgress, 0.027) + } + statusState = .cloudProgress(color: item.theme.list.itemAccentColor, strokeBackgroundColor: item.theme.list.itemAccentColor.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress)) + case .Local: + break + case .Remote: + if let image = PresentationResourcesItemList.cloudFetchIcon(item.theme) { + statusState = .customIcon(image) + } + } + strongSelf.statusNode.transitionToState(statusState, completion: {}) + strongSelf.statusButtonNode.isUserInteractionEnabled = statusState != .none + switch status { case let .fetchStatus(fetchStatus): switch fetchStatus { @@ -804,4 +848,23 @@ final class ListMessageFileItemNode: ListMessageNode { item.controllerInteraction.openMessageContextMenu(item.message, self, self.bounds) } } + + @objc private func statusPressed() { + guard let item = self.item, let fetchStatus = self.fetchStatus else { + return + } + + switch fetchStatus { + case .Fetching: + if let cancel = self.fetchControls.with({ return $0?.cancel }) { + cancel() + } + case .Remote: + if let fetch = self.fetchControls.with({ return $0?.fetch }) { + fetch() + } + case .Local: + break + } + } } diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index e3e1196028..2eff5d1425 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -401,7 +401,7 @@ public func openExternalUrl(account: Account, context: OpenURLContext = .generic account.telegramApplicationContext.applicationBindings.dismissNativeController() navigationController.view.window?.endEditing(true) - account.telegramApplicationContext.applicationBindings.getWindowHost()?.present(controller, on: .root) + account.telegramApplicationContext.applicationBindings.getWindowHost()?.present(controller, on: .root, blockInteraction: false) } } return diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index 694fbb3b66..0359ecba6a 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -66,6 +66,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec }, canSetupReply: { _ in return false }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift b/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift index 11520a0a1b..9176d9829f 100644 --- a/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift +++ b/TelegramUI/PeerChannelMemberCategoriesContextsManager.swift @@ -26,12 +26,12 @@ enum PeerChannelMemberContextKey: Equatable, Hashable { private final class PeerChannelMemberCategoriesContextsManagerImpl { fileprivate var contexts: [PeerId: PeerChannelMemberCategoriesContext] = [:] - func getContext(postbox: Postbox, network: Network, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { + func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { if let current = self.contexts[peerId] { return current.getContext(key: key, requestUpdate: requestUpdate, updated: updated) } else { var becameEmptyImpl: ((Bool) -> Void)? - let context = PeerChannelMemberCategoriesContext(postbox: postbox, network: network, peerId: peerId, becameEmpty: { value in + let context = PeerChannelMemberCategoriesContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, becameEmpty: { value in becameEmptyImpl?(value) }) becameEmptyImpl = { [weak self, weak context] value in @@ -71,10 +71,10 @@ final class PeerChannelMemberCategoriesContextsManager { } } - private func getContext(postbox: Postbox, network: Network, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + private func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { assert(Queue.mainQueue().isCurrent()) if let (disposable, control) = self.impl.syncWith({ impl in - return impl.getContext(postbox: postbox, network: network, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) + return impl.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) }) { return (disposable, control) } else { @@ -92,21 +92,21 @@ final class PeerChannelMemberCategoriesContextsManager { } } - func recent(postbox: Postbox, network: Network, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + func recent(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { let key: PeerChannelMemberContextKey if let searchQuery = searchQuery { key = .recentSearch(searchQuery) } else { key = .recent } - return self.getContext(postbox: postbox, network: network, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) } - func recentOnline(postbox: Postbox, network: Network, peerId: PeerId) -> Signal { + func recentOnline(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal { return Signal { [weak self] subscriber in var previousIds: Set? let statusesDisposable = MetaDisposable() - let disposableAndControl = self?.recent(postbox: postbox, network: network, peerId: peerId, updated: { state in + let disposableAndControl = self?.recent(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { state in var idList: [PeerId] = [] for item in state.list { idList.append(item.peer.id) @@ -152,12 +152,12 @@ final class PeerChannelMemberCategoriesContextsManager { } - func admins(postbox: Postbox, network: Network, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) + func admins(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) } - func restrictedAndBanned(postbox: Postbox, network: Network, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(postbox: postbox, network: network, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated) + func restrictedAndBanned(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated) } func updateMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChannelBannedRights?) -> Signal { diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 3d2d7d541b..d5e5430761 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -230,6 +230,7 @@ public class PeerMediaCollectionController: TelegramController { }, canSetupReply: { _ in return false }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/PeerMessagesMediaPlaylist.swift b/TelegramUI/PeerMessagesMediaPlaylist.swift index f4f2d39172..66cbe6b0aa 100644 --- a/TelegramUI/PeerMessagesMediaPlaylist.swift +++ b/TelegramUI/PeerMessagesMediaPlaylist.swift @@ -140,6 +140,30 @@ private enum NavigatedMessageFromViewPosition { case exact } +private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] { + guard let index = view.entries.index(where: { $0.index.id == centralIndex.id }) else { + return [] + } + var result: [Message] = [] + if index != 0 { + for i in (0 ..< index).reversed() { + if case let .MessageEntry(message, _, _, _) = view.entries[i] { + result.append(message) + break + } + } + } + if index != view.entries.count - 1 { + for i in index + 1 ..< view.entries.count { + if case let .MessageEntry(message, _, _, _) = view.entries[i] { + result.append(message) + break + } + } + } + return result +} + private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { var index = 0 for entry in view.entries { @@ -148,7 +172,7 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M case .exact: switch entry { case let .MessageEntry(message, _, _, _): - return (message, [], true) + return (message, aroundMessagesFromView(view: view, centralIndex: entry.index), true) default: return nil } @@ -156,7 +180,7 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M if index + 1 < view.entries.count { switch view.entries[index + 1] { case let .MessageEntry(message, _, _, _): - return (message, [], true) + return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index + 1].index), true) default: return nil } @@ -167,7 +191,7 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M if index != 0 { switch view.entries[index - 1] { case let .MessageEntry(message, _, _, _): - return (message, [], true) + return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index - 1].index), true) default: return nil } @@ -183,14 +207,14 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M case .later, .exact: switch view.entries[view.entries.count - 1] { case let .MessageEntry(message, _, _, _): - return (message, [], false) + return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[view.entries.count - 1].index), false) default: return nil } case .earlier: switch view.entries[0] { case let .MessageEntry(message, _, _, _): - return (message, [], false) + return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[0].index), false) default: return nil } @@ -410,21 +434,56 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.updateState() switch anchor { case let .messageId(messageId): - self.navigationDisposable.set((self.postbox.messageAtId(messageId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] message in - if let strongSelf = self { - assert(strongSelf.loadingItem) - - strongSelf.loadingItem = false - if let message = message { - strongSelf.currentItem = (message, []) - } else { - strongSelf.currentItem = nil + if case let .messages(peerId, tagMask, _) = self.messagesLocation { + let historySignal = self.postbox.messageAtId(messageId) + |> take(1) + |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in + guard let message = message else { + return .single(nil) + } + return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), index: .message(MessageIndex(message)), anchorIndex: .message(MessageIndex(message)), count: 10, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, orderStatistics: []) + |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: MessageIndex(message), position: .exact) { + return .single((message, aroundMessages)) + } else { + return .single((message, [])) + } } - strongSelf.updateState() } - })) + |> take(1) + |> deliverOnMainQueue + self.navigationDisposable.set(historySignal.start(next: { [weak self] messageAndAroundMessages in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let (message, aroundMessages) = messageAndAroundMessages { + strongSelf.currentItem = (message, aroundMessages) + strongSelf.playedToEnd = false + } else { + strongSelf.playedToEnd = true + } + strongSelf.updateState() + } + })) + } else { + + self.navigationDisposable.set((self.postbox.messageAtId(messageId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] message in + if let strongSelf = self { + assert(strongSelf.loadingItem) + + strongSelf.loadingItem = false + if let message = message { + strongSelf.currentItem = (message, []) + } else { + strongSelf.currentItem = nil + } + strongSelf.updateState() + } + })) + } case let .index(index): switch self.messagesLocation { case let .messages(peerId, tagMask, _): diff --git a/TelegramUI/PeerSelectionController.swift b/TelegramUI/PeerSelectionController.swift index 5ef8445050..02912b4450 100644 --- a/TelegramUI/PeerSelectionController.swift +++ b/TelegramUI/PeerSelectionController.swift @@ -72,7 +72,6 @@ public final class PeerSelectionController: ViewController { self.displayNode = PeerSelectionControllerNode(account: self.account, filter: self.filter, dismiss: { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) }) - self.displayNode.backgroundColor = .white self.peerSelectionNode.navigationBar = self.navigationBar diff --git a/TelegramUI/PeerSelectionControllerNode.swift b/TelegramUI/PeerSelectionControllerNode.swift index 2eb4c5fc3b..69541a34a7 100644 --- a/TelegramUI/PeerSelectionControllerNode.swift +++ b/TelegramUI/PeerSelectionControllerNode.swift @@ -70,26 +70,28 @@ final class PeerSelectionControllerNode: ASDisplayNode { return UITracingLayerView() }) + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.chatListNode.activateSearch = { [weak self] in self?.requestActivateSearch?() } - self.chatListNode.peerSelected = { [weak self] peerId, _ in + self.chatListNode.peerSelected = { [weak self] peerId, _, _ in self?.requestOpenPeer?(peerId) } self.addSubnode(self.chatListNode) self.presentationDataDisposable = (account.telegramApplicationContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings - strongSelf.presentationData = presentationData - if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { - strongSelf.updateThemeAndStrings() - } + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + strongSelf.presentationData = presentationData + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + strongSelf.updateThemeAndStrings() } - }) + } + }) self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarSeparatorNode) @@ -106,6 +108,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } private func updateThemeAndStrings() { + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations) } diff --git a/TelegramUI/PresentationResourceKey.swift b/TelegramUI/PresentationResourceKey.swift index 4c002ec1ae..2680edcab0 100644 --- a/TelegramUI/PresentationResourceKey.swift +++ b/TelegramUI/PresentationResourceKey.swift @@ -49,6 +49,8 @@ enum PresentationResourceKey: Int32 { case itemListStickerItemUnreadDot case itemListVerifiedPeerIcon + case itemListCloudFetchIcon + case chatListLockTopLockedImage case chatListLockBottomLockedImage case chatListLockTopUnlockedImage @@ -118,6 +120,8 @@ enum PresentationResourceKey: Int32 { case chatBubbleReplyThumbnailPlayImage + case chatBubbleDeliveryFailedIcon + case chatInfoItemBackgroundImageWithWallpaper case chatInfoItemBackgroundImageWithoutWallpaper diff --git a/TelegramUI/PresentationResourcesChat.swift b/TelegramUI/PresentationResourcesChat.swift index afa4a335f8..105ba040e4 100644 --- a/TelegramUI/PresentationResourcesChat.swift +++ b/TelegramUI/PresentationResourcesChat.swift @@ -1047,4 +1047,19 @@ struct PresentationResourcesChat { generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetched"), color: theme.chat.bubble.outgoingAccentControlColor) }) } + + static func chatBubbleDeliveryFailedIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatBubbleDeliveryFailedIcon.rawValue, { theme in + return generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(theme.chat.bubble.deliveryFailedFillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.scaleBy(x: 0.3333, y: 0.3333) + + context.setFillColor(theme.chat.bubble.deliveryFailedForegroundColor.cgColor) + let _ = try? drawSvgPath(context, path: "M30.3209839,35.4970703 L29.5397339,23.8027344 C29.3932488,21.5240771 29.3200073,19.8883513 29.3200073,18.8955078 C29.3200073,17.5445896 29.6740077,16.4907264 30.382019,15.7338867 C31.0900304,14.977047 32.0218245,14.5986328 33.1774292,14.5986328 C34.5771758,14.5986328 35.5130388,15.0828402 35.9850464,16.0512695 C36.457054,17.0196989 36.6930542,18.4153555 36.6930542,20.2382812 C36.6930542,21.3125054 36.6360886,22.4029893 36.5221558,23.5097656 L35.4723511,35.5458984 C35.3584182,36.9781973 35.11428,38.0768191 34.7399292,38.8417969 C34.3655784,39.6067747 33.747095,39.9892578 32.8844604,39.9892578 C32.0055498,39.9892578 31.3952043,39.6189816 31.0534058,38.878418 C30.7116072,38.1378544 30.467469,37.0107498 30.3209839,35.4970703 Z M33.0309448,51.5615234 C32.0381013,51.5615234 31.1714108,51.2400748 30.4308472,50.597168 C29.6902836,49.9542611 29.3200073,49.0550188 29.3200073,47.8994141 C29.3200073,46.8902944 29.6740077,46.0317418 30.382019,45.3237305 C31.0900304,44.6157191 31.9567209,44.2617188 32.9821167,44.2617188 C34.0075125,44.2617188 34.8823409,44.6157191 35.6066284,45.3237305 C36.3309159,46.0317418 36.6930542,46.8902944 36.6930542,47.8994141 C36.6930542,49.0387427 36.3268469,49.933916 35.5944214,50.5849609 C34.8619958,51.2360059 34.0075122,51.5615234 33.0309448,51.5615234 Z ") + }) + }) + } } diff --git a/TelegramUI/PresentationResourcesItemList.swift b/TelegramUI/PresentationResourcesItemList.swift index 16f8ab5fad..f7f680f786 100644 --- a/TelegramUI/PresentationResourcesItemList.swift +++ b/TelegramUI/PresentationResourcesItemList.swift @@ -123,4 +123,10 @@ struct PresentationResourcesItemList { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.rootController.activeNavigationSearchBar.inputIconColor) }) } + + static func cloudFetchIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.itemListCloudFetchIcon.rawValue, { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.list.itemAccentColor) + }) + } } diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index d1b7df3a13..1f9d7edeed 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -485,7 +485,10 @@ public final class PresentationThemeChatBubble { public let mediaHighlightOverlayColor: UIColor - public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor) { + public let deliveryFailedFillColor: UIColor + public let deliveryFailedForegroundColor: UIColor + + public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: UIColor, shareButtonStrokeColor: UIColor, shareButtonForegroundColor: UIColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: UIColor, actionButtonsIncomingStrokeColor: UIColor, actionButtonsIncomingTextColor: UIColor, actionButtonsOutgoingFillColor: UIColor, actionButtonsOutgoingStrokeColor: UIColor, actionButtonsOutgoingTextColor: UIColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor, deliveryFailedFillColor: UIColor, deliveryFailedForegroundColor: UIColor) { self.incoming = incoming self.outgoing = outgoing self.freeform = freeform @@ -544,6 +547,9 @@ public final class PresentationThemeChatBubble { self.selectionControlForegroundColor = selectionControlForegroundColor self.mediaHighlightOverlayColor = mediaHighlightOverlayColor + + self.deliveryFailedFillColor = deliveryFailedFillColor + self.deliveryFailedForegroundColor = deliveryFailedForegroundColor } } diff --git a/TelegramUI/PrivacyAndSecurityController.swift b/TelegramUI/PrivacyAndSecurityController.swift index 5e7404d69d..df83d59cc0 100644 --- a/TelegramUI/PrivacyAndSecurityController.swift +++ b/TelegramUI/PrivacyAndSecurityController.swift @@ -527,7 +527,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign let preferencesKey = PostboxViewKey.preferences(keys: Set([ApplicationSpecificPreferencesKeys.contactSynchronizationSettings])) - actionsDisposable.add(managedUpdatedRecentPeers(postbox: account.postbox, network: account.network).start()) + actionsDisposable.add(managedUpdatedRecentPeers(accountPeerId: account.peerId, postbox: account.postbox, network: account.network).start()) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, privacySettingsPromise.get(), account.postbox.combinedView(keys: [.noticeEntry(ApplicationSpecificNotice.secretChatLinkPreviewsKey()), preferencesKey]), recentPeers(account: account)) |> map { presentationData, state, privacySettings, combined, recentPeers -> (ItemListControllerState, (ItemListNodeState, PrivacyAndSecurityEntry.ItemGenerationArguments)) in diff --git a/TelegramUI/ProxyServerActionSheetController.swift b/TelegramUI/ProxyServerActionSheetController.swift index 3e8436e026..c0a1a117e9 100644 --- a/TelegramUI/ProxyServerActionSheetController.swift +++ b/TelegramUI/ProxyServerActionSheetController.swift @@ -27,6 +27,9 @@ final class ProxyServerActionSheetController: ActionSheetController { self._ready.set(.single(true)) var items: [ActionSheetItem] = [] + if case .mtp = server.connection { + items.append(ActionSheetTextItem(title: strings.SocksProxySetup_AdNoticeHelp)) + } items.append(ProxyServerInfoItem(strings: strings, server: server)) items.append(ProxyServerActionItem(account: account, strings: strings, server: server, dismiss: { [weak self] success in guard let strongSelf = self, !strongSelf.isDismissed else { diff --git a/TelegramUI/RadialProgressContentNode.swift b/TelegramUI/RadialProgressContentNode.swift index a65349cbc1..f957a30c50 100644 --- a/TelegramUI/RadialProgressContentNode.swift +++ b/TelegramUI/RadialProgressContentNode.swift @@ -210,7 +210,7 @@ private final class RadialProgressContentCancelNode: ASDisplayNode { let factor = diameter / 50.0 context.setStrokeColor(parameters.color.cgColor) - context.setLineWidth(max(1.6, 2.0 * factor)) + context.setLineWidth(max(1.3, 2.0 * factor)) context.setLineCap(.round) let crossSize: CGFloat = 14.0 * factor diff --git a/TelegramUI/RadialStatusIconContentNode.swift b/TelegramUI/RadialStatusIconContentNode.swift index 83476d777e..a98ac45411 100644 --- a/TelegramUI/RadialStatusIconContentNode.swift +++ b/TelegramUI/RadialStatusIconContentNode.swift @@ -49,7 +49,11 @@ final class RadialStatusIconContentNode: RadialStatusContentNode { switch parameters.icon { case let .download(color): context.setStrokeColor(color.cgColor) - context.setLineWidth(2.0) + var lineWidth: CGFloat = 2.0 + if diameter < 24.0 { + lineWidth = 1.3 + } + context.setLineWidth(lineWidth) context.setLineCap(.round) context.setLineJoin(.round) diff --git a/TelegramUI/SearchPeerMembers.swift b/TelegramUI/SearchPeerMembers.swift index a05f03be61..a1bcba2277 100644 --- a/TelegramUI/SearchPeerMembers.swift +++ b/TelegramUI/SearchPeerMembers.swift @@ -11,7 +11,7 @@ func searchPeerMembers(account: Account, peerId: PeerId, query: String) -> Signa |> mapToSignal { cachedData -> Signal<[Peer], NoError> in if let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 { return Signal { subscriber in - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in if case .ready = state.loadingState { let normalizedQuery = query.lowercased() subscriber.putNext(state.list.compactMap { participant -> Peer? in @@ -38,7 +38,7 @@ func searchPeerMembers(account: Account, peerId: PeerId, query: String) -> Signa } return Signal { subscriber in - let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list.map { $0.peer }) } @@ -50,6 +50,6 @@ func searchPeerMembers(account: Account, peerId: PeerId, query: String) -> Signa } |> runOn(Queue.mainQueue()) } } else { - return searchGroupMembers(postbox: account.postbox, network: account.network, peerId: peerId, query: query) + return searchGroupMembers(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, peerId: peerId, query: query) } } diff --git a/TelegramUI/SharedMediaPlayer.swift b/TelegramUI/SharedMediaPlayer.swift index f8e5754627..733691f4c9 100644 --- a/TelegramUI/SharedMediaPlayer.swift +++ b/TelegramUI/SharedMediaPlayer.swift @@ -408,6 +408,9 @@ final class SharedMediaPlayer { private var inForegroundDisposable: Disposable? + private var currentPrefetchItems: (SharedMediaPlaybackDataSource, SharedMediaPlaybackDataSource)? + private let prefetchDisposable = MetaDisposable() + init(mediaManager: MediaManager, inForeground: Signal, postbox: Postbox, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool) { self.mediaManager = mediaManager self.postbox = postbox @@ -428,6 +431,7 @@ final class SharedMediaPlayer { |> deliverOnMainQueue).start(next: { [weak self] state in if let strongSelf = self { let previousPlaybackItem = strongSelf.playbackItem + strongSelf.updatePrefetchItems(item: state.item, previousItem: state.previousItem, nextItem: state.nextItem, ordering: state.order) if state.item?.playbackData != strongSelf.stateValue?.item?.playbackData { if let playbackItem = strongSelf.playbackItem { switch playbackItem { @@ -598,6 +602,7 @@ final class SharedMediaPlayer { self.markItemAsPlayedDisposable.dispose() self.inForegroundDisposable?.dispose() self.playbackStateValueDisposable?.dispose() + self.prefetchDisposable.dispose() if let proximityManagerIndex = self.proximityManagerIndex { DeviceProximityManager.shared().remove(proximityManagerIndex) @@ -679,4 +684,50 @@ final class SharedMediaPlayer { } } } + + private func updatePrefetchItems(item: SharedMediaPlaylistItem?, previousItem: SharedMediaPlaylistItem?, nextItem: SharedMediaPlaylistItem?, ordering: MusicPlaybackSettingsOrder) { + var prefetchItems: (SharedMediaPlaybackDataSource, SharedMediaPlaybackDataSource)? + if let playbackData = item?.playbackData { + switch ordering { + case .regular: + if let previousItem = previousItem?.playbackData { + prefetchItems = (playbackData.source, previousItem.source) + } + case .reversed: + if let nextItem = nextItem?.playbackData { + prefetchItems = (playbackData.source, nextItem.source) + } + case .random: + break + } + } + if self.currentPrefetchItems?.0 != prefetchItems?.0 || self.currentPrefetchItems?.1 != prefetchItems?.1 { + self.currentPrefetchItems = prefetchItems + if let (current, next) = prefetchItems { + let fetchedCurrentSignal: Signal + let fetchedNextSignal: Signal + switch current { + case let .telegramFile(file): + fetchedCurrentSignal = self.postbox.mediaBox.resourceData(file.media.resource) + |> mapToSignal { data -> Signal in + if data.complete { + return .single(Void()) + } else { + return .complete() + } + } + |> take(1) + |> ignoreValues + } + switch next { + case let .telegramFile(file): + fetchedNextSignal = fetchedMediaResource(postbox: self.postbox, reference: file.resourceReference(file.media.resource)) + |> ignoreValues + } + self.prefetchDisposable.set((fetchedCurrentSignal |> then(fetchedNextSignal)).start()) + } else { + self.prefetchDisposable.set(nil) + } + } + } } diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index d2308a2488..9fb2ff1273 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -266,11 +266,15 @@ func storageUsageController(account: Account) -> ViewController { return current.withUpdatedDefaultCacheStorageTimeout(timeout) }).start() } - let values: [Int32] = [ + var values: [Int32] = [ + 3 * 24 * 60 * 60, 7 * 24 * 60 * 60, 1 * 31 * 24 * 60 * 60, Int32.max ] + #if DEBUG + values.insert(60 * 60, at: 0) + #endif let timeoutItems: [ActionSheetItem] = values.map { value in return ActionSheetButtonItem(title: stringForKeepMediaTimeout(strings: presentationData.strings, timeout: value), action: { dismissAction() diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index 4cb82d3cc6..601fed7edd 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -98,6 +98,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { }, canSetupReply: { _ in return false }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings)