From 0d41a372c99cfa9cac812f8586f4380c5bb94b1d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 17 Oct 2018 16:03:43 +0300 Subject: [PATCH] Fixed media thumbnail not appearing immediately after upload started Fixed instant video automatic download Fixed Passport document recognition for uploaded scans --- TelegramUI.xcodeproj/project.pbxproj | 4 + TelegramUI/CallController.swift | 2 +- TelegramUI/CallControllerNode.swift | 142 ++++++++++++++++-- TelegramUI/ChannelInfoController.swift | 55 +------ TelegramUI/ChannelVisibilityController.swift | 2 +- TelegramUI/ChatController.swift | 50 +----- TelegramUI/ChatListController.swift | 2 +- TelegramUI/ChatListNode.swift | 39 ++++- TelegramUI/ChatListViewTransition.swift | 24 +-- .../ChatMessageAttachedContentNode.swift | 9 +- .../ChatMessageInstantVideoItemNode.swift | 2 +- ...atMessageInteractiveInstantVideoNode.swift | 21 ++- .../ChatMessageWebpageBubbleContentNode.swift | 6 +- TelegramUI/GroupInfoController.swift | 52 +------ TelegramUI/LegacyInstantVideoController.swift | 2 +- TelegramUI/LegacyMediaPickers.swift | 112 +++++--------- TelegramUI/LegacySecureIdAttachmentMenu.swift | 22 +-- .../NotificationMuteSettingsController.swift | 75 +++++++++ TelegramUI/OngoingCallContext.swift | 95 +++++++----- TelegramUI/OngoingCallThreadLocalContext.h | 3 + TelegramUI/OngoingCallThreadLocalContext.mm | 17 +++ TelegramUI/PresentationCall.swift | 4 + TelegramUI/UserInfoController.swift | 55 +------ 23 files changed, 422 insertions(+), 373 deletions(-) create mode 100644 TelegramUI/NotificationMuteSettingsController.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index bd156f692f..16edc980d1 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 0941A9A0210B057200EBE194 /* OpenInActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A99F210B057200EBE194 /* OpenInActionSheetController.swift */; }; 0941A9A4210B0E2E00EBE194 /* OpenInAppIconResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */; }; 0941A9A6210B822D00EBE194 /* OpenInOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */; }; + 0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */; }; 09797873210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */; }; 0979787C210642CB0077D77F /* WebEmbedPlayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */; }; 0979787E210646C00077D77F /* YoutubeEmbedImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */; }; @@ -1042,6 +1043,7 @@ 0941A99F210B057200EBE194 /* OpenInActionSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInActionSheetController.swift; sourceTree = ""; }; 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAppIconResources.swift; sourceTree = ""; }; 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInOptions.swift; sourceTree = ""; }; + 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMuteSettingsController.swift; sourceTree = ""; }; 09797872210633CD0077D77F /* InstantPageSettingsButtonItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsButtonItemNode.swift; sourceTree = ""; }; 0979787B210642CB0077D77F /* WebEmbedPlayerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebEmbedPlayerNode.swift; sourceTree = ""; }; 0979787D210646C00077D77F /* YoutubeEmbedImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubeEmbedImplementation.swift; sourceTree = ""; }; @@ -3301,6 +3303,7 @@ D0C50E3D1E93D09200F62E39 /* NotificationItemContainerNode.swift */, D0C50E3B1E93CC2600F62E39 /* NotificationItem.swift */, D0C50E3F1E93D3B000F62E39 /* ChatMessageNotificationItem.swift */, + 0952D1742176DEB500194860 /* NotificationMuteSettingsController.swift */, ); name = Notifications; sourceTree = ""; @@ -5537,6 +5540,7 @@ D0EC6E811EB9F58900EBF1C3 /* NotificationContainerController.swift in Sources */, D0754D271EEE10C800884F6E /* BotCheckoutController.swift in Sources */, D053DADA201A4C4400993D32 /* ChatTextInputAttributes.swift in Sources */, + 0952D1752176DEB500194860 /* NotificationMuteSettingsController.swift in Sources */, D0EC6E821EB9F58900EBF1C3 /* NotificationContainerControllerNode.swift in Sources */, D0EC6E831EB9F58900EBF1C3 /* NotificationItemContainerNode.swift in Sources */, D0CB27D220C17A7F001ACF93 /* TermsOfServiceControllerNode.swift in Sources */, diff --git a/TelegramUI/CallController.swift b/TelegramUI/CallController.swift index 08b7aab956..a274dd6aec 100644 --- a/TelegramUI/CallController.swift +++ b/TelegramUI/CallController.swift @@ -89,7 +89,7 @@ public final class CallController: ViewController { } override public func loadDisplayNode() { - self.displayNode = CallControllerNode(account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit) + self.displayNode = CallControllerNode(account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit) self.displayNodeDidLoad() self.controllerNode.toggleMute = { [weak self] in diff --git a/TelegramUI/CallControllerNode.swift b/TelegramUI/CallControllerNode.swift index c47ceae07b..334f996804 100644 --- a/TelegramUI/CallControllerNode.swift +++ b/TelegramUI/CallControllerNode.swift @@ -14,6 +14,7 @@ final class CallControllerNode: ASDisplayNode { private var presentationData: PresentationData private var peer: Peer? + private let debugInfo: Signal<(String, String), NoError> private let containerNode: ASDisplayNode @@ -51,10 +52,11 @@ final class CallControllerNode: ASDisplayNode { var back: (() -> Void)? var dismissedInteractively: (() -> Void)? - init(account: Account, presentationData: PresentationData, statusBar: StatusBar, shouldStayHiddenUntilConnection: Bool = false) { + init(account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false) { self.account = account self.presentationData = presentationData self.statusBar = statusBar + self.debugInfo = debugInfo self.shouldStayHiddenUntilConnection = shouldStayHiddenUntilConnection self.containerNode = ASDisplayNode() @@ -377,6 +379,10 @@ final class CallControllerNode: ASDisplayNode { let keyTextSize = self.keyButtonNode.frame.size transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: navigationOffset + 8.0), size: keyTextSize)) + + if let debugNode = self.debugNode { + transition.updateFrame(node: debugNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + } } @objc func keyPressed() { @@ -410,14 +416,56 @@ final class CallControllerNode: ASDisplayNode { } } + private var debugTapCounter: (Double, Int) = (0.0, 0) + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { if let _ = self.keyPreviewNode { self.backPressed() + } else { + let point = recognizer.location(in: recognizer.view) + if self.statusNode.frame.contains(point) { + let timestamp = CACurrentMediaTime() + if self.debugTapCounter.0 < timestamp - 0.4 { + self.debugTapCounter.0 = timestamp + self.debugTapCounter.1 = 0 + } + + if self.debugTapCounter.0 >= timestamp - 0.4 { + self.debugTapCounter.0 = timestamp + self.debugTapCounter.1 += 1 + } + + if self.debugTapCounter.1 >= 10 { + self.debugTapCounter.1 = 0 + + self.presentDebugNode() + } + } } } } + private func presentDebugNode() { + guard self.debugNode == nil else { + return + } + + let debugNode = CallDebugNode(signal: self.debugInfo) + debugNode.dismiss = { [weak self] in + if let strongSelf = self { + strongSelf.debugNode?.removeFromSupernode() + strongSelf.debugNode = nil + } + } + self.addSubnode(debugNode) + self.debugNode = debugNode + + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .changed: @@ -454,25 +502,96 @@ final class CallControllerNode: ASDisplayNode { } } +private func attributedStringForDebugInfo(_ info: String, version: String) -> NSAttributedString { + guard !info.isEmpty else { + return NSAttributedString(string: "") + } + + var string = info + string = "libtgvoip v\(version)\n" + string + string = string.replacingOccurrences(of: "Remote endpoints: \n", with: "") + string = string.replacingOccurrences(of: "Jitter ", with: "\nJitter ") + string = string.replacingOccurrences(of: "Key fingerprint:\n", with: "Key fingerprint: ") + + let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedStringKey.font: Font.monospace(15), NSAttributedStringKey.foregroundColor: UIColor.white]) + + let titleStyle = NSMutableParagraphStyle() + titleStyle.alignment = .center + titleStyle.lineSpacing = 7.0 + + let style = NSMutableParagraphStyle() + style.lineHeightMultiple = 1.15 + + let secondaryColor = UIColor(rgb: 0xa6a9a8) + let activeColor = UIColor(rgb: 0xa0d875) + + let titleAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(17), NSAttributedStringKey.paragraphStyle: titleStyle] + let nameAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(15), NSAttributedStringKey.foregroundColor: secondaryColor] + let styleAttributes = [NSAttributedStringKey.paragraphStyle: style] + let typeAttributes = [NSAttributedStringKey.foregroundColor: secondaryColor] + let activeAttributes = [NSAttributedStringKey.font: Font.semiboldMonospace(15), NSAttributedStringKey.foregroundColor: activeColor] + + let range = string.startIndex ..< string.endIndex + string.enumerateSubstrings(in: range, options: NSString.EnumerationOptions.byLines) { (line, range, _, _) in + guard let line = line else { + return + } + if range.lowerBound == string.startIndex { + attributedString.addAttributes(titleAttributes, range: NSRange(range, in: string)) + } + else { + if let semicolonRange = line.range(of: ":") { + if let bracketRange = line.range(of: "[") { + if let _ = line.range(of: "IN_USE") { + attributedString.addAttributes(activeAttributes, range: NSRange(range, in: string)) + } else { + let offset = line.distance(from: line.startIndex, to: bracketRange.lowerBound) + let distance = line.distance(from: line.startIndex, to: line.endIndex) + attributedString.addAttributes(typeAttributes, range: NSRange(string.index(range.lowerBound, offsetBy: offset) ..< string.index(range.lowerBound, offsetBy: distance), in: string)) + } + } else { + attributedString.addAttributes(styleAttributes, range: NSRange(range, in: string)) + + let offset = line.distance(from: line.startIndex, to: semicolonRange.upperBound) + attributedString.addAttributes(nameAttributes, range: NSRange(range.lowerBound ..< string.index(range.lowerBound, offsetBy: offset), in: string)) + } + } + } + } + + return attributedString +} + final private class CallDebugNode: ASDisplayNode { private let disposable = MetaDisposable() private let dimNode: ASDisplayNode private let textNode: ASTextNode - override init() { + public var dismiss: (() -> Void)? + + init(signal: Signal<(String, String), NoError>) { self.dimNode = ASDisplayNode() self.dimNode.isLayerBacked = true - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.dimNode.backgroundColor = UIColor(rgb: 0x26282c, alpha: 0.95) + self.dimNode.isUserInteractionEnabled = false self.textNode = ASTextNode() self.textNode.isUserInteractionEnabled = false super.init() + + self.addSubnode(self.dimNode) + self.addSubnode(self.textNode) + + self.disposable.set((signal + |> deliverOnMainQueue).start(next: { [weak self] (version, info) in + self?.update(info, version: version) + })) } deinit { - disposable.dispose() + self.disposable.dispose() } override func didLoad() { @@ -482,19 +601,22 @@ final private class CallDebugNode: ASDisplayNode { self.view.addGestureRecognizer(tapRecognizer) } + private func update(_ info: String, version: String) { + self.textNode.attributedText = attributedStringForDebugInfo(info, version: version) + self.setNeedsLayout() + } + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - + self.dismiss?() } override func layout() { super.layout() let size = self.bounds.size + self.dimNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)) - self.dimNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)) - - - //let labelSize = labelNode.measure(CGSize(width: , height: 100.0)) - //textNode.frame = CGRect(origin: CGPoint(x: floor(size.width, y: 81.0), size: labelSize) + let textSize = textNode.measure(CGSize(width: size.width - 20.0, height: size.height)) + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) } } diff --git a/TelegramUI/ChannelInfoController.swift b/TelegramUI/ChannelInfoController.swift index 43fcd90619..d99ee5c804 100644 --- a/TelegramUI/ChannelInfoController.swift +++ b/TelegramUI/ChannelInfoController.swift @@ -720,58 +720,9 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .generic), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) }, changeNotificationMuteSettings: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationTheme: presentationData.theme) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - let notificationAction: (Int32?) -> Void = { muteUntil in - let muteInterval: Int32? - if let muteUntil = muteUntil { - if muteUntil <= 0 { - muteInterval = 0 - } else if muteUntil == Int32.max { - muteInterval = Int32.max - } else { - muteInterval = muteUntil - } - } else { - muteInterval = nil - } - - changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: muteInterval).start()) - } - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, action: { - dismissAction() - notificationAction(0) - })) - let intervals: [Int32?] = [ - nil, - 1 * 60 * 60, - 8 * 60 * 60, - 2 * 24 * 60 * 60 - ] - for value in intervals { - let title: String - if let value = value { - title = muteForIntervalString(strings: presentationData.strings, value: value) - } else { - title = presentationData.strings.UserInfo_NotificationsDefault - } - items.append(ActionSheetButtonItem(title: title, action: { - dismissAction() - notificationAction(value) - })) - } - items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, action: { - dismissAction() - notificationAction(Int32.max) - })) - - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) + let controller = notificationMuteSettingsController(presentationData: presentationData, updateSettings: { value in + changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: value).start()) + }) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, changeNotificationSoundSettings: { let _ = (account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in diff --git a/TelegramUI/ChannelVisibilityController.swift b/TelegramUI/ChannelVisibilityController.swift index f5280937a5..26de8dc850 100644 --- a/TelegramUI/ChannelVisibilityController.swift +++ b/TelegramUI/ChannelVisibilityController.swift @@ -818,7 +818,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: doneEnabled = false } } else { - doneEnabled = false + doneEnabled = !(peer.addressName?.isEmpty ?? true) } } } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index f2cf7f138a..b279719a8a 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -4904,6 +4904,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV actionSheet?.dismissAnimated() }) ])]) + strongSelf.chatDisplayNode.dismissInput() strongSelf.present(actionSheet, in: .window(.root)) } })) @@ -5122,12 +5123,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV let otherShortcuts: [KeyShortcut] = [ KeyShortcut(title: strings.KeyCommand_ScrollUp, input: UIKeyInputUpArrow, modifiers: [.shift], action: { [weak self] in if let strongSelf = self { - strongSelf.chatDisplayNode.historyNode.scrollWithDeltaOffset(-75) + strongSelf.chatDisplayNode.historyNode.scrollWithDeltaOffset(75) } }), KeyShortcut(title: strings.KeyCommand_ScrollDown, input: UIKeyInputDownArrow, modifiers: [.shift], action: { [weak self] in if let strongSelf = self { - strongSelf.chatDisplayNode.historyNode.scrollWithDeltaOffset(75) + strongSelf.chatDisplayNode.historyNode.scrollWithDeltaOffset(-75) } }), KeyShortcut(title: strings.KeyCommand_ChatInfo, input: "I", modifiers: [.command, .control], action: { [weak self] in @@ -5144,51 +5145,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV }) } }), - KeyShortcut(input: "W", modifiers: [.command], action: { - + KeyShortcut(input: "W", modifiers: [.command], action: { [weak self] in + if let strongSelf = self { + } }) ] return inputShortcuts + otherShortcuts } - - -// NSMutableArray *commands = [[NSMutableArray alloc] init]; -// -// if (!_inputTextPanel.maybeInputField.isFirstResponder) -// { -// TGKeyCommand *focusKeyCommand = [TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.FocusOnInputField") input:@"\r" modifierFlags:0]; -// -// if ([self canEditLastMessage]) -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputUpArrow modifierFlags:0]]; -// } -// else -// { -// [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.SendMessage") input:@"\r" modifierFlags:0]]; -// -// if ([_inputTextPanel associatedPanel] != nil) -// { -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputUpArrow modifierFlags:0]]; -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputDownArrow modifierFlags:0]]; -// } -// else if ([self canEditLastMessage]) -// { -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputUpArrow modifierFlags:0]]; -// } -// -// if (_inputTextPanel.messageEditingContext != nil) -// { -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0]]; -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"\t" modifierFlags:0]]; -// } -// } -// -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"/" modifierFlags:UIKeyModifierCommand]]; -// -// [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ScrollUp") input:UIKeyInputUpArrow modifierFlags:UIKeyModifierShift]]; -// [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ScrollDown") input:UIKeyInputDownArrow modifierFlags:UIKeyModifierShift]]; -// [commands addObject:[TGKeyCommand keyCommandWithTitle:TGLocalized(@"KeyCommand.ChatInfo") input:@"I" modifierFlags:UIKeyModifierControl | UIKeyModifierCommand]]; -// [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"W" modifierFlags:UIKeyModifierCommand]]; -// -// return commands; } diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index a07bddc56b..99240a55f8 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -140,7 +140,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie var checkProxy = false switch state { case .waitingForNetwork: - strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isPasscodeSet, isManuallyLocked: isManuallyLocked) + strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isPasscodeSet, isManuallyLocked: isManuallyLocked) case let .connecting(proxy): var text = strongSelf.presentationData.strings.State_Connecting if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index c8c0ef70e6..c49b243499 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -419,6 +419,7 @@ final class ChatListNode: ListView { return chatListViewForLocation(groupId: groupId, location: location, account: account) } + let previousState = Atomic(value: self.currentState) let previousView = Atomic(value: nil) let savedMessagesPeer: Signal @@ -430,13 +431,14 @@ final class ChatListNode: ListView { let chatListNodeViewTransition = combineLatest(savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) |> mapToQueue { (savedMessagesPeer, update, state) -> Signal in let processedView = ChatListNodeView(originalView: update.view, filteredEntries: chatListNodeEntriesForView(update.view, state: state, savedMessagesPeer: savedMessagesPeer, mode: mode)) - let previous = previousView.swap(processedView) + let previousView = previousView.swap(processedView) + let previousState = previousState.swap(state) let reason: ChatListNodeViewTransitionReason var prepareOnMainQueue = false var previousWasEmptyOrSingleHole = false - if let previous = previous { + if let previous = previousView { if previous.filteredEntries.count == 1 { if case .HoleEntry = previous.filteredEntries[0] { previousWasEmptyOrSingleHole = true @@ -450,11 +452,11 @@ final class ChatListNode: ListView { if previousWasEmptyOrSingleHole { reason = .initial - if previous == nil { + if previousView == nil { prepareOnMainQueue = true } } else { - if previous?.originalView === update.view { + if previousView?.originalView === update.view { reason = .interactiveChanges updatedScrollPosition = nil } else { @@ -472,7 +474,34 @@ final class ChatListNode: ListView { } } - return preparedChatListNodeViewTransition(from: previous, to: processedView, reason: reason, disableAnimations: state.presentationData.disableAnimations, account: account, scrollPosition: updatedScrollPosition) + var disableAnimations = state.presentationData.disableAnimations + if previousState.editing != state.editing { + disableAnimations = false + } else { + var previousPinnedCount = 0 + var updatedPinnedCount = 0 + if let previous = previousView { + for entry in previous.filteredEntries { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry { + if index.pinningIndex != nil { + previousPinnedCount += 1 + } + } + } + } + for entry in processedView.filteredEntries { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry { + if index.pinningIndex != nil { + updatedPinnedCount += 1 + } + } + } + if previousPinnedCount != updatedPinnedCount { + disableAnimations = false + } + } + + return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, disableAnimations: disableAnimations, account: account, scrollPosition: updatedScrollPosition) |> map({ mappedChatListNodeViewListTransition(account: account, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } diff --git a/TelegramUI/ChatListViewTransition.swift b/TelegramUI/ChatListViewTransition.swift index 27faf126b7..67b13e5343 100644 --- a/TelegramUI/ChatListViewTransition.swift +++ b/TelegramUI/ChatListViewTransition.swift @@ -71,27 +71,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV case .initial: let _ = options.insert(.LowLatency) let _ = options.insert(.Synchronous) - case .interactiveChanges: - var previousPinnedCount = 0 - var updatedPinnedCount = 0 - - if let fromView = fromView { - for entry in fromView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry { - if index.pinningIndex != nil { - previousPinnedCount += 1 - } - } - } - } - for entry in toView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _) = entry { - if index.pinningIndex != nil { - updatedPinnedCount += 1 - } - } - } - + case .interactiveChanges: for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) { let adjustedIndex = updatedCount - 1 - index if adjustedIndex == maxAnimatedInsertionIndex + 1 { @@ -118,7 +98,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV let _ = options.insert(.AnimateCrossfade) } else { let _ = options.insert(.AnimateAlpha) - if !disableAnimations || previousPinnedCount != updatedPinnedCount { + if !disableAnimations { let _ = options.insert(.AnimateInsertion) } } diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 84e54428e0..89a71b7cba 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -375,12 +375,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let (media, flags) = mediaAndFlags { if let file = media as? TelegramMediaFile { if file.isInstantVideo { - let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(account: account, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 180.0, height: 180.0), .bubble) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) + let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(account: account, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 180.0, height: 180.0), .bubble, automaticDownload) initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight contentInstantVideoSizeAndApply = (videoLayout, apply) } else if file.isVideo { - var automaticDownload = false - automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout @@ -390,8 +390,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else { - var automaticDownload = false - automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) let statusType: ChatMessageDateAndStatusType if message.effectivelyIncoming(account.peerId) { diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index 0eb6487a19..8e32926fa0 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -121,7 +121,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { let displaySize = CGSize(width: 212.0, height: 212.0) - let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free) + let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(account: item.account, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, true) let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: videoLayout.contentSize) diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index e30d9a5338..d8f3ca9f4b 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -33,6 +33,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { private var videoFrame: CGRect? private var item: ChatMessageBubbleContentItem? + private var automaticDownload: Bool? var telegramFile: TelegramMediaFile? private var secretProgressIcon: UIImage? @@ -108,14 +109,15 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.view.addGestureRecognizer(recognizer) } - func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { + func asyncLayout() -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) { let previousFile = self.telegramFile let currentItem = self.item + let previousAutomaticDownload = self.automaticDownload let makeDateAndStatusLayout = self.dateAndStatusNode.asyncLayout() - return { item, width, displaySize, statusDisplayType in + return { item, width, displaySize, statusDisplayType, automaticDownload in var updatedTheme: ChatPresentationThemeData? var secretVideoPlaceholderBackgroundImage: UIImage? @@ -251,6 +253,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { strongSelf.item = item strongSelf.videoFrame = videoFrame strongSelf.secretProgressIcon = secretProgressIcon + strongSelf.automaticDownload = automaticDownload if let updatedInfoBackgroundImage = updatedInfoBackgroundImage { strongSelf.infoBackgroundNode.image = updatedInfoBackgroundImage @@ -337,7 +340,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } } - }), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: false, enableSound: false), priority: .embedded, autoplay: true) + }), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: false, enableSound: false, fetchAutomatically: false), priority: .embedded, autoplay: true) let previousVideoNode = strongSelf.videoNode strongSelf.videoNode = videoNode strongSelf.insertSubnode(videoNode, belowSubnode: previousVideoNode ?? strongSelf.dateAndStatusNode) @@ -375,6 +378,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { applySecretPlaceholder() strongSelf.updateStatus() + + if let telegramFile = updatedFile, previousAutomaticDownload != automaticDownload, automaticDownload { + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(account: item.account, message: item.message, file: telegramFile, userInitiated: false).start()) + } } }) } @@ -628,16 +635,16 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { return nil } - static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { + static func asyncLayout(_ node: ChatMessageInteractiveInstantVideoNode?) -> (_ item: ChatMessageBubbleContentItem, _ width: CGFloat, _ displaySize: CGSize, _ statusType: ChatMessageInteractiveInstantVideoNodeStatusType, _ automaticDownload: Bool) -> (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> ChatMessageInteractiveInstantVideoNode) { let makeLayout = node?.asyncLayout() - return { item, width, displaySize, statusType in + return { item, width, displaySize, statusType, automaticDownload in var createdNode: ChatMessageInteractiveInstantVideoNode? let sizeAndApplyLayout: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ContainedViewLayoutTransition) -> Void) if let makeLayout = makeLayout { - sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType) + sizeAndApplyLayout = makeLayout(item, width, displaySize, statusType, automaticDownload) } else { let node = ChatMessageInteractiveInstantVideoNode() - sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType) + sizeAndApplyLayout = node.asyncLayout()(item, width, displaySize, statusType, automaticDownload) createdNode = node } return (sizeAndApplyLayout.0, { [weak node] layoutData, transition in diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 11e566694c..7cab20bd84 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -237,7 +237,11 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } mediaAndFlags = (image, flags) } else if let _ = largestImageRepresentation(image.representations)?.dimensions { - mediaAndFlags = (image, [.preferMediaInline]) + var flags = ChatMessageAttachedContentNodeMediaFlags() + if webpage.instantPage == nil { + flags.insert(.preferMediaInline) + } + mediaAndFlags = (image, flags) } } diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index a77fa36615..6a4795eb13 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -1278,55 +1278,9 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl presentControllerImpl?(controller, presentationArguments) }, changeNotificationMuteSettings: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationTheme: presentationData.theme) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - let notificationAction: (Int32?) -> Void = { muteUntil in - let muteInterval: Int32? - if let muteUntil = muteUntil { - if muteUntil <= 0 { - muteInterval = 0 - } else if muteUntil == Int32.max { - muteInterval = Int32.max - } else { - muteInterval = muteUntil - } - } else { - muteInterval = nil - } - - changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: muteInterval).start()) - } - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDefault, action: { - dismissAction() - notificationAction(nil) - }), - ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, action: { - dismissAction() - notificationAction(0) - }), - ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 1 * 60 * 60), action: { - dismissAction() - notificationAction(1 * 60 * 60) - }), - ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 8 * 60 * 60), action: { - dismissAction() - notificationAction(8 * 60 * 60) - }), - ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: 2 * 24 * 60 * 60), action: { - dismissAction() - notificationAction(2 * 24 * 60 * 60) - }), - ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, action: { - dismissAction() - notificationAction(Int32.max) - }) - ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) + let controller = notificationMuteSettingsController(presentationData: presentationData, updateSettings: { value in + changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: value).start()) + }) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, changeNotificationSoundSettings: { let _ = (account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in diff --git a/TelegramUI/LegacyInstantVideoController.swift b/TelegramUI/LegacyInstantVideoController.swift index b0af8e32a0..663389c497 100644 --- a/TelegramUI/LegacyInstantVideoController.swift +++ b/TelegramUI/LegacyInstantVideoController.swift @@ -86,7 +86,7 @@ final class InstantVideoController: LegacyController { func legacyInstantVideoController(theme: PresentationTheme, panelFrame: CGRect, account: Account, peerId: PeerId, send: @escaping (EnqueueMessage) -> Void) -> InstantVideoController { let legacyController = InstantVideoController(presentation: .custom, theme: theme) - legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) + legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) legacyController.statusBar.statusBarStyle = .Hide let baseController = TGViewController(context: legacyController.context)! legacyController.bind(controller: baseController) diff --git a/TelegramUI/LegacyMediaPickers.swift b/TelegramUI/LegacyMediaPickers.swift index ef57063416..ae91954f04 100644 --- a/TelegramUI/LegacyMediaPickers.swift +++ b/TelegramUI/LegacyMediaPickers.swift @@ -81,9 +81,9 @@ private enum LegacyAssetVideoData { } private enum LegacyAssetItem { - case image(data: LegacyAssetImageData, caption: String?) - case file(data: LegacyAssetImageData, mimeType: String, name: String, caption: String?) - case video(data: LegacyAssetVideoData, previewImage: UIImage?, adjustments: TGVideoEditAdjustments?, caption: String?, asFile: Bool, asAnimation: Bool) + case image(data: LegacyAssetImageData, thumbnail: UIImage?, caption: String?) + case file(data: LegacyAssetImageData, thumbnail: UIImage?, mimeType: String, name: String, caption: String?) + case video(data: LegacyAssetVideoData, thumbnail: UIImage?, adjustments: TGVideoEditAdjustments?, caption: String?, asFile: Bool, asAnimation: Bool) } private final class LegacyAssetItemWrapper: NSObject { @@ -105,11 +105,13 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A let dict = anyDict as! NSDictionary if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" { let image = dict["image"] as! UIImage + let thumbnail = dict["previewImage"] as? UIImage var result: [AnyHashable : Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } else if (dict["type"] as! NSString) == "cloudPhoto" { let asset = dict["asset"] as! TGMediaAsset + let thumbnail = dict["previewImage"] as? UIImage var asFile = false if let document = dict["document"] as? NSNumber, document.boolValue { asFile = true @@ -125,13 +127,14 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A name = customName } - result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: nil) + result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: nil) } else { - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) } return result } else if (dict["type"] as! NSString) == "file" { if let tempFileUrl = dict["tempFileUrl"] as? URL { + let thumbnail = dict["previewImage"] as? UIImage var mimeType = "application/binary" if let customMimeType = dict["mimeType"] as? String { mimeType = customMimeType @@ -147,15 +150,16 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), previewImage: dict["previewImage"] as? UIImage, adjustments: nil, caption: caption, asFile: false, asAnimation: true), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: tempFileUrl.path, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: nil, caption: caption, asFile: false, asAnimation: true), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .tempFile(tempFileUrl.path), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } else if (dict["type"] as! NSString) == "video" { + var thumbnail = dict["previewImage"] as? UIImage var asFile = false if let document = dict["document"] as? NSNumber, document.boolValue { asFile = true @@ -163,16 +167,17 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A if let asset = dict["asset"] as? TGMediaAsset { var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), previewImage: dict["previewImage"] as? UIImage, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), previewImage: dict["previewImage"] as? UIImage, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } else if (dict["type"] as! NSString) == "cameraVideo" { + let thumbnail = dict["previewImage"] as? UIImage var asFile = false if let document = dict["document"] as? NSNumber, document.boolValue { asFile = true @@ -184,7 +189,7 @@ func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [A let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), previewImage: dict["previewImage"] as? UIImage, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } @@ -242,7 +247,17 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa outer: for item in (anyValues as! NSArray) { if let item = (item as? NSDictionary)?.object(forKey: "item") as? LegacyAssetItemWrapper { switch item.item { - case let .image(data, caption): + case let .image(data, thumbnail, caption): + var representations: [TelegramMediaImageRepresentation] = [] + if let thumbnail = thumbnail { + let resource = LocalFileMediaResource(fileId: arc4random64()) + let thumbnailSize = thumbnail.size.aspectFitted(CGSize(width: 90.0, height: 90.0)) + let thumbnailImage = TGScaleImageToPixelSize(thumbnail, thumbnailSize)! + if let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.4) { + account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData) + representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailSize, resource: resource)) + } + } switch data { case let .image(image): var randomId: Int64 = 0 @@ -268,9 +283,11 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa } } #endif - + let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId) - let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)], reference: nil, partialReference: nil) + representations.append(TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)) + + let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, reference: nil, partialReference: nil) var attributes: [MessageAttribute] = [] if let timer = item.timer, timer > 0 && timer <= 60 { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) @@ -284,8 +301,9 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight)) let scaledSize = size.aspectFitted(CGSize(width: 1280.0, height: 1280.0)) let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: arc4random64()) + representations.append(TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)) - let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)], reference: nil, partialReference: nil) + let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, reference: nil, partialReference: nil) var attributes: [MessageAttribute] = [] if let timer = item.timer, timer > 0 && timer <= 60 { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) @@ -294,7 +312,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa case .tempFile: break } - case let .file(data, mimeType, name, caption): + case let .file(data, thumbnail, mimeType, name, caption): switch data { case let .tempFile(path): var randomId: Int64 = 0 @@ -311,7 +329,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa default: break } - case let .video(data, previewImage, adjustments, caption, asFile, asAnimation): + case let .video(data, thumbnail, adjustments, caption, asFile, asAnimation): var finalDimensions: CGSize var finalDuration: Double switch data { @@ -328,10 +346,10 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa } var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if let previewImage = previewImage { + if let thumbnail = thumbnail { let resource = LocalFileMediaResource(fileId: arc4random64()) let thumbnailSize = finalDimensions.aspectFitted(CGSize(width: 90.0, height: 90.0)) - let thumbnailImage = TGScaleImageToPixelSize(previewImage, thumbnailSize)! + let thumbnailImage = TGScaleImageToPixelSize(thumbnail, thumbnailSize)! if let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.4) { account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData) previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: thumbnailSize, resource: resource)) @@ -409,59 +427,3 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa } } } - -func legacyAssetPickerDataSignals(account: Account, signals: [Any]) -> Signal<[TelegramMediaResource], Void> { - return Signal { subscriber in - let disposable = SSignal.combineSignals(signals).start(next: { anyValues in - var datas: [TelegramMediaResource] = [] - - outer: for item in (anyValues as! NSArray) { - if let item = (item as? NSDictionary)?.object(forKey: "item") as? LegacyAssetItemWrapper { - switch item.item { - case let .image(data, _): - switch data { - case let .image(image): - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let tempFilePath = NSTemporaryDirectory() + "\(randomId).jpeg" - let scaledSize = image.size.aspectFitted(CGSize(width: 2048.0, height: 2048.0)) - if let scaledImage = TGScaleImageToPixelSize(image, scaledSize) { - if let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.84) { - let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath)) - let resource = LocalFileReferenceMediaResource(localFilePath: tempFilePath, randomId: randomId) - datas.append(resource) - } - } - case let .asset(asset): - break - case .tempFile: - break - } - case let .file(data, mimeType, name, caption): - switch data { - case let .tempFile(path): - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId) - datas.append(resource) - default: - break - } - case .video: - break - } - } - } - - subscriber.putNext(datas) - subscriber.putCompletion() - }, error: { _ in - subscriber.putError(Void()) - }, completed: nil) - - return ActionDisposable { - disposable?.dispose() - } - } -} - diff --git a/TelegramUI/LegacySecureIdAttachmentMenu.swift b/TelegramUI/LegacySecureIdAttachmentMenu.swift index 1414b602af..d9215dd4ae 100644 --- a/TelegramUI/LegacySecureIdAttachmentMenu.swift +++ b/TelegramUI/LegacySecureIdAttachmentMenu.swift @@ -55,14 +55,18 @@ func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (V if let signal = signal { let _ = (processedLegacySecureIdAttachmentItems(postbox: account.postbox, signal: signal) |> mapToSignal { resources -> Signal<([TelegramMediaResource], SecureIdRecognizedDocumentData?), NoError> in - if case .generic = type, recognizeDocumentData { - return recognizedResources(postbox: account.postbox, resources: resources) - |> map { data -> ([TelegramMediaResource], SecureIdRecognizedDocumentData?) in - return (resources, data) - } - } else { - return .single((resources, nil)) + switch type { + case .generic, .idCard: + if recognizeDocumentData { + return recognizedResources(postbox: account.postbox, resources: resources, shouldBeDriversLicense: false) + |> map { data -> ([TelegramMediaResource], SecureIdRecognizedDocumentData?) in + return (resources, data) + } + } + default: + break } + return .single((resources, nil)) } |> deliverOnMainQueue).start(next: { resourcesAndData in completion(resourcesAndData.0, resourcesAndData.1) @@ -152,7 +156,7 @@ private func processedLegacySecureIdAttachmentItems(postbox: Postbox, signal: SS return collectedItems } -private func recognizedResources(postbox: Postbox, resources: [TelegramMediaResource]) -> Signal { +private func recognizedResources(postbox: Postbox, resources: [TelegramMediaResource], shouldBeDriversLicense: Bool) -> Signal { var signals: [Signal] = [] for resource in resources { let image = Signal { subscriber in @@ -178,7 +182,7 @@ private func recognizedResources(postbox: Postbox, resources: [TelegramMediaReso |> mapToSignal { image -> Signal in if let image = image { return Signal { subscriber in - let disposable = TGPassportOCR.recognizeMRZ(in: image)?.start(next: { value in + let disposable = TGPassportOCR.recognizeData(in: image, shouldBeDriversLicense: shouldBeDriversLicense)?.start(next: { value in if let value = value as? TGPassportMRZ { var issuingCountry: String? = nil if let issuingCountryValue = value.issuingCountry { diff --git a/TelegramUI/NotificationMuteSettingsController.swift b/TelegramUI/NotificationMuteSettingsController.swift new file mode 100644 index 0000000000..53e041046c --- /dev/null +++ b/TelegramUI/NotificationMuteSettingsController.swift @@ -0,0 +1,75 @@ +import Foundation +import Display + +private enum NotificationMuteOption { + case `default` + case enable + case interval(Int32) + case disable +} + +func notificationMuteSettingsController(presentationData: PresentationData, updateSettings: @escaping (Int32?) -> Void) -> ViewController { + let controller = ActionSheetController(presentationTheme: presentationData.theme) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + let notificationAction: (Int32?) -> Void = { muteUntil in + let muteInterval: Int32? + if let muteUntil = muteUntil { + if muteUntil <= 0 { + muteInterval = 0 + } else if muteUntil == Int32.max { + muteInterval = Int32.max + } else { + muteInterval = muteUntil + } + } else { + muteInterval = nil + } + + updateSettings(muteInterval) + } + + let options: [NotificationMuteOption] = [ + .default, + .enable, + .interval(1 * 60 * 60), + .interval(8 * 60 * 60), + .interval(2 * 24 * 60 * 60), + .disable + ] + var items: [ActionSheetItem] = [] + for option in options { + let item: ActionSheetButtonItem + switch option { + case .default: + item = ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDefault, action: { + dismissAction() + notificationAction(nil) + }) + break + case .enable: + item = ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, action: { + dismissAction() + notificationAction(0) + }) + case let .interval(value): + item = ActionSheetButtonItem(title: muteForIntervalString(strings: presentationData.strings, value: value), action: { + dismissAction() + notificationAction(value) + }) + case .disable: + item = ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, action: { + dismissAction() + notificationAction(Int32.max) + }) + } + items.append(item) + } + + controller.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + return controller +} diff --git a/TelegramUI/OngoingCallContext.swift b/TelegramUI/OngoingCallContext.swift index ae6b27e755..fb7230b8b9 100644 --- a/TelegramUI/OngoingCallContext.swift +++ b/TelegramUI/OngoingCallContext.swift @@ -9,6 +9,39 @@ private func callConnectionDescription(_ connection: CallSessionConnection) -> O return OngoingCallConnectionDescription(connectionId: connection.id, ip: connection.ip, ipv6: connection.ipv6, port: connection.port, peerTag: connection.peerTag) } +private let callLogsLimit = 20 + +private func callLogsPath(account: Account) -> String { + let path = account.basePath + "/calls" + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: path, isDirectory: nil) { + try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) + } + + var oldest: (URL, Date)? = nil + var count = 0 + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { + for url in enumerator { + if let url = url as? URL { + if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate { + if let currentOldest = oldest { + if date < currentOldest.1 { + oldest = (url, date) + } + } else { + oldest = (url, date) + } + count += 1 + } + } + } + } + if count > callLogsLimit, let oldest = oldest { + try? fileManager.removeItem(atPath: oldest.0.path) + } + return path +} + private let setupLogs: Bool = { OngoingCallThreadLocalContext.setupLoggingFunction({ value in if let value = value { @@ -64,25 +97,6 @@ private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetwor } } -private enum UsageCalculationConnection: Int32 { - case cellular = 0 - case wifi = 1 -} - -private enum UsageCalculationDirection: Int32 { - case incoming = 0 - case outgoing = 1 -} - -private struct UsageCalculationTag { - let connection: UsageCalculationConnection - let direction: UsageCalculationDirection - - var key: Int32 { - return 5 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 - } -} - final class OngoingCallContext { let internalId: CallSessionInternalId @@ -133,24 +147,15 @@ final class OngoingCallContext { context.stateChanged = { [weak self] state in self?.contextState.set(.single(state)) } - context.callEnded = { [weak self] debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in - if let strongSelf = self { - var update: [Int32 : Int64] = [:] - update[UsageCalculationTag(connection: .cellular, direction: .incoming).key] = bytesReceivedMobile - update[UsageCalculationTag(connection: .cellular, direction: .outgoing).key] = bytesSentMobile - - update[UsageCalculationTag(connection: .wifi, direction: .incoming).key] = bytesReceivedWifi - update[UsageCalculationTag(connection: .wifi, direction: .outgoing).key] = bytesSentWifi - - let delta = NetworkUsageStatsConnectionsEntry( - cellular: NetworkUsageStatsDirectionsEntry( - incoming: bytesReceivedMobile, - outgoing: bytesSentMobile), - wifi: NetworkUsageStatsDirectionsEntry( - incoming: bytesReceivedWifi, - outgoing: bytesSentWifi)) - let _ = updateAccountNetworkUsageStats(account: account, category: .call, delta: delta) - } + context.callEnded = { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in + let delta = NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: bytesReceivedMobile, + outgoing: bytesSentMobile), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: bytesReceivedWifi, + outgoing: bytesSentWifi)) + let _ = updateAccountNetworkUsageStats(account: account, category: .call, delta: delta) } } @@ -204,4 +209,20 @@ final class OngoingCallContext { context.setIsMuted(value) } } + + func debugInfo() -> Signal<(String, String), NoError> { + let poll = Signal<(String, String), NoError> { subscriber in + self.withContext { context in + let version = context.version() + let debugInfo = context.debugInfo() + if let version = version, let debugInfo = debugInfo { + subscriber.putNext((version, debugInfo)) + } + subscriber.putCompletion() + } + + return EmptyDisposable + } + return (poll |> then(.complete() |> delay(0.5, queue: Queue.concurrentDefaultQueue()))) |> restart + } } diff --git a/TelegramUI/OngoingCallThreadLocalContext.h b/TelegramUI/OngoingCallThreadLocalContext.h index ef719e2ff5..d6af838966 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.h +++ b/TelegramUI/OngoingCallThreadLocalContext.h @@ -58,6 +58,9 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) { - (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer; - (void)stop; +- (NSString * _Nullable)debugInfo; +- (NSString * _Nullable)version; + - (void)setIsMuted:(bool)isMuted; - (void)setNetworkType:(OngoingCallNetworkType)networkType; diff --git a/TelegramUI/OngoingCallThreadLocalContext.mm b/TelegramUI/OngoingCallThreadLocalContext.mm index d91f2d0167..3dc1e363f4 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.mm +++ b/TelegramUI/OngoingCallThreadLocalContext.mm @@ -287,6 +287,23 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { } } +- (NSString *)debugInfo { + if (_controller != nil) { + auto rawDebugString = _controller->GetDebugString(); + return [NSString stringWithUTF8String:rawDebugString.c_str()]; + } else { + return nil; + } +} + +- (NSString *)version { + if (_controller != nil) { + return [NSString stringWithUTF8String:_controller->GetVersion()]; + } else { + return nil; + } +} + - (void)controllerStateChanged:(int)state { OngoingCallState callState = OngoingCallStateInitializing; switch (state) { diff --git a/TelegramUI/PresentationCall.swift b/TelegramUI/PresentationCall.swift index fbd1acab9e..ab444a92a1 100644 --- a/TelegramUI/PresentationCall.swift +++ b/TelegramUI/PresentationCall.swift @@ -586,4 +586,8 @@ public final class PresentationCall { audioSessionControl.setOutputMode(.custom(output)) } } + + func debugInfo() -> Signal<(String, String), NoError> { + return self.ongoingContext.debugInfo() + } } diff --git a/TelegramUI/UserInfoController.swift b/TelegramUI/UserInfoController.swift index 2302d0d2c9..0e5584d2c1 100644 --- a/TelegramUI/UserInfoController.swift +++ b/TelegramUI/UserInfoController.swift @@ -860,58 +860,9 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC startSecretChatImpl?() }, changeNotificationMuteSettings: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationTheme: presentationData.theme) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - let notificationAction: (Int32?) -> Void = { muteUntil in - let muteInterval: Int32? - if let muteUntil = muteUntil { - if muteUntil <= 0 { - muteInterval = 0 - } else if muteUntil == Int32.max { - muteInterval = Int32.max - } else { - muteInterval = muteUntil - } - } else { - muteInterval = nil - } - - changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: muteInterval).start()) - } - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsEnable, action: { - dismissAction() - notificationAction(0) - })) - let intervals: [Int32?] = [ - nil, - 1 * 60 * 60, - 8 * 60 * 60, - 2 * 24 * 60 * 60 - ] - for value in intervals { - let title: String - if let value = value { - title = muteForIntervalString(strings: presentationData.strings, value: value) - } else { - title = presentationData.strings.UserInfo_NotificationsDefault - } - items.append(ActionSheetButtonItem(title: title, action: { - dismissAction() - notificationAction(value) - })) - } - items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_NotificationsDisable, action: { - dismissAction() - notificationAction(Int32.max) - })) - - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) + let controller = notificationMuteSettingsController(presentationData: presentationData, updateSettings: { value in + changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: value).start()) + }) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, changeNotificationSoundSettings: { let _ = (account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in