diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index c60bd46dd3..8747fffe8a 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 091EB4EB213F48B4005284DE /* Vision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 094981C52138D73B00A10660 /* Vision.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D02DADBE2138D76F00116225 /* Vision.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 09310D2C213ED5FB0020033A /* anim_read.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D14213BC5DE0020033A /* anim_read.json */; }; 09310D2D213ED5FB0020033A /* anim_pin.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D15213BC5DE0020033A /* anim_pin.json */; }; 09310D2E213ED5FB0020033A /* anim_unmute.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D16213BC5DE0020033A /* anim_unmute.json */; }; @@ -1032,7 +1032,6 @@ 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 = ""; }; - 094981C52138D73B00A10660 /* Vision.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Vision.framework; path = System/Library/Frameworks/Vision.framework; sourceTree = SDKROOT; }; 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 = ""; }; @@ -2123,8 +2122,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */, D0C45E9F213FFAFD00988156 /* Lottie.framework in Frameworks */, - 091EB4EB213F48B4005284DE /* Vision.framework in Frameworks */, D00ACA4B20222C280045D427 /* libtgvoip.framework in Frameworks */, D07BCBFE1F2B792300ED97AA /* LegacyComponents.framework in Frameworks */, D053B4371F1A9CA000E2D58A /* WebKit.framework in Frameworks */, @@ -2161,14 +2160,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 091EB4E8213F3D86005284DE /* Recovered References */ = { - isa = PBXGroup; - children = ( - 094981C52138D73B00A10660 /* Vision.framework */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 09310D13213BC5DE0020033A /* Animations */ = { isa = PBXGroup; children = ( @@ -4381,7 +4372,6 @@ D0FC408C1D5B8E7500261D9D /* TelegramUITests */, D0FC40801D5B8E7400261D9D /* Products */, D08D45281D5E340200A7428A /* Frameworks */, - 091EB4E8213F3D86005284DE /* Recovered References */, ); sourceTree = ""; }; @@ -5829,7 +5819,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_COMPILATION_MODE = singlefile; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; @@ -6135,7 +6125,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_COMPILATION_MODE = singlefile; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; @@ -6172,7 +6162,7 @@ PRODUCT_NAME = TelegramUI; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_COMPILATION_MODE = singlefile; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.0; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; diff --git a/TelegramUI/BotCheckoutActionButton.swift b/TelegramUI/BotCheckoutActionButton.swift index ede3d2ccf1..7a593ebafd 100644 --- a/TelegramUI/BotCheckoutActionButton.swift +++ b/TelegramUI/BotCheckoutActionButton.swift @@ -41,7 +41,7 @@ enum BotCheckoutActionButtonState: Equatable { private let titleFont = Font.semibold(17.0) -final class BotCheckoutActionButton: HighlightTrackingButtonNode { +final class BotCheckoutActionButton: HighlightableButtonNode { static var diameter: CGFloat = 48.0 private var inactiveFillColor: UIColor diff --git a/TelegramUI/BotCheckoutInfoControllerNode.swift b/TelegramUI/BotCheckoutInfoControllerNode.swift index 1e9e365e33..19bc6d1e12 100644 --- a/TelegramUI/BotCheckoutInfoControllerNode.swift +++ b/TelegramUI/BotCheckoutInfoControllerNode.swift @@ -225,9 +225,44 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi } } + let fieldsAndTypes = { [weak self] () -> [(BotPaymentFieldItemNode, BotCheckoutInfoControllerFocus)] in + guard let strongSelf = self else { + return [] + } + var fieldsAndTypes: [(BotPaymentFieldItemNode, BotCheckoutInfoControllerFocus)] = [] + if let addressItems = strongSelf.addressItems { + fieldsAndTypes.append((addressItems.address1, .address(.street1))) + fieldsAndTypes.append((addressItems.address2, .address(.street2))) + fieldsAndTypes.append((addressItems.city, .address(.city))) + fieldsAndTypes.append((addressItems.state, .address(.state))) + fieldsAndTypes.append((addressItems.postcode, .address(.postcode))) + } + if let nameItem = strongSelf.nameItem { + fieldsAndTypes.append((nameItem, .name)) + } + if let phoneItem = strongSelf.phoneItem { + fieldsAndTypes.append((phoneItem, .phone)) + } + if let emailItem = strongSelf.emailItem { + fieldsAndTypes.append((emailItem, .email)) + } + return fieldsAndTypes + } + for items in itemNodes { for item in items { if let item = item as? BotPaymentFieldItemNode { + item.focused = { [weak self, weak item] in + guard let strongSelf = self, let item = item else { + return + } + for (node, focus) in fieldsAndTypes() { + if node === item { + strongSelf.focus = focus + break + } + } + } item.textUpdated = { [weak self] in self?.updateDone() } @@ -236,32 +271,13 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi return } - var fieldsAndTypes: [(BotPaymentFieldItemNode, BotCheckoutInfoControllerFocus)] = [] - if let addressItems = strongSelf.addressItems { - fieldsAndTypes.append((addressItems.address1, .address(.street1))) - fieldsAndTypes.append((addressItems.address2, .address(.street2))) - fieldsAndTypes.append((addressItems.city, .address(.city))) - fieldsAndTypes.append((addressItems.state, .address(.state))) - fieldsAndTypes.append((addressItems.postcode, .address(.postcode))) - } - if let nameItem = strongSelf.nameItem { - fieldsAndTypes.append((nameItem, .name)) - } - if let phoneItem = strongSelf.phoneItem { - fieldsAndTypes.append((phoneItem, .phone)) - } - if let emailItem = strongSelf.emailItem { - fieldsAndTypes.append((emailItem, .email)) - } - - var activateNext = true + var activateNext = false outer: for section in strongSelf.itemNodes { for i in 0 ..< section.count { if section[i] === item { activateNext = true } else if activateNext, let field = section[i] as? BotPaymentFieldItemNode { - - for (node, focus) in fieldsAndTypes { + for (node, focus) in fieldsAndTypes() { if node === field { strongSelf.focus = focus if let containerLayout = strongSelf.containerLayout { @@ -431,8 +447,19 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi if let focus = self.focus { var focusItem: ASDisplayNode? switch focus { - case .address: - focusItem = self.addressItems?.address1 + case let .address(field): + switch field { + case .street1: + focusItem = self.addressItems?.address1 + case .street2: + focusItem = self.addressItems?.address2 + case .city: + focusItem = self.addressItems?.city + case .state: + focusItem = self.addressItems?.state + case .postcode: + focusItem = self.addressItems?.postcode + } case .name: focusItem = self.nameItem case .email: @@ -447,7 +474,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, UIScrollVi contentOffset.y = max(contentOffset.y, -insets.top) transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size)) - if previousLayout == nil, let focusItem = focusItem as? BotPaymentFieldItemNode { + if let focusItem = focusItem as? BotPaymentFieldItemNode { focusItem.activateInput() } } diff --git a/TelegramUI/BotPaymentFieldItemNode.swift b/TelegramUI/BotPaymentFieldItemNode.swift index c365ffe9c4..03bf732aff 100644 --- a/TelegramUI/BotPaymentFieldItemNode.swift +++ b/TelegramUI/BotPaymentFieldItemNode.swift @@ -29,6 +29,7 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate { private var theme: PresentationTheme? + var focused: (() -> Void)? var textUpdated: (() -> Void)? var returnPressed: (() -> Void)? @@ -50,7 +51,7 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate { case .name, .address: self.textField.textField.autocorrectionType = .no case .phoneNumber: - self.textField.textField.keyboardType = .numberPad + self.textField.textField.keyboardType = .phonePad if #available(iOSApplicationExtension 10.0, *) { self.textField.textField.textContentType = .telephoneNumber } @@ -122,6 +123,10 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate { self.textUpdated?() } + func textFieldDidBeginEditing(_ textField: UITextField) { + self.focused?() + } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.returnPressed?() return false diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index a201e8f354..b9b74e20dc 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -2718,6 +2718,14 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin raiseToListen.activateBasedOnProximity() } } + self.tempVoicePlaylistItemChanged = { [weak self] previousItem, currentItem in + guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { + return + } + if let currentItem = currentItem?.id as? PeerMessagesMediaPlaylistItemId, let previousItem = previousItem?.id as? PeerMessagesMediaPlaylistItemId, previousItem.messageId.peerId == peerId, currentItem.messageId.peerId == peerId, currentItem.messageId != previousItem.messageId { + strongSelf.navigateToMessage(from: nil, to: .id(currentItem.messageId), scrollPosition: .center(.bottom), rememberInStack: false, animated: true, completion: nil) + } + } } if let arguments = self.presentationArguments as? ChatControllerOverlayPresentationData { @@ -4285,22 +4293,28 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin private func reportPeer() { if let peer = self.presentationInterfaceState.renderedPeer?.peer { let title: String + var infoString: String? if let _ = peer as? TelegramGroup { title = self.presentationData.strings.Conversation_ReportSpam } else if let _ = peer as? TelegramChannel { title = self.presentationData.strings.Conversation_ReportSpam } else { title = self.presentationData.strings.Conversation_ReportSpam + infoString = self.presentationData.strings.Conversation_ReportSpamConfirmation } let actionSheet = ActionSheetController(presentationTheme: self.presentationData.theme) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.deleteChat(reportChatSpam: true) - } - }) - ]), ActionSheetItemGroup(items: [ + + var items: [ActionSheetItem] = [] + if let infoString = infoString { + items.append(ActionSheetTextItem(title: infoString)) + } + items.append(ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.deleteChat(reportChatSpam: true) + } + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) diff --git a/TelegramUI/ChatImageGalleryItem.swift b/TelegramUI/ChatImageGalleryItem.swift index 2f7f34a06f..a2b3caf2d6 100644 --- a/TelegramUI/ChatImageGalleryItem.swift +++ b/TelegramUI/ChatImageGalleryItem.swift @@ -30,9 +30,11 @@ enum ChatMediaGalleryThumbnail: Equatable { final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem { private let account: Account private let thumbnail: ChatMediaGalleryThumbnail + private let requestForIndex: () -> Int? - init?(account: Account, mediaReference: AnyMediaReference) { + init?(account: Account, mediaReference: AnyMediaReference, requestForIndex: @escaping () -> Int?) { self.account = account + self.requestForIndex = requestForIndex if let imageReference = mediaReference.concrete(TelegramMediaImage.self) { self.thumbnail = .image(imageReference) } else if let fileReference = mediaReference.concrete(TelegramMediaFile.self), fileReference.media.isVideo { @@ -66,6 +68,10 @@ final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem { } } } + + var index: Int? { + return self.requestForIndex() + } } class ChatImageGalleryItem: GalleryItem { @@ -132,7 +138,7 @@ class ChatImageGalleryItem: GalleryItem { } } if let mediaReference = mediaReference { - if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: mediaReference) { + if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: mediaReference, requestForIndex: { return 0 }) { return (Int64(id), item) } } diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index 29261525bc..3ca0ebd966 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -436,7 +436,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } if !data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty && isAction { actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete), action: { - let _ = deleteMessagesInteractively(postbox: account.postbox, messageIds: messages.map { $0.id }, type: .forEveryone).start() + interfaceInteraction.deleteMessages(messages) }))) } diff --git a/TelegramUI/DeviceContactData.swift b/TelegramUI/DeviceContactData.swift index 325f9ba215..c0e64b780c 100644 --- a/TelegramUI/DeviceContactData.swift +++ b/TelegramUI/DeviceContactData.swift @@ -293,8 +293,8 @@ public final class DeviceContactExtendedData: Equatable { } } -extension DeviceContactExtendedData { - convenience init?(vcard: Data) { +public extension DeviceContactExtendedData { + public convenience init?(vcard: Data) { if #available(iOSApplicationExtension 9.0, *) { guard let contact = (try? CNContactVCardSerialization.contacts(with: vcard))?.first else { return nil @@ -340,7 +340,7 @@ extension DeviceContactExtendedData { return contact } - func serializedVCard() -> String? { + public func serializedVCard() -> String? { if #available(iOSApplicationExtension 9.0, *) { guard let data = try? CNContactVCardSerialization.data(with: [self.asMutableCNContact()]) else { return nil diff --git a/TelegramUI/DeviceContactInfoController.swift b/TelegramUI/DeviceContactInfoController.swift index 560926b9b1..867486ca31 100644 --- a/TelegramUI/DeviceContactInfoController.swift +++ b/TelegramUI/DeviceContactInfoController.swift @@ -649,7 +649,7 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen return entries } -enum DeviceContactInfoSubject { +public enum DeviceContactInfoSubject { case vcard(Peer?, DeviceContactStableId?, DeviceContactExtendedData) case filter(peer: Peer?, contactId: DeviceContactStableId?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactExtendedData) -> Void) case create(peer: Peer?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) @@ -705,7 +705,7 @@ private final class DeviceContactInfoController: ItemListController ViewController { +public func deviceContactInfoController(account: Account, subject: DeviceContactInfoSubject, cancelled: (() -> Void)? = nil) -> ViewController { var initialState = DeviceContactInfoState() if case let .create(peer, contactData, _) = subject { var peerPhoneNumber: String? @@ -911,6 +911,7 @@ func deviceContactInfoController(account: Account, subject: DeviceContactInfoSub case .filter, .create: leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?(true) + cancelled?() }) } diff --git a/TelegramUI/FormControllerNode.swift b/TelegramUI/FormControllerNode.swift index 1dd74c022d..d15e8412ca 100644 --- a/TelegramUI/FormControllerNode.swift +++ b/TelegramUI/FormControllerNode.swift @@ -4,6 +4,18 @@ import AsyncDisplayKit import Display import TelegramCore +private func hasFirstResponder(_ view: UIView) -> Bool { + if view.isFirstResponder { + return true + } + for subview in view.subviews { + if hasFirstResponder(subview) { + return true + } + } + return false +} + struct FormControllerLayoutState { var layout: ContainerViewLayout var navigationHeight: CGFloat @@ -155,6 +167,8 @@ class FormControllerNode: View } } + private weak var preivousItemNodeWithFocus: (ASDisplayNode & FormControllerItemNode)? + func stateUpdated(state: State, transition: ContainedViewLayoutTransition) { let previousLayout = self.appliedLayout self.appliedLayout = state.layoutState @@ -289,6 +303,17 @@ class FormControllerNode: View self.scrollNode.view.scrollIndicatorInsets = insets self.scrollNode.view.ignoreUpdateBounds = false + var updatedItemNodeWithFocus: (ASDisplayNode & FormControllerItemNode)? + for itemNode in self.itemNodes { + if hasFirstResponder(itemNode.view) { + if self.preivousItemNodeWithFocus !== itemNode { + self.preivousItemNodeWithFocus = itemNode + updatedItemNodeWithFocus = itemNode + } + break + } + } + if let previousLayout = previousLayout { var previousInsets = previousLayout.layout.insets(options: [.input]) previousInsets.top += max(previousLayout.navigationHeight, previousLayout.layout.insets(options: [.statusBar]).top) @@ -302,6 +327,13 @@ class FormControllerNode: View contentOffset.y = max(contentOffset.y, -insets.top) contentOffset.y += negativeOverscroll + if let updatedItemNodeWithFocus = updatedItemNodeWithFocus { + let itemRect = updatedItemNodeWithFocus.view.convert(updatedItemNodeWithFocus.view.bounds, to: self.scrollNode.view) + if contentOffset.y + layout.size.height - insets.bottom < itemRect.maxY + 4.0 { + contentOffset.y = itemRect.maxY + 4.0 - (layout.size.height - insets.bottom) + } + } + transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: contentOffset.y), size: layout.size)) } else { let contentOffset = CGPoint(x: 0.0, y: -insets.top) @@ -318,4 +350,18 @@ class FormControllerNode: View completion?() }) } + + func enumerateItemsAndEntries(_ f: (InnerState.Entry, ASDisplayNode & FormControllerItemNode) -> Bool) { + for i in 0 ..< self.appliedEntries.count { + if !f(self.appliedEntries[i], self.itemNodes[i]) { + break + } + } + } + + func forceUpdateState(transition: ContainedViewLayoutTransition) { + if let layoutState = self.layoutState, let innerState = self.innerState { + self.stateUpdated(state: FormControllerState(layoutState: layoutState, presentationState: self.internalState.presentationState, innerState: innerState), transition: transition) + } + } } diff --git a/TelegramUI/FormControllerTextInputItem.swift b/TelegramUI/FormControllerTextInputItem.swift index 7b901e21cd..ec41e1709e 100644 --- a/TelegramUI/FormControllerTextInputItem.swift +++ b/TelegramUI/FormControllerTextInputItem.swift @@ -19,14 +19,16 @@ final class FormControllerTextInputItem: FormControllerItem { let type: FormControllerTextInputItemType let error: String? let textUpdated: (String) -> Void + let returnPressed: () -> Void - init(title: String, text: String, placeholder: String, type: FormControllerTextInputItemType, error: String? = nil, textUpdated: @escaping (String) -> Void) { + init(title: String, text: String, placeholder: String, type: FormControllerTextInputItemType, error: String? = nil, textUpdated: @escaping (String) -> Void, returnPressed: @escaping () -> Void) { self.title = title self.text = text self.placeholder = placeholder self.type = type self.error = error self.textUpdated = textUpdated + self.returnPressed = returnPressed } func node() -> ASDisplayNode & FormControllerItemNode { @@ -44,7 +46,7 @@ final class FormControllerTextInputItem: FormControllerItem { } } -final class FormControllerTextInputItemNode: FormBlockItemNode { +final class FormControllerTextInputItemNode: FormBlockItemNode, UITextFieldDelegate { private let titleNode: ImmediateTextNode private let errorNode: ImmediateTextNode private let textField: TextFieldNode @@ -70,6 +72,7 @@ final class FormControllerTextInputItemNode: FormBlockItemNode Bool { + self.item?.returnPressed() + return false + } + + func activate() { + self.textField.textField.becomeFirstResponder() + } } diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index d87724b6c6..24e0c7d029 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -86,6 +86,9 @@ private let internalExtensions = Set([ "m", "mm", "java", + "jpg", + "png", + "jpeg" ]) private let internalMimeTypes = Set([ diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index e44797390e..d8a08653c4 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -123,6 +123,11 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog let convertedIndex = (index, progress) if strongSelf.currentThumbnailContainerNode?.groupId != centralId { node = GalleryThumbnailContainerNode(groupId: centralId) + node?.itemChanged = { [weak self] index in + if let strongSelf = self { + //strongSelf.pager.transaction(GalleryPagerTransaction(deleteItems: [], insertItems: [], updateItems: [], focusOnItem: index)) + } + } node?.updateItems(items, centralIndex: convertedIndex.0, progress: convertedIndex.1) } else { node = strongSelf.currentThumbnailContainerNode diff --git a/TelegramUI/GalleryPagerNode.swift b/TelegramUI/GalleryPagerNode.swift index 2a90ea85a8..234230800f 100644 --- a/TelegramUI/GalleryPagerNode.swift +++ b/TelegramUI/GalleryPagerNode.swift @@ -214,6 +214,10 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { self.updateItemNodes(transition: .immediate) } + else if let focusOnItem = transaction.focusOnItem { + self.centralItemIndex = focusOnItem + self.updateItemNodes(transition: .immediate) + } } private func makeNodeForItem(at index: Int) -> GalleryItemNode { @@ -261,6 +265,11 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { } var resetOffsetToCentralItem = false + if let centralItemIndex = self.centralItemIndex, self.visibleItemNode(at: centralItemIndex) == nil, !self.itemNodes.isEmpty { + repeat { + self.removeVisibleItemNode(internalIndex: self.itemNodes.count - 1) + } while self.itemNodes.count > 0 + } if self.itemNodes.isEmpty { let node = self.makeNodeForItem(at: self.centralItemIndex ?? 0) node.frame = CGRect(origin: CGPoint(), size: scrollView.bounds.size) diff --git a/TelegramUI/GalleryThumbnailContainerNode.swift b/TelegramUI/GalleryThumbnailContainerNode.swift index bbd2e6e812..4b0cb8cb65 100644 --- a/TelegramUI/GalleryThumbnailContainerNode.swift +++ b/TelegramUI/GalleryThumbnailContainerNode.swift @@ -3,9 +3,13 @@ import AsyncDisplayKit import Display import SwiftSignalKit +private let itemBaseSize = CGSize(width: 23.0, height: 42.0) +private let spacing: CGFloat = 2.0 + protocol GalleryThumbnailItem { func isEqual(to: GalleryThumbnailItem) -> Bool var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) { get } + var index: Int? { get } } private final class GalleryThumbnailItemNode: ASDisplayNode { @@ -30,9 +34,8 @@ private final class GalleryThumbnailItemNode: ASDisplayNode { } func updateLayout(height: CGFloat, progress: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let baseWidth: CGFloat = 23.0 let boundingSize = self.imageSize.aspectFilled(CGSize(width: 1.0, height: height)) - let width = baseWidth * (1.0 - progress) + boundingSize.width * progress + let width = itemBaseSize.width * (1.0 - progress) + boundingSize.width * progress let arguments = TransformImageArguments(corners: ImageCorners(radius: 0), imageSize: boundingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) let makeLayout = self.imageNode.asyncLayout() let apply = makeLayout(arguments) @@ -50,9 +53,13 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { private(set) var items: [GalleryThumbnailItem] = [] private var itemNodes: [GalleryThumbnailItemNode] = [] - private var centralIndexAndProgress: (Int, CGFloat)? + private var centralIndexAndProgress: (Int, CGFloat?)? private var currentLayout: CGSize? + private var isPanning: Bool = false + + public var itemChanged: ((Int) -> Void)? + init(groupId: Int64) { self.groupId = groupId self.scrollNode = ASScrollNode() @@ -60,10 +67,24 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { super.init() self.scrollNode.view.delegate = self + self.scrollNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) self.addSubnode(self.scrollNode) } + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) + { + let location = recognizer.location(in: recognizer.view) + for i in 0 ..< self.itemNodes.count { + let view = self.itemNodes[i] + if view.frame.contains(location) { + self.updateCentralIndexAndProgress(centralIndex: i, progress: 0.0) + self.itemChanged?(self.items[i].index!) + break + } + } + } + func updateItems(_ items: [GalleryThumbnailItem], centralIndex: Int, progress: CGFloat) { var items: [GalleryThumbnailItem] = items.count <= 1 ? [] : items var updated = false @@ -120,18 +141,40 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateLayout(size: CGSize, centralIndex: Int, progress: CGFloat, transition: ContainedViewLayoutTransition) { + private func contentOffsetToCenterItem(index: Int, progress: CGFloat?, contentInset: UIEdgeInsets) -> CGPoint { + let progress = progress ?? 0.0 + return CGPoint(x: -contentInset.left + (CGFloat(index) + progress) * (itemBaseSize.width + spacing), y: 0.0) + } + + func updateLayout(size: CGSize, centralIndex: Int, progress: CGFloat?, transition: ContainedViewLayoutTransition) { self.currentLayout = size self.scrollNode.frame = CGRect(origin: CGPoint(), size: size) - let spacing: CGFloat = 2.0 let centralSpacing: CGFloat = 8.0 - let itemHeight: CGFloat = 42.0 + let contentInset = UIEdgeInsetsMake(0.0, size.width / 2.0, 0.0, 0.0) + let contentSize = CGSize(width: size.width - contentInset.left + (itemBaseSize.width + spacing) * CGFloat(self.itemNodes.count - 1), height : size.height) + + var updated = false + if contentInset != self.scrollNode.view.contentInset { + self.scrollNode.view.contentInset = contentInset + updated = true + } + if contentSize != self.scrollNode.view.contentSize { + self.scrollNode.view.contentSize = contentSize + updated = true + } + + if updated || !self.isPanning { + self.scrollNode.view.contentOffset = contentOffsetToCenterItem(index: centralIndex, progress: progress, contentInset: contentInset) + } + + let progress = progress ?? 0.0 var itemFrames: [CGRect] = [] var lastTrailingSpacing: CGFloat = 0.0 + var xOffset: CGFloat = -itemBaseSize.width / 2.0 for i in 0 ..< self.itemNodes.count { let itemProgress: CGFloat - if i == centralIndex { + if i == centralIndex && !self.isPanning { itemProgress = 1.0 - abs(progress) } else if i == centralIndex - 1 { itemProgress = max(0.0, -progress) @@ -141,43 +184,26 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { itemProgress = 0.0 } let itemSpacing = itemProgress * centralSpacing + (1.0 - itemProgress) * spacing + let itemWidth = self.itemNodes[i].updateLayout(height: itemBaseSize.height, progress: itemProgress, transition: transition) + if i == centralIndex { + xOffset = -itemWidth / 2.0 + } let itemX: CGFloat if i == 0 { - itemX = lastTrailingSpacing + itemX = 0.0 } else { - itemX = lastTrailingSpacing + itemFrames[itemFrames.count - 1].maxX + itemSpacing * 0.5 + itemX = itemFrames[itemFrames.count - 1].maxX + lastTrailingSpacing + itemSpacing * 0.5 } if i == self.itemNodes.count - 1 { lastTrailingSpacing = 0.0 } else { lastTrailingSpacing = itemSpacing * 0.5 - } - let itemWidth = self.itemNodes[i].updateLayout(height: itemHeight, progress: itemProgress, transition: transition) - itemFrames.append(CGRect(origin: CGPoint(x: itemX, y: 0.0), size: CGSize(width: itemWidth, height: itemHeight))) + } + itemFrames.append(CGRect(origin: CGPoint(x: itemX, y: 0.0), size: CGSize(width: itemWidth, height: itemBaseSize.height))) } for i in 0 ..< itemFrames.count { - if i == centralIndex { - var midX = itemFrames[i].midX - if progress < 0.0 { - if i != 0 { - midX = midX * (1.0 - abs(progress)) + itemFrames[i - 1].midX * abs(progress) - } else { - midX = midX * (1.0 - abs(progress)) + itemFrames[i].offsetBy(dx: -itemFrames[i].width, dy: 0.0).midX * abs(progress) - } - } else if progress > 0.0 { - if i != itemFrames.count - 1 { - midX = midX * (1.0 - abs(progress)) + itemFrames[i + 1].midX * abs(progress) - } else { - midX = midX * (1.0 - abs(progress)) + itemFrames[i].offsetBy(dx: itemFrames[i].width, dy: 0.0).midX * abs(progress) - } - } - let offset = size.width / 2.0 - midX - for j in 0 ..< itemFrames.count { - itemFrames[j].origin.x += offset - } - break - } + itemFrames[i].origin.x += xOffset } for i in 0 ..< self.itemNodes.count { @@ -208,14 +234,65 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard let currentLayout = self.currentLayout else { + return + } + + if scrollView.isDragging && !self.isPanning { + if let (currentCentralIndex, _) = self.centralIndexAndProgress { + self.centralIndexAndProgress = (currentCentralIndex, nil) + } + self.isPanning = true + + self.updateLayout(size: currentLayout, transition: .animated(duration: 0.4, curve: .spring)) + } + if scrollView.isDragging || scrollView.isDecelerating { + let position = scrollView.contentInset.left + scrollView.contentOffset.x + let index = max(0, min(self.items.count, Int(round(position / (itemBaseSize.width + spacing))))) + + if let (currentCentralIndex, _) = self.centralIndexAndProgress, currentCentralIndex != index { + self.centralIndexAndProgress = (index, nil) + self.itemChanged?(self.items[index].index!) + } + } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + guard let currentLayout = self.currentLayout else { + return + } + if let (centralIndex, progress) = self.centralIndexAndProgress { + let contentOffset = contentOffsetToCenterItem(index: centralIndex, progress: progress, contentInset: self.scrollNode.view.contentInset) + + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + if !decelerate { + self.isPanning = false + self.updateLayout(size: currentLayout, transition: transition) + } + transition.animateView { + self.scrollNode.view.contentOffset = contentOffset + } + } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + guard let currentLayout = self.currentLayout, !scrollView.isTracking else { + return + } + if let (centralIndex, progress) = self.centralIndexAndProgress { + let contentOffset = contentOffsetToCenterItem(index: centralIndex, progress: progress, contentInset: self.scrollNode.view.contentInset) + + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + if self.isPanning { + self.isPanning = false + self.updateLayout(size: currentLayout, transition: transition) + } + transition.animateView { + self.scrollNode.view.contentOffset = contentOffset + } + } } } diff --git a/TelegramUI/InstantImageGalleryItem.swift b/TelegramUI/InstantImageGalleryItem.swift index 24b1470dc0..c2ab6b8abe 100644 --- a/TelegramUI/InstantImageGalleryItem.swift +++ b/TelegramUI/InstantImageGalleryItem.swift @@ -8,6 +8,7 @@ import TelegramCore private struct InstantImageGalleryThumbnailItem: GalleryThumbnailItem { let account: Account let mediaReference: AnyMediaReference + let requestForIndex: () -> Int? var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) { if let imageReferene = mediaReference.concrete(TelegramMediaImage.self), let representation = largestImageRepresentation(imageReferene.media.representations) { @@ -26,6 +27,10 @@ private struct InstantImageGalleryThumbnailItem: GalleryThumbnailItem { return false } } + + var index: Int? { + return self.requestForIndex() + } } class InstantImageGalleryItem: GalleryItem { @@ -66,7 +71,13 @@ class InstantImageGalleryItem: GalleryItem { } func thumbnailItem() -> (Int64, GalleryThumbnailItem)? { - return (0, InstantImageGalleryThumbnailItem(account: self.account, mediaReference: imageReference.abstract)) + return (0, InstantImageGalleryThumbnailItem(account: self.account, mediaReference: imageReference.abstract, requestForIndex: { [weak self] in + if let strongSelf = self { + return 0 //return strongSelf..index + } else { + return nil + } + })) } } diff --git a/TelegramUI/InstantPageLayout.swift b/TelegramUI/InstantPageLayout.swift index 05ac5ee745..c718d07ca0 100644 --- a/TelegramUI/InstantPageLayout.swift +++ b/TelegramUI/InstantPageLayout.swift @@ -274,7 +274,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) var items: [InstantPageItem] = [] - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - safeInset * 2.0 - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -319,11 +319,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, block: InstantPageBlo var items: [InstantPageItem] = [] if autoplay { - let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - safeInset * 2.0 - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true) + let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true) items.append(mediaItem) } else { - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - safeInset * 2.0 - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, caption: caption.plainText), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) } diff --git a/TelegramUI/InstantPageLayoutSpacings.swift b/TelegramUI/InstantPageLayoutSpacings.swift index fcf047d89b..6d1271feca 100644 --- a/TelegramUI/InstantPageLayoutSpacings.swift +++ b/TelegramUI/InstantPageLayoutSpacings.swift @@ -12,8 +12,8 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> return 27.0 case (_, .title): return 20.0 - case (.title, .authorDate): - return 26.0 + case (.title, .authorDate), (.subtitle, .authorDate): + return 18.0 case (_, .authorDate): return 20.0 case (.title, .paragraph), (.authorDate, .paragraph): @@ -24,6 +24,8 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> return 31.0 case (.preformatted, .paragraph): return 19.0 + case (.paragraph, .paragraph): + return 25.0 case (_, .paragraph): return 20.0 case (.title, .list), (.authorDate, .list): diff --git a/TelegramUI/ItemListTextWithLabelItem.swift b/TelegramUI/ItemListTextWithLabelItem.swift index 05633b88a2..9d145c7fd2 100644 --- a/TelegramUI/ItemListTextWithLabelItem.swift +++ b/TelegramUI/ItemListTextWithLabelItem.swift @@ -176,7 +176,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { if let selected = item.selected { let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected) selectionNodeWidthAndApply = (selectionWidth, selectionApply) - leftOffset += selectionWidth - 24.0 + leftOffset += selectionWidth - 8.0 } let labelColor: UIColor diff --git a/TelegramUI/LegacySecureIdAttachmentMenu.swift b/TelegramUI/LegacySecureIdAttachmentMenu.swift index 7c6af1f920..2c83764d6a 100644 --- a/TelegramUI/LegacySecureIdAttachmentMenu.swift +++ b/TelegramUI/LegacySecureIdAttachmentMenu.swift @@ -12,23 +12,6 @@ enum SecureIdAttachmentMenuType { case selfie } -/* - @property (nonatomic, readonly) NSString *documentType; - @property (nonatomic, readonly) NSString *documentSubtype; - @property (nonatomic, readonly) NSString *issuingCountry; - @property (nonatomic, readonly) NSString *lastName; - @property (nonatomic, readonly) NSString *firstName; - @property (nonatomic, readonly) NSString *documentNumber; - @property (nonatomic, readonly) NSString *nationality; - @property (nonatomic, readonly) NSDate *birthDate; - @property (nonatomic, readonly) NSString *gender; - @property (nonatomic, readonly) NSDate *expiryDate; - @property (nonatomic, readonly) NSString *optional1; - @property (nonatomic, readonly) NSString *optional2; - - @property (nonatomic, readonly) NSString *mrz; - */ - struct SecureIdRecognizedDocumentData { let documentType: String? let documentSubtype: String? @@ -41,7 +24,7 @@ struct SecureIdRecognizedDocumentData { let expiryDate: Date? } -func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (ViewController) -> Void, validLayout: ContainerViewLayout, type: SecureIdAttachmentMenuType, completion: @escaping ([TelegramMediaResource], SecureIdRecognizedDocumentData?) -> Void) { +func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (ViewController) -> Void, validLayout: ContainerViewLayout, type: SecureIdAttachmentMenuType, recognizeDocumentData: Bool, completion: @escaping ([TelegramMediaResource], SecureIdRecognizedDocumentData?) -> Void) { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: validLayout) legacyController.statusBar.statusBarStyle = .Ignore @@ -49,6 +32,7 @@ func presentLegacySecureIdAttachmentMenu(account: Account, present: @escaping (V let emptyController = LegacyEmptyController(context: legacyController.context)! let navigationController = makeLegacyNavigationController(rootController: emptyController) navigationController.setNavigationBarHidden(true, animated: false) + navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) legacyController.bind(controller: navigationController) present(legacyController) @@ -67,7 +51,7 @@ 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 { + if case .generic = type, recognizeDocumentData { return recognizedResources(postbox: account.postbox, resources: resources) |> map { data -> ([TelegramMediaResource], SecureIdRecognizedDocumentData?) in return (resources, data) diff --git a/TelegramUI/Notices.swift b/TelegramUI/Notices.swift index 535c528edf..e366f90b30 100644 --- a/TelegramUI/Notices.swift +++ b/TelegramUI/Notices.swift @@ -140,7 +140,7 @@ private struct ApplicationSpecificNoticeKeys { } } -struct ApplicationSpecificNotice { +public struct ApplicationSpecificNotice { static func getBotPaymentLiability(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Bool in if let _ = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId)) as? ApplicationSpecificBoolNotice { @@ -173,6 +173,10 @@ struct ApplicationSpecificNotice { } } + public static func setSecretChatInlineBotUsage(transaction: Transaction) { + transaction.setNoticeEntry(key: ApplicationSpecificNoticeKeys.secretChatInlineBotUsage(), value: ApplicationSpecificBoolNotice()) + } + static func getSecretChatLinkPreviews(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> Bool? in if let value = transaction.getNoticeEntry(key: ApplicationSpecificNoticeKeys.secretChatLinkPreviews()) as? ApplicationSpecificVariantNotice { @@ -197,6 +201,10 @@ struct ApplicationSpecificNotice { } } + public static func setSecretChatLinkPreviews(transaction: Transaction, value: Bool) { + transaction.setNoticeEntry(key: ApplicationSpecificNoticeKeys.secretChatLinkPreviews(), value: ApplicationSpecificVariantNotice(value: value)) + } + static func secretChatLinkPreviewsKey() -> NoticeEntryKey { return ApplicationSpecificNoticeKeys.secretChatLinkPreviews() } diff --git a/TelegramUI/OverlayPlayerControlsNode.swift b/TelegramUI/OverlayPlayerControlsNode.swift index e82bb3c08d..95edeed7ac 100644 --- a/TelegramUI/OverlayPlayerControlsNode.swift +++ b/TelegramUI/OverlayPlayerControlsNode.swift @@ -2,6 +2,7 @@ import Foundation import AsyncDisplayKit import Display import Postbox +import TelegramCore import SwiftSignalKit private func generateBackground(theme: PresentationTheme) -> UIImage? { @@ -193,6 +194,11 @@ final class OverlayPlayerControlsNode: ASDisplayNode { strongSelf.currentItemId = value?.item.id strongSelf.scrubberNode.ignoreSeekId = nil } + if let itemId = value?.item.id as? PeerMessagesMediaPlaylistItemId, itemId.messageId.peerId.namespace != Namespaces.Peer.SecretChat { + strongSelf.shareNode.isHidden = false + } else { + strongSelf.shareNode.isHidden = true + } var displayData: SharedMediaPlaybackDisplayData? if let value = value { let isPaused: Bool diff --git a/TelegramUI/OverlayStatusController.swift b/TelegramUI/OverlayStatusController.swift index 483dae5f49..e211fca5eb 100644 --- a/TelegramUI/OverlayStatusController.swift +++ b/TelegramUI/OverlayStatusController.swift @@ -4,16 +4,20 @@ import Display import LegacyComponents enum OverlayStatusControllerType { + case loading case success case proxySettingSuccess } private enum OverlayStatusContentController { + case loading(TGProgressWindowController) case progress(TGProgressWindowController) case proxy(TGProxyWindowController) var view: UIView { switch self { + case let .loading(controller): + return controller.view case let .progress(controller): return controller.view case let .proxy(controller): @@ -23,6 +27,8 @@ private enum OverlayStatusContentController { func updateLayout() { switch self { + case let .loading(controller): + controller.updateLayout() case let .progress(controller): controller.updateLayout() case let .proxy(controller): @@ -30,14 +36,25 @@ private enum OverlayStatusContentController { } } - func dismiss(success: @escaping () -> Void) { + func show(success: @escaping () -> Void) { switch self { + case let .loading(controller): + controller.show(true) case let .progress(controller): controller.dismiss(success: success) case let .proxy(controller): controller.dismiss(success: success) } } + + func dismiss() { + switch self { + case let .loading(controller): + controller.dismiss(true) {} + default: + break + } + } } private final class OverlayStatusControllerNode: ViewControllerTracingNode { @@ -47,6 +64,8 @@ private final class OverlayStatusControllerNode: ViewControllerTracingNode { init(theme: PresentationTheme, type: OverlayStatusControllerType, dismissed: @escaping () -> Void) { self.dismissed = dismissed switch type { + case .loading: + self.contentController = .loading(TGProgressWindowController(light: theme.actionSheet.backgroundType == .light)) case .success: self.contentController = .progress(TGProgressWindowController(light: theme.actionSheet.backgroundType == .light)) case .proxySettingSuccess: @@ -67,10 +86,15 @@ private final class OverlayStatusControllerNode: ViewControllerTracingNode { } func begin() { - self.contentController.dismiss(success: { [weak self] in + self.contentController.show(success: { [weak self] in self?.dismissed() }) } + + func dismiss() { + self.contentController.dismiss() + self.dismissed() + } } final class OverlayStatusController: ViewController { @@ -98,7 +122,7 @@ final class OverlayStatusController: ViewController { override func loadDisplayNode() { self.displayNode = OverlayStatusControllerNode(theme: self.theme, type: self.type, dismissed: { [weak self] in - self?.dismiss() + self?.presentingViewController?.dismiss(animated: false, completion: nil) }) self.displayNodeDidLoad() @@ -119,7 +143,7 @@ final class OverlayStatusController: ViewController { } } - override func dismiss(completion: (() -> Void)? = nil) { - self.presentingViewController?.dismiss(animated: false, completion: nil) + func dismiss() { + self.controllerNode.dismiss() } } diff --git a/TelegramUI/PeerAvatarImageGalleryItem.swift b/TelegramUI/PeerAvatarImageGalleryItem.swift index 8f424dd74a..2147daba38 100644 --- a/TelegramUI/PeerAvatarImageGalleryItem.swift +++ b/TelegramUI/PeerAvatarImageGalleryItem.swift @@ -23,6 +23,14 @@ private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { let account: Account let peer: Peer let content: PeerAvatarImageGalleryThumbnailContent + private let requestForIndex: () -> Int? + + init(account: Account, peer: Peer, content: PeerAvatarImageGalleryThumbnailContent, requestForIndex: @escaping () -> Int?) { + self.account = account + self.peer = peer + self.content = content + self.requestForIndex = requestForIndex + } var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) { if let representation = largestImageRepresentation(self.content.representations) { @@ -44,6 +52,10 @@ private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { return false } } + + var index: Int? { + return self.requestForIndex() + } } class PeerAvatarImageGalleryItem: GalleryItem { @@ -98,7 +110,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { content = .standaloneImage(image.representations) } - return (0, PeerAvatarImageGalleryThumbnailItem(account: self.account, peer: self.peer, content: content)) + return (0, PeerAvatarImageGalleryThumbnailItem(account: self.account, peer: self.peer, content: content, requestForIndex: { return 0 })) } } diff --git a/TelegramUI/PeerMediaCollectionEmptyNode.swift b/TelegramUI/PeerMediaCollectionEmptyNode.swift index 739fddf809..be65603948 100644 --- a/TelegramUI/PeerMediaCollectionEmptyNode.swift +++ b/TelegramUI/PeerMediaCollectionEmptyNode.swift @@ -9,7 +9,7 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode { private let strings: PresentationStrings private let iconNode: ASImageNode - private let textNode: ASTextNode + private let textNode: ImmediateTextNode private let activityIndicator: ActivityIndicator @@ -39,10 +39,12 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode { self.iconNode.displayWithoutProcessing = true self.iconNode.displaysAsynchronously = false - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 0 + self.textNode.textAlignment = .center self.textNode.isLayerBacked = true self.textNode.displaysAsynchronously = false - self.textNode.isHidden = true + self.textNode.isHidden = false self.activityIndicator = ActivityIndicator(type: .custom(theme.list.itemSecondaryTextColor, 22.0, 2.0), speed: .regular) @@ -81,7 +83,36 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode { func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) { let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) - let textSize = self.textNode.measure(CGSize(width: size.width - 20.0, height: size.height)) + if interfaceState.theme !== self.theme { + self.theme = interfaceState.theme + self.backgroundColor = theme.list.plainBackgroundColor + let icon: UIImage? + let text: NSAttributedString + switch mode { + case .photoOrVideo: + icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/ImagesAndVideo")?.precomposed() + let string1 = NSAttributedString(string: strings.SharedMedia_EmptyTitle, font: Font.medium(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + let string2 = NSAttributedString(string: "\n\n\(strings.SharedMedia_EmptyText)", font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + let string = NSMutableAttributedString() + string.append(string1) + string.append(string2) + text = string + case .file: + icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Files")?.precomposed() + text = NSAttributedString(string: strings.SharedMedia_EmptyFilesText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + case .webpage: + icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Links")?.precomposed() + text = NSAttributedString(string: strings.SharedMedia_EmptyLinksText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + case .music: + icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Music")?.precomposed() + text = NSAttributedString(string: strings.SharedMedia_EmptyMusicText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) + } + self.iconNode.image = icon + self.textNode.attributedText = text + activityIndicator.type = .custom(theme.list.itemSecondaryTextColor, 22.0, 2.0) + } + + let textSize = self.textNode.updateLayout(CGSize(width: size.width - 20.0, height: size.height)) if let image = self.iconNode.image { let imageSpacing: CGFloat = 22.0 @@ -94,34 +125,5 @@ final class PeerMediaCollectionEmptyNode: ASDisplayNode { let activitySize = self.activityIndicator.measure(size) transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: displayRect.minX + floor((displayRect.width - activitySize.width) / 2.0), y: displayRect.minY + floor((displayRect.height - activitySize.height) / 2.0)), size: activitySize)) - - if interfaceState.theme !== self.theme { - self.theme = interfaceState.theme - self.backgroundColor = theme.list.plainBackgroundColor - let icon: UIImage? - let text: NSAttributedString - switch mode { - case .photoOrVideo: - icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/ImagesAndVideo")?.precomposed() - let string1 = NSAttributedString(string: strings.SharedMedia_EmptyTitle, font: Font.medium(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) - let string2 = NSAttributedString(string: "\n\n\(strings.SharedMedia_EmptyText)", font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) - let string = NSMutableAttributedString() - string.append(string1) - string.append(string2) - text = string - case .file: - icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Files")?.precomposed() - text = NSAttributedString(string: strings.SharedMedia_EmptyFilesText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) - case .webpage: - icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Links")?.precomposed() - text = NSAttributedString(string: strings.SharedMedia_EmptyLinksText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) - case .music: - icon = UIImage(bundleImageName: "Media Grid/Empty List Placeholders/Music")?.precomposed() - text = NSAttributedString(string: strings.SharedMedia_EmptyMusicText, font: Font.regular(16.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center) - } - self.iconNode.image = icon - self.textNode.attributedText = text - activityIndicator.type = .custom(theme.list.itemSecondaryTextColor, 22.0, 2.0) - } } } diff --git a/TelegramUI/SecureIdAuthControllerNode.swift b/TelegramUI/SecureIdAuthControllerNode.swift index d10562f912..f2433f7db8 100644 --- a/TelegramUI/SecureIdAuthControllerNode.swift +++ b/TelegramUI/SecureIdAuthControllerNode.swift @@ -138,9 +138,11 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { } acceptNodeTransition.updateFrame(node: self.acceptNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - acceptHeight), size: CGSize(width: layout.size.width, height: acceptHeight))) + var minContentSpacing: CGFloat = 10.0 if self.acceptNode.supernode != nil { - footerHeight += acceptHeight + footerHeight += (acceptHeight - layout.intrinsicInsets.bottom) contentSpacing = 25.0 + minContentSpacing = 25.0 } else { if self.contentNode is SecureIdAuthListContentNode { contentSpacing = 16.0 @@ -164,7 +166,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { let contentLayout = contentNode.updateLayout(width: layout.size.width, transition: contentNodeTransition) let headerHeight: CGFloat - if self.contentNode is SecureIdAuthPasswordOptionContentNode && headerLayout.expanded + contentLayout.height + 10.0 + 14.0 + 16.0 > contentRect.height { + if self.contentNode is SecureIdAuthPasswordOptionContentNode && headerLayout.expanded + contentLayout.height + minContentSpacing + 14.0 + 16.0 > contentRect.height { headerHeight = headerLayout.compact headerLayout.apply(false) } else { @@ -172,7 +174,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { headerLayout.apply(true) } - contentSpacing = max(10.0, min(contentSpacing, contentRect.height - (headerHeight + contentLayout.height + 10.0 - 14.0 - 16.0))) + contentSpacing = max(minContentSpacing, min(contentSpacing, contentRect.height - (headerHeight + contentLayout.height + minContentSpacing - 14.0 - 16.0))) let boundingHeight = headerHeight + contentLayout.height + contentSpacing @@ -534,11 +536,11 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { break } - let controller = SecureIdDocumentTypeSelectionController(theme: self.presentationData.theme, strings: self.presentationData.strings, field: field, currentValues: formData.values, completion: { [weak self] requestedData in + let completionImpl: (SecureIdDocumentFormRequestedData) -> Void = { [weak self] requestedData in guard let strongSelf = self, let state = strongSelf.state, let verificationState = state.verificationState, case let .verified(context) = verificationState, let formData = form.formData else { return } - + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: requestedData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, values: formData.values, updatedValues: { values in var keys: [SecureIdValueKey] = [] switch requestedData { @@ -559,8 +561,15 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { } updatedValues(keys, values) }), nil) - }) - self.interaction.present(controller, nil) + } + + let itemsForField = documentSelectionItemsForField(field: field, strings: self.presentationData.strings) + if itemsForField.count == 1 { + completionImpl(itemsForField[0].1) + } else { + let controller = SecureIdDocumentTypeSelectionController(theme: self.presentationData.theme, strings: self.presentationData.strings, field: field, currentValues: formData.values, completion: completionImpl) + self.interaction.present(controller, nil) + } } private func presentPlaintextSelection(type: SecureIdPlaintextFormType) { diff --git a/TelegramUI/SecureIdAuthFormFieldNode.swift b/TelegramUI/SecureIdAuthFormFieldNode.swift index 2f36583a5f..9787226aac 100644 --- a/TelegramUI/SecureIdAuthFormFieldNode.swift +++ b/TelegramUI/SecureIdAuthFormFieldNode.swift @@ -155,8 +155,15 @@ func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [Se if requestedValues.identity.documents.isEmpty { result.append(.identity(personalDetails: ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames), document: nil, selfie: false, translation: false)) } else { - for document in requestedValues.identity.documents { - result.append(.identity(personalDetails: requestedValues.identity.details ? ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames) : nil, document: document, selfie: requestedValues.identity.selfie, translation: requestedValues.identity.translation)) + if requestedValues.identity.details && requestedValues.identity.documents.count == 1 { + result.append(.identity(personalDetails: requestedValues.identity.details ? ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames) : nil, document: requestedValues.identity.documents.first, selfie: requestedValues.identity.selfie, translation: requestedValues.identity.translation)) + } else { + if requestedValues.identity.details { + result.append(.identity(personalDetails: ParsedRequestedPersonalDetails(nativeNames: requestedValues.identity.nativeNames), document: nil, selfie: requestedValues.identity.selfie, translation: false)) + } + for document in requestedValues.identity.documents { + result.append(.identity(personalDetails: nil, document: document, selfie: requestedValues.identity.selfie, translation: requestedValues.identity.translation)) + } } } } @@ -164,8 +171,15 @@ func parseRequestedFormFields(_ types: [SecureIdRequestedFormField], values: [Se if requestedValues.address.documents.isEmpty { result.append(.address(addressDetails: true, document: nil, translation: false)) } else { - for document in requestedValues.address.documents { - result.append(.address(addressDetails: requestedValues.address.details, document: document, translation: requestedValues.address.translation)) + if requestedValues.address.details && requestedValues.address.documents.count == 1 { + result.append(.address(addressDetails: true, document: requestedValues.address.documents.first, translation: requestedValues.address.translation)) + } else { + if requestedValues.address.details { + result.append(.address(addressDetails: true, document: nil, translation: false)) + } + for document in requestedValues.address.documents { + result.append(.address(addressDetails: false, document: document, translation: requestedValues.address.translation)) + } } } } @@ -375,22 +389,144 @@ private func countryName(code: String, strings: PresentationStrings) -> String { return AuthorizationSequenceCountrySelectionController.lookupCountryNameById(code, strings: strings) ?? "" } +private func stringForDocumentType(_ type: SecureIdRequestedIdentityDocument, strings: PresentationStrings) -> String { + switch type { + case .passport: + return strings.Passport_Identity_TypePassport + case .internalPassport: + return strings.Passport_Identity_TypeInternalPassport + case .idCard: + return strings.Passport_Identity_TypeIdentityCard + case .driversLicense: + return strings.Passport_Identity_TypeDriversLicense + } +} + +private func placeholderForDocumentType(_ type: SecureIdRequestedIdentityDocument, strings: PresentationStrings) -> String { + switch type { + case .passport: + return strings.Passport_Identity_TypePassportUploadScan + case .internalPassport: + return strings.Passport_Identity_TypeInternalPassportUploadScan + case .idCard: + return strings.Passport_Identity_TypeIdentityCardUploadScan + case .driversLicense: + return strings.Passport_Identity_TypeDriversLicenseUploadScan + } +} + +private func stringForDocumentType(_ type: SecureIdRequestedAddressDocument, strings: PresentationStrings) -> String { + switch type { + case .rentalAgreement: + return strings.Passport_Address_TypeRentalAgreement + case .bankStatement: + return strings.Passport_Address_TypeBankStatement + case .passportRegistration: + return strings.Passport_Address_TypePassportRegistration + case .temporaryRegistration: + return strings.Passport_Address_TypeTemporaryRegistration + case .utilityBill: + return strings.Passport_Address_TypeUtilityBill + } +} + +private func placeholderForDocumentType(_ type: SecureIdRequestedAddressDocument, strings: PresentationStrings) -> String { + switch type { + case .rentalAgreement: + return strings.Passport_Address_TypeRentalAgreementUploadScan + case .bankStatement: + return strings.Passport_Address_TypeBankStatementUploadScan + case .passportRegistration: + return strings.Passport_Address_TypePassportRegistrationUploadScan + case .temporaryRegistration: + return strings.Passport_Address_TypeTemporaryRegistrationUploadScan + case .utilityBill: + return strings.Passport_Address_TypeUtilityBillUploadScan + } +} + +private func placeholderForDocumentTypes(_ types: [SecureIdRequestedIdentityDocument], strings: PresentationStrings) -> String { + func stringForDocumentType(_ type: SecureIdRequestedIdentityDocument, strings: PresentationStrings) -> String { + switch type { + case .passport: + return strings.Passport_Identity_OneOfTypePassport + case .internalPassport: + return strings.Passport_Identity_OneOfTypeInternalPassport + case .idCard: + return strings.Passport_Identity_OneOfTypeIdentityCard + case .driversLicense: + return strings.Passport_Identity_OneOfTypeDriversLicense + } + } + + var string = "" + for i in 0 ..< types.count { + let type = types[i] + string.append(stringForDocumentType(type, strings: strings)) + if i < types.count - 2 { + string.append(strings.Passport_FieldOneOf_Delimeter) + } else if i < types.count - 1 { + string.append(strings.Passport_FieldOneOf_FinalDelimeter) + } + } + + return strings.Passport_Identity_UploadOneOfScan(string).0 +} + +private func placeholderForDocumentTypes(_ types: [SecureIdRequestedAddressDocument], strings: PresentationStrings) -> String { + func stringForDocumentType(_ type: SecureIdRequestedAddressDocument, strings: PresentationStrings) -> String { + switch type { + case .rentalAgreement: + return strings.Passport_Address_OneOfTypeRentalAgreement + case .bankStatement: + return strings.Passport_Address_OneOfTypeBankStatement + case .passportRegistration: + return strings.Passport_Address_OneOfTypePassportRegistration + case .temporaryRegistration: + return strings.Passport_Address_OneOfTypeTemporaryRegistration + case .utilityBill: + return strings.Passport_Address_OneOfTypeUtilityBill + } + } + + var string = "" + for i in 0 ..< types.count { + let type = types[i] + string.append(stringForDocumentType(type, strings: strings)) + if i < types.count - 2 { + string.append(strings.Passport_FieldOneOf_Delimeter) + } else if i < types.count - 1 { + string.append(strings.Passport_FieldOneOf_FinalDelimeter) + } + } + + return strings.Passport_Address_UploadOneOfScan(string).0 +} + private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: PresentationStrings, values: [SecureIdValueWithContext]) -> (String, String) { - let title: String - let placeholder: String + var title: String + var placeholder: String var text: String = "" switch field { case let .identity(personalDetails, document, _, _): if let document = document { title = strings.Passport_FieldIdentity + placeholder = strings.Passport_FieldIdentityUploadHelp + switch document { case let .just(type): + title = stringForDocumentType(type, strings: strings) + placeholder = placeholderForDocumentType(type, strings: strings) break case let .oneOf(types): + let typesArray = Array(types) + if typesArray.count == 2 { + title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0 + } + placeholder = placeholderForDocumentTypes(typesArray, strings: strings) break } - placeholder = strings.Passport_FieldIdentityUploadHelp } else { title = strings.Passport_Identity_TypePersonalDetails placeholder = strings.Passport_FieldIdentityDetailsHelp @@ -407,13 +543,20 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: case let .address(addressDetails, document, _): if let document = document { title = strings.Passport_FieldAddress + placeholder = strings.Passport_FieldAddressUploadHelp switch document { case let .just(type): + title = stringForDocumentType(type, strings: strings) + placeholder = placeholderForDocumentType(type, strings: strings) break case let .oneOf(types): + let typesArray = Array(types) + if typesArray.count == 2 { + title = strings.Passport_FieldOneOf_Or(stringForDocumentType(typesArray[0], strings: strings), stringForDocumentType(typesArray[1], strings: strings)).0 + } + placeholder = placeholderForDocumentTypes(typesArray, strings: strings) break } - placeholder = strings.Passport_FieldAddressUploadHelp } else { title = strings.Passport_FieldAddress placeholder = strings.Passport_FieldAddressHelp @@ -472,6 +615,8 @@ private func extractValueAdditionalData(_ value: SecureIdValue) -> ValueAddition case let .driversLicense(value): data.selfie = value.selfieDocument != nil data.translation = !value.translations.isEmpty + case let .utilityBill(value): + data.translation = !value.translations.isEmpty case let .rentalAgreement(value): data.translation = !value.translations.isEmpty case let .bankStatement(value): @@ -533,7 +678,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.isLayerBacked = true - self.textNode.maximumNumberOfLines = 1 + self.textNode.maximumNumberOfLines = 4 self.disclosureNode = ASImageNode() self.disclosureNode.isLayerBacked = true @@ -694,16 +839,15 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { self.validLayout = (width, hasPrevious, hasNext) let leftInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0 - let height: CGFloat = 64.0 let rightTextInset = rightInset + 24.0 - let titleTextSpacing: CGFloat = 5.0 let titleSize = self.titleNode.updateLayout(CGSize(width: width - leftInset - rightTextInset, height: 100.0)) let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightTextInset, height: 100.0)) + let height = max(64.0, 11.0 + titleSize.height + titleTextSpacing + textSize.height + 11.0) - let textOrigin = floor((height - titleSize.height - titleTextSpacing - textSize.height) / 2.0) + let textOrigin: CGFloat = 11.0 let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textOrigin), size: titleSize) self.titleNode.frame = titleFrame let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleTextSpacing), size: textSize) diff --git a/TelegramUI/SecureIdDocumentFormController.swift b/TelegramUI/SecureIdDocumentFormController.swift index ff475b063b..cf4f02057b 100644 --- a/TelegramUI/SecureIdDocumentFormController.swift +++ b/TelegramUI/SecureIdDocumentFormController.swift @@ -81,7 +81,13 @@ final class SecureIdDocumentFormController: FormController Void fileprivate let openDocument: (SecureIdVerificationDocument) -> Void fileprivate let updateText: (SecureIdDocumentFormTextField, String) -> Void + fileprivate let selectNextInputItem: (SecureIdDocumentFormEntry) -> Void fileprivate let activateSelection: (SecureIdDocumentFormSelectionField) -> Void fileprivate let deleteValue: () -> Void - fileprivate init(account: Account, context: SecureIdAccessContext, addFile: @escaping (AddFileTarget) -> Void, openDocument: @escaping (SecureIdVerificationDocument) -> Void, updateText: @escaping (SecureIdDocumentFormTextField, String) -> Void, activateSelection: @escaping (SecureIdDocumentFormSelectionField) -> Void, deleteValue: @escaping () -> Void) { + fileprivate init(account: Account, context: SecureIdAccessContext, addFile: @escaping (AddFileTarget) -> Void, openDocument: @escaping (SecureIdVerificationDocument) -> Void, updateText: @escaping (SecureIdDocumentFormTextField, String) -> Void, selectNextInputItem: @escaping (SecureIdDocumentFormEntry) -> Void, activateSelection: @escaping (SecureIdDocumentFormSelectionField) -> Void, deleteValue: @escaping () -> Void) { self.account = account self.context = context self.addFile = addFile self.openDocument = openDocument self.updateText = updateText + self.selectNextInputItem = selectNextInputItem self.activateSelection = activateSelection self.deleteValue = deleteValue } @@ -817,6 +819,8 @@ struct SecureIdDocumentFormState: FormControllerInnerState { break } + var documentsRequired = false + switch self.documentState { case let .identity(identity): if !identity.isComplete() { @@ -826,6 +830,9 @@ struct SecureIdDocumentFormState: FormControllerInnerState { if !address.isComplete() { return .saveNotAvailable } + if address.document != nil { + documentsRequired = true + } } func isDocumentReady(_ document: SecureIdVerificationDocument?) -> Bool { @@ -869,12 +876,18 @@ struct SecureIdDocumentFormState: FormControllerInnerState { return .saveNotAvailable } } + if documentsRequired && self.documents.isEmpty { + return .saveNotAvailable + } for document in self.translations { guard isDocumentReady(document) else { return .saveNotAvailable } } + if self.translationsRequired && self.translations.isEmpty { + return .saveNotAvailable + } return .saveAvailable } @@ -1569,18 +1582,26 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { case let .identifier(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_DocumentNumber, text: value, placeholder: strings.Passport_Identity_DocumentNumberPlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.identifier, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .firstName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_Name, text: value, placeholder: strings.Passport_Identity_NamePlaceholder, type: .latin, error: error, textUpdated: { text in params.updateText(.firstName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .middleName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_MiddleName, text: value, placeholder: strings.Passport_Identity_MiddleNamePlaceholder, type: .latin, error: error, textUpdated: { text in params.updateText(.middleName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .lastName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, type: .latin, error: error, textUpdated: { text in params.updateText(.lastName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .nativeInfoHeader(language): let title: String @@ -1593,14 +1614,20 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { case let .nativeFirstName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_Name, text: value, placeholder: strings.Passport_Identity_NamePlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.nativeFirstName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .nativeMiddleName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_MiddleName, text: value, placeholder: strings.Passport_Identity_MiddleNamePlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.nativeMiddleName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .nativeLastName(value, error): return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.nativeLastName, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .nativeInfo(language, countryCode): let text: String @@ -1647,7 +1674,7 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { params.activateSelection(.date(value?.timestamp, .birthdate)) }) case let .expiryDate(value, error): - return FormControllerDetailActionItem(title: strings.Passport_Identity_ExpiryDate, text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? "", placeholder: strings.Passport_Identity_ExpiryDatePlaceholder, error: error, activated: { + return FormControllerDetailActionItem(title: strings.Passport_Identity_ExpiryDate, text: value.flatMap({ stringForDate(timestamp: $0.timestamp, strings: strings) }) ?? strings.Passport_Identity_ExpiryDateNone, placeholder: strings.Passport_Identity_ExpiryDatePlaceholder, error: error, activated: { params.activateSelection(.date(value?.timestamp, .expiry)) }) case .deleteDocument: @@ -1657,22 +1684,32 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { case let .street1(value, error): return FormControllerTextInputItem(title: strings.Passport_Address_Street, text: value, placeholder: strings.Passport_Address_Street1Placeholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.street1, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .street2(value, error): return FormControllerTextInputItem(title: "", text: value, placeholder: strings.Passport_Address_Street2Placeholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.street2, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .city(value, error): return FormControllerTextInputItem(title: strings.Passport_Address_City, text: value, placeholder: strings.Passport_Address_CityPlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.city, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .state(value, error): return FormControllerTextInputItem(title: strings.Passport_Address_Region, text: value, placeholder: strings.Passport_Address_RegionPlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in params.updateText(.state, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case let .postcode(value, error): return FormControllerTextInputItem(title: strings.Passport_Address_Postcode, text: value, placeholder: strings.Passport_Address_PostcodePlaceholder, type: .regular(capitalization: .allCharacters, autocorrection: false), error: error, textUpdated: { text in params.updateText(.postcode, text) + }, returnPressed: { + params.selectNextInputItem(self) }) case .requestedDocumentsHeader: return FormControllerHeaderItem(text: strings.Passport_Identity_FilesTitle) @@ -1852,6 +1889,21 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode Bool { + guard var innerState = self.innerState else { + return false + } + guard let values = innerState.makeValues(), !values.isEmpty else { + return false + } + + for (key, value) in values { + if innerState.previousValues[key]?.value != value { + return true + } + } + for (key, _) in innerState.previousValues { + if values[key] == nil { + return true + } + } + + return false + } + func save() { guard var innerState = self.innerState else { return @@ -2258,33 +2345,55 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode deliverOnMainQueue).start(error: { [weak self] error in - if let strongSelf = self { - guard var innerState = strongSelf.innerState else { - return - } - guard case .deleting = innerState.actionState else { - return - } - innerState.actionState = .none - strongSelf.updateInnerState(transition: .immediate, with: innerState) - } - }, completed: { [weak self] in - if let strongSelf = self { - strongSelf.completedWithValues?([]) - } - })) + let controller = ActionSheetController(presentationTheme: theme) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: self.strings.Passport_DeleteDocumentConfirmation), + ActionSheetButtonItem(title: strings.Passport_DeleteDocument, color: .destructive, action: { [weak self] in + dismissAction() + guard let strongSelf = self else { + return + } + guard var innerState = strongSelf.innerState, !innerState.previousValues.isEmpty else { + return + } + innerState.actionState = .deleting + strongSelf.updateInnerState(transition: .immediate, with: innerState) + + strongSelf.actionDisposable.set((deleteSecureIdValues(network: strongSelf.account.network, keys: Set(innerState.previousValues.keys)) + |> deliverOnMainQueue).start(error: { error in + guard let strongSelf = self else { + return + } + guard var innerState = strongSelf.innerState else { + return + } + guard case .deleting = innerState.actionState else { + return + } + innerState.actionState = .none + strongSelf.updateInnerState(transition: .immediate, with: innerState) + }, completed: { [weak self] in + self?.completedWithValues?([]) + })) + }) + ]), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: self.strings.Common_Cancel, action: { + dismissAction() + })]) + ]) + self.view.endEditing(true) + self.present(controller, nil) } private func presentGallery(document: SecureIdVerificationDocument) { diff --git a/TelegramUI/SecureIdDocumentTypeSelectionController.swift b/TelegramUI/SecureIdDocumentTypeSelectionController.swift index a2bf6500a3..ee4d2e1016 100644 --- a/TelegramUI/SecureIdDocumentTypeSelectionController.swift +++ b/TelegramUI/SecureIdDocumentTypeSelectionController.swift @@ -32,7 +32,7 @@ private func stringForDocumentType(_ type: SecureIdRequestedAddressDocument, str } } -private func itemsForField(field: SecureIdParsedRequestedFormField, strings: PresentationStrings) -> [(String, SecureIdDocumentFormRequestedData)] { +func documentSelectionItemsForField(field: SecureIdParsedRequestedFormField, strings: PresentationStrings) -> [(String, SecureIdDocumentFormRequestedData)] { switch field { case let .identity(personalDetails, document, selfie, translation): var result: [(String, SecureIdDocumentFormRequestedData)] = [] @@ -89,7 +89,7 @@ final class SecureIdDocumentTypeSelectionController: ActionSheetController { self._ready.set(.single(true)) var items: [ActionSheetItem] = [] - for (title, data) in itemsForField(field: field, strings: strings) { + for (title, data) in documentSelectionItemsForField(field: field, strings: strings) { items.append(ActionSheetButtonItem(title: title, action: { [weak self] in self?.dismissAnimated() completion(data) diff --git a/TelegramUI/SecureIdPlaintextFormControllerNode.swift b/TelegramUI/SecureIdPlaintextFormControllerNode.swift index f8eb355011..49848f2dd1 100644 --- a/TelegramUI/SecureIdPlaintextFormControllerNode.swift +++ b/TelegramUI/SecureIdPlaintextFormControllerNode.swift @@ -511,16 +511,22 @@ enum SecureIdPlaintextFormEntry: FormControllerEntry { case let .numberCode(code): return FormControllerTextInputItem(title: strings.ChangePhoneNumberCode_CodePlaceholder, text: code, placeholder: strings.ChangePhoneNumberCode_CodePlaceholder, type: .number, textUpdated: { value in params.updateTextField(.code, value) + }, returnPressed: { + }) case .numberVerifyInfo: return FormControllerTextItem(text: strings.ChangePhoneNumberCode_Help) case let .emailAddress(address): return FormControllerTextInputItem(title: strings.TwoStepAuth_Email, text: address, placeholder: strings.TwoStepAuth_Email, type: .email, textUpdated: { value in params.updateTextField(.email, value) + }, returnPressed: { + }) case let .emailCode(code): return FormControllerTextInputItem(title: strings.TwoStepAuth_RecoveryCode, text: code, placeholder: strings.TwoStepAuth_RecoveryCode, type: .number, textUpdated: { value in params.updateTextField(.code, value) + }, returnPressed: { + }) case .emailVerifyInfo: return FormControllerTextItem(text: strings.TwoStepAuth_EmailSent) diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 3e7d5a35b9..37df5063a5 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -436,11 +436,14 @@ public func settingsController(account: Account, accountManager: AccountManager) let archivedPacks = Promise<[ArchivedStickerPackItem]?>() let openFaq: (Promise) -> Void = { resolvedUrl in + let controller = OverlayStatusController(theme: account.telegramApplicationContext.currentPresentationData.with { $0 }.theme, type: .loading) + presentControllerImpl?(controller, nil) let _ = (resolvedUrl.get() |> take(1) - |> deliverOnMainQueue).start(next: { resolvedUrl in + |> deliverOnMainQueue).start(next: { [weak controller] resolvedUrl in + controller?.dismiss() + openResolvedUrl(resolvedUrl, account: account, navigationController: getNavigationControllerImpl?(), openPeer: { peer, navigation in - }, present: { controller, arguments in pushControllerImpl?(controller) }, dismissInput: {}) diff --git a/TelegramUI/ShareController.swift b/TelegramUI/ShareController.swift index 30c3338758..6c55fc271c 100644 --- a/TelegramUI/ShareController.swift +++ b/TelegramUI/ShareController.swift @@ -260,7 +260,8 @@ public final class ShareController: ViewController { switch entry { case let .MessageEntry(_, _, _, _, _, renderedPeer, _): if let peer = renderedPeer.chatMainPeer, peer.id != accountPeer.id { - if canSendMessagesToPeer(peer) { + if let user = peer as? TelegramUser, (user.firstName ?? "").isEmpty, (user.lastName ?? "").isEmpty { + } else if canSendMessagesToPeer(peer) { peers.append(peer) } } diff --git a/TelegramUI/StorageUsageController.swift b/TelegramUI/StorageUsageController.swift index ea5b0d5c7d..febef4646a 100644 --- a/TelegramUI/StorageUsageController.swift +++ b/TelegramUI/StorageUsageController.swift @@ -270,7 +270,9 @@ func storageUsageController(account: Account) -> ViewController { ]) presentControllerImpl?(controller) }, openClearAll: { - let _ = (statsPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak statsPromise] result in + let _ = (statsPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak statsPromise] result in if let result = result, case let .result(stats) = result { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationTheme: presentationData.theme) @@ -356,6 +358,7 @@ func storageUsageController(account: Account) -> ViewController { } if otherSize.1 != 0 { + totalSize += otherSize.1 let index = itemIndex items.append(ActionSheetCheckboxItem(title: presentationData.strings.Localization_LanguageOther, label: dataSizeString(Int(otherSize.1)), value: true, action: { value in toggleCheck(nil, index) diff --git a/TelegramUI/TelegramController.swift b/TelegramUI/TelegramController.swift index 3109252df1..eaf0e94e7e 100644 --- a/TelegramUI/TelegramController.swift +++ b/TelegramUI/TelegramController.swift @@ -45,6 +45,7 @@ public class TelegramController: ViewController { private(set) var playlistStateAndType: (SharedMediaPlaylistItem, MusicPlaybackSettingsOrder, MediaManagerPlayerType)? var tempVoicePlaylistEnded: (() -> Void)? + var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? @@ -80,25 +81,36 @@ public class TelegramController: ViewController { if enableMediaAccessoryPanel { self.mediaStatusDisposable = (account.telegramApplicationContext.mediaManager.globalMediaPlayerState |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in - if let strongSelf = self, strongSelf.enableMediaAccessoryPanel { - if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.0.item) || - strongSelf.playlistStateAndType?.1 != playlistStateAndType?.0.order || strongSelf.playlistStateAndType?.2 != playlistStateAndType?.1 { - if let playlistStateAndType = playlistStateAndType { - strongSelf.playlistStateAndType = (playlistStateAndType.0.item, playlistStateAndType.0.order, playlistStateAndType.1) - } else { - var voiceEnded = false - if strongSelf.playlistStateAndType?.2 == .voice { - voiceEnded = true - } - strongSelf.playlistStateAndType = nil - if voiceEnded { - strongSelf.tempVoicePlaylistEnded?() - } - } - strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + if let strongSelf = self, strongSelf.enableMediaAccessoryPanel { + if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.0.item) || + strongSelf.playlistStateAndType?.1 != playlistStateAndType?.0.order || strongSelf.playlistStateAndType?.2 != playlistStateAndType?.1 { + var previousVoiceItem: SharedMediaPlaylistItem? + if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.2 == .voice { + previousVoiceItem = playlistStateAndType.0 } + + var updatedVoiceItem: SharedMediaPlaylistItem? + if let playlistStateAndType = playlistStateAndType, playlistStateAndType.1 == .voice { + updatedVoiceItem = playlistStateAndType.0.item + } + + strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) + if let playlistStateAndType = playlistStateAndType { + strongSelf.playlistStateAndType = (playlistStateAndType.0.item, playlistStateAndType.0.order, playlistStateAndType.1) + } else { + var voiceEnded = false + if strongSelf.playlistStateAndType?.2 == .voice { + voiceEnded = true + } + strongSelf.playlistStateAndType = nil + if voiceEnded { + strongSelf.tempVoicePlaylistEnded?() + } + } + strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } - }) + } + }) } if let liveLocationManager = account.telegramApplicationContext.liveLocationManager { diff --git a/TelegramUI/ThemeGridControllerNode.swift b/TelegramUI/ThemeGridControllerNode.swift index 62fcb4d66e..3c23ce19fb 100644 --- a/TelegramUI/ThemeGridControllerNode.swift +++ b/TelegramUI/ThemeGridControllerNode.swift @@ -232,15 +232,15 @@ final class ThemeGridControllerNode: ASDisplayNode { let spacing = floor((layout.size.width - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) - let buttonInset: CGFloat = 24.0 + 44.0 + 16.0 + let buttonInset: CGFloat = 32.0 + 44.0 + 32.0 let buttonOffset = buttonInset + 10.0 - transition.updateFrame(node: self.customWallpaperBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: 16.0 + 44.0 + 16.0 + 500.0))) - transition.updateFrame(node: self.customWallpaperSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 16.0 + 44.0 + 16.0 - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.customWallpaperButtonTopSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 16.0 - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.customWallpaperButtonBottomSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 16.0 + 44.0), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.customWallpaperButtonBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 16.0), size: CGSize(width: layout.size.width, height: 44.0))) - transition.updateFrame(node: self.customWallpaperButton, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 16.0), size: CGSize(width: layout.size.width, height: 44.0))) + transition.updateFrame(node: self.customWallpaperBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: 32.0 + 44.0 + 32.0 + 500.0))) + transition.updateFrame(node: self.customWallpaperSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 32.0 + 44.0 + 32.0 - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.customWallpaperButtonTopSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 32.0 - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.customWallpaperButtonBottomSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 32.0 + 44.0), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.customWallpaperButtonBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 32.0), size: CGSize(width: layout.size.width, height: 44.0))) + transition.updateFrame(node: self.customWallpaperButton, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + 32.0), size: CGSize(width: layout.size.width, height: 44.0))) insets.top += spacing + buttonInset diff --git a/TelegramUI/TonePlayer.swift b/TelegramUI/TonePlayer.swift index 5f66154579..553cc62e39 100644 --- a/TelegramUI/TonePlayer.swift +++ b/TelegramUI/TonePlayer.swift @@ -24,12 +24,18 @@ private final class TonePlayerContext { private var scheduledData: (TonePlayerData, () -> Void)? + private let initialVolume: Float + init(queue: Queue) { + self.initialVolume = AVAudioSession.sharedInstance().outputVolume self.queue = queue self.audioEngine = AVAudioEngine() self.playerNode = AVAudioPlayerNode() self.audioEngine.attach(self.playerNode) - self.audioEngine.connect(self.playerNode, to: audioEngine.outputNode, format: nil) + self.audioEngine.connect(self.playerNode, to: self.audioEngine.mainMixerNode, format: nil) + let gainFactor = self.initialVolume + print("gain \(gainFactor)") + self.audioEngine.mainMixerNode.outputVolume = gainFactor self.audioEngine.prepare() } @@ -39,6 +45,9 @@ private final class TonePlayerContext { func start() { do { + let currentVolume = AVAudioSession.sharedInstance().outputVolume + //let gainFactor = max(0.1, min(1.5, self.initialVolume / currentVolume)) + try self.audioEngine.start() if let (data, completion) = self.scheduledData { diff --git a/TelegramUI/UniversalVideoCalleryItem.swift b/TelegramUI/UniversalVideoCalleryItem.swift index abbcab8237..5a8199fff3 100644 --- a/TelegramUI/UniversalVideoCalleryItem.swift +++ b/TelegramUI/UniversalVideoCalleryItem.swift @@ -73,13 +73,25 @@ class UniversalVideoGalleryItem: GalleryItem { } } if let mediaReference = mediaReference { - if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: mediaReference) { + if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: mediaReference, requestForIndex: { [weak self] in + if let strongSelf = self { + return 0 //strongSelf.index + } else { + return nil + } + }) { return (Int64(id), item) } } } } else if case let .webPage(webPage, file) = contentInfo { - if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: .webPage(webPage: WebpageReference(webPage), media: file)) { + if let item = ChatMediaGalleryThumbnailItem(account: self.account, mediaReference: .webPage(webPage: WebpageReference(webPage), media: file), requestForIndex: { [weak self] in + if let strongSelf = self { + return 0 //strongSelf.index + } else { + return nil + } + }) { return (0, item) } } diff --git a/TelegramUI/YoutubeEmbedImplementation.swift b/TelegramUI/YoutubeEmbedImplementation.swift index 689510fb03..43b75835c8 100644 --- a/TelegramUI/YoutubeEmbedImplementation.swift +++ b/TelegramUI/YoutubeEmbedImplementation.swift @@ -28,7 +28,7 @@ func extractYoutubeVideoIdAndTimestamp(url: String) -> (String, Int)? { if value.contains("s") { var range = value.startIndex.. (String, Int)? { } if let minutesRange = value.range(of: "m", options: .caseInsensitive, range: range, locale: nil) { - let subvalue = String(value[range.lowerBound.. (String, Int)? { } if let secondsRange = value.range(of: "s", options: .caseInsensitive, range: range, locale: nil) { - let subvalue = String(value[range.lowerBound..