diff --git a/Telegram/Telegram-iOS/Resources/Banned.tgs b/Telegram/Telegram-iOS/Resources/Banned.tgs new file mode 100644 index 0000000000..e0b883bf5a Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Banned.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/Location.tgs b/Telegram/Telegram-iOS/Resources/Location.tgs new file mode 100644 index 0000000000..51c22c0b2e Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Location.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/Photos.tgs b/Telegram/Telegram-iOS/Resources/Photos.tgs new file mode 100644 index 0000000000..0fb36135ee Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Photos.tgs differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 216168d964..709cb6cbfd 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7328,3 +7328,6 @@ Sorry for the inconvenience."; "Attachment.DeselectedItems_0" = "%@ items deselected"; "PrivacyPhoneNumberSettings.CustomPublicLink" = "Users who have your number saved in their contacts will also see it on Telegram.\n\nThis public link opens a chat with you:\n[https://t.me/%@]()"; + +"Attachment.MyAlbums" = "My Albums"; +"Attachment.MediaTypes" = "Media Types"; diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index 4f0748fcd6..f13ca03a53 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -3,7 +3,7 @@ import Display import SwiftSignalKit public protocol ContactSelectionController: ViewController { - var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?)?, NoError> { get } + var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> { get } var displayProgress: Bool { get set } var dismissed: (() -> Void)? { get set } diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 8873fd3664..62d803b41b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -8,6 +8,8 @@ import DirectionalPanGesture import TelegramPresentationData import MapKit +private let overflowInset: CGFloat = 70.0 + final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let wrappingNode: ASDisplayNode let clipNode: ASDisplayNode @@ -18,6 +20,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { var isReadyUpdated: (() -> Void)? var updateDismissProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)? var interactivelyDismissed: (() -> Void)? + var controllerRemoved: ((ViewController) -> Void)? var updateModalProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)? @@ -49,8 +52,12 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { self.wrappingNode = ASDisplayNode() self.clipNode = ASDisplayNode() - self.container = NavigationContainer(controllerRemoved: { _ in }) + var controllerRemovedImpl: ((ViewController) -> Void)? + self.container = NavigationContainer(controllerRemoved: { c in + controllerRemovedImpl?(c) + }) self.container.clipsToBounds = true + self.container.overflowInset = overflowInset super.init() @@ -72,12 +79,16 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { } applySmoothRoundedCorners(self.container.layer) + + controllerRemovedImpl = { [weak self] c in + self?.controllerRemoved?(c) + } } override func didLoad() { super.didLoad() - let panRecognizer = DirectionalPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true @@ -85,11 +96,23 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { self.wrappingNode.view.addGestureRecognizer(panRecognizer) } + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if let (layout, _, _) = self.validLayout { + if case .regular = layout.metrics.widthClass { + return false + } + } + return true + } + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { if let _ = otherGestureRecognizer.view?.superview as? MKMapView { return false } + if let _ = otherGestureRecognizer.view?.asyncdisplaykit_node as? CollectionIndexNode { + return false + } return true } return false @@ -102,7 +125,11 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { return 210.0 } if case .compact = layout.metrics.widthClass { - return max(layout.size.width, layout.size.height) * 0.2488 + var factor: CGFloat = 0.2488 + if layout.size.width <= 320.0 { + factor = 0.15 + } + return floor(max(layout.size.width, layout.size.height) * factor) } else { return 210.0 } @@ -165,7 +192,6 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { self.panGestureArguments = (topInset, translation, scrollView, listNode) - if !self.isExpanded { if currentOffset > 0.0, let scrollView = scrollView { scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) @@ -306,15 +332,20 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let isLandscape = layout.orientation == .landscape let edgeTopInset = isLandscape ? 0.0 : defaultTopInset + var effectiveExpanded = self.isExpanded + if case .regular = layout.metrics.widthClass { + effectiveExpanded = true + } + let topInset: CGFloat if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments { - if self.isExpanded { + if effectiveExpanded { topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) } else { topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) } } else { - topInset = self.isExpanded ? 0.0 : edgeTopInset + topInset = effectiveExpanded ? 0.0 : edgeTopInset } transition.updateFrame(node: self.wrappingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size)) @@ -359,7 +390,6 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let effectiveStatusBarHeight: CGFloat? = nil - let overflowInset: CGFloat = 70.0 var safeInsets = layout.safeInsets safeInsets.left += overflowInset safeInsets.right += overflowInset @@ -379,29 +409,12 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height) } } else { - self.clipNode.clipsToBounds = true - self.clipNode.cornerRadius = 10.0 - if #available(iOS 11.0, *) { - self.clipNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] - } + containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) - let verticalInset: CGFloat = 44.0 - - let maxSide = max(layout.size.width, layout.size.height) - let minSide = min(layout.size.width, layout.size.height) - let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) - containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) + let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size) containerScale = 1.0 - clipFrame = containerFrame - - var inputHeight: CGFloat? - if let inputHeightValue = layout.inputHeight { - inputHeight = max(0.0, inputHeightValue - (layout.size.height - containerFrame.maxY)) - } - - let effectiveStatusBarHeight: CGFloat? = nil - - containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: effectiveStatusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + containerFrame = unscaledFrame + clipFrame = unscaledFrame } transition.updateFrameAsPositionAndBounds(node: self.clipNode, frame: clipFrame) transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 0.0), size: containerFrame.size)) diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index d2b9df3430..9c7bed9a4c 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -22,6 +22,8 @@ public enum AttachmentButtonType: Equatable { public protocol AttachmentContainable: ViewController { var requestAttachmentMenuExpansion: () -> Void { get set } + var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void { get set } + var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set } func resetForReuse() func prepareForReuse() @@ -51,6 +53,19 @@ public protocol AttachmentMediaPickerContext { func schedule() } +private func generateShadowImage() -> UIImage? { + return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor) + + let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 11.0 - UIScreenPixel).cgPath + context.addPath(path) + context.fillPath() + })?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70) +} + + public class AttachmentController: ViewController { private let context: AccountContext private let updatedPresentationData: (initial: PresentationData, signal: Signal)? @@ -68,11 +83,12 @@ public class AttachmentController: ViewController { private final class Node: ASDisplayNode { private weak var controller: AttachmentController? private let dim: ASDisplayNode + private let shadowNode: ASImageNode private let container: AttachmentContainer let panel: AttachmentPanel private var currentType: AttachmentButtonType? - private var currentController: AttachmentContainable? + private var currentControllers: [AttachmentContainable] = [] private var validLayout: ContainerViewLayout? private var modalProgress: CGFloat = 0.0 @@ -104,7 +120,9 @@ public class AttachmentController: ViewController { } } } - + + private let wrapperNode: ASDisplayNode + init(controller: AttachmentController) { self.controller = controller @@ -112,6 +130,11 @@ public class AttachmentController: ViewController { self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) + self.shadowNode = ASImageNode() + + self.wrapperNode = ASDisplayNode() + self.wrapperNode.clipsToBounds = true + self.container = AttachmentContainer() self.container.canHaveKeyboardFocus = true self.panel = AttachmentPanel(context: controller.context, updatedPresentationData: controller.updatedPresentationData) @@ -119,7 +142,16 @@ public class AttachmentController: ViewController { super.init() self.addSubnode(self.dim) + self.addSubnode(self.shadowNode) + self.addSubnode(self.wrapperNode) + self.container.controllerRemoved = { [weak self] controller in + if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing { + strongSelf.currentControllers = strongSelf.currentControllers.filter { $0 !== controller } + strongSelf.containerLayoutUpdated(layout, transition: .immediate) + } + } + self.container.updateModalProgress = { [weak self] progress, transition in if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing { strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) @@ -218,7 +250,10 @@ public class AttachmentController: ViewController { func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) { guard self.currentType != type else { - if let controller = self.currentController { + if self.animating { + return + } + if let controller = self.currentControllers.last { controller.scrollToTopWithTabBar?() controller.requestAttachmentMenuExpansion() } @@ -235,10 +270,22 @@ public class AttachmentController: ViewController { controller.requestAttachmentMenuExpansion = { [weak self] in self?.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) } - - let previousController = strongSelf.currentController + controller.updateNavigationStack = { [weak self] f in + if let strongSelf = self { + strongSelf.currentControllers = f(strongSelf.currentControllers) + if let layout = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + } + } + } + controller.updateTabBarAlpha = { [weak self, weak controller] alpha, transition in + if let strongSelf = self, strongSelf.currentControllers.contains(where: { $0 === controller }) { + strongSelf.panel.updateBackgroundAlpha(alpha, transition: transition) + } + } + let previousController = strongSelf.currentControllers.last let animateTransition = previousType != nil - strongSelf.currentController = controller + strongSelf.currentControllers = [controller] if animateTransition, let snapshotView = strongSelf.container.container.view.snapshotView(afterScreenUpdates: false) { snapshotView.frame = strongSelf.container.container.frame @@ -271,41 +318,70 @@ public class AttachmentController: ViewController { }) } + private var animating = false func animateIn() { - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) + guard let layout = self.validLayout else { + return + } - let targetPosition = self.container.position - let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) - - self.container.position = startPosition - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - transition.animateView(allowUserInteraction: true, { - self.container.position = targetPosition - }) + self.animating = true + if case .regular = layout.metrics.widthClass { + self.animating = false + } else { + ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) + + let targetPosition = self.container.position + let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) + + self.container.position = startPosition + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + transition.animateView(allowUserInteraction: true, { + self.animating = false + self.container.position = targetPosition + }) + } } func animateOut(completion: @escaping () -> Void = {}) { self.isDismissing = true - let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in - let _ = self?.container.dismiss(transition: .immediate, completion: completion) - }) - let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) + guard let layout = self.validLayout else { + return + } - self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) + self.animating = true + if case .regular = layout.metrics.widthClass { + self.layer.allowsGroupOpacity = true + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + let _ = self.container.dismiss(transition: .immediate, completion: completion) + self.animating = false + }) + } else { + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in + let _ = self?.container.dismiss(transition: .immediate, completion: completion) + self?.animating = false + }) + let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) + + self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) + } } func scrollToTop() { - self.currentController?.scrollToTop?() + self.currentControllers.last?.scrollToTop?() } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let controller = self.controller, controller.isInteractionDisabled() { return self.view } else { - return super.hitTest(point, with: event) + let result = super.hitTest(point, with: event) + if result == self.wrapperNode.view { + return self.dim.view + } + return result } } @@ -322,14 +398,48 @@ public class AttachmentController: ViewController { } else if self.modalProgress == 1.0 { self.isCollapsed = true } + + var containerLayout = layout + let containerRect: CGRect + if case .regular = layout.metrics.widthClass { + let size = CGSize(width: 390.0, height: 600.0) + let position: CGPoint + if layout.size.width > layout.size.height { + position = CGPoint(x: 225.0, y: layout.size.height - size.height - layout.intrinsicInsets.bottom - 54.0) + } else { + position = CGPoint(x: 152.0, y: layout.size.height - size.height - layout.intrinsicInsets.bottom - 54.0) + } + containerRect = CGRect(origin: position, size: size) + containerLayout.size = containerRect.size + containerLayout.intrinsicInsets.bottom = 0.0 + + self.wrapperNode.cornerRadius = 10.0 + if #available(iOS 13.0, *) { + self.wrapperNode.layer.cornerCurve = .continuous + } + + self.shadowNode.alpha = 1.0 + if self.shadowNode.image == nil { + self.shadowNode.image = generateShadowImage() + } + } else { + containerRect = CGRect(origin: CGPoint(), size: layout.size) + + self.wrapperNode.cornerRadius = 0.0 + self.shadowNode.alpha = 0.0 + } + let isEffecitvelyCollapsedUpdated = (self.isCollapsed || self.selectionCount > 0) != (self.panel.isCollapsed || self.panel.isSelecting) - let panelHeight = self.panel.update(layout: layout, buttons: self.controller?.buttons ?? [], isCollapsed: self.isCollapsed, isSelecting: self.selectionCount > 0, transition: transition) + let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isCollapsed: self.isCollapsed, isSelecting: self.selectionCount > 0, transition: transition) var panelTransition = transition if isEffecitvelyCollapsedUpdated { panelTransition = .animated(duration: 0.25, curve: .easeInOut) } - panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight))) + panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: containerRect.height - panelHeight), size: CGSize(width: containerRect.width, height: panelHeight))) + + transition.updateFrame(node: self.shadowNode, frame: containerRect.insetBy(dx: -60.0, dy: -60.0)) + transition.updateFrame(node: self.wrapperNode, frame: containerRect) if !self.isUpdatingContainer && !self.isDismissing { self.isUpdatingContainer = true @@ -341,17 +451,17 @@ public class AttachmentController: ViewController { containerTransition = transition } - let controllers = self.currentController.flatMap { [$0] } ?? [] - containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: layout.size)) + let controllers = self.currentControllers + containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) - var containerInsets = layout.intrinsicInsets + var containerInsets = containerLayout.intrinsicInsets containerInsets.bottom = panelHeight - let containerLayout = layout.withUpdatedIntrinsicInsets(containerInsets) + let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady { - self.addSubnode(self.container) + self.wrapperNode.addSubnode(self.container) self.container.addSubnode(self.panel) self.animateIn() diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index cd6f535a4f..9cd92117f0 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -410,6 +410,11 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { self.scrollNode.view.showsVerticalScrollIndicator = false } + func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.separatorNode, alpha: alpha) + transition.updateAlpha(node: self.backgroundNode, alpha: alpha) + } + func updateCaption(_ caption: NSAttributedString) { if !caption.string.isEmpty { self.loadTextNodeIfNeeded() diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 57a9d79643..97a3577e35 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -385,7 +385,7 @@ public final class CallListController: TelegramBaseController { |> take(1) |> deliverOnMainQueue).start(next: { [weak controller, weak self] result in controller?.dismissSearch() - if let strongSelf = self, let (contactPeers, action, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer { + if let strongSelf = self, let (contactPeers, action, _, _, _) = result, let contactPeer = contactPeers.first, case let .peer(peer, _, _) = contactPeer { strongSelf.call(peer.id, isVideo: action == .videoCall, began: { if let strongSelf = self { let _ = (strongSelf.context.sharedContext.hasOngoingCall.get() diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index fcb9d7ee49..f13ee4b9a3 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -518,6 +518,8 @@ public final class ComposedPoll { private class CreatePollControllerImpl: ItemListController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } } public func createPollController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, isQuiz: Bool? = nil, completion: @escaping (ComposedPoll) -> Void) -> AttachmentContainable { @@ -904,8 +906,8 @@ public func createPollController(context: AccountContext, updatedPresentationDat focusItemTag = CreatePollEntryTag.solution ensureVisibleItemTag = focusItemTag } else { - focusItemTag = CreatePollEntryTag.text - ensureVisibleItemTag = focusItemTag +// focusItemTag = CreatePollEntryTag.text +// ensureVisibleItemTag = focusItemTag } let title: String @@ -927,6 +929,15 @@ public func createPollController(context: AccountContext, updatedPresentationDat weak var currentTooltipController: TooltipController? let controller = CreatePollControllerImpl(context: context, state: signal) controller.navigationPresentation = .modal +// controller.visibleBottomContentOffsetChanged = { [weak controller] offset in +// switch offset { +// case let .known(value): +// let backgroundAlpha: CGFloat = min(30.0, value) / 30.0 +// controller?.updateTabBarAlpha(backgroundAlpha, .immediate) +// case .unknown, .none: +// controller?.updateTabBarAlpha(1.0, .immediate) +// } +// } presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } diff --git a/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift b/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift index 4609c3c173..59a103283c 100644 --- a/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift +++ b/submodules/ComposePollUI/Sources/CreatePollOptionItem.swift @@ -362,6 +362,8 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, if let checkNode = strongSelf.checkNode { transition.updateFrame(node: checkNode, frame: checkFrame) checkNode.setSelected(isSelected, animated: true) + + transition.updateAlpha(node: checkNode, alpha: strongSelf.textNode.textView.text.isEmpty ? 0.0 : 1.0) } else { let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: item.presentationData.theme.list.itemSwitchColors.positiveColor, strokeColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, borderColor: item.presentationData.theme.list.itemCheckColors.strokeColor, overlayBorder: false, hasInset: false, hasShadow: false)) checkNode.setSelected(isSelected, animated: false) @@ -372,6 +374,8 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode, strongSelf.containerNode.addSubnode(checkNode) checkNode.frame = checkFrame transition.animatePositionAdditive(node: checkNode, offset: CGPoint(x: -checkFrame.maxX, y: 0.0)) + + transition.updateAlpha(node: checkNode, alpha: strongSelf.textNode.textView.text.isEmpty ? 0.0 : 1.0) } } else if let checkNode = strongSelf.checkNode { strongSelf.checkNode = nil diff --git a/submodules/Display/Source/Font.swift b/submodules/Display/Source/Font.swift index c3ba77888c..e7de5243eb 100644 --- a/submodules/Display/Source/Font.swift +++ b/submodules/Display/Source/Font.swift @@ -31,10 +31,11 @@ public struct Font { case medium case semibold case bold + case heavy var isBold: Bool { switch self { - case .medium, .semibold, .bold: + case .medium, .semibold, .bold, .heavy: return true default: return false @@ -51,6 +52,8 @@ public struct Font { return .semibold case .bold: return .bold + case .heavy: + return .heavy default: return .regular } @@ -185,6 +188,11 @@ public struct Font { } } + + public static func heavy(_ size: CGFloat) -> UIFont { + return self.with(size: size, design: .regular, weight: .heavy, traits: []) + } + public static func light(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light) diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index 92e8b3cf9f..babe28bc0b 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -109,6 +109,8 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega self.state.top?.value.isInFocus = isInFocus } + public var overflowInset: CGFloat = 0.0 + private var currentKeyboardLeftEdge: CGFloat = 0.0 private var additionalKeyboardLeftEdgeOffset: CGFloat = 0.0 diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 1ab09e7555..eba494576f 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -61,6 +61,10 @@ public final class NavigationBarTheme { self.badgeTextColor = badgeTextColor } + public func withUpdatedBackgroundColor(_ color: UIColor) -> NavigationBarTheme { + return NavigationBarTheme(buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) + } + public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { return NavigationBarTheme(buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) } @@ -1334,6 +1338,8 @@ open class NavigationBar: ASDisplayNode { public var intrinsicCanTransitionInline: Bool = true + public var passthroughTouches = true + public var canTransitionInline: Bool { if let contentNode = self.contentNode, case .replacement = contentNode.mode { return false @@ -1470,8 +1476,8 @@ open class NavigationBar: ASDisplayNode { return nil } - //result == self.view || - if result == self.buttonsContainerNode.view { + + if self.passthroughTouches && (result == self.view || result == self.buttonsContainerNode.view) { return nil } diff --git a/submodules/Display/Source/NavigationTransitionCoordinator.swift b/submodules/Display/Source/NavigationTransitionCoordinator.swift index b133473604..2fd7380d58 100644 --- a/submodules/Display/Source/NavigationTransitionCoordinator.swift +++ b/submodules/Display/Source/NavigationTransitionCoordinator.swift @@ -34,7 +34,7 @@ final class NavigationTransitionCoordinator { } } - private let container: ASDisplayNode + private let container: NavigationContainer private let transition: NavigationTransition let isInteractive: Bool let topNode: ASDisplayNode @@ -51,7 +51,7 @@ final class NavigationTransitionCoordinator { private var currentCompletion: (() -> Void)? private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? - init(transition: NavigationTransition, isInteractive: Bool, container: ASDisplayNode, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { + init(transition: NavigationTransition, isInteractive: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { self.transition = transition self.isInteractive = isInteractive self.container = container @@ -147,7 +147,7 @@ final class NavigationTransitionCoordinator { } }) canInvokeCompletion = true - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX), height: self.container.bounds.size.height - dimInset))) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset), height: self.container.bounds.size.height - dimInset))) transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset))) transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15) transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9) diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 88607d6f23..ea84d08279 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -174,6 +174,13 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } + public var visibleBottomContentOffset: ListViewVisibleContentOffset { + if self.isNodeLoaded { + return (self.displayNode as! ItemListControllerNode).listNode.visibleBottomContentOffset() + } else { + return .unknown + } + } public var visibleBottomContentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? { didSet { if self.isNodeLoaded { diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index 4182ec33f2..0564428835 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -166,10 +166,10 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { self.isAccessibilityElement = true - self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) self.addSubnode(self.addressNode) self.addSubnode(self.infoButton) + self.addSubnode(self.iconNode) self.infoButton.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside) } @@ -301,7 +301,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { strongSelf.topStripeNode.removeFromSupernode() } if strongSelf.bottomStripeNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1) + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) } if strongSelf.maskNode.supernode != nil { strongSelf.maskNode.removeFromSupernode() @@ -377,6 +377,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { shimmerNode = ShimmerEffectNode() strongSelf.placeholderNode = shimmerNode if strongSelf.bottomStripeNode.supernode != nil { + strongSelf.insertSubnode(shimmerNode, belowSubnode: strongSelf.bottomStripeNode) } else { strongSelf.addSubnode(shimmerNode) @@ -403,6 +404,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { shapes.append(.roundedRectLine(startPoint: CGPoint(x: subtitleFrame.minX, y: subtitleFrame.minY + floor((subtitleFrame.height - lineDiameter) / 2.0)), width: subtitleLineWidth, diameter: lineDiameter)) shimmerNode.update(backgroundColor: placeholderBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize) + + strongSelf.iconNode.removeFromSupernode() } else if let shimmerNode = strongSelf.placeholderNode { strongSelf.placeholderNode = nil shimmerNode.removeFromSupernode() @@ -418,16 +421,10 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.bottomStripeNode) } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.backgroundNode) } else { self.addSubnode(self.highlightedBackgroundNode) } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h index 01bfa00ecb..cfc9c7588c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h @@ -54,6 +54,8 @@ typedef enum { @property (nonatomic, copy) void(^recognizedQRCode)(NSString *code); +@property (nonatomic, copy) void(^finishedTransitionIn)(void); + @property (nonatomic, copy) CGRect(^beginTransitionOut)(void); @property (nonatomic, copy) void(^finishedTransitionOut)(void); @property (nonatomic, copy) void(^customPresentOverlayController)(TGOverlayController *(^)(id)); diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m index ffbbf47204..cead73940e 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCameraView.m @@ -26,6 +26,8 @@ TGCameraPreviewView *_previewView; __weak PGCamera *_camera; + + UIInterfaceOrientation _innerInterfaceOrientation; } @end @@ -233,10 +235,22 @@ { void(^block)(void) = ^ { - _wrapperView.transform = CGAffineTransformMakeRotation(-1 * TGRotationForInterfaceOrientation(orientation)); - _wrapperView.frame = self.bounds; + CGAffineTransform transform = CGAffineTransformMakeRotation(-1 * TGRotationForInterfaceOrientation(orientation)); + CGFloat scale = 1.0; + if (self.frame.size.width != 0.0) { + scale = self.frame.size.height / self.frame.size.width; + } + if (_innerInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) { + transform = CGAffineTransformScale(transform, scale, scale); + } else if (_innerInterfaceOrientation == UIInterfaceOrientationLandscapeRight) { + transform = CGAffineTransformScale(transform, scale, scale); + } + _wrapperView.transform = transform; + [self layoutSubviews]; }; + _innerInterfaceOrientation = orientation; + if (animated) [UIView animateWithDuration:0.3f animations:block]; else @@ -247,12 +261,19 @@ { [super layoutSubviews]; - _wrapperView.frame = self.bounds; + _wrapperView.bounds = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height); + _wrapperView.center = CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0); TGCameraPreviewView *previewView = _previewView; if (previewView.superview == _wrapperView) previewView.frame = self.bounds; +// if (_innerInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) { +// _wrapperView.frame = CGRectOffset(_wrapperView.frame, 0, 100.0); +// } else if (_innerInterfaceOrientation == UIInterfaceOrientationLandscapeRight) { +// _wrapperView.frame = CGRectOffset(_wrapperView.frame, 0, -100.0); +// } + _iconView.frame = CGRectMake(self.frame.size.width - _iconView.frame.size.width - 3.0, 3.0 - TGScreenPixel, _iconView.frame.size.width, _iconView.frame.size.height); } diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 9da963c36f..a5b5238c77 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -2225,11 +2225,21 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus if (!CGRectEqualToRect(fromFrame, CGRectZero)) { + __weak TGCameraController *weakSelf = self; POPSpringAnimation *frameAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame]; frameAnimation.fromValue = [NSValue valueWithCGRect:fromFrame]; frameAnimation.toValue = [NSValue valueWithCGRect:toFrame]; frameAnimation.springSpeed = 20; frameAnimation.springBounciness = 1; + frameAnimation.completionBlock = ^(POPAnimation *anim, BOOL finished) { + __strong TGCameraController *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + if (strongSelf.finishedTransitionIn != NULL) { + ;strongSelf.finishedTransitionIn(); + } + }; [_previewView pop_addAnimation:frameAnimation forKey:@"frame"]; POPSpringAnimation *cornersFrameAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame]; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 65a399330d..909b4328ca 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -927,6 +927,11 @@ grouping = false; } } + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + grouping = true; + } + } } } @@ -942,8 +947,12 @@ NSAttributedString *caption = [editingContext captionForItem:asset]; - if (editingContext.isForcedCaption && num > 0) { - caption = nil; + if (editingContext.isForcedCaption) { + if (grouping && num > 0) { + caption = nil; + } else if (!grouping && num < selectedItems.count - 1) { + caption = nil; + } } switch (asset.type) diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 63660f66c4..1a672efcde 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -406,6 +406,8 @@ open class LegacyController: ViewController, PresentableController, AttachmentCo public var disposables = DisposableSet() open var requestAttachmentMenuExpansion: () -> Void = {} + open var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + open var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) { self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber)) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 1a272feb94..6d409deda3 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -61,6 +61,10 @@ private func generateExtensionImage(colors: (UInt32, UInt32)) -> UIImage? { context.restoreGState() + context.beginPath() + let _ = try? drawSvgPath(context, path: "M6,0 L26.7573593,0 C27.5530088,-8.52837125e-16 28.3160705,0.316070521 28.8786797,0.878679656 L39.1213203,11.1213203 C39.6839295,11.6839295 40,12.4469912 40,13.2426407 L40,34 C40,37.3137085 37.3137085,40 34,40 L6,40 C2.6862915,40 4.05812251e-16,37.3137085 0,34 L0,6 C-4.05812251e-16,2.6862915 2.6862915,6.08718376e-16 6,0 ") + context.clip() + context.setFillColor(UIColor(rgb: 0xffffff, alpha: 0.2).cgColor) context.translateBy(x: 40.0 - 14.0, y: 0.0) let _ = try? drawSvgPath(context, path: "M-1,0 L14,0 L14,15 L14,14 C14,12.8954305 13.1045695,12 12,12 L4,12 C2.8954305,12 2,11.1045695 2,10 L2,2 C2,0.8954305 1.1045695,-2.02906125e-16 0,0 L-1,0 L-1,0 Z ") @@ -894,10 +898,10 @@ public final class ListMessageFileItemNode: ListMessageNode { } transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.contentSize.height + UIScreenPixel)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) if let backgroundNode = strongSelf.backgroundNode { - backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height)) + backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.size.height - nodeLayout.insets.bottom)) } switch item.style { diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index c6429d4081..b6e1baa559 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -72,7 +72,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab private var interaction: LocationPickerInteraction? public var requestAttachmentMenuExpansion: () -> Void = {} - + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, completion: @escaping (TelegramMediaMap, String?) -> Void) { self.context = context self.mode = mode @@ -319,6 +321,8 @@ public final class LocationPickerController: ViewController, AttachmentContainab break } }) + + self.navigationBar?.passthroughTouches = false } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -342,4 +346,8 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.interaction?.dismissSearch() self.scrollToTop?() } + + public func prepareForReuse() { + self.updateTabBarAlpha(1.0, .animated(duration: 0.25, curve: .easeInOut)) + } } diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index f999855e59..1b4b7058eb 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -74,7 +74,7 @@ enum LegacyMediaPickerGallerySource { case selection(item: TGMediaSelectableItem) } -func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void) { +func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void) { let reminder = peer?.id == context.account.peerId let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) @@ -179,6 +179,8 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, } controller.finishedTransitionIn = { [weak model] _, _ in model?.interfaceView.setSelectedItemsModel(model?.selectedItemsModel) + + finishedTransitionIn() } controller.completedTransitionOut = { [weak legacyController] in updateHiddenMedia(nil) diff --git a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift index 64ed227dc9..113bf267e6 100644 --- a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift +++ b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift @@ -29,7 +29,8 @@ class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver { self.changeSink.putNext(changeInstance) } - func fetchResultAssets(_ initialFetchResult: PHFetchResult) -> Signal?, NoError> { + func fetchAssets(_ collection: PHAssetCollection) -> Signal, NoError> { + let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil) let fetchResult = Atomic>(value: initialFetchResult) return .single(initialFetchResult) |> then( @@ -45,16 +46,33 @@ class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver { ) } + func fetchAssetsCollections(_ type: PHAssetCollectionType) -> Signal, NoError> { + let initialFetchResult = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) + let fetchResult = Atomic>(value: initialFetchResult) + return .single(initialFetchResult) + |> then( + self.changeSink.signal() + |> mapToSignal { change in + if let updatedFetchResult = change.changeDetails(for: fetchResult.with { $0 })?.fetchResultAfterChanges { + let _ = fetchResult.modify { _ in return updatedFetchResult } + return .single(updatedFetchResult) + } else { + return .complete() + } + } + ) + } + func recentAssets() -> Signal?, NoError> { let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil) if let collection = collections.firstObject { - let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil) - return fetchResultAssets(initialFetchResult) + return fetchAssets(collection) + |> map(Optional.init) } else { return .single(nil) } } - + func mediaAccess() -> Signal { let initialStatus: PHAuthorizationStatus if #available(iOS 14.0, *) { diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift new file mode 100644 index 0000000000..6d63017e7e --- /dev/null +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumGridItem.swift @@ -0,0 +1,367 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import MergeLists +import Photos + +private struct MediaGroupsGridAlbumEntry: Comparable, Identifiable { + let theme: PresentationTheme + let index: Int + let collection: PHAssetCollection + let firstItem: PHAsset? + let count: String + + var stableId: String { + return self.collection.localIdentifier + } + + static func ==(lhs: MediaGroupsGridAlbumEntry, rhs: MediaGroupsGridAlbumEntry) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.index != rhs.index { + return false + } + if lhs.collection != rhs.collection { + return false + } + if lhs.firstItem != rhs.firstItem { + return false + } + if lhs.count != rhs.count { + return false + } + return true + } + + static func <(lhs: MediaGroupsGridAlbumEntry, rhs: MediaGroupsGridAlbumEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(action: @escaping (PHAssetCollection) -> Void) -> ListViewItem { + return MediaGroupsGridAlbumItem(theme: theme, collection: self.collection, firstItem: self.firstItem, count: self.count, action: action) + } +} + + +private class MediaGroupsGridAlbumItem: ListViewItem { + let theme: PresentationTheme + let collection: PHAssetCollection + let firstItem: PHAsset? + let count: String + let action: (PHAssetCollection) -> Void + + public init(theme: PresentationTheme, collection: PHAssetCollection, firstItem: PHAsset?, count: String, action: @escaping (PHAssetCollection) -> Void) { + self.theme = theme + self.collection = collection + self.firstItem = firstItem + self.count = count + self.action = action + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = MediaGroupsGridAlbumItemNode() + let (nodeLayout, apply) = node.asyncLayout()(self, params) + node.insets = nodeLayout.insets + node.contentSize = nodeLayout.contentSize + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in + apply(false) + }) + }) + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is MediaGroupsGridAlbumItemNode) + if let nodeValue = node() as? MediaGroupsGridAlbumItemNode { + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply(animation.isAnimated) + }) + } + } + } + } + } + + public var selectable = true + public func selected(listView: ListView) { + self.action(self.collection) + } +} + + +private let textFont = Font.regular(15.0) + +private final class MediaGroupsGridAlbumItemNode : ListViewItemNode { + private let containerNode: ASDisplayNode + private let imageNode: ImageNode + private let titleNode: TextNode + private let countNode: TextNode + + var item: MediaGroupsGridAlbumItem? + + init() { + self.containerNode = ASDisplayNode() + + self.imageNode = ImageNode() + self.imageNode.clipsToBounds = true + self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0)) + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + + self.countNode = TextNode() + self.countNode.isUserInteractionEnabled = false + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.imageNode) + self.containerNode.addSubnode(self.titleNode) + self.containerNode.addSubnode(self.countNode) + } + + override func didLoad() { + super.didLoad() + + self.imageNode.cornerRadius = 10.0 + if #available(iOS 13.0, *) { + self.imageNode.layer.cornerCurve = .continuous + } + + self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + } + + func asyncLayout() -> (MediaGroupsGridAlbumItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeCountLayout = TextNode.asyncLayout(self.countNode) + + return { [weak self] item, params in + let title = NSAttributedString(string: item.collection.localizedTitle ?? "", font: textFont, textColor: item.theme.list.itemPrimaryTextColor) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 170.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let count = NSAttributedString(string: item.count, font: textFont, textColor: item.theme.list.itemSecondaryTextColor) + let (countLayout, countApply) = makeCountLayout(TextNodeLayoutArguments(attributedString: count, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 170.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 220.0, height: 182.0), insets: UIEdgeInsets()) + return (itemLayout, { animated in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 220.0, height: 220.0)) + + if let firstItem = item.firstItem { + let scale = min(2.0, UIScreenScale) + let targetSize = CGSize(width: 160.0 * scale, height: 160.0 * scale) + strongSelf.imageNode.setSignal(assetImage(asset: firstItem, targetSize: targetSize, exact: false)) + } + + strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 0.0), size: CGSize(width: 170.0, height: 170.0)) + + let _ = titleApply() + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 176.0), size: titleLayout.size) + + let _ = countApply() + strongSelf.countNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 196.0), size: countLayout.size) + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + super.animateInsertion(currentTimestamp, duration: duration, short: short) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + super.animateRemoved(currentTimestamp, duration: duration) + + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + super.animateAdded(currentTimestamp, duration: duration) + + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } +} + +private struct MediaGroupsAlbumGridItemNodeTransition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] + let entries: [MediaGroupsGridAlbumEntry] +} + +private func preparedTransition(action: @escaping (PHAssetCollection) -> Void, from fromEntries: [MediaGroupsGridAlbumEntry], to toEntries: [MediaGroupsGridAlbumEntry]) -> MediaGroupsAlbumGridItemNodeTransition { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) + + let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action), directionHint: .Down) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(action: action), directionHint: nil) } + + return MediaGroupsAlbumGridItemNodeTransition(deletions: deletions, insertions: insertions, updates: updates, entries: toEntries) +} + +final class MediaGroupsAlbumGridItem: ListViewItem { + let presentationData: ItemListPresentationData + let collections: [PHAssetCollection] + let action: (PHAssetCollection) -> Void + + public init(presentationData: ItemListPresentationData, collections: [PHAssetCollection], action: @escaping (PHAssetCollection) -> Void) { + self.presentationData = presentationData + self.collections = collections + self.action = action + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + Queue.mainQueue().async { + let node = MediaGroupsAlbumGridItemNode() + let makeLayout = node.asyncLayout() + async { + let (nodeLayout, nodeApply) = makeLayout(self, params) + node.contentSize = nodeLayout.contentSize + node.insets = nodeLayout.insets + + Queue.mainQueue().async { + completion(node, nodeApply) + } + } + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? MediaGroupsAlbumGridItemNode { + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params) + Queue.mainQueue().async { + completion(nodeLayout, { info in + apply().1(info) + }) + } + } + } + } + } + + public var selectable: Bool { + return false + } +} + +private let titleFont = Font.bold(20.0) + +private class MediaGroupsAlbumGridItemNode: ListViewItemNode { + private var item: MediaGroupsAlbumGridItem? + private var layoutParams: ListViewItemLayoutParams? + + private let listNode: ListView + private var entries: [MediaGroupsGridAlbumEntry]? + private var enqueuedTransitions: [MediaGroupsAlbumGridItemNodeTransition] = [] + + init() { + self.listNode = ListView() + self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.listNode) + } + + private func enqueueTransition(_ transition: MediaGroupsAlbumGridItemNodeTransition) { + self.enqueuedTransitions.append(transition) + + if let _ = self.item { + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } + } + } + + private func dequeueTransition() { + guard let _ = self.item, let transition = self.enqueuedTransitions.first else { + return + } + self.enqueuedTransitions.remove(at: 0) + + var options = ListViewDeleteAndInsertOptions() + options.insert(.Synchronous) + + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in + }) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let item = self.item { + let makeLayout = self.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(item, params) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + let _ = nodeApply() + } + } + + func asyncLayout() -> (_ item: MediaGroupsAlbumGridItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { + return { [weak self] item, params in + let contentSize = CGSize(width: params.width, height: 220.0) + let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) + + return (nodeLayout, { [weak self] in + return (nil, { _ in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + let listInsets = UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0) + strongSelf.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width - params.leftInset - params.rightInset) + strongSelf.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0) + strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + var entries: [MediaGroupsGridAlbumEntry] = [] + var index: Int = 0 + for collection in item.collections { + let result = PHAsset.fetchAssets(in: collection, options: nil) + if let firstItem = result.firstObject { + entries.append(MediaGroupsGridAlbumEntry(theme: item.presentationData.theme, index: index, collection: collection, firstItem: firstItem, count: presentationStringsFormattedNumber(Int32(result.count)))) + index += 1 + } + } + + let previousEntries = strongSelf.entries ?? [] + let transition = preparedTransition(action: { [weak item] collection in + item?.action(collection) + }, from: previousEntries, to: entries) + strongSelf.enqueueTransition(transition) + + strongSelf.entries = entries + } + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift new file mode 100644 index 0000000000..319427a2c6 --- /dev/null +++ b/submodules/MediaPickerUI/Sources/MediaGroupsAlbumItem.swift @@ -0,0 +1,329 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import AppBundle + +class MediaGroupsAlbumItem: ListViewItem, ListViewItemWithHeader { + enum Icon { + case bursts + case panoramas + case screenshots + case selfPortraits + case slomoVideos + case timelapses + case videos + case animated + case depthEffect + case livePhotos + + var image: UIImage? { + switch self { + case .bursts: + return UIImage(bundleImageName: "Chat/Attach Menu/Burst") + case .panoramas: + return UIImage(bundleImageName: "Chat/Attach Menu/Panorama") + case .screenshots: + return UIImage(bundleImageName: "Chat/Attach Menu/Screenshot") + case .selfPortraits: + return UIImage(bundleImageName: "Chat/Attach Menu/Selfie") + case .slomoVideos: + return UIImage(bundleImageName: "Chat/Attach Menu/SloMo") + case .timelapses: + return UIImage(bundleImageName: "Chat/Attach Menu/Timelapse") + case .videos: + return UIImage(bundleImageName: "Chat/Attach Menu/Video") + case .animated: + return UIImage(bundleImageName: "Chat/Attach Menu/Animated") + case .depthEffect: + return UIImage(bundleImageName: "Chat/Attach Menu/Portrait") + case .livePhotos: + return UIImage(bundleImageName: "Chat/Attach Menu/LivePhoto") + } + } + } + let presentationData: ItemListPresentationData + let title: String + let count: String + let icon: Icon? + let action: () -> Void + let header: ListViewItemHeader? = nil + + init(presentationData: ItemListPresentationData, title: String, count: String, icon: Icon?, action: @escaping () -> Void) { + self.presentationData = presentationData + self.title = title + self.count = count + self.icon = icon + self.action = action + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = MediaGroupsAlbumItemNode() + let (first, last) = MediaGroupsAlbumItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (layout, apply) = node.asyncLayout()(self, params, first, last) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? MediaGroupsAlbumItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (first, last) = MediaGroupsAlbumItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (layout, apply) = makeLayout(self, params, first, last) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } + + var selectable: Bool = true + + public func selected(listView: ListView){ + self.action() + + listView.clearHighlightAnimated(true) + } + + static func mergeType(item: MediaGroupsAlbumItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool) { + var first = false + var last = false + + if let previousItem = previousItem, !(previousItem is MediaGroupsAlbumItem) { + first = true + } + if nextItem == nil { + last = true + } + + return (first, last) + } +} + +class MediaGroupsAlbumItemNode: ListViewItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + + private let iconNode: ASImageNode + private let titleNode: TextNode + private let countNode: TextNode + private let arrowNode: ASImageNode + + private let activateArea: AccessibilityAreaNode + + private var item: MediaGroupsAlbumItem? + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = .white + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + + self.countNode = TextNode() + self.countNode.isUserInteractionEnabled = false + self.countNode.contentMode = .left + self.countNode.contentsScale = UIScreen.main.scale + + self.arrowNode = ASImageNode() + self.arrowNode.isLayerBacked = true + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.displaysAsynchronously = false + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.iconNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.countNode) + self.addSubnode(self.arrowNode) + self.addSubnode(self.activateArea) + + self.activateArea.activate = { [weak self] in + self?.item?.action() + return true + } + } + + func asyncLayout() -> (_ item: MediaGroupsAlbumItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeCountLayout = TextNode.asyncLayout(self.countNode) + let currentItem = self.item + + return { item, params, first, last in + var updatedTheme: PresentationTheme? + + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + + let titleFont = Font.regular(21.0) + let countFont = Font.regular(17.0) + + let leftInset: CGFloat = 60.0 + params.leftInset + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - 10.0 - leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (countLayout, countApply) = makeCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.count, font: countFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - 10.0 - leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let contentHeight: CGFloat = 48.0 + + let contentSize = CGSize(width: params.width, height: contentHeight) + let insets = UIEdgeInsets() + let separatorHeight = UIScreenPixel + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.activateArea.accessibilityLabel = item.title + strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: layout.contentSize.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + + strongSelf.iconNode.image = generateTintedImage(image: item.icon?.image, color: item.presentationData.theme.list.itemAccentColor) + strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme) + } + + strongSelf.addSubnode(strongSelf.activateArea) + + let _ = titleApply() + let _ = countApply() + + let titleOffset = leftInset + let hideBottomStripe: Bool = last + + if let image = strongSelf.iconNode.image { + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 14.0, y: floorToScreenPixels((contentSize.height - image.size.height) / 2.0)), size: image.size) + } + + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + + strongSelf.topStripeNode.isHidden = true + strongSelf.bottomStripeNode.isHidden = hideBottomStripe + + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) + + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) + + if let arrowSize = strongSelf.arrowNode.image?.size { + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - arrowSize.width - 12.0, y: floorToScreenPixels((contentSize.height - arrowSize.height) / 2.0)), size: arrowSize) + + strongSelf.countNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - countLayout.size.width - arrowSize.width - 12.0 - 2.0, y: floorToScreenPixels((contentSize.height - countLayout.size.height) / 2.0) + 1.0), size: countLayout.size) + } + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) + } + }) + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted { + self.highlightedBackgroundNode.alpha = 1.0 + if self.highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + if self.bottomStripeNode.supernode != nil { + anchorNode = self.bottomStripeNode + } else if self.topStripeNode.supernode != nil { + anchorNode = self.topStripeNode + } else if self.backgroundNode.supernode != nil { + anchorNode = self.backgroundNode + } + if let anchorNode = anchorNode { + self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(self.highlightedBackgroundNode) + } + } + } else { + if self.highlightedBackgroundNode.supernode != nil { + if animated { + self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in + if let strongSelf = self { + if completed { + strongSelf.highlightedBackgroundNode.removeFromSupernode() + } + } + }) + self.highlightedBackgroundNode.alpha = 0.0 + } else { + self.highlightedBackgroundNode.removeFromSupernode() + } + } + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + + override public func headers() -> [ListViewItemHeader]? { + if let item = self.item { + return item.header.flatMap { [$0] } + } else { + return nil + } + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift b/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift new file mode 100644 index 0000000000..2dbaa1e84e --- /dev/null +++ b/submodules/MediaPickerUI/Sources/MediaGroupsHeaderItem.swift @@ -0,0 +1,110 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI + +final class MediaGroupsHeaderItem: ListViewItem { + let presentationData: ItemListPresentationData + let title: String + + public init(presentationData: ItemListPresentationData, title: String) { + self.presentationData = presentationData + self.title = title + } + + public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = MediaGroupsHeaderItemNode() + let makeLayout = node.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(self, params) + node.contentSize = nodeLayout.contentSize + node.insets = nodeLayout.insets + + completion(node, nodeApply) + } + } + + public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? MediaGroupsHeaderItemNode { + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params) + Queue.mainQueue().async { + completion(nodeLayout, { info in + apply().1(info) + }) + } + } + } + } + } + + public var selectable: Bool { + return false + } +} + +private let titleFont = Font.bold(22.0) + +private class MediaGroupsHeaderItemNode: ListViewItemNode { + private let titleNode: TextNode + + private var item: MediaGroupsHeaderItem? + private var layoutParams: ListViewItemLayoutParams? + + init() { + self.titleNode = TextNode() + + super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) + + self.addSubnode(self.titleNode) + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + if let item = self.item { + let makeLayout = self.asyncLayout() + let (nodeLayout, nodeApply) = makeLayout(item, params) + self.contentSize = nodeLayout.contentSize + self.insets = nodeLayout.insets + let _ = nodeApply() + } + } + + func asyncLayout() -> (_ item: MediaGroupsHeaderItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + + + return { [weak self] item, params in + let titleString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 16.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let contentSize = CGSize(width: params.width, height: 45.0) + let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) + + return (nodeLayout, { [weak self] in + return (nil, { _ in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + let _ = titleApply() + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 17.0, y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0) + 2.0), size: titleLayout.size) + } + }) + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index f25956c192..7587121ecc 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -1,8 +1,401 @@ -// -// MediaGroupsScreen.swift -// _idx_TelegramUI_F082088E_ios_min9.0 -// -// Created by Ilya Laktyushin on 22.02.2022. -// - import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import MergeLists +import Photos +import LegacyComponents +import AttachmentUI +import ItemListUI + +private enum MediaGroupsEntry: Comparable, Identifiable { + enum StableId: Hashable { + case albumsHeader + case albums + case smartAlbumsHeader + case smartAlbum(String) + } + + case albumsHeader(PresentationTheme, String) + case albums(PresentationTheme, [PHAssetCollection]) + case smartAlbumsHeader(PresentationTheme, String) + case smartAlbum(PresentationTheme, Int, PHAssetCollection, Int) + + var stableId: StableId { + switch self { + case .albumsHeader: + return .albumsHeader + case .albums: + return .albums + case .smartAlbumsHeader: + return .smartAlbumsHeader + case let .smartAlbum(_, _, album, _): + return .smartAlbum(album.localIdentifier) + } + } + + static func ==(lhs: MediaGroupsEntry, rhs: MediaGroupsEntry) -> Bool { + switch lhs { + case let .albumsHeader(lhsTheme, lhsText): + if case let .albumsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .albums(lhsTheme, lhsAssetCollections): + if case let .albums(rhsTheme, rhsAssetCollections) = rhs, lhsTheme === rhsTheme, lhsAssetCollections == rhsAssetCollections { + return true + } else { + return false + } + case let .smartAlbumsHeader(lhsTheme, lhsText): + if case let .smartAlbumsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .smartAlbum(lhsTheme, lhsIndex, lhsAssetCollection, lhsCount): + if case let .smartAlbum(rhsTheme, rhsIndex, rhsAssetCollection, rhsCount) = rhs, lhsTheme === rhsTheme, lhsIndex == rhsIndex, lhsAssetCollection == rhsAssetCollection, lhsCount == rhsCount { + return true + } else { + return false + } + } + } + + private var sortId: Int { + switch self { + case .albumsHeader: + return 0 + case .albums: + return 1 + case .smartAlbumsHeader: + return 2 + case let .smartAlbum(_, index, _, _): + return 3 + index + } + } + + static func <(lhs: MediaGroupsEntry, rhs: MediaGroupsEntry) -> Bool { + return lhs.sortId < rhs.sortId + } + + func item(presentationData: PresentationData, openGroup: @escaping (PHAssetCollection) -> Void) -> ListViewItem { + switch self { + case let .albumsHeader(_, text), let .smartAlbumsHeader(_, text): + return MediaGroupsHeaderItem(presentationData: ItemListPresentationData(presentationData), title: text) + case let .albums(_, collections): + return MediaGroupsAlbumGridItem(presentationData: ItemListPresentationData(presentationData), collections: collections, action: { collection in + openGroup(collection) + }) + case let .smartAlbum(_, _, collection, count): + let title = collection.localizedTitle ?? "" + + let count = presentationStringsFormattedNumber(Int32(count), presentationData.dateTimeFormat.groupingSeparator) + var icon: MediaGroupsAlbumItem.Icon? + switch collection.assetCollectionSubtype { + case .smartAlbumAnimated: + icon = .animated + case .smartAlbumBursts: + icon = .bursts + case .smartAlbumDepthEffect: + icon = .selfPortraits + case .smartAlbumLivePhotos: + icon = .depthEffect + case .smartAlbumPanoramas: + icon = .panoramas + case .smartAlbumScreenshots: + icon = .screenshots + case .smartAlbumSelfPortraits: + icon = .selfPortraits + case .smartAlbumSlomoVideos: + icon = .slomoVideos + case .smartAlbumTimelapses: + icon = .timelapses + case .smartAlbumVideos: + icon = .videos + default: + icon = nil + } + return MediaGroupsAlbumItem(presentationData: ItemListPresentationData(presentationData), title: title, count: count, icon: icon, action: { + openGroup(collection) + }) + } + } +} + +private struct MediaGroupsTransition { + let deletions: [ListViewDeleteItem] + let insertions: [ListViewInsertItem] + let updates: [ListViewUpdateItem] +} + +private func preparedTransition(from fromEntries: [MediaGroupsEntry], to toEntries: [MediaGroupsEntry], presentationData: PresentationData, openGroup: @escaping (PHAssetCollection) -> Void) -> MediaGroupsTransition { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) + + let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, openGroup: openGroup), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, openGroup: openGroup), directionHint: nil) } + + return MediaGroupsTransition(deletions: deletions, insertions: insertions, updates: updates) +} + +public final class MediaGroupsScreen: ViewController { + private let context: AccountContext + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + private let mediaAssetsContext: MediaAssetsContext + private let openGroup: (PHAssetCollection) -> Void + + private class Node: ViewControllerTracingNode { + struct State { + let albums: PHFetchResult + let smartAlbums: PHFetchResult + } + + private weak var controller: MediaGroupsScreen? + private var presentationData: PresentationData + + private let containerNode: ASDisplayNode + private let listNode: ListView + + private var nextStableId: Int = 1 + private var currentEntries: [MediaGroupsEntry] = [] + private var enqueuedTransactions: [MediaGroupsTransition] = [] + private var state: State? + + private var itemsDisposable: Disposable? + + private var didSetReady = false + private let _ready = Promise() + var ready: Promise { + return self._ready + } + + private var validLayout: (ContainerViewLayout, CGFloat)? + + init(controller: MediaGroupsScreen) { + self.controller = controller + self.presentationData = controller.presentationData + + self.containerNode = ASDisplayNode() + self.listNode = ListView() + + super.init() + + self.containerNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.listNode) + + let updatedState = combineLatest(queue: Queue.mainQueue(), controller.mediaAssetsContext.fetchAssetsCollections(.album), controller.mediaAssetsContext.fetchAssetsCollections(.smartAlbum)) + self.itemsDisposable = (updatedState + |> deliverOnMainQueue).start(next: { [weak self] albums, smartAlbums in + guard let strongSelf = self else { + return + } + strongSelf.updateState(State(albums: albums, smartAlbums: smartAlbums)) + }) + + self.listNode.beganInteractiveDragging = { [weak self] _ in + self?.view.window?.endEditing(true) + } + } + + deinit { + self.itemsDisposable?.dispose() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result == self.view { + return nil + } + return result + } + + private func updateState(_ state: State) { + self.state = state + + var entries: [MediaGroupsEntry] = [] + + var albums: [PHAssetCollection] = [] + entries.append(.albumsHeader(self.presentationData.theme, self.presentationData.strings.Attachment_MyAlbums)) + state.smartAlbums.enumerateObjects { collection, _, _ in + if [.smartAlbumUserLibrary, .smartAlbumFavorites].contains(collection.assetCollectionSubtype) { + albums.append(collection) + } + } + state.albums.enumerateObjects { collection, _, _ in + albums.append(collection) + } + entries.append(.albums(self.presentationData.theme, albums)) + + let smartAlbumsHeaderIndex = entries.count + + var addedSmartAlbum = false + state.smartAlbums.enumerateObjects { collection, index, _ in + var supportedAlbums: [PHAssetCollectionSubtype] = [ + .smartAlbumBursts, + .smartAlbumPanoramas, + .smartAlbumScreenshots, + .smartAlbumSelfPortraits, + .smartAlbumSlomoVideos, + .smartAlbumTimelapses, + .smartAlbumVideos + ] + if #available(iOS 11, *) { + supportedAlbums.append(.smartAlbumAnimated) + supportedAlbums.append(.smartAlbumDepthEffect) + supportedAlbums.append(.smartAlbumLivePhotos) + } + if supportedAlbums.contains(collection.assetCollectionSubtype) { + let result = PHAsset.fetchAssets(in: collection, options: nil) + if result.count > 0 { + addedSmartAlbum = true + entries.append(.smartAlbum(self.presentationData.theme, index, collection, result.count)) + } + } + } + if addedSmartAlbum { + entries.insert(.smartAlbumsHeader(self.presentationData.theme, self.presentationData.strings.Attachment_MediaTypes), at: smartAlbumsHeaderIndex) + } + + let previousEntries = self.currentEntries + self.currentEntries = entries + + let transaction = preparedTransition(from: previousEntries, to: entries, presentationData: self.presentationData, openGroup: { [weak self] collection in + self?.view.window?.endEditing(true) + self?.controller?.openGroup(collection) + }) + self.enqueueTransaction(transaction) + } + + + func updatePresentationData(_ presentationData: PresentationData) { + self.presentationData = presentationData + + self.backgroundColor = presentationData.theme.list.plainBackgroundColor + } + + private func enqueueTransaction(_ transaction: MediaGroupsTransition) { + self.enqueuedTransactions.append(transaction) + + if let _ = self.validLayout { + while !self.enqueuedTransactions.isEmpty { + self.dequeueTransaction() + } + } + } + + private func dequeueTransaction() { + if self.enqueuedTransactions.isEmpty { + return + } + let transaction = self.enqueuedTransactions.removeFirst() + + let options = ListViewDeleteAndInsertOptions() + + self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in + if let strongSelf = self { + if !strongSelf.didSetReady { + strongSelf.didSetReady = true + strongSelf._ready.set(.single(true)) + } + } + }) + + } + + func scrollToTop() { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + let firstTime = self.validLayout == nil + self.validLayout = (layout, navigationBarHeight) + + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 2.0), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - 2.0))) + + let size = layout.size + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 7.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + 50.0, right: layout.safeInsets.right), headerInsets: UIEdgeInsets(), scrollIndicatorInsets: UIEdgeInsets(), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) + + if firstTime { + self.dequeueTransaction() + } + } + } + + private var validLayout: ContainerViewLayout? + + private var controllerNode: Node { + return self.displayNode as! Node + } + + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mediaAssetsContext: MediaAssetsContext, openGroup: @escaping (PHAssetCollection) -> Void) { + self.context = context + self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + self.mediaAssetsContext = mediaAssetsContext + self.openGroup = openGroup + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + let previousStrings = strongSelf.presentationData.strings + + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + strongSelf.controllerNode.updatePresentationData(presentationData) + } + } + }) + + self.scrollToTop = { [weak self] in + if let strongSelf = self { + strongSelf.controllerNode.scrollToTop() + } + } + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + override public func loadDisplayNode() { + self.displayNode = Node(controller: self) + + self._ready.set(self.controllerNode.ready.get()) + + super.displayNodeDidLoad() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.validLayout = layout + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift index 90ceb0c4e9..9b39f92ae1 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift @@ -9,6 +9,13 @@ import SolidRoundedButtonNode import PresentationDataUtils final class MediaPickerPlaceholderNode: ASDisplayNode { + enum Content { + case intro + case bannedSendMedia(String) + } + + private let content: Content + private var animationNode: AnimatedStickerNode private let titleNode: ImmediateTextNode private let textNode: ImmediateTextNode @@ -22,9 +29,22 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { var settingsPressed: () -> Void = {} var cameraPressed: () -> Void = {} - override init() { + init(content: Content) { + self.content = content + + let name: String + let playbackMode: AnimatedStickerPlaybackMode + switch content { + case .intro: + name = "Photos" + playbackMode = .loop + case .bannedSendMedia: + name = "Banned" + playbackMode = .once + } + self.animationNode = AnimatedStickerNode() - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Files"), width: 320, height: 320, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: name), width: 320, height: 320, playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil)) self.animationNode.visibility = true self.titleNode = ImmediateTextNode() @@ -54,33 +74,36 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { super.init() self.addSubnode(self.animationNode) - self.addSubnode(self.titleNode) self.addSubnode(self.textNode) - self.addSubnode(self.buttonNode) - self.addSubnode(self.cameraButtonNode) - self.cameraButtonNode.addSubnode(self.cameraTextNode) - self.cameraButtonNode.addSubnode(self.cameraIconNode) - - self.cameraButtonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.cameraTextNode.layer.removeAnimation(forKey: "opacity") - strongSelf.cameraTextNode.alpha = 0.4 - strongSelf.cameraIconNode.layer.removeAnimation(forKey: "opacity") - strongSelf.cameraIconNode.alpha = 0.4 - } else { - strongSelf.cameraTextNode.alpha = 1.0 - strongSelf.cameraTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.cameraIconNode.alpha = 1.0 - strongSelf.cameraIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + if case .intro = self.content { + self.addSubnode(self.titleNode) + self.addSubnode(self.buttonNode) + + self.addSubnode(self.cameraButtonNode) + self.cameraButtonNode.addSubnode(self.cameraTextNode) + self.cameraButtonNode.addSubnode(self.cameraIconNode) + + self.cameraButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.cameraTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.cameraTextNode.alpha = 0.4 + strongSelf.cameraIconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.cameraIconNode.alpha = 0.4 + } else { + strongSelf.cameraTextNode.alpha = 1.0 + strongSelf.cameraTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.cameraIconNode.alpha = 1.0 + strongSelf.cameraIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } } } - } - self.cameraButtonNode.addTarget(self, action: #selector(self.cameraButtonPressed), forControlEvents: .touchUpInside) - - self.buttonNode.pressed = { [weak self] in - self?.settingsPressed() + self.cameraButtonNode.addTarget(self, action: #selector(self.cameraButtonPressed), forControlEvents: .touchUpInside) + + self.buttonNode.pressed = { [weak self] in + self?.settingsPressed() + } } } @@ -94,20 +117,22 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { let themeUpdated = self.theme != theme self.theme = theme + var imageSize = CGSize(width: 144.0, height: 144.0) var insets = layout.insets(options: []) - insets.top += -160.0 + if layout.size.width == 460.0 { + insets.top += -60.0 + imageSize = CGSize(width: 112.0, height: 112.0) + } else { + insets.top += -160.0 + } let imageSpacing: CGFloat = 12.0 - let textSpacing: CGFloat = 16.0 + let textSpacing: CGFloat = 12.0 let buttonSpacing: CGFloat = 15.0 let cameraSpacing: CGFloat = 13.0 - let imageSize = CGSize(width: 144.0, height: 144.0) let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 - self.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: -10.0), size: imageSize) - self.animationNode.updateLayout(size: imageSize) - if themeUpdated { self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: theme)) self.cameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/OpenCamera"), color: theme.list.itemAccentColor) @@ -116,12 +141,23 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { let buttonWidth: CGFloat = 248.0 let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) - self.titleNode.attributedText = NSAttributedString(string: strings.Attachment_MediaAccessTitle, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center) - self.textNode.attributedText = NSAttributedString(string: strings.Attachment_MediaAccessText, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) + let title: String + let text: String + switch self.content { + case .intro: + title = strings.Attachment_MediaAccessTitle + text = strings.Attachment_MediaAccessText + case let .bannedSendMedia(banDescription): + title = "" + text = banDescription + } + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) self.cameraTextNode.attributedText = NSAttributedString(string: strings.Attachment_OpenCamera, font: Font.regular(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center) - let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) - let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let cameraSize = self.cameraTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height + buttonSpacing + buttonHeight + cameraSpacing + cameraSize.height @@ -129,6 +165,8 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) + self.animationNode.updateLayout(size: imageSize) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize)) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index ea1c2abd14..b9d5ce757e 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -75,12 +75,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let context: AccountContext private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + private let updatedPresentationData: (initial: PresentationData, signal: Signal)? fileprivate var interaction: MediaPickerInteraction? private let peer: EnginePeer? private let chatLocation: ChatLocation? private let bannedSendMedia: (Int32, Bool)? + private let collection: PHAssetCollection? private let titleView: MediaPickerTitleView private let moreButtonNode: MediaPickerMoreButtonNode @@ -91,13 +93,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)? public var presentSchedulePicker: (Bool, @escaping (Int32) -> Void) -> Void = { _, _ in } public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in } - public var presentWebSearch: () -> Void = {} + public var presentWebSearch: (MediaGroupsScreen) -> Void = { _ in } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?) -> Void = { _, _, _ in } - public var requestAttachmentMenuExpansion: () -> Void = {} - + public var requestAttachmentMenuExpansion: () -> Void = { } + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + private class Node: ViewControllerTracingNode { enum DisplayMode { case all @@ -111,14 +115,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private weak var controller: MediaPickerScreen? private var presentationData: PresentationData - private let mediaAssetsContext: MediaAssetsContext + fileprivate let mediaAssetsContext: MediaAssetsContext + private var requestedMediaAccess = false + private var requestedCameraAccess = false + + private let containerNode: ASDisplayNode private let backgroundNode: NavigationBackgroundNode private let gridNode: GridNode fileprivate var cameraView: TGAttachmentCameraView? private var placeholderNode: MediaPickerPlaceholderNode? private var manageNode: MediaPickerManageNode? - private var bannedTextNode: ImmediateTextNode? private var selectionNode: MediaPickerSelectedListNode? @@ -137,7 +144,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let hiddenMediaId = Promise(nil) private var didSetReady = false - private let _ready = Promise(true) + private let _ready = Promise() var ready: Promise { return self._ready } @@ -151,16 +158,20 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { let mediaAssetsContext = MediaAssetsContext() self.mediaAssetsContext = mediaAssetsContext + self.containerNode = ASDisplayNode() self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) + self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.gridNode = GridNode() super.init() - self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor +// self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.addSubnode(self.backgroundNode) - self.addSubnode(self.gridNode) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.backgroundNode) + self.containerNode.addSubnode(self.gridNode) + let collection = controller.collection let preloadPromise = self.preloadPromise let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess()) |> mapToSignal { mediaAccess, cameraAccess -> Signal in @@ -169,9 +180,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } else if [.restricted, .denied].contains(mediaAccess) { return .single(.noAccess(cameraAccess: cameraAccess)) } else { - return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get()) - |> map { fetchResult, preload in - return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + if let collection = collection { + return combineLatest(mediaAssetsContext.fetchAssets(collection), preloadPromise.get()) + |> map { fetchResult, preload in + return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + } + } else { + return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get()) + |> map { fetchResult, preload in + return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess) + } } } } @@ -188,6 +206,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self?.dismissInput() } + self.gridNode.visibleContentOffsetChanged = { [weak self] _ in + self?.updateNavigation(transition: .immediate) + } + self.hiddenMediaDisposable = (self.hiddenMediaId.get() |> deliverOnMainQueue).start(next: { [weak self] id in if let strongSelf = self { @@ -257,18 +279,22 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false - let cameraView = TGAttachmentCameraView(forSelfPortrait: false)! - cameraView.clipsToBounds = true - cameraView.removeCorners() - cameraView.pressed = { [weak self] in - if let strongSelf = self { - strongSelf.controller?.openCamera?(strongSelf.cameraView) + if self.controller?.collection == nil { + let cameraView = TGAttachmentCameraView(forSelfPortrait: false)! + cameraView.clipsToBounds = true + cameraView.removeCorners() + cameraView.pressed = { [weak self] in + if let strongSelf = self, !strongSelf.openingMedia { + strongSelf.controller?.openCamera?(strongSelf.cameraView) + } } + self.cameraView = cameraView + cameraView.startPreview() + + self.gridNode.scrollView.addSubview(cameraView) + } else { + self.containerNode.clipsToBounds = true } - self.cameraView = cameraView - cameraView.startPreview() - - self.gridNode.scrollView.addSubview(cameraView) // self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) } @@ -277,9 +303,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.view.window?.endEditing(true) } - private var requestedMediaAccess = false - private var requestedCameraAccess = false - private func updateState(_ state: State) { guard let controller = self.controller, let interaction = controller.interaction else { return @@ -337,6 +360,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if updateLayout, let (layout, navigationBarHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: previousState == nil ? .immediate : .animated(duration: 0.2, curve: .easeInOut)) } + self.updateNavigation(transition: .immediate) } private func updateSelectionState() { @@ -358,19 +382,22 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.backgroundColor = presentationData.theme.list.plainBackgroundColor + self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) } private var currentDisplayMode: DisplayMode = .all func updateMode(_ displayMode: DisplayMode) { + let updated = self.currentDisplayMode != displayMode self.currentDisplayMode = displayMode if case .selected = displayMode, self.selectionNode == nil, let controller = self.controller { let selectionNode = MediaPickerSelectedListNode(context: controller.context) + selectionNode.layer.allowsGroupOpacity = true selectionNode.alpha = 0.0 selectionNode.isUserInteractionEnabled = false selectionNode.interaction = self.controller?.interaction - self.insertSubnode(selectionNode, aboveSubnode: self.gridNode) + self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode) self.selectionNode = selectionNode if let (layout, navigationBarHeight) = self.validLayout { @@ -381,22 +408,42 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) self.gridNode.isUserInteractionEnabled = displayMode == .all + var completion: () -> Void = {} + if updated && displayMode == .all { + completion = { + self.updateNavigation(transition: .animated(duration: 0.1, curve: .easeInOut)) + } + } + if let selectionNode = self.selectionNode { - transition.updateAlpha(node: selectionNode, alpha: displayMode == .selected ? 1.0 : 0.0) + transition.updateAlpha(node: selectionNode, alpha: displayMode == .selected ? 1.0 : 0.0, completion: { _ in + completion() + }) selectionNode.isUserInteractionEnabled = displayMode == .selected } + + if updated && displayMode == .selected { + self.updateNavigation(transition: .immediate) + } } + var openingMedia = false fileprivate func openMedia(fetchResult: PHFetchResult, index: Int, immediateThumbnail: UIImage?) { - guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else { + guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout, !self.openingMedia else { return } Queue.mainQueue().justDispatch { self.dismissInput() } + var hasTimer = false + if controller.chatLocation?.peerId != controller.context.account.peerId && controller.chatLocation?.peerId.namespace == Namespaces.Peer.CloudUser { + hasTimer = true + } + + self.openingMedia = true let index = fetchResult.count - index - 1 - presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: true, updateHiddenMedia: { [weak self] id in + presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in self?.hiddenMediaId.set(.single(id)) }, initialLayout: layout, transitionHostView: { [weak self] in return self?.gridNode.view @@ -408,18 +455,26 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a) + }, finishedTransitionIn: { + self.openingMedia = false }) } fileprivate func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) { - guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else { + guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout, !self.openingMedia else { return } Queue.mainQueue().justDispatch { self.dismissInput() } - presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: true, updateHiddenMedia: { [weak self] id in + var hasTimer = false + if controller.chatLocation?.peerId != controller.context.account.peerId && controller.chatLocation?.peerId.namespace == Namespaces.Peer.CloudUser { + hasTimer = true + } + + self.openingMedia = true + presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in self?.hiddenMediaId.set(.single(id)) }, initialLayout: layout, transitionHostView: { [weak self] in return self?.selectionNode?.view @@ -430,16 +485,40 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false) } }, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in - self?.controller?.present(c, in: .window(.root), with: a) + self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) + }, finishedTransitionIn: { + self.openingMedia = false }) } fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) { - guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else { - return + var hasHeic = false + let allItems = self.controller?.interaction?.selectionState?.selectedItems() ?? [] + for item in allItems { + if item is TGCameraCapturedVideo { + } else if let asset = item as? TGMediaAsset, asset.uniformTypeIdentifier.contains("heic") { + hasHeic = true + break + } + } + + let proceed: (Bool) -> Void = { convertToJpeg in + guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else { + return + } + self.controller?.legacyCompletion(signals, silently, scheduleTime) + self.controller?.dismiss(animated: animated) + } + + if asFile && hasHeic { + self.controller?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.MediaPicker_JpegConversionText, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.MediaPicker_KeepHeic, action: { + proceed(false) + }), TextAlertAction(type: .genericAction, title: self.presentationData.strings.MediaPicker_ConvertToJpeg, action: { + proceed(true) + })], actionLayout: .vertical), in: .window(.root)) + } else { + proceed(false) } - self.controller?.legacyCompletion(signals, silently, scheduleTime) - self.controller?.dismiss(animated: animated) } private func openLimitedMediaOptions() { @@ -504,6 +583,42 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } + private var previousContentOffset: GridNodeVisibleContentOffset? + + func updateNavigation(transition: ContainedViewLayoutTransition) { + if let selectionNode = self.selectionNode, selectionNode.alpha > 0.0 { + self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) + self.controller?.updateTabBarAlpha(1.0, transition) + } else if self.placeholderNode != nil { + self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) + self.controller?.updateTabBarAlpha(0.0, transition) + } else { + var previousContentOffsetValue: CGFloat? + if let previousContentOffset = self.previousContentOffset, case let .known(value) = previousContentOffset { + previousContentOffsetValue = value + } + + let offset = self.gridNode.visibleContentOffset() + switch offset { + case let .known(value): + let transition: ContainedViewLayoutTransition + if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 2.0 { + transition = .animated(duration: 0.2, curve: .easeInOut) + } else { + transition = .immediate + } + self.controller?.navigationBar?.updateBackgroundAlpha(min(2.0, value) / 2.0, transition: transition) + case .unknown, .none: + self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) + } + } + + let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0) + if count > 0 { + self.controller?.updateTabBarAlpha(1.0, transition) + } + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { let firstTime = self.validLayout == nil self.validLayout = (layout, navigationBarHeight) @@ -511,10 +626,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { var insets = layout.insets(options: []) insets.top += navigationBarHeight + let overflowInset: CGFloat = 70.0 let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)) + let innerBounds = CGRect(origin: CGPoint(x: -overflowInset, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height)) let itemsPerRow: Int if case .compact = layout.metrics.widthClass { + self._ready.set(.single(true)) switch layout.orientation { case .portrait: itemsPerRow = 3 @@ -529,6 +647,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { let itemWidth = floorToScreenPixels((width - itemSpacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0)) + if self.cameraView == nil { + cameraRect = nil + } var manageHeight: CGFloat = 0.0 if case let .assets(_, _, mediaAccess, cameraAccess) = self.state { @@ -536,8 +657,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { cameraRect = nil } if let (untilDate, personal) = self.controller?.bannedSendMedia { - self.gridNode.alpha = 0.3 - self.gridNode.isUserInteractionEnabled = false + self.gridNode.isHidden = true let banDescription: String if untilDate != 0 && untilDate != Int32.max { @@ -548,24 +668,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { banDescription = self.presentationData.strings.Conversation_DefaultRestrictedMedia } - let bannedTextNode: ImmediateTextNode - if let current = self.bannedTextNode { - bannedTextNode = current + var placeholderTransition = transition + let placeholderNode: MediaPickerPlaceholderNode + if let current = self.placeholderNode { + placeholderNode = current } else { - bannedTextNode = ImmediateTextNode() - bannedTextNode.maximumNumberOfLines = 0 - bannedTextNode.textAlignment = .center - self.bannedTextNode = bannedTextNode - self.addSubnode(bannedTextNode) + placeholderNode = MediaPickerPlaceholderNode(content: .bannedSendMedia(banDescription)) + self.containerNode.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) + self.placeholderNode = placeholderNode + + placeholderTransition = .immediate } + placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: false, transition: placeholderTransition) + placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds) - bannedTextNode.attributedText = NSAttributedString(string: banDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.list.freeTextColor, paragraphAlignment: .center) - - let bannedTextSize = bannedTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0, height: layout.size.height)) - - manageHeight = max(44.0, bannedTextSize.height + 20.0) - - transition.updateFrame(node: bannedTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - bannedTextSize.width) / 2.0), y: insets.top + floorToScreenPixels((manageHeight - bannedTextSize.height) / 2.0) - 4.0), size: bannedTextSize)) + self.updateNavigation(transition: .immediate) } else if case .notDetermined = mediaAccess { } else { @@ -612,11 +729,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { let cleanGridInsets = UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right) let gridInsets = UIEdgeInsets(top: insets.top + manageHeight, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right) - transition.updateFrame(node: self.gridNode, frame: bounds) + transition.updateFrame(node: self.gridNode, frame: innerBounds) - transition.updateFrame(node: self.backgroundNode, frame: bounds) + transition.updateFrame(node: self.backgroundNode, frame: innerBounds) self.backgroundNode.update(size: bounds.size, transition: transition) + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: overflowInset, y: 0.0), size: CGSize(width: bounds.width - overflowInset * 2.0, height: bounds.height))) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemWidth, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in guard let strongSelf = self else { return @@ -645,7 +764,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } else { updateSelectionNode() } - transition.updateFrame(node: selectionNode, frame: bounds) + transition.updateFrame(node: selectionNode, frame: innerBounds) } if let cameraView = self.cameraView { @@ -669,24 +788,26 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let current = self.placeholderNode { placeholderNode = current } else { - placeholderNode = MediaPickerPlaceholderNode() + placeholderNode = MediaPickerPlaceholderNode(content: .intro) placeholderNode.settingsPressed = { [weak self] in self?.controller?.context.sharedContext.applicationBindings.openSettings() } placeholderNode.cameraPressed = { [weak self] in self?.controller?.openCamera?(nil) } - self.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) + self.containerNode.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) self.placeholderNode = placeholderNode if transition.isAnimated { placeholderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } placeholderTransition = .immediate + + self.updateNavigation(transition: .immediate) } placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: placeholderTransition) - placeholderTransition.updateFrame(node: placeholderNode, frame: bounds) - } else if let placeholderNode = self.placeholderNode { + placeholderTransition.updateFrame(node: placeholderNode, frame: innerBounds) + } else if let placeholderNode = self.placeholderNode, self.controller?.bannedSendMedia == nil { self.placeholderNode = nil placeholderNode.removeFromSupernode() } @@ -716,19 +837,23 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } private let groupedPromise = ValuePromise(true) - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?, collection: PHAssetCollection? = nil, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil) { self.context = context - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = presentationData + self.updatedPresentationData = updatedPresentationData self.peer = peer self.chatLocation = chatLocation self.bannedSendMedia = bannedSendMedia + self.collection = collection self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) - self.titleView.title = self.presentationData.strings.Attachment_Gallery + self.titleView.title = collection?.localizedTitle ?? presentationData.strings.Attachment_Gallery self.moreButtonNode = MediaPickerMoreButtonNode(theme: self.presentationData.theme) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData, hideBackground: false, hideBadge: false, hideSeparator: true)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) self.statusBar.statusBarStyle = .Ignore @@ -753,8 +878,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } self.navigationItem.titleView = self.titleView - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + + if collection == nil { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + } self.moreButtonNode.action = { [weak self] _, gesture in if let strongSelf = self { @@ -807,7 +939,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if let strongSelf = self { strongSelf.controllerNode.dismissInput() } - }, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext()) + }, selectionState: selectionContext ?? TGMediaSelectionContext(), editingState: editingContext ?? TGMediaEditingContext()) self.interaction?.selectionState?.grouping = true } @@ -853,10 +985,20 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.controllerNode.updatePresentationData(self.presentationData) } + @objc private func backPressed() { + self.updateNavigationStack { current in + return current.filter { $0 !== self } + } + } + @objc private func cancelPressed() { self.dismiss() } + @objc private func rightButtonPressed() { + self.moreButtonNode.action?(self.moreButtonNode.contextSourceNode, nil) + } + public func resetForReuse() { if let webSearchController = self.webSearchController { self.webSearchController = nil @@ -869,13 +1011,36 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public func prepareForReuse() { self.controllerNode.cameraView?.resumePreview() + + Queue.mainQueue().after(0.2, { + self.controllerNode.updateNavigation(transition: .animated(duration: 0.15, curve: .easeInOut)) + }) } @objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { switch self.moreButtonNode.iconNode.iconState { case .search: self.requestAttachmentMenuExpansion() - self.presentWebSearch() + self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in + if let strongSelf = self { + if let webSearchController = strongSelf.webSearchController { + strongSelf.webSearchController = nil + if collection.assetCollectionSubtype != .smartAlbumUserLibrary { + Queue.mainQueue().after(0.5) { + webSearchController.cancel() + } + } else { + webSearchController.cancel() + } + } + if collection.assetCollectionSubtype != .smartAlbumUserLibrary { + let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, chatLocation: strongSelf.chatLocation, bannedSendMedia: strongSelf.bannedSendMedia, collection: collection, editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) + mediaPicker._presentedInModal = true + mediaPicker.updateNavigationStack = strongSelf.updateNavigationStack + strongSelf.updateNavigationStack({ _ in return [strongSelf, mediaPicker]}) + } + } + })) case .more: let strings = self.presentationData.strings let selectionCount = self.selectionCount diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index ecf456e9d8..b7f65ac1cc 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -652,7 +652,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { private let moved: (CGPoint) -> Void private var initialLocation: CGPoint? - private var longTapTimer: SwiftSignalKit.Timer? private var longPressTimer: SwiftSignalKit.Timer? private var itemNode: MediaPickerSelectedItemNode? @@ -668,28 +667,12 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { } deinit { - self.longTapTimer?.invalidate() self.longPressTimer?.invalidate() } - private func startLongTapTimer() { - self.longTapTimer?.invalidate() - let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in - self?.longTapTimerFired() - }, queue: Queue.mainQueue()) - self.longTapTimer = longTapTimer - longTapTimer.start() - } - - private func stopLongTapTimer() { - self.itemNode = nil - self.longTapTimer?.invalidate() - self.longTapTimer = nil - } - private func startLongPressTimer() { self.longPressTimer?.invalidate() - let longPressTimer = SwiftSignalKit.Timer(timeout: 0.6, repeat: false, completion: { [weak self] in + let longPressTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in self?.longPressTimerFired() }, queue: Queue.mainQueue()) self.longPressTimer = longPressTimer @@ -706,22 +689,11 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { super.reset() self.itemNode = nil - self.stopLongTapTimer() self.stopLongPressTimer() self.initialLocation = nil } - private func longTapTimerFired() { - guard let location = self.initialLocation else { - return - } - - self.longTapTimer?.invalidate() - self.longTapTimer = nil - - self.willBegin(location) - } - + private func longPressTimerFired() { guard let _ = self.initialLocation else { return @@ -730,13 +702,12 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { self.state = .began self.longPressTimer?.invalidate() self.longPressTimer = nil - self.longTapTimer?.invalidate() - self.longTapTimer = nil if let itemNode = self.itemNode { self.began(itemNode) } } + private var currentItemNode: ASDisplayNode? override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) @@ -750,10 +721,12 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { if let location = touches.first?.location(in: self.view) { let (allowed, requiresLongPress, itemNode) = self.shouldBegin(location) if allowed { + if let itemNode = itemNode { + itemNode.layer.animateScale(from: 1.0, to: 0.98, duration: 0.2, delay: 0.1) + } self.itemNode = itemNode self.initialLocation = location if requiresLongPress { - self.startLongTapTimer() self.startLongPressTimer() } else { self.state = .began @@ -775,7 +748,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { self.initialLocation = nil - self.stopLongTapTimer() if self.longPressTimer != nil { self.stopLongPressTimer() self.state = .failed @@ -795,7 +767,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { self.initialLocation = nil - self.stopLongTapTimer() if self.longPressTimer != nil { self.stopLongPressTimer() self.state = .failed @@ -818,7 +789,8 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer { let dY = touchLocation.y - initialTapLocation.y if dX * dX + dY * dY > 3.0 * 3.0 { - self.stopLongTapTimer() + self.itemNode?.layer.removeAllAnimations() + self.stopLongPressTimer() self.initialLocation = nil self.state = .failed @@ -907,8 +879,8 @@ private final class ReorderingItemNode: ASDisplayNode { self.copyView.shadow.frame = CGRect(origin: CGPoint(x: -30.0, y: -30.0), size: CGSize(width: itemNode.bounds.size.width + 60.0, height: itemNode.bounds.size.height + 60.0)) self.copyView.shadow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - self.copyView.snapshotView?.layer.animateScale(from: 1.0, to: 1.05, duration: 0.25, removeOnCompletion: false) - self.copyView.shadow.layer.animateScale(from: 1.0, to: 1.05, duration: 0.25, removeOnCompletion: false) + self.copyView.snapshotView?.layer.animateScale(from: 1.0, to: 1.05, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + self.copyView.shadow.layer.animateScale(from: 1.0, to: 1.05, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } func updateOffset(offset: CGPoint) { diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 65035ab72b..4224870378 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1322,7 +1322,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie (parentController.navigationController as? NavigationController)?.pushViewController(contactsController) let _ = (contactsController.result |> deliverOnMainQueue).start(next: { result in - if let (peers, _, _, _) = result, let peer = peers.first { + if let (peers, _, _, _, _) = result, let peer = peers.first { let dataSignal: Signal<(Peer?, DeviceContactStableId?), NoError> switch peer { case let .peer(contact, _, _): diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index 96b50ab5b8..17d2adb293 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -713,6 +713,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { public var textReturned: ((String) -> Void)? public var clearPrefix: (() -> Void)? public var clearTokens: (() -> Void)? + public var focusUpdated: ((Bool) -> Void)? public var tokensUpdated: (([SearchBarToken]) -> Void)? @@ -1106,6 +1107,10 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) } + public func textFieldDidBeginEditing(_ textField: UITextField) { + self.focusUpdated?(true) + } + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if let _ = self.textField.selectedTokenIndex { if !string.isEmpty { @@ -1137,6 +1142,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } public func textFieldDidEndEditing(_ textField: UITextField) { + self.focusUpdated?(false) self.textField.selectedTokenIndex = nil } diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 5b1c3eb82f..1efafe699a 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -53,7 +53,9 @@ private class MediaHeaderItemNode: ASDisplayNode { let titleText: String = author.flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" let subtitleText: String if let peer = peer { - if peer is TelegramGroup || peer is TelegramChannel { + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + subtitleText = strings.MusicPlayer_VoiceNote + } else if peer is TelegramGroup || peer is TelegramChannel { subtitleText = EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder) } else { subtitleText = strings.MusicPlayer_VoiceNote diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/Contents.json new file mode 100644 index 0000000000..6ea7f2ba06 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_animated.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/attach_animated.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/attach_animated.pdf new file mode 100644 index 0000000000..8baa9f0ecb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Animated.imageset/attach_animated.pdf @@ -0,0 +1,173 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.629883 6.766602 cm +0.000000 0.000000 0.000000 scn +11.547852 0.000000 m +11.988281 0.000000 12.342773 0.322266 12.493164 0.623047 c +6.724609 10.581055 l +6.638672 10.731445 6.574219 10.871094 6.574219 10.989258 c +6.574219 11.107422 6.638672 11.247070 6.724609 11.397461 c +12.493164 21.355469 l +12.342773 21.656250 11.988281 21.978516 11.547852 21.978516 c +11.214844 21.978516 10.838867 21.785156 10.602539 21.387695 c +5.156250 12.160156 l +4.876953 11.708984 4.694336 11.386719 4.694336 10.989258 c +4.694336 10.602539 4.876953 10.269531 5.156250 9.818359 c +10.613281 0.590820 l +10.860352 0.182617 11.214844 0.000000 11.547852 0.000000 c +h +16.918945 0.010742 m +17.509766 0.010742 17.896484 0.354492 18.401367 1.213867 c +23.299805 9.625000 l +23.600586 10.140625 23.740234 10.559570 23.740234 10.989258 c +23.740234 11.429688 23.600586 11.837891 23.299805 12.353516 c +18.401367 20.764648 l +17.896484 21.624023 17.509766 21.967773 16.918945 21.967773 c +16.328125 21.967773 15.941406 21.624023 15.425781 20.764648 c +10.538086 12.353516 l +10.226562 11.837891 10.097656 11.429688 10.097656 10.989258 c +10.097656 10.559570 10.237305 10.140625 10.538086 9.625000 c +15.425781 1.213867 l +15.941406 0.354492 16.328125 0.010742 16.918945 0.010742 c +h +6.294922 20.356445 m +6.681641 20.356445 6.993164 20.667969 6.993164 21.054688 c +6.993164 21.452148 6.681641 21.763672 6.294922 21.763672 c +5.897461 21.763672 5.585938 21.452148 5.585938 21.054688 c +5.585938 20.667969 5.897461 20.356445 6.294922 20.356445 c +h +5.177734 18.369141 m +5.575195 18.369141 5.886719 18.680664 5.886719 19.067383 c +5.886719 19.464844 5.575195 19.776367 5.177734 19.776367 c +4.791016 19.776367 4.479492 19.464844 4.479492 19.067383 c +4.479492 18.680664 4.791016 18.369141 5.177734 18.369141 c +h +16.918945 2.255859 m +16.865234 2.255859 16.833008 2.288086 16.811523 2.341797 c +12.127930 10.516602 l +12.020508 10.688477 11.966797 10.838867 11.966797 10.989258 c +11.966797 11.139648 12.020508 11.290039 12.117188 11.461914 c +16.811523 19.647461 l +16.833008 19.690430 16.865234 19.722656 16.918945 19.722656 c +16.972656 19.722656 16.994141 19.690430 17.026367 19.647461 c +21.709961 11.461914 l +21.817383 11.290039 21.860352 11.139648 21.860352 10.989258 c +21.860352 10.838867 21.817383 10.688477 21.709961 10.516602 c +17.026367 2.341797 l +16.994141 2.288086 16.972656 2.255859 16.918945 2.255859 c +h +4.060547 16.360352 m +4.458008 16.360352 4.769531 16.682617 4.769531 17.069336 c +4.769531 17.456055 4.458008 17.778320 4.060547 17.778320 c +3.673828 17.778320 3.362305 17.456055 3.362305 17.069336 c +3.362305 16.682617 3.673828 16.360352 4.060547 16.360352 c +h +2.932617 14.383789 m +3.330078 14.383789 3.641602 14.695312 3.641602 15.092773 c +3.641602 15.479492 3.330078 15.791016 2.932617 15.791016 c +2.545898 15.791016 2.234375 15.479492 2.234375 15.092773 c +2.234375 14.695312 2.545898 14.383789 2.932617 14.383789 c +h +1.826172 12.385742 m +2.212891 12.385742 2.524414 12.697266 2.524414 13.083984 c +2.524414 13.481445 2.212891 13.792969 1.826172 13.792969 c +1.439453 13.792969 1.117188 13.481445 1.117188 13.083984 c +1.117188 12.697266 1.439453 12.385742 1.826172 12.385742 c +h +0.708984 10.398438 m +1.095703 10.398438 1.407227 10.709961 1.407227 11.096680 c +1.407227 11.494141 1.095703 11.805664 0.708984 11.805664 c +0.311523 11.805664 0.000000 11.494141 0.000000 11.096680 c +0.000000 10.709961 0.311523 10.398438 0.708984 10.398438 c +h +1.826172 8.411133 m +2.212891 8.411133 2.524414 8.722656 2.524414 9.109375 c +2.524414 9.506836 2.212891 9.818359 1.826172 9.818359 c +1.439453 9.818359 1.117188 9.506836 1.117188 9.109375 c +1.117188 8.722656 1.439453 8.411133 1.826172 8.411133 c +h +2.932617 6.402344 m +3.330078 6.402344 3.641602 6.724609 3.641602 7.111328 c +3.641602 7.498047 3.330078 7.820312 2.932617 7.820312 c +2.545898 7.820312 2.234375 7.498047 2.234375 7.111328 c +2.234375 6.724609 2.545898 6.402344 2.932617 6.402344 c +h +4.060547 4.425781 m +4.458008 4.425781 4.769531 4.748047 4.769531 5.134766 c +4.769531 5.521484 4.458008 5.843750 4.060547 5.843750 c +3.673828 5.843750 3.362305 5.521484 3.362305 5.134766 c +3.362305 4.748047 3.673828 4.425781 4.060547 4.425781 c +h +5.177734 2.438477 m +5.575195 2.438477 5.886719 2.750000 5.886719 3.147461 c +5.886719 3.534180 5.575195 3.845703 5.177734 3.845703 c +4.791016 3.845703 4.479492 3.534180 4.479492 3.147461 c +4.479492 2.750000 4.791016 2.438477 5.177734 2.438477 c +h +6.294922 0.440430 m +6.681641 0.440430 6.993164 0.751953 6.993164 1.149414 c +6.993164 1.546875 6.681641 1.858398 6.294922 1.858398 c +5.897461 1.858398 5.585938 1.546875 5.585938 1.149414 c +5.585938 0.751953 5.897461 0.440430 6.294922 0.440430 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 4693 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000004783 00000 n +0000004806 00000 n +0000004979 00000 n +0000005053 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5112 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/Contents.json new file mode 100644 index 0000000000..defe57707b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_bursts.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/attach_bursts.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/attach_bursts.pdf new file mode 100644 index 0000000000..a93cb94f66 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Burst.imageset/attach_bursts.pdf @@ -0,0 +1,113 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.929688 5.337891 cm +0.000000 0.000000 0.000000 scn +1.106445 5.102539 m +1.299805 5.102539 1.525391 5.166992 1.740234 5.317383 c +1.740234 16.811523 l +1.740234 17.198242 1.815430 17.348633 2.148438 17.531250 c +12.106445 23.267578 l +12.149414 23.976562 11.687500 24.416992 11.085938 24.416992 c +10.806641 24.416992 10.505859 24.341797 10.183594 24.148438 c +1.170898 18.970703 l +0.118164 18.358398 0.000000 18.165039 0.000000 16.940430 c +0.000000 6.660156 l +0.000000 5.736328 0.440430 5.102539 1.106445 5.102539 c +h +5.392578 2.621094 m +5.596680 2.621094 5.811523 2.685547 6.026367 2.835938 c +6.026367 14.330078 l +6.026367 14.749023 6.090820 14.856445 6.445312 15.049805 c +16.403320 20.796875 l +16.435547 21.495117 15.995117 21.946289 15.361328 21.946289 c +15.092773 21.946289 14.791992 21.860352 14.469727 21.688477 c +5.457031 16.500000 l +4.404297 15.898438 4.275391 15.672852 4.275391 14.469727 c +4.275391 4.178711 l +4.275391 3.254883 4.748047 2.621094 5.392578 2.621094 c +h +10.000977 0.000000 m +10.376953 0.000000 10.828125 0.150391 11.365234 0.451172 c +19.787109 5.285156 l +20.775391 5.854492 21.129883 6.445312 21.129883 7.648438 c +21.129883 17.380859 l +21.129883 18.669922 20.678711 19.325195 19.873047 19.325195 c +19.507812 19.325195 19.067383 19.185547 18.562500 18.895508 c +10.119141 14.029297 l +9.109375 13.438477 8.754883 12.836914 8.754883 11.666016 c +8.754883 1.933594 l +8.754883 0.687500 9.184570 0.000000 10.000977 0.000000 c +h +10.581055 2.094727 m +10.516602 2.105469 10.505859 2.137695 10.505859 2.212891 c +10.581055 11.633789 l +10.581055 12.041992 10.699219 12.256836 11.053711 12.460938 c +19.185547 17.241211 l +19.228516 17.262695 19.260742 17.262695 19.292969 17.251953 c +19.346680 17.241211 19.368164 17.208984 19.368164 17.133789 c +19.335938 7.680664 l +19.335938 7.326172 19.217773 7.057617 18.863281 6.842773 c +10.688477 2.105469 l +10.645508 2.083984 10.613281 2.083984 10.581055 2.094727 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1984 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002074 00000 n +0000002097 00000 n +0000002270 00000 n +0000002344 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2403 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/Contents.json new file mode 100644 index 0000000000..6ad5ea16c4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_cinematic.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/attach_cinematic.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/attach_cinematic.pdf new file mode 100644 index 0000000000..7dac088a12 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Cinematic.imageset/attach_cinematic.pdf @@ -0,0 +1,485 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.592773 8.925781 cm +0.000000 0.000000 0.000000 scn +4.189453 0.000000 m +15.415039 0.000000 l +18.036133 0.000000 19.604492 1.525391 19.604492 4.146484 c +19.604492 5.618164 l +23.665039 2.180664 l +24.094727 1.826172 24.567383 1.579102 25.007812 1.579102 c +25.953125 1.579102 26.576172 2.277344 26.576172 3.276367 c +26.576172 14.383789 l +26.576172 15.382812 25.953125 16.081055 25.007812 16.081055 c +24.567383 16.081055 24.094727 15.833984 23.665039 15.479492 c +19.604492 12.041992 l +19.604492 13.524414 l +19.604492 16.134766 18.036133 17.660156 15.415039 17.660156 c +4.189453 17.660156 l +1.686523 17.660156 0.000000 16.134766 0.000000 13.524414 c +0.000000 4.146484 l +0.000000 1.525391 1.568359 0.000000 4.189453 0.000000 c +h +4.490234 1.622070 m +2.728516 1.622070 1.729492 2.535156 1.729492 4.393555 c +1.729492 13.266602 l +1.729492 15.135742 2.728516 16.048828 4.490234 16.048828 c +15.114258 16.048828 l +16.865234 16.048828 17.875000 15.135742 17.875000 13.266602 c +17.875000 4.393555 l +17.875000 2.535156 16.865234 1.622070 15.114258 1.622070 c +4.490234 1.622070 l +h +24.470703 3.641602 m +19.604492 7.659180 l +19.604492 10.000977 l +24.470703 14.018555 l +24.567383 14.093750 24.631836 14.147461 24.728516 14.147461 c +24.857422 14.147461 24.911133 14.040039 24.911133 13.889648 c +24.911133 3.770508 l +24.911133 3.620117 24.857422 3.523438 24.728516 3.523438 c +24.631836 3.523438 24.567383 3.577148 24.470703 3.641602 c +h +f* +n +Q +q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 11.225647 cm +0.000000 0.000000 0.000000 scn +0.450664 7.941575 m +0.693185 7.941575 0.952624 8.012091 1.149094 8.213968 c +1.343381 8.413603 1.406961 8.670843 1.406961 8.903505 c +0.406961 8.903505 l +0.406961 8.874201 0.402920 8.864758 0.404416 8.869144 c +0.406454 8.875122 0.414068 8.892520 0.432455 8.911413 c +0.451071 8.930541 0.469642 8.939795 0.478687 8.943048 c +0.485932 8.945654 0.478955 8.941575 0.450664 8.941575 c +0.450664 7.941575 l +h +1.406961 8.903505 m +1.406961 10.232964 l +0.406961 10.232964 l +0.406961 8.903505 l +1.406961 8.903505 l +h +1.406961 10.232964 m +1.406961 10.441644 1.461145 10.516622 1.485847 10.541323 c +1.510620 10.566097 1.582848 10.617392 1.780123 10.617392 c +1.780123 11.617392 l +1.414067 11.617392 1.049714 11.519404 0.778740 11.248430 c +0.507693 10.977384 0.406961 10.610147 0.406961 10.232964 c +1.406961 10.232964 l +h +1.780123 10.617392 m +3.143381 10.617392 l +3.143381 11.617392 l +1.780123 11.617392 l +1.780123 10.617392 l +h +3.143381 10.617392 m +3.381150 10.617392 3.639620 10.683693 3.838258 10.880542 c +4.037554 11.078042 4.105311 11.336237 4.105311 11.573689 c +3.105311 11.573689 l +3.105311 11.546071 3.101378 11.538866 3.103720 11.545491 c +3.106672 11.553840 3.115477 11.572130 3.134358 11.590840 c +3.153171 11.609484 3.170862 11.617524 3.177536 11.619841 c +3.182541 11.621578 3.173327 11.617392 3.143381 11.617392 c +3.143381 10.617392 l +h +4.105311 11.573689 m +4.105311 11.811355 4.037323 12.070365 3.834985 12.267233 c +3.634857 12.461953 3.376599 12.524353 3.143381 12.524353 c +3.143381 11.524353 l +3.175038 11.524353 3.185540 11.520061 3.181437 11.521447 c +3.175622 11.523413 3.157468 11.531206 3.137630 11.550508 c +3.117559 11.570037 3.107757 11.589691 3.104216 11.599611 c +3.101361 11.607611 3.105311 11.601369 3.105311 11.573689 c +4.105311 11.573689 l +h +3.143381 12.524353 m +1.768856 12.524353 l +1.768856 11.524353 l +3.143381 11.524353 l +3.143381 12.524353 l +h +1.768856 12.524353 m +1.104903 12.524353 0.514360 12.360654 0.092355 11.945700 c +-0.331199 11.529222 -0.500000 10.943424 -0.500000 10.283664 c +0.500000 10.283664 l +0.500000 10.784363 0.626947 11.068910 0.793481 11.232662 c +0.961564 11.397937 1.255449 11.524353 1.768856 11.524353 c +1.768856 12.524353 l +h +-0.500000 10.283664 m +-0.500000 8.903505 l +0.500000 8.903505 l +0.500000 10.283664 l +-0.500000 10.283664 l +h +-0.500000 8.903505 m +-0.500000 8.666346 -0.432617 8.411142 -0.240725 8.213968 c +-0.046923 8.014833 0.208894 7.941575 0.450664 7.941575 c +0.450664 8.941575 l +0.427263 8.941575 0.422817 8.945058 0.431106 8.942020 c +0.440899 8.938431 0.458558 8.929247 0.475915 8.911413 c +0.493081 8.893774 0.500330 8.877576 0.502352 8.871754 c +0.503901 8.867291 0.500000 8.876007 0.500000 8.903505 c +-0.500000 8.903505 l +h +10.573703 7.941575 m +10.816223 7.941575 11.075663 8.012091 11.272133 8.213969 c +11.466420 8.413603 11.530000 8.670844 11.530000 8.903505 c +10.530000 8.903505 l +10.530000 8.874201 10.525958 8.864758 10.527454 8.869144 c +10.529492 8.875122 10.537107 8.892520 10.555493 8.911412 c +10.574109 8.930541 10.592681 8.939795 10.601726 8.943048 c +10.608971 8.945654 10.601994 8.941575 10.573703 8.941575 c +10.573703 7.941575 l +h +11.530000 8.903505 m +11.530000 10.283664 l +10.530000 10.283664 l +10.530000 8.903505 l +11.530000 8.903505 l +h +11.530000 10.283664 m +11.530000 10.943424 11.361198 11.529222 10.937644 11.945700 c +10.515639 12.360654 9.925097 12.524353 9.261144 12.524353 c +9.261144 11.524353 l +9.774550 11.524353 10.068436 11.397937 10.236519 11.232662 c +10.403052 11.068910 10.530000 10.784363 10.530000 10.283664 c +11.530000 10.283664 l +h +9.261144 12.524353 m +7.880985 12.524353 l +7.880985 11.524353 l +9.261144 11.524353 l +9.261144 12.524353 l +h +7.880985 12.524353 m +7.648191 12.524353 7.391307 12.460656 7.192692 12.265653 c +6.992592 12.069190 6.924688 11.811304 6.924688 11.573689 c +7.924688 11.573689 l +7.924688 11.601334 7.928629 11.607865 7.925930 11.600269 c +7.922583 11.590851 7.913094 11.571540 7.893283 11.552089 c +7.873628 11.532791 7.855208 11.524535 7.848195 11.522132 c +7.842865 11.520305 7.851656 11.524353 7.880985 11.524353 c +7.880985 12.524353 l +h +6.924688 11.573689 m +6.924688 11.336283 6.992366 11.079206 7.189434 10.882138 c +7.386502 10.685069 7.643578 10.617392 7.880985 10.617392 c +7.880985 11.617392 l +7.853402 11.617392 7.845911 11.621316 7.852134 11.619125 c +7.859986 11.616362 7.877925 11.607861 7.896541 11.589245 c +7.915157 11.570628 7.923658 11.552690 7.926422 11.544838 c +7.928613 11.538614 7.924688 11.546105 7.924688 11.573689 c +6.924688 11.573689 l +h +7.880985 10.617392 m +9.244244 10.617392 l +9.244244 11.617392 l +7.880985 11.617392 l +7.880985 10.617392 l +h +9.244244 10.617392 m +9.437582 10.617392 9.513561 10.566607 9.541740 10.538825 c +9.568624 10.512321 9.623038 10.436918 9.623038 10.232964 c +10.623038 10.232964 l +10.623038 10.614872 10.516905 10.981685 10.243814 11.250929 c +9.972020 11.518894 9.608603 11.617392 9.244244 11.617392 c +9.244244 10.617392 l +h +9.623038 10.232964 m +9.623038 8.903505 l +10.623038 8.903505 l +10.623038 10.232964 l +9.623038 10.232964 l +h +9.623038 8.903505 m +9.623038 8.666346 9.690422 8.411142 9.882315 8.213968 c +10.076117 8.014833 10.331933 7.941575 10.573703 7.941575 c +10.573703 8.941575 l +10.550302 8.941575 10.545856 8.945058 10.554144 8.942020 c +10.563938 8.938431 10.581596 8.929247 10.598953 8.911413 c +10.616119 8.893775 10.623368 8.877576 10.625390 8.871755 c +10.626940 8.867291 10.623038 8.876008 10.623038 8.903505 c +9.623038 8.903505 l +h +1.768856 0.499987 m +3.143381 0.499987 l +3.143381 1.499987 l +1.768856 1.499987 l +1.768856 0.499987 l +h +3.143381 0.499987 m +3.381126 0.499987 3.638457 0.566224 3.836632 0.760829 c +4.036100 0.956702 4.105311 1.213470 4.105311 1.450650 c +3.105311 1.450650 l +3.105311 1.425433 3.101656 1.419969 3.104469 1.427811 c +3.107882 1.437328 3.117153 1.455843 3.135983 1.474335 c +3.154683 1.492697 3.172005 1.500437 3.178184 1.502572 c +3.182789 1.504164 3.173292 1.499987 3.143381 1.499987 c +3.143381 0.499987 l +h +4.105311 1.450650 m +4.105311 1.688103 4.037554 1.946297 3.838258 2.143798 c +3.639621 2.340646 3.381150 2.406948 3.143381 2.406948 c +3.143381 1.406948 l +3.173327 1.406948 3.182540 1.402761 3.177535 1.404499 c +3.170861 1.406816 3.153170 1.414856 3.134358 1.433499 c +3.115477 1.452209 3.106672 1.470500 3.103720 1.478848 c +3.101378 1.485473 3.105311 1.478268 3.105311 1.450650 c +4.105311 1.450650 l +h +3.143381 2.406948 m +1.780123 2.406948 l +1.780123 1.406948 l +3.143381 1.406948 l +3.143381 2.406948 l +h +1.780123 2.406948 m +1.582848 2.406948 1.510620 2.458242 1.485847 2.483017 c +1.461145 2.507718 1.406961 2.582696 1.406961 2.791376 c +0.406961 2.791376 l +0.406961 2.414193 0.507693 2.046957 0.778740 1.775910 c +1.049714 1.504936 1.414067 1.406948 1.780123 1.406948 c +1.780123 2.406948 l +h +1.406961 2.791376 m +1.406961 4.120834 l +0.406961 4.120834 l +0.406961 2.791376 l +1.406961 2.791376 l +h +1.406961 4.120834 m +1.406961 4.358603 1.340660 4.617074 1.143812 4.815711 c +0.946311 5.015007 0.688117 5.082765 0.450664 5.082765 c +0.450664 4.082765 l +0.478281 4.082765 0.485486 4.078832 0.478862 4.081174 c +0.470513 4.084126 0.452223 4.092931 0.433512 4.111812 c +0.414869 4.130625 0.406829 4.148315 0.404512 4.154989 c +0.402775 4.159994 0.406961 4.150780 0.406961 4.120834 c +1.406961 4.120834 l +h +0.450664 5.082765 m +0.212998 5.082765 -0.046012 5.014776 -0.242880 4.812439 c +-0.437600 4.612310 -0.500000 4.354052 -0.500000 4.120834 c +0.500000 4.120834 l +0.500000 4.152492 0.504292 4.162993 0.502905 4.158891 c +0.500940 4.153075 0.493147 4.134922 0.473846 4.115084 c +0.454316 4.095012 0.434662 4.085211 0.424742 4.081670 c +0.416742 4.078815 0.422984 4.082765 0.450664 4.082765 c +0.450664 5.082765 l +h +-0.500000 4.120834 m +-0.500000 2.746309 l +0.500000 2.746309 l +0.500000 4.120834 l +-0.500000 4.120834 l +h +-0.500000 2.746309 m +-0.500000 2.084355 -0.331527 1.497156 0.091924 1.079769 c +0.514044 0.663693 1.104907 0.499987 1.768856 0.499987 c +1.768856 1.499987 l +1.255445 1.499987 0.961880 1.626395 0.793913 1.791957 c +0.627276 1.956208 0.500000 2.242170 0.500000 2.746309 c +-0.500000 2.746309 l +h +7.880985 0.499987 m +9.261144 0.499987 l +9.261144 1.499987 l +7.880985 1.499987 l +7.880985 0.499987 l +h +9.261144 0.499987 m +9.926300 0.499987 10.516878 0.665709 10.938506 1.082307 c +11.361227 1.499982 11.530000 2.086557 11.530000 2.746309 c +10.530000 2.746309 l +10.530000 2.245602 10.403025 1.959016 10.235657 1.793645 c +10.067197 1.627195 9.773346 1.499987 9.261144 1.499987 c +9.261144 0.499987 l +h +11.530000 2.746309 m +11.530000 4.120834 l +10.530000 4.120834 l +10.530000 2.746309 l +11.530000 2.746309 l +h +11.530000 4.120834 m +11.530000 4.358603 11.463698 4.617073 11.266850 4.815711 c +11.069350 5.015007 10.811155 5.082765 10.573703 5.082765 c +10.573703 4.082765 l +10.601320 4.082765 10.608525 4.078832 10.601901 4.081174 c +10.593552 4.084126 10.575261 4.092931 10.556551 4.111812 c +10.537908 4.130625 10.529867 4.148315 10.527551 4.154989 c +10.525813 4.159994 10.530000 4.150780 10.530000 4.120834 c +11.530000 4.120834 l +h +10.573703 5.082765 m +10.336037 5.082765 10.077027 5.014776 9.880158 4.812439 c +9.685439 4.612311 9.623038 4.354053 9.623038 4.120834 c +10.623038 4.120834 l +10.623038 4.152492 10.627330 4.162993 10.625944 4.158891 c +10.623979 4.153075 10.616185 4.134922 10.596884 4.115084 c +10.577354 4.095012 10.557701 4.085210 10.547780 4.081670 c +10.539782 4.078815 10.546023 4.082765 10.573703 4.082765 c +10.573703 5.082765 l +h +9.623038 4.120834 m +9.623038 2.791376 l +10.623038 2.791376 l +10.623038 4.120834 l +9.623038 4.120834 l +h +9.623038 2.791376 m +9.623038 2.587421 9.568624 2.512020 9.541740 2.485516 c +9.513561 2.457733 9.437582 2.406948 9.244244 2.406948 c +9.244244 1.406948 l +9.608603 1.406948 9.972020 1.505445 10.243814 1.773412 c +10.516905 2.042655 10.623038 2.409468 10.623038 2.791376 c +9.623038 2.791376 l +h +9.244244 2.406948 m +7.880985 2.406948 l +7.880985 1.406948 l +9.244244 1.406948 l +9.244244 2.406948 l +h +7.880985 2.406948 m +7.643578 2.406948 7.386502 2.339270 7.189434 2.142201 c +6.992366 1.945133 6.924688 1.688057 6.924688 1.450650 c +7.924688 1.450650 l +7.924688 1.478234 7.928613 1.485724 7.926422 1.479502 c +7.923658 1.471650 7.915157 1.453712 7.896542 1.435096 c +7.877926 1.416480 7.859987 1.407979 7.852135 1.405214 c +7.845911 1.403024 7.853402 1.406948 7.880985 1.406948 c +7.880985 2.406948 l +h +6.924688 1.450650 m +6.924688 1.213532 6.993807 0.957870 7.191052 0.762418 c +7.387669 0.567588 7.643617 0.499987 7.880985 0.499987 c +7.880985 1.499987 l +7.853435 1.499987 7.845659 1.503901 7.851480 1.501862 c +7.858835 1.499284 7.876413 1.491087 7.894923 1.472746 c +7.913496 1.454342 7.922459 1.436171 7.925679 1.427151 c +7.928335 1.419713 7.924688 1.425466 7.924688 1.450650 c +6.924688 1.450650 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 11.225647 cm +0.000000 0.000000 0.000000 scn +0.450664 8.441575 m +0.749229 8.441575 0.906961 8.610574 0.906961 8.903505 c +0.906961 10.232964 l +0.906961 10.818827 1.216793 11.117392 1.780123 11.117392 c +3.143381 11.117392 l +3.441946 11.117392 3.605311 11.280758 3.605311 11.573689 c +3.605311 11.866621 3.441946 12.024353 3.143381 12.024353 c +1.768856 12.024353 l +0.591496 12.024353 0.000000 11.444123 0.000000 10.283664 c +0.000000 8.903505 l +0.000000 8.610574 0.163366 8.441575 0.450664 8.441575 c +h +10.573703 8.441575 m +10.872268 8.441575 11.030000 8.610574 11.030000 8.903505 c +11.030000 10.283664 l +11.030000 11.444123 10.438503 12.024353 9.261144 12.024353 c +7.880985 12.024353 l +7.588054 12.024353 7.424688 11.866621 7.424688 11.573689 c +7.424688 11.280758 7.588054 11.117392 7.880985 11.117392 c +9.244244 11.117392 l +9.801940 11.117392 10.123038 10.818827 10.123038 10.232964 c +10.123038 8.903505 l +10.123038 8.610574 10.286405 8.441575 10.573703 8.441575 c +h +1.768856 0.999987 m +3.143381 0.999987 l +3.441946 0.999987 3.605311 1.163352 3.605311 1.450650 c +3.605311 1.743582 3.441946 1.906948 3.143381 1.906948 c +1.780123 1.906948 l +1.216793 1.906948 0.906961 2.205513 0.906961 2.791376 c +0.906961 4.120834 l +0.906961 4.419399 0.743595 4.582765 0.450664 4.582765 c +0.157732 4.582765 0.000000 4.419399 0.000000 4.120834 c +0.000000 2.746309 l +0.000000 1.580216 0.591496 0.999987 1.768856 0.999987 c +h +7.880985 0.999987 m +9.261144 0.999987 l +10.438503 0.999987 11.030000 1.585850 11.030000 2.746309 c +11.030000 4.120834 l +11.030000 4.419399 10.866634 4.582765 10.573703 4.582765 c +10.280771 4.582765 10.123038 4.419399 10.123038 4.120834 c +10.123038 2.791376 l +10.123038 2.205513 9.801940 1.906948 9.244244 1.906948 c +7.880985 1.906948 l +7.588054 1.906948 7.424688 1.743582 7.424688 1.450650 c +7.424688 1.163352 7.588054 0.999987 7.880985 0.999987 c +h +f* +n +Q +Q + +endstream +endobj + +3 0 obj + 14103 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000014193 00000 n +0000014217 00000 n +0000014390 00000 n +0000014464 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +14523 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/Contents.json new file mode 100644 index 0000000000..a4c5b18fc8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "favefilled_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/favefilled_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/favefilled_24.pdf new file mode 100644 index 0000000000..a3874530af --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Favorite.imageset/favefilled_24.pdf @@ -0,0 +1,75 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.500000 3.753906 cm +0.000000 0.000000 0.000000 scn +8.500000 0.000054 m +8.718812 0.000054 9.030198 0.159956 9.282673 0.319857 c +13.987128 3.349560 17.000000 6.901045 17.000000 10.503025 c +17.000000 13.591639 14.870791 15.746094 12.186138 15.746094 c +10.511386 15.746094 9.257425 14.820352 8.500000 13.431738 c +7.759406 14.811935 6.497030 15.746094 4.822277 15.746094 c +2.137624 15.746094 0.000000 13.591639 0.000000 10.503025 c +0.000000 6.901045 3.012871 3.349560 7.717327 0.319857 c +7.978218 0.159956 8.289604 0.000054 8.500000 0.000054 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 615 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000705 00000 n +0000000727 00000 n +0000000900 00000 n +0000000974 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1033 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/Contents.json new file mode 100644 index 0000000000..c899fca6e5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_live.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/attach_live.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/attach_live.pdf new file mode 100644 index 0000000000..0d4e036ff6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/LivePhoto.imageset/attach_live.pdf @@ -0,0 +1,305 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.065430 5.799805 cm +0.000000 0.000000 0.000000 scn +11.934570 22.751953 m +12.246094 22.751953 12.482422 22.999023 12.482422 23.299805 c +12.482422 23.600586 12.246094 23.869141 11.934570 23.869141 c +11.644531 23.869141 11.375977 23.600586 11.375977 23.299805 c +11.375977 22.999023 11.644531 22.751953 11.934570 22.751953 c +h +13.921875 22.580078 m +14.211914 22.580078 14.480469 22.837891 14.480469 23.127930 c +14.480469 23.428711 14.211914 23.697266 13.921875 23.697266 c +13.599609 23.697266 13.363281 23.428711 13.363281 23.127930 c +13.363281 22.837891 13.599609 22.580078 13.921875 22.580078 c +h +9.947266 22.580078 m +10.258789 22.580078 10.495117 22.837891 10.495117 23.127930 c +10.495117 23.428711 10.258789 23.697266 9.947266 23.697266 c +9.657227 23.697266 9.388672 23.428711 9.388672 23.127930 c +9.388672 22.837891 9.657227 22.580078 9.947266 22.580078 c +h +15.833984 22.075195 m +16.113281 22.075195 16.381836 22.322266 16.381836 22.612305 c +16.381836 22.913086 16.113281 23.181641 15.833984 23.181641 c +15.511719 23.181641 15.275391 22.913086 15.275391 22.612305 c +15.275391 22.322266 15.511719 22.075195 15.833984 22.075195 c +h +8.035156 22.075195 m +8.346680 22.075195 8.583008 22.322266 8.583008 22.612305 c +8.583008 22.913086 8.346680 23.181641 8.035156 23.181641 c +7.745117 23.181641 7.487305 22.913086 7.487305 22.612305 c +7.487305 22.322266 7.745117 22.075195 8.035156 22.075195 c +h +17.627930 21.226562 m +17.917969 21.226562 18.175781 21.473633 18.175781 21.774414 c +18.175781 22.075195 17.917969 22.333008 17.627930 22.333008 c +17.316406 22.333008 17.069336 22.075195 17.069336 21.774414 c +17.069336 21.473633 17.316406 21.226562 17.627930 21.226562 c +h +6.241211 21.226562 m +6.563477 21.226562 6.799805 21.473633 6.799805 21.774414 c +6.799805 22.075195 6.563477 22.333008 6.241211 22.333008 c +5.940430 22.333008 5.682617 22.075195 5.682617 21.774414 c +5.682617 21.473633 5.940430 21.226562 6.241211 21.226562 c +h +19.250000 20.087891 m +19.550781 20.087891 19.808594 20.345703 19.808594 20.625000 c +19.808594 20.936523 19.550781 21.194336 19.250000 21.194336 c +18.949219 21.194336 18.712891 20.936523 18.712891 20.625000 c +18.712891 20.345703 18.949219 20.087891 19.250000 20.087891 c +h +4.608398 20.087891 m +4.919922 20.087891 5.166992 20.345703 5.166992 20.625000 c +5.166992 20.936523 4.919922 21.194336 4.608398 21.194336 c +4.318359 21.194336 4.049805 20.936523 4.049805 20.625000 c +4.049805 20.345703 4.318359 20.087891 4.608398 20.087891 c +h +11.945312 3.254883 m +16.736328 3.254883 20.614258 7.122070 20.614258 11.934570 c +20.614258 16.704102 16.714844 20.603516 11.945312 20.603516 c +7.122070 20.603516 3.265625 16.736328 3.265625 11.934570 c +3.265625 7.100586 7.111328 3.254883 11.945312 3.254883 c +h +20.657227 18.691406 m +20.958008 18.691406 21.215820 18.938477 21.215820 19.228516 c +21.215820 19.540039 20.958008 19.797852 20.657227 19.797852 c +20.345703 19.797852 20.098633 19.540039 20.098633 19.228516 c +20.098633 18.938477 20.345703 18.691406 20.657227 18.691406 c +h +3.201172 18.691406 m +3.512695 18.691406 3.759766 18.938477 3.759766 19.228516 c +3.759766 19.540039 3.512695 19.797852 3.201172 19.797852 c +2.911133 19.797852 2.653320 19.540039 2.653320 19.228516 c +2.653320 18.938477 2.911133 18.691406 3.201172 18.691406 c +h +11.945312 4.307617 m +7.691406 4.307617 4.318359 7.680664 4.318359 11.934570 c +4.318359 16.156250 7.712891 19.550781 11.945312 19.550781 c +16.145508 19.550781 19.561523 16.134766 19.561523 11.934570 c +19.561523 7.702148 16.166992 4.307617 11.945312 4.307617 c +h +21.795898 17.058594 m +22.085938 17.058594 22.343750 17.305664 22.343750 17.606445 c +22.343750 17.896484 22.085938 18.165039 21.795898 18.165039 c +21.484375 18.165039 21.248047 17.896484 21.248047 17.606445 c +21.248047 17.305664 21.484375 17.058594 21.795898 17.058594 c +h +2.073242 17.058594 m +2.374023 17.058594 2.631836 17.305664 2.631836 17.606445 c +2.631836 17.896484 2.374023 18.165039 2.073242 18.165039 c +1.772461 18.165039 1.514648 17.896484 1.514648 17.606445 c +1.514648 17.305664 1.772461 17.058594 2.073242 17.058594 c +h +22.633789 15.253906 m +22.923828 15.253906 23.181641 15.500977 23.181641 15.801758 c +23.181641 16.102539 22.923828 16.371094 22.633789 16.371094 c +22.311523 16.371094 22.075195 16.102539 22.075195 15.801758 c +22.075195 15.500977 22.311523 15.253906 22.633789 15.253906 c +h +1.235352 15.253906 m +1.557617 15.253906 1.793945 15.500977 1.793945 15.801758 c +1.793945 16.102539 1.557617 16.371094 1.235352 16.371094 c +0.934570 16.371094 0.676758 16.102539 0.676758 15.801758 c +0.676758 15.500977 0.934570 15.253906 1.235352 15.253906 c +h +11.945312 7.498047 m +14.383789 7.498047 16.349609 9.463867 16.349609 11.913086 c +16.349609 14.351562 14.383789 16.317383 11.945312 16.317383 c +9.496094 16.317383 7.530273 14.351562 7.530273 11.913086 c +7.530273 9.453125 9.485352 7.498047 11.945312 7.498047 c +h +23.138672 13.341797 m +23.428711 13.341797 23.697266 13.599609 23.697266 13.900391 c +23.697266 14.190430 23.428711 14.458984 23.138672 14.458984 c +22.816406 14.458984 22.580078 14.190430 22.580078 13.900391 c +22.580078 13.599609 22.816406 13.341797 23.138672 13.341797 c +h +0.719727 13.341797 m +1.041992 13.341797 1.278320 13.599609 1.278320 13.900391 c +1.278320 14.190430 1.041992 14.458984 0.719727 14.458984 c +0.429688 14.458984 0.171875 14.190430 0.171875 13.900391 c +0.171875 13.599609 0.429688 13.341797 0.719727 13.341797 c +h +11.945312 9.517578 m +10.613281 9.517578 9.549805 10.581055 9.549805 11.913086 c +9.549805 13.223633 10.624023 14.297852 11.945312 14.297852 c +13.255859 14.297852 14.330078 13.212891 14.330078 11.913086 c +14.330078 10.591797 13.266602 9.517578 11.945312 9.517578 c +h +0.558594 11.375977 m +0.870117 11.375977 1.106445 11.633789 1.106445 11.913086 c +1.106445 12.224609 0.870117 12.482422 0.558594 12.482422 c +0.257812 12.482422 0.000000 12.224609 0.000000 11.913086 c +0.000000 11.633789 0.257812 11.375977 0.558594 11.375977 c +h +23.310547 11.375977 m +23.600586 11.375977 23.869141 11.633789 23.869141 11.913086 c +23.869141 12.224609 23.600586 12.482422 23.310547 12.482422 c +22.999023 12.482422 22.751953 12.224609 22.751953 11.913086 c +22.751953 11.633789 22.999023 11.375977 23.310547 11.375977 c +h +0.719727 9.399414 m +1.041992 9.399414 1.278320 9.667969 1.278320 9.968750 c +1.278320 10.258789 1.041992 10.516602 0.719727 10.516602 c +0.429688 10.516602 0.171875 10.258789 0.171875 9.968750 c +0.171875 9.667969 0.429688 9.399414 0.719727 9.399414 c +h +23.138672 9.399414 m +23.428711 9.399414 23.697266 9.667969 23.697266 9.968750 c +23.697266 10.258789 23.428711 10.516602 23.138672 10.516602 c +22.816406 10.516602 22.580078 10.258789 22.580078 9.968750 c +22.580078 9.667969 22.816406 9.399414 23.138672 9.399414 c +h +1.235352 7.498047 m +1.557617 7.498047 1.793945 7.755859 1.793945 8.067383 c +1.793945 8.346680 1.557617 8.615234 1.235352 8.615234 c +0.934570 8.615234 0.676758 8.346680 0.676758 8.067383 c +0.676758 7.755859 0.934570 7.498047 1.235352 7.498047 c +h +22.633789 7.498047 m +22.923828 7.498047 23.181641 7.755859 23.181641 8.067383 c +23.181641 8.346680 22.923828 8.615234 22.633789 8.615234 c +22.311523 8.615234 22.075195 8.346680 22.075195 8.067383 c +22.075195 7.755859 22.311523 7.498047 22.633789 7.498047 c +h +2.073242 5.693359 m +2.374023 5.693359 2.631836 5.961914 2.631836 6.262695 c +2.631836 6.563477 2.374023 6.810547 2.073242 6.810547 c +1.772461 6.810547 1.514648 6.563477 1.514648 6.262695 c +1.514648 5.961914 1.772461 5.693359 2.073242 5.693359 c +h +21.795898 5.693359 m +22.085938 5.693359 22.343750 5.961914 22.343750 6.262695 c +22.343750 6.563477 22.085938 6.810547 21.795898 6.810547 c +21.484375 6.810547 21.248047 6.563477 21.248047 6.262695 c +21.248047 5.961914 21.484375 5.693359 21.795898 5.693359 c +h +3.201172 4.071289 m +3.512695 4.071289 3.759766 4.329102 3.759766 4.629883 c +3.759766 4.930664 3.512695 5.177734 3.201172 5.177734 c +2.911133 5.177734 2.653320 4.930664 2.653320 4.629883 c +2.653320 4.329102 2.911133 4.071289 3.201172 4.071289 c +h +20.657227 4.071289 m +20.958008 4.071289 21.215820 4.329102 21.215820 4.629883 c +21.215820 4.930664 20.958008 5.177734 20.657227 5.177734 c +20.345703 5.177734 20.098633 4.930664 20.098633 4.629883 c +20.098633 4.329102 20.345703 4.071289 20.657227 4.071289 c +h +4.608398 2.664062 m +4.919922 2.664062 5.166992 2.921875 5.166992 3.233398 c +5.166992 3.523438 4.919922 3.770508 4.608398 3.770508 c +4.318359 3.770508 4.049805 3.523438 4.049805 3.233398 c +4.049805 2.921875 4.318359 2.664062 4.608398 2.664062 c +h +19.250000 2.664062 m +19.550781 2.664062 19.808594 2.921875 19.808594 3.233398 c +19.808594 3.523438 19.550781 3.770508 19.250000 3.770508 c +18.949219 3.770508 18.712891 3.523438 18.712891 3.233398 c +18.712891 2.921875 18.949219 2.664062 19.250000 2.664062 c +h +6.241211 1.525391 m +6.563477 1.525391 6.799805 1.793945 6.799805 2.094727 c +6.799805 2.384766 6.563477 2.642578 6.241211 2.642578 c +5.940430 2.642578 5.682617 2.384766 5.682617 2.094727 c +5.682617 1.793945 5.940430 1.525391 6.241211 1.525391 c +h +17.627930 1.525391 m +17.917969 1.525391 18.175781 1.793945 18.175781 2.094727 c +18.175781 2.384766 17.917969 2.642578 17.627930 2.642578 c +17.316406 2.642578 17.069336 2.384766 17.069336 2.094727 c +17.069336 1.793945 17.316406 1.525391 17.627930 1.525391 c +h +8.035156 0.676758 m +8.346680 0.676758 8.583008 0.945312 8.583008 1.246094 c +8.583008 1.546875 8.346680 1.793945 8.035156 1.793945 c +7.745117 1.793945 7.487305 1.546875 7.487305 1.246094 c +7.487305 0.945312 7.745117 0.676758 8.035156 0.676758 c +h +15.833984 0.676758 m +16.113281 0.676758 16.381836 0.945312 16.381836 1.246094 c +16.381836 1.546875 16.113281 1.793945 15.833984 1.793945 c +15.511719 1.793945 15.275391 1.546875 15.275391 1.246094 c +15.275391 0.945312 15.511719 0.676758 15.833984 0.676758 c +h +9.947266 0.171875 m +10.258789 0.171875 10.495117 0.440430 10.495117 0.741211 c +10.495117 1.031250 10.258789 1.278320 9.947266 1.278320 c +9.657227 1.278320 9.388672 1.031250 9.388672 0.741211 c +9.388672 0.440430 9.657227 0.171875 9.947266 0.171875 c +h +13.921875 0.171875 m +14.211914 0.171875 14.480469 0.440430 14.480469 0.741211 c +14.480469 1.031250 14.211914 1.278320 13.921875 1.278320 c +13.599609 1.278320 13.363281 1.031250 13.363281 0.741211 c +13.363281 0.440430 13.599609 0.171875 13.921875 0.171875 c +h +11.934570 0.000000 m +12.246094 0.000000 12.482422 0.257812 12.482422 0.569336 c +12.482422 0.848633 12.246094 1.106445 11.934570 1.106445 c +11.644531 1.106445 11.375977 0.848633 11.375977 0.569336 c +11.375977 0.257812 11.644531 0.000000 11.934570 0.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 10544 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000010634 00000 n +0000010658 00000 n +0000010831 00000 n +0000010905 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +10964 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/Contents.json new file mode 100644 index 0000000000..bcbf32a5c4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_pano.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/attach_pano.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/attach_pano.pdf new file mode 100644 index 0000000000..a28a98fe52 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Panorama.imageset/attach_pano.pdf @@ -0,0 +1,89 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.889648 8.539062 cm +0.000000 0.000000 0.000000 scn +1.804688 0.000000 m +3.791992 0.000000 6.821289 2.363281 13.610352 2.363281 c +20.388672 2.363281 23.439453 0.010742 25.416016 0.010742 c +26.597656 0.010742 27.220703 0.730469 27.220703 1.922852 c +27.220703 16.478516 l +27.220703 17.681641 26.597656 18.401367 25.416016 18.401367 c +23.439453 18.401367 20.388672 16.038086 13.610352 16.038086 c +6.842773 16.038086 3.791992 18.401367 1.804688 18.401367 c +0.623047 18.401367 0.000000 17.681641 0.000000 16.489258 c +0.000000 1.912109 l +0.000000 0.719727 0.623047 0.000000 1.804688 0.000000 c +h +2.030273 1.783203 m +1.836914 1.783203 1.729492 1.890625 1.729492 2.094727 c +1.729492 16.306641 l +1.729492 16.510742 1.836914 16.618164 2.030273 16.618164 c +3.083008 16.618164 6.778320 14.405273 13.610352 14.405273 c +20.442383 14.405273 24.374023 16.628906 25.201172 16.628906 c +25.383789 16.628906 25.491211 16.521484 25.491211 16.306641 c +25.491211 2.094727 l +25.491211 1.890625 25.383789 1.783203 25.201172 1.783203 c +24.148438 1.783203 20.442383 3.996094 13.610352 3.996094 c +6.756836 3.996094 2.857422 1.783203 2.030273 1.783203 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1200 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001290 00000 n +0000001313 00000 n +0000001486 00000 n +0000001560 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1619 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/Contents.json new file mode 100644 index 0000000000..a4ce6aceb6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_portrait.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/attach_portrait.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/attach_portrait.pdf new file mode 100644 index 0000000000..f7f96c90a9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Portrait.imageset/attach_portrait.pdf @@ -0,0 +1,100 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.150391 5.654785 cm +0.000000 0.000000 0.000000 scn +1.503906 5.322754 m +9.958008 0.531738 l +10.559570 0.187988 11.128906 0.187988 11.741211 0.531738 c +20.184570 5.322754 l +21.172852 5.881348 21.688477 6.450684 21.688477 7.986816 c +21.688477 16.494629 l +21.688477 17.611816 21.280273 18.310059 20.377930 18.825684 c +12.772461 23.144043 l +11.472656 23.895996 10.215820 23.895996 8.916016 23.144043 c +1.321289 18.825684 l +0.408203 18.310059 0.000000 17.611816 0.000000 16.494629 c +0.000000 7.986816 l +0.000000 6.450684 0.526367 5.881348 1.503906 5.322754 c +h +10.849609 12.874512 m +2.556641 17.568848 l +9.635742 21.607910 l +10.462891 22.080566 11.225586 22.091309 12.063477 21.607910 c +19.142578 17.568848 l +10.849609 12.874512 l +h +2.470703 6.740723 m +1.847656 7.084473 1.632812 7.449707 1.632812 8.040527 c +1.632812 16.150879 l +10.000977 11.370605 l +10.000977 2.454590 l +2.470703 6.740723 l +h +19.228516 6.740723 m +11.687500 2.454590 l +11.687500 11.370605 l +20.055664 16.150879 l +20.055664 8.040527 l +20.055664 7.449707 19.840820 7.084473 19.228516 6.740723 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1132 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001222 00000 n +0000001245 00000 n +0000001418 00000 n +0000001492 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1551 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/Contents.json new file mode 100644 index 0000000000..1c25609cbd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_screenrec.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/attach_screenrec.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/attach_screenrec.pdf new file mode 100644 index 0000000000..673f6403aa --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/ScreenRecording.imageset/attach_screenrec.pdf @@ -0,0 +1,83 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.042969 6.798828 cm +0.000000 0.000000 0.000000 scn +10.957031 0.000000 m +16.951172 0.000000 21.914062 4.973633 21.914062 10.957031 c +21.914062 16.951172 16.940430 21.914062 10.946289 21.914062 c +4.962891 21.914062 0.000000 16.951172 0.000000 10.957031 c +0.000000 4.973633 4.973633 0.000000 10.957031 0.000000 c +h +10.957031 1.826172 m +5.886719 1.826172 1.836914 5.886719 1.836914 10.957031 c +1.836914 16.027344 5.875977 20.087891 10.946289 20.087891 c +16.016602 20.087891 20.077148 16.027344 20.087891 10.957031 c +20.098633 5.886719 16.027344 1.826172 10.957031 1.826172 c +h +10.967773 6.756836 m +13.288086 6.756836 15.178711 8.647461 15.178711 10.978516 c +15.178711 13.298828 13.288086 15.178711 10.967773 15.178711 c +8.636719 15.178711 6.746094 13.298828 6.746094 10.978516 c +6.746094 8.647461 8.636719 6.756836 10.967773 6.756836 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 909 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000999 00000 n +0000001021 00000 n +0000001194 00000 n +0000001268 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1327 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/Contents.json new file mode 100644 index 0000000000..defa717431 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_screen.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/attach_screen.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/attach_screen.pdf new file mode 100644 index 0000000000..3d255a5b5a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Screenshot.imageset/attach_screen.pdf @@ -0,0 +1,113 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.983398 7.228516 cm +0.000000 0.000000 0.000000 scn +0.859375 14.190430 m +1.428711 14.190430 1.729492 14.512695 1.729492 15.071289 c +1.729492 17.606445 l +1.729492 18.723633 2.320312 19.292969 3.394531 19.292969 c +5.994141 19.292969 l +6.563477 19.292969 6.875000 19.604492 6.875000 20.163086 c +6.875000 20.721680 6.563477 21.022461 5.994141 21.022461 c +3.373047 21.022461 l +1.127930 21.022461 0.000000 19.916016 0.000000 17.703125 c +0.000000 15.071289 l +0.000000 14.512695 0.311523 14.190430 0.859375 14.190430 c +h +20.163086 14.190430 m +20.732422 14.190430 21.033203 14.512695 21.033203 15.071289 c +21.033203 17.703125 l +21.033203 19.916016 19.905273 21.022461 17.660156 21.022461 c +15.028320 21.022461 l +14.469727 21.022461 14.158203 20.721680 14.158203 20.163086 c +14.158203 19.604492 14.469727 19.292969 15.028320 19.292969 c +17.627930 19.292969 l +18.691406 19.292969 19.303711 18.723633 19.303711 17.606445 c +19.303711 15.071289 l +19.303711 14.512695 19.615234 14.190430 20.163086 14.190430 c +h +3.373047 0.000000 m +5.994141 0.000000 l +6.563477 0.000000 6.875000 0.311523 6.875000 0.859375 c +6.875000 1.417969 6.563477 1.729492 5.994141 1.729492 c +3.394531 1.729492 l +2.320312 1.729492 1.729492 2.298828 1.729492 3.416016 c +1.729492 5.951172 l +1.729492 6.520508 1.417969 6.832031 0.859375 6.832031 c +0.300781 6.832031 0.000000 6.520508 0.000000 5.951172 c +0.000000 3.330078 l +0.000000 1.106445 1.127930 0.000000 3.373047 0.000000 c +h +15.028320 0.000000 m +17.660156 0.000000 l +19.905273 0.000000 21.033203 1.117188 21.033203 3.330078 c +21.033203 5.951172 l +21.033203 6.520508 20.721680 6.832031 20.163086 6.832031 c +19.604492 6.832031 19.303711 6.520508 19.303711 5.951172 c +19.303711 3.416016 l +19.303711 2.298828 18.691406 1.729492 17.627930 1.729492 c +15.028320 1.729492 l +14.469727 1.729492 14.158203 1.417969 14.158203 0.859375 c +14.158203 0.311523 14.469727 0.000000 15.028320 0.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1970 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002060 00000 n +0000002083 00000 n +0000002256 00000 n +0000002330 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2389 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/Contents.json new file mode 100644 index 0000000000..40cedb6e28 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_selfie.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/attach_selfie.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/attach_selfie.pdf new file mode 100644 index 0000000000..a72cc43451 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Selfie.imageset/attach_selfie.pdf @@ -0,0 +1,92 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 8.106445 7.851562 cm +0.000000 0.000000 0.000000 scn +3.373047 0.000000 m +16.403320 0.000000 l +18.659180 0.000000 19.776367 1.117188 19.776367 3.330078 c +19.776367 16.446289 l +19.776367 18.659180 18.659180 19.776367 16.403320 19.776367 c +3.373047 19.776367 l +1.127930 19.776367 0.000000 18.669922 0.000000 16.446289 c +0.000000 3.330078 l +0.000000 1.106445 1.127930 0.000000 3.373047 0.000000 c +h +1.729492 3.416016 m +1.729492 16.360352 l +1.729492 17.477539 2.320312 18.046875 3.394531 18.046875 c +16.381836 18.046875 l +17.445312 18.046875 18.046875 17.477539 18.046875 16.360352 c +18.046875 3.416016 l +18.046875 2.578125 17.703125 2.051758 17.080078 1.836914 c +16.156250 4.468750 13.320312 6.305664 9.904297 6.305664 c +6.477539 6.305664 3.641602 4.458008 2.707031 1.826172 c +2.073242 2.041016 1.729492 2.567383 1.729492 3.416016 c +h +9.893555 8.099609 m +11.945312 8.078125 13.567383 9.829102 13.567383 12.127930 c +13.567383 14.287109 11.945312 16.070312 9.893555 16.070312 c +7.841797 16.070312 6.208984 14.287109 6.219727 12.127930 c +6.230469 9.829102 7.841797 8.121094 9.893555 8.099609 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1162 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001252 00000 n +0000001275 00000 n +0000001448 00000 n +0000001522 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1581 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/Contents.json new file mode 100644 index 0000000000..03c9176982 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_slowmo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/attach_slowmo.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/attach_slowmo.pdf new file mode 100644 index 0000000000..33f4b37a8b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/SloMo.imageset/attach_slowmo.pdf @@ -0,0 +1,161 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.382812 5.627930 cm +0.000000 0.000000 0.000000 scn +12.106445 24.223633 m +11.676758 24.223633 11.343750 23.879883 11.343750 23.450195 c +11.343750 19.636719 l +11.343750 19.207031 11.676758 18.863281 12.106445 18.863281 c +12.546875 18.863281 12.879883 19.207031 12.879883 19.636719 c +12.879883 23.450195 l +12.879883 23.879883 12.546875 24.223633 12.106445 24.223633 c +h +6.058594 22.612305 m +5.671875 22.386719 5.564453 21.924805 5.779297 21.548828 c +7.680664 18.240234 l +7.906250 17.864258 8.357422 17.735352 8.733398 17.960938 c +9.120117 18.175781 9.227539 18.637695 9.023438 19.002930 c +7.111328 22.322266 l +6.885742 22.687500 6.434570 22.816406 6.058594 22.612305 c +h +18.175781 22.601562 m +17.789062 22.816406 17.337891 22.687500 17.112305 22.322266 c +15.210938 19.002930 l +14.996094 18.637695 15.103516 18.175781 15.490234 17.950195 c +15.866211 17.746094 16.317383 17.864258 16.542969 18.240234 c +18.455078 21.548828 l +18.659180 21.924805 18.551758 22.386719 18.175781 22.601562 c +h +1.632812 18.175781 m +1.407227 17.789062 1.536133 17.337891 1.912109 17.112305 c +5.220703 15.200195 l +5.596680 14.996094 6.047852 15.103516 6.273438 15.479492 c +6.477539 15.866211 6.359375 16.317383 5.983398 16.542969 c +2.674805 18.455078 l +2.298828 18.659180 1.836914 18.551758 1.632812 18.175781 c +h +22.612305 18.165039 m +22.386719 18.551758 21.924805 18.659180 21.548828 18.455078 c +18.240234 16.542969 l +17.864258 16.317383 17.735352 15.866211 17.971680 15.479492 c +18.175781 15.114258 18.637695 14.996094 19.002930 15.200195 c +22.322266 17.112305 l +22.687500 17.337891 22.816406 17.789062 22.612305 18.165039 c +h +24.223633 12.117188 m +24.223633 12.546875 23.890625 12.879883 23.450195 12.879883 c +19.636719 12.879883 l +19.207031 12.879883 18.863281 12.546875 18.863281 12.117188 c +18.863281 11.676758 19.207031 11.343750 19.636719 11.343750 c +23.450195 11.343750 l +23.890625 11.343750 24.223633 11.676758 24.223633 12.117188 c +h +0.000000 12.117188 m +0.000000 11.676758 0.343750 11.343750 0.773438 11.343750 c +4.586914 11.343750 l +5.027344 11.343750 5.360352 11.676758 5.360352 12.117188 c +5.360352 12.546875 5.027344 12.879883 4.586914 12.879883 c +0.773438 12.879883 l +0.343750 12.879883 0.000000 12.546875 0.000000 12.117188 c +h +22.601562 6.058594 m +22.827148 6.434570 22.687500 6.885742 22.322266 7.111328 c +19.002930 9.023438 l +18.637695 9.227539 18.175781 9.120117 17.960938 8.744141 c +17.746094 8.357422 17.864258 7.906250 18.240234 7.680664 c +21.548828 5.768555 l +21.924805 5.564453 22.386719 5.671875 22.601562 6.058594 c +h +1.632812 6.058594 m +1.836914 5.671875 2.298828 5.564453 2.674805 5.768555 c +5.983398 7.680664 l +6.359375 7.906250 6.488281 8.357422 6.273438 8.733398 c +6.047852 9.120117 5.596680 9.227539 5.220703 9.023438 c +1.912109 7.111328 l +1.536133 6.885742 1.407227 6.434570 1.632812 6.058594 c +h +18.165039 1.622070 m +18.551758 1.836914 18.659180 2.298828 18.455078 2.674805 c +16.542969 5.983398 l +16.328125 6.359375 15.866211 6.488281 15.490234 6.273438 c +15.103516 6.047852 14.996094 5.596680 15.210938 5.220703 c +17.112305 1.912109 l +17.337891 1.536133 17.789062 1.407227 18.165039 1.622070 c +h +6.058594 1.622070 m +6.434570 1.407227 6.885742 1.536133 7.111328 1.912109 c +9.023438 5.220703 l +9.227539 5.596680 9.120117 6.047852 8.744141 6.262695 c +8.357422 6.477539 7.906250 6.359375 7.680664 5.983398 c +5.779297 2.674805 l +5.564453 2.298828 5.671875 1.836914 6.058594 1.622070 c +h +12.106445 0.000000 m +12.546875 0.000000 12.879883 0.343750 12.879883 0.773438 c +12.879883 4.586914 l +12.879883 5.016602 12.546875 5.360352 12.106445 5.360352 c +11.676758 5.360352 11.343750 5.016602 11.343750 4.586914 c +11.343750 0.773438 l +11.343750 0.343750 11.676758 0.000000 12.106445 0.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3768 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003858 00000 n +0000003881 00000 n +0000004054 00000 n +0000004128 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4187 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/Contents.json new file mode 100644 index 0000000000..2ab7b31cb5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_timelapse.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/attach_timelapse.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/attach_timelapse.pdf new file mode 100644 index 0000000000..60204b6aa6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Timelapse.imageset/attach_timelapse.pdf @@ -0,0 +1,257 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.382812 5.627930 cm +0.000000 0.000000 0.000000 scn +12.106445 24.223633 m +11.751953 24.223633 11.472656 23.933594 11.472656 23.579102 c +11.472656 19.593750 l +11.472656 19.228516 11.751953 18.949219 12.106445 18.949219 c +12.471680 18.949219 12.750977 19.228516 12.750977 19.593750 c +12.750977 23.579102 l +12.750977 23.933594 12.471680 24.223633 12.106445 24.223633 c +h +8.937500 23.933594 m +8.583008 23.858398 8.389648 23.546875 8.497070 23.138672 c +8.851562 21.785156 l +8.948242 21.452148 9.281250 21.269531 9.678711 21.387695 c +10.054688 21.505859 10.205078 21.806641 10.097656 22.150391 c +9.721680 23.450195 l +9.614258 23.836914 9.302734 23.998047 8.937500 23.933594 c +h +15.286133 23.901367 m +14.942383 24.019531 14.609375 23.847656 14.501953 23.450195 c +14.136719 22.128906 l +14.040039 21.785156 14.233398 21.430664 14.577148 21.344727 c +14.920898 21.258789 15.264648 21.441406 15.361328 21.795898 c +15.715820 23.117188 l +15.812500 23.482422 15.629883 23.804688 15.286133 23.901367 c +h +6.058594 22.612305 m +5.747070 22.429688 5.650391 22.042969 5.833008 21.731445 c +7.820312 18.272461 l +8.002930 17.950195 8.389648 17.853516 8.690430 18.036133 c +9.001953 18.208008 9.109375 18.594727 8.926758 18.906250 c +6.928711 22.375977 l +6.746094 22.676758 6.370117 22.773438 6.058594 22.612305 c +h +18.175781 22.601562 m +17.864258 22.773438 17.477539 22.676758 17.294922 22.375977 c +15.307617 18.906250 l +15.114258 18.594727 15.221680 18.208008 15.533203 18.036133 c +15.833984 17.853516 16.231445 17.950195 16.403320 18.272461 c +18.401367 21.731445 l +18.573242 22.042969 18.476562 22.418945 18.175781 22.601562 c +h +3.458984 20.753906 m +3.201172 20.506836 3.233398 20.098633 3.469727 19.851562 c +4.425781 18.884766 l +4.694336 18.616211 5.059570 18.605469 5.338867 18.874023 c +5.585938 19.121094 5.596680 19.507812 5.328125 19.787109 c +4.372070 20.743164 l +4.092773 21.022461 3.727539 21.011719 3.458984 20.753906 c +h +20.764648 20.743164 m +20.506836 21.000977 20.098633 20.979492 19.862305 20.743164 c +18.884766 19.776367 l +18.616211 19.497070 18.616211 19.131836 18.884766 18.874023 c +19.142578 18.605469 19.529297 18.605469 19.776367 18.863281 c +20.732422 19.830078 l +21.000977 20.109375 21.011719 20.485352 20.764648 20.743164 c +h +1.632812 18.175781 m +1.450195 17.864258 1.546875 17.477539 1.858398 17.294922 c +5.317383 15.296875 l +5.639648 15.114258 6.015625 15.221680 6.198242 15.522461 c +6.370117 15.833984 6.273438 16.220703 5.951172 16.403320 c +2.492188 18.401367 l +2.180664 18.573242 1.804688 18.476562 1.632812 18.175781 c +h +22.612305 18.165039 m +22.429688 18.476562 22.042969 18.573242 21.731445 18.401367 c +18.272461 16.403320 l +17.950195 16.220703 17.853516 15.833984 18.036133 15.522461 c +18.208008 15.221680 18.594727 15.114258 18.906250 15.296875 c +22.375977 17.294922 l +22.676758 17.477539 22.773438 17.853516 22.612305 18.165039 c +h +23.922852 15.264648 m +23.836914 15.619141 23.471680 15.823242 23.149414 15.726562 c +21.806641 15.361328 l +21.462891 15.264648 21.258789 14.931641 21.376953 14.534180 c +21.462891 14.254883 21.763672 14.029297 22.150391 14.125977 c +23.450195 14.491211 l +23.826172 14.587891 24.008789 14.931641 23.922852 15.264648 c +h +0.290039 15.275391 m +0.214844 14.931641 0.429688 14.587891 0.751953 14.491211 c +2.073242 14.115234 l +2.438477 14.029297 2.782227 14.222656 2.857422 14.577148 c +2.932617 14.920898 2.771484 15.286133 2.416992 15.372070 c +1.095703 15.705078 l +0.708984 15.801758 0.365234 15.608398 0.290039 15.275391 c +h +24.223633 12.117188 m +24.223633 12.471680 23.944336 12.750977 23.579102 12.750977 c +19.593750 12.750977 l +19.228516 12.750977 18.949219 12.471680 18.949219 12.117188 c +18.949219 11.751953 19.228516 11.472656 19.593750 11.472656 c +23.579102 11.472656 l +23.944336 11.472656 24.223633 11.751953 24.223633 12.117188 c +h +0.000000 12.117188 m +0.000000 11.751953 0.290039 11.472656 0.644531 11.472656 c +4.629883 11.472656 l +5.005859 11.472656 5.274414 11.751953 5.274414 12.117188 c +5.274414 12.471680 5.005859 12.750977 4.629883 12.750977 c +0.644531 12.750977 l +0.290039 12.750977 0.000000 12.471680 0.000000 12.117188 c +h +23.922852 8.916016 m +24.019531 9.281250 23.858398 9.614258 23.482422 9.721680 c +22.150391 10.097656 l +21.785156 10.194336 21.452148 9.990234 21.366211 9.646484 c +21.280273 9.291992 21.473633 8.926758 21.806641 8.840820 c +23.127930 8.497070 l +23.503906 8.400391 23.826172 8.593750 23.922852 8.916016 c +h +0.300781 8.948242 m +0.386719 8.604492 0.751953 8.421875 1.084961 8.497070 c +2.416992 8.830078 l +2.782227 8.926758 2.964844 9.259766 2.868164 9.614258 c +2.771484 9.958008 2.427734 10.162109 2.073242 10.076172 c +0.784180 9.721680 l +0.386719 9.614258 0.214844 9.281250 0.300781 8.948242 c +h +22.601562 6.058594 m +22.773438 6.359375 22.676758 6.746094 22.375977 6.928711 c +18.906250 8.926758 l +18.594727 9.109375 18.208008 9.001953 18.036133 8.701172 c +17.853516 8.389648 17.950195 8.002930 18.272461 7.820312 c +21.731445 5.822266 l +22.042969 5.650391 22.418945 5.747070 22.601562 6.058594 c +h +1.622070 6.058594 m +1.793945 5.747070 2.180664 5.650391 2.492188 5.822266 c +5.951172 7.820312 l +6.273438 8.002930 6.370117 8.389648 6.198242 8.690430 c +6.015625 9.001953 5.639648 9.109375 5.317383 8.926758 c +1.858398 6.928711 l +1.546875 6.746094 1.450195 6.370117 1.622070 6.058594 c +h +6.058594 1.622070 m +6.359375 1.450195 6.756836 1.546875 6.928711 1.858398 c +8.926758 5.317383 l +9.109375 5.628906 9.001953 6.015625 8.701172 6.198242 c +8.389648 6.370117 8.002930 6.273438 7.820312 5.951172 c +5.833008 2.492188 l +5.650391 2.180664 5.747070 1.804688 6.058594 1.622070 c +h +18.165039 1.622070 m +18.476562 1.793945 18.573242 2.180664 18.401367 2.492188 c +16.403320 5.951172 l +16.231445 6.273438 15.833984 6.370117 15.533203 6.198242 c +15.221680 6.015625 15.114258 5.628906 15.307617 5.317383 c +17.294922 1.858398 l +17.477539 1.546875 17.853516 1.450195 18.165039 1.622070 c +h +20.753906 3.458984 m +21.022461 3.727539 21.000977 4.135742 20.775391 4.372070 c +19.787109 5.338867 l +19.529297 5.585938 19.142578 5.618164 18.863281 5.338867 c +18.616211 5.070312 18.637695 4.705078 18.884766 4.447266 c +19.851562 3.480469 l +20.120117 3.211914 20.496094 3.201172 20.753906 3.458984 c +h +3.469727 3.469727 m +3.738281 3.211914 4.125000 3.211914 4.361328 3.448242 c +5.338867 4.415039 l +5.607422 4.694336 5.618164 5.059570 5.349609 5.317383 c +5.102539 5.575195 4.694336 5.585938 4.447266 5.328125 c +3.491211 4.361328 l +3.222656 4.082031 3.211914 3.727539 3.469727 3.469727 c +h +12.106445 0.000000 m +12.471680 0.000000 12.750977 0.290039 12.750977 0.644531 c +12.750977 4.629883 l +12.750977 4.995117 12.471680 5.274414 12.106445 5.274414 c +11.751953 5.274414 11.472656 4.995117 11.472656 4.629883 c +11.472656 0.644531 l +11.472656 0.290039 11.751953 0.000000 12.106445 0.000000 c +h +15.286133 0.290039 m +15.694336 0.386719 15.812500 0.741211 15.726562 1.074219 c +15.382812 2.427734 l +15.286133 2.771484 14.920898 2.964844 14.577148 2.868164 c +14.201172 2.760742 14.040039 2.406250 14.136719 2.083984 c +14.523438 0.773438 l +14.630859 0.418945 14.899414 0.214844 15.286133 0.290039 c +h +8.883789 0.300781 m +9.281250 0.171875 9.635742 0.408203 9.721680 0.741211 c +10.086914 2.062500 l +10.183594 2.406250 9.990234 2.760742 9.646484 2.846680 c +9.302734 2.932617 8.958984 2.750000 8.862305 2.395508 c +8.507812 1.074219 l +8.411133 0.708984 8.529297 0.408203 8.883789 0.300781 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 7389 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000007479 00000 n +0000007502 00000 n +0000007675 00000 n +0000007749 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +7808 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/Contents.json new file mode 100644 index 0000000000..35681e9d68 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_video.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/attach_video.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/attach_video.pdf new file mode 100644 index 0000000000..af2a2d8472 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Video.imageset/attach_video.pdf @@ -0,0 +1,103 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.592773 8.925781 cm +0.000000 0.000000 0.000000 scn +4.189453 0.000000 m +15.415039 0.000000 l +18.036133 0.000000 19.604492 1.525391 19.604492 4.146484 c +19.604492 5.618164 l +23.665039 2.180664 l +24.094727 1.826172 24.567383 1.579102 25.007812 1.579102 c +25.953125 1.579102 26.576172 2.277344 26.576172 3.276367 c +26.576172 14.383789 l +26.576172 15.382812 25.953125 16.081055 25.007812 16.081055 c +24.567383 16.081055 24.094727 15.833984 23.665039 15.479492 c +19.604492 12.041992 l +19.604492 13.524414 l +19.604492 16.134766 18.036133 17.660156 15.415039 17.660156 c +4.189453 17.660156 l +1.686523 17.660156 0.000000 16.134766 0.000000 13.524414 c +0.000000 4.146484 l +0.000000 1.525391 1.568359 0.000000 4.189453 0.000000 c +h +4.490234 1.622070 m +2.728516 1.622070 1.729492 2.535156 1.729492 4.393555 c +1.729492 13.266602 l +1.729492 15.135742 2.728516 16.048828 4.490234 16.048828 c +15.114258 16.048828 l +16.865234 16.048828 17.875000 15.135742 17.875000 13.266602 c +17.875000 4.393555 l +17.875000 2.535156 16.865234 1.622070 15.114258 1.622070 c +4.490234 1.622070 l +h +24.470703 3.641602 m +19.604492 7.659180 l +19.604492 10.000977 l +24.470703 14.018555 l +24.567383 14.093750 24.631836 14.147461 24.728516 14.147461 c +24.857422 14.147461 24.911133 14.040039 24.911133 13.889648 c +24.911133 3.770508 l +24.911133 3.620117 24.857422 3.523438 24.728516 3.523438 c +24.631836 3.523438 24.567383 3.577148 24.470703 3.641602 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1489 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001579 00000 n +0000001602 00000 n +0000001775 00000 n +0000001849 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1908 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json index a19a549220..b74f21545c 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/Contents.json @@ -1,16 +1,8 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "filename" : "target_24.pdf", + "idiom" : "universal" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/target_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/target_24.pdf new file mode 100644 index 0000000000..ac56d9ee42 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReportPersonal.imageset/target_24.pdf @@ -0,0 +1,131 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.868011 1.601807 cm +0.000000 0.000000 0.000000 scn +9.467012 16.798109 m +9.467012 16.430840 9.764743 16.133110 10.132012 16.133110 c +10.499282 16.133110 10.797012 16.430840 10.797012 16.798109 c +10.797012 17.703321 l +14.317344 17.386963 17.120886 14.583434 17.437263 11.063108 c +16.532019 11.063108 l +16.164749 11.063108 15.867019 10.765378 15.867019 10.398108 c +15.867019 10.030839 16.164749 9.733109 16.532019 9.733109 c +17.437273 9.733109 l +17.120937 6.212737 14.317376 3.409161 10.797012 3.092800 c +10.797012 3.998110 l +10.797012 4.365379 10.499282 4.663109 10.132012 4.663109 c +9.764743 4.663109 9.467012 4.365379 9.467012 3.998110 c +9.467012 3.092798 l +5.946643 3.409155 3.143077 6.212733 2.826741 9.733109 c +3.732019 9.733109 l +4.099288 9.733109 4.397019 10.030839 4.397019 10.398108 c +4.397019 10.765378 4.099288 11.063108 3.732019 11.063108 c +2.826750 11.063108 l +3.143129 14.583438 5.946676 17.386969 9.467012 17.703321 c +9.467012 16.798109 l +h +10.797012 19.037922 m +10.797012 19.998110 l +10.797012 20.365379 10.499282 20.663109 10.132012 20.663109 c +9.764743 20.663109 9.467012 20.365379 9.467012 19.998110 c +9.467012 19.037922 l +5.211674 18.714972 1.815125 15.318439 1.492149 11.063108 c +0.532019 11.063108 l +0.164750 11.063108 -0.132981 10.765378 -0.132981 10.398108 c +-0.132981 10.030839 0.164750 9.733109 0.532019 9.733109 c +1.492141 9.733109 l +1.815073 5.477732 5.211642 2.081148 9.467012 1.758196 c +9.467012 0.798109 l +9.467012 0.430840 9.764743 0.133108 10.132012 0.133108 c +10.499282 0.133108 10.797012 0.430840 10.797012 0.798109 c +10.797012 1.758198 l +15.052378 2.081156 18.448942 5.477736 18.771873 9.733109 c +19.732019 9.733109 l +20.099289 9.733109 20.397018 10.030839 20.397018 10.398108 c +20.397018 10.765378 20.099289 11.063108 19.732019 11.063108 c +18.771868 11.063108 l +18.448893 15.318436 15.052345 18.714966 10.797012 19.037922 c +h +10.132013 14.132962 m +9.173799 14.132962 8.397013 13.356176 8.397013 12.397963 c +8.397013 11.439748 9.173799 10.662962 10.132013 10.662962 c +11.090227 10.662962 11.867013 11.439748 11.867013 12.397963 c +11.867013 13.356176 11.090227 14.132962 10.132013 14.132962 c +h +7.067013 12.397963 m +7.067013 14.090715 8.439260 15.462962 10.132013 15.462962 c +11.824766 15.462962 13.197013 14.090715 13.197013 12.397963 c +13.197013 10.705210 11.824766 9.332962 10.132013 9.332962 c +8.439260 9.332962 7.067013 10.705210 7.067013 12.397963 c +h +6.310586 5.946628 m +6.984863 6.676261 8.128437 7.332962 10.132026 7.332962 c +12.135614 7.332962 13.279188 6.676261 13.953466 5.946628 c +14.202730 5.676899 14.623459 5.660309 14.893188 5.909575 c +15.162916 6.158839 15.179506 6.579567 14.930241 6.849297 c +13.975744 7.882154 12.462423 8.662962 10.132026 8.662962 c +7.801628 8.662962 6.288308 7.882154 5.333811 6.849297 c +5.084546 6.579567 5.101135 6.158839 5.370864 5.909575 c +5.640593 5.660309 6.061321 5.676899 6.310586 5.946628 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2949 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003039 00000 n +0000003062 00000 n +0000003235 00000 n +0000003309 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3368 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index c458d54394..d44e94053e 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -136,7 +136,10 @@ private enum AttachmentFileEntry: ItemListNodeEntry { } } -private func attachmentFileControllerEntries(presentationData: PresentationData, recentDocuments: [Message]?) -> [AttachmentFileEntry] { +private func attachmentFileControllerEntries(presentationData: PresentationData, recentDocuments: [Message]?, empty: Bool) -> [AttachmentFileEntry] { + guard !empty else { + return [] + } var entries: [AttachmentFileEntry] = [] entries.append(.selectFromGallery(presentationData.theme, presentationData.strings.Attachment_SelectFromGallery)) entries.append(.selectFromFiles(presentationData.theme, presentationData.strings.Attachment_SelectFromFiles)) @@ -152,7 +155,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, } } else { entries.append(.recentHeader(presentationData.theme, presentationData.strings.Attachment_RecentlySentFiles.uppercased())) - for i in 0 ..< 8 { + for i in 0 ..< 11 { entries.append(.file(Int32(i), presentationData.theme, nil)) } } @@ -162,19 +165,25 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, private class AttachmentFileControllerImpl: ItemListController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} + public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } - var prepareForReuseImpl: () -> Void = {} + var resetForReuseImpl: () -> Void = {} public func resetForReuse() { - self.prepareForReuseImpl() + self.resetForReuseImpl() self.scrollToTop?() } + + public func prepareForReuse() { + self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset) + } } private struct AttachmentFileControllerState: Equatable { var searching: Bool } -public func attachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentContainable { +public func attachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentContainable { let actionsDisposable = DisposableSet() let statePromise = ValuePromise(AttachmentFileControllerState(searching: false), ignoreRepeated: true) @@ -236,9 +245,8 @@ public func attachmentFileController(context: AccountContext, updatedPresentatio } var rightNavigationButton: ItemListNavigationButton? - if let recentDocuments = recentDocuments, recentDocuments.count > 10 { + if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10) { rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { - expandImpl?() updateState { state in var updatedState = state updatedState.searching = true @@ -252,13 +260,25 @@ public func attachmentFileController(context: AccountContext, updatedPresentatio }), rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) var emptyItem: AttachmentFileEmptyStateItem? - if let recentDocuments = recentDocuments, recentDocuments.isEmpty { - emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings) + if let (untilDate, personal) = bannedSendMedia { + let banDescription: String + if untilDate != 0 && untilDate != Int32.max { + banDescription = presentationData.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)).string + } else if personal { + banDescription = presentationData.strings.Conversation_RestrictedMedia + } else { + banDescription = presentationData.strings.Conversation_DefaultRestrictedMedia + } + emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .bannedSendMedia(banDescription)) + } else if let recentDocuments = recentDocuments, recentDocuments.isEmpty { + emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .intro) } var searchItem: ItemListControllerSearch? if state.searching { - searchItem = AttachmentFileSearchItem(context: context, presentationData: presentationData, cancel: { + searchItem = AttachmentFileSearchItem(context: context, presentationData: presentationData, focus: { + expandImpl?() + }, cancel: { updateState { state in var updatedState = state updatedState.searching = false @@ -271,7 +291,7 @@ public func attachmentFileController(context: AccountContext, updatedPresentatio }) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData, recentDocuments: recentDocuments), style: .blocks, emptyStateItem: emptyItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: attachmentFileControllerEntries(presentationData: presentationData, recentDocuments: recentDocuments, empty: bannedSendMedia != nil), style: .blocks, emptyStateItem: emptyItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } |> afterDisposed { @@ -279,7 +299,16 @@ public func attachmentFileController(context: AccountContext, updatedPresentatio } let controller = AttachmentFileControllerImpl(context: context, state: signal) - controller.prepareForReuseImpl = { + controller.visibleBottomContentOffsetChanged = { [weak controller] offset in + switch offset { + case let .known(value): + let backgroundAlpha: CGFloat = min(30.0, value) / 30.0 + controller?.updateTabBarAlpha(backgroundAlpha, .immediate) + case .unknown, .none: + controller?.updateTabBarAlpha(1.0, .immediate) + } + } + controller.resetForReuseImpl = { updateState { state in var updatedState = state updatedState.searching = false diff --git a/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift b/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift index 9b381b665a..37e4f8dcbd 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift @@ -10,19 +10,26 @@ import TelegramAnimatedStickerNode import AccountContext final class AttachmentFileEmptyStateItem: ItemListControllerEmptyStateItem { + enum Content: Equatable { + case intro + case bannedSendMedia(String) + } + let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let content: Content - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content) { self.context = context self.theme = theme self.strings = strings + self.content = content } func isEqual(to: ItemListControllerEmptyStateItem) -> Bool { if let item = to as? AttachmentFileEmptyStateItem { - return self.theme === item.theme && self.strings === item.strings + return self.theme === item.theme && self.strings === item.strings && self.content == item.content } else { return false } @@ -75,22 +82,32 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo } private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - self.textNode.attributedText = NSAttributedString(string: strings.Attachment_FilesIntro, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) + let text: String + switch self.item.content { + case .intro: + text = strings.Attachment_FilesIntro + case let .bannedSendMedia(banDescription): + text = banDescription + } + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .center) } override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (layout, navigationBarHeight) var insets = layout.insets(options: []) - insets.top += navigationBarHeight - 92.0 - + insets.top += navigationBarHeight + let imageSpacing: CGFloat = 12.0 - let imageSize = CGSize(width: 144.0, height: 144.0) let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 - - self.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: -10.0), size: imageSize) - self.animationNode.updateLayout(size: imageSize) - + if !imageHeight.isZero { + if case .intro = self.item.content { + insets.top -= 92.0 + } else { + insets.top -= 160.0 + } + } + let textSize = self.textNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 70.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let totalHeight = imageHeight + textSize.height @@ -98,6 +115,8 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo transition.updateAlpha(node: self.animationNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) transition.updateFrame(node: self.animationNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) + self.animationNode.updateLayout(size: imageSize) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right) / 2.0), y: topOffset + imageHeight), size: textSize)) } } diff --git a/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift index d758cdab1f..ff098e5978 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift @@ -24,6 +24,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont private var theme: PresentationTheme private let strings: PresentationStrings + private let focus: () -> Void private let cancel: () -> Void private let searchBar: SearchBarNode @@ -34,10 +35,11 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont self.searchBar.activity = activity } } - init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, focus: @escaping () -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) { self.theme = theme self.strings = strings + self.focus = focus self.cancel = cancel self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) @@ -45,7 +47,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont super.init() self.addSubnode(self.searchBar) - + self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) self?.cancel() @@ -55,6 +57,12 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont self?.queryUpdated?(query) } + self.searchBar.focusUpdated = { [weak self] focus in + if focus { + self?.focus() + } + } + updateActivity({ [weak self] value in self?.activity = value }) @@ -99,6 +107,7 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont final class AttachmentFileSearchItem: ItemListControllerSearch { let context: AccountContext let presentationData: PresentationData + let focus: () -> Void let cancel: () -> Void let send: (Message) -> Void let dismissInput: () -> Void @@ -107,9 +116,10 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, presentationData: PresentationData, cancel: @escaping () -> Void, send: @escaping (Message) -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, presentationData: PresentationData, focus: @escaping () -> Void, cancel: @escaping () -> Void, send: @escaping (Message) -> Void, dismissInput: @escaping () -> Void) { self.context = context self.presentationData = presentationData + self.focus = focus self.cancel = cancel self.send = send self.dismissInput = dismissInput @@ -145,7 +155,7 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { current.updateTheme(presentationData.theme) return current } else { - return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, updateActivity: { [weak self] value in + return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, focus: self.focus, cancel: self.cancel, updateActivity: { [weak self] value in self?.updateActivity = value }) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 54e29ef7b6..57f9ae38ed 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10267,13 +10267,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let storeEditedPhotos = settings.storeEditedPhotos let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { - // if editMediaOptions != nil { - // strongSelf.editMessageMediaWithLegacySignals(signals!) - // } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) - // } + strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) if !inputText.string.isEmpty { strongSelf.clearInputText() } @@ -10316,6 +10312,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return self?.getCaptionPanelView() }, dismissedWithResult: { [weak self] in self?.attachmentController?.dismiss(animated: false, completion: nil) + }, finishedTransitionIn: { [weak self] in + self?.attachmentController?.scrollToTop?() }) }) } @@ -10348,7 +10346,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var availableTabs: [AttachmentButtonType] = [.gallery, .file, .location, .contact] if canSendPolls { - availableTabs.append(.poll) + availableTabs.insert(.poll, at: availableTabs.count - 1) } let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText @@ -10367,12 +10365,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.controllerNavigationDisposable.set(nil) let existingController = currentMediaController.with { $0 } if let controller = existingController { - controller.prepareForReuse() completion(controller, nil) + controller.prepareForReuse() return } strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in let _ = currentMediaController.swap(controller) + mediaPickerContext?.setCaption(inputText) completion(controller, mediaPickerContext) }, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in attachmentController?.mediaPickerContext = mediaPickerContext @@ -10387,9 +10386,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let existingController = currentFilesController.with { $0 } if let controller = existingController { completion(controller, nil) + controller.prepareForReuse() return } - let controller = attachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, presentGallery: { [weak self, weak attachmentController] in + let controller = attachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bannedSendMedia: bannedSendMedia, presentGallery: { [weak self, weak attachmentController] in attachmentController?.dismiss(animated: true) self?.presentFileGallery() }, presentFiles: { [weak self, weak attachmentController] in @@ -10415,6 +10415,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let existingController = currentLocationController.with { $0 } if let controller = existingController { completion(controller, nil) + controller.prepareForReuse() return } let selfPeerId: PeerId @@ -10463,9 +10464,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G completion(contactsController, contactsController.mediaPickerContext) strongSelf.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, silent, scheduleTime) = peers { + if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers { + var textEnqueueMessage: EnqueueMessage? + if let text = text, text.length > 0 { + var attributes: [MessageAttribute] = [] + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } + textEnqueueMessage = .message(text: text.string, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) + } if peers.count > 1 { var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } for peer in peers { var media: TelegramMediaContact? switch peer { @@ -10555,8 +10568,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) - strongSelf.sendMessages(strongSelf.transformEnqueueMessages([message], silentPosting: silent, scheduleTime: scheduleTime)) + + var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } + enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) } else { let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { @@ -10573,8 +10591,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }, nil) - let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil) - strongSelf.sendMessages(strongSelf.transformEnqueueMessages([message], silentPosting: silent, scheduleTime: scheduleTime)) + + var enqueueMessages: [EnqueueMessage] = [] + if let textEnqueueMessage = textEnqueueMessage { + enqueueMessages.append(textEnqueueMessage) + } + enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) } }), completed: nil, cancelled: nil) strongSelf.effectiveNavigationController?.pushViewController(contactController) @@ -11007,10 +11030,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G controller.openCamera = { [weak self] cameraView in self?.openCamera(cameraView: cameraView) } - controller.presentWebSearch = { [weak self, weak controller] in + controller.presentWebSearch = { [weak self, weak controller] mediaGroups in self?.presentWebSearch(editingMessage: false, attachment: true, present: { [weak controller] c, a in controller?.present(c, in: .current) if let webSearchController = c as? WebSearchController { + webSearchController.searchingUpdated = { [weak mediaGroups] searching in + if let mediaGroups = mediaGroups, mediaGroups.isNodeLoaded { + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: mediaGroups.displayNode, alpha: searching ? 0.0 : 1.0) + mediaGroups.displayNode.isUserInteractionEnabled = !searching + } + } + webSearchController.present(mediaGroups, in: .current) webSearchController.dismissed = { updateMediaPickerContext(mediaPickerContext) } @@ -11292,7 +11323,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.effectiveNavigationController?.pushViewController(contactsController) self.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak self] peers in - if let strongSelf = self, let (peers, _, _, _) = peers { + if let strongSelf = self, let (peers, _, _, _, _) = peers { if peers.count > 1 { var enqueueMessages: [EnqueueMessage] = [] for peer in peers { diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 301ca68aee..73e28679f5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -868,6 +868,20 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } } + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { + transition.updateAlpha(node: forwardBackgroundNode, alpha: isPlaying ? 0.0 : 1.0) + } + if let replyBackgroundNode = strongSelf.replyBackgroundNode { + transition.updateAlpha(node: replyBackgroundNode, alpha: isPlaying ? 0.0 : 1.0) + } + if let forwardInfoNode = strongSelf.forwardInfoNode { + transition.updateAlpha(node: forwardInfoNode, alpha: isPlaying ? 0.0 : 1.0) + } + if let replyInfoNode = strongSelf.replyInfoNode { + transition.updateAlpha(node: replyInfoNode, alpha: isPlaying ? 0.0 : 1.0) + } + if let (_, f) = strongSelf.awaitingAppliedReaction { strongSelf.awaitingAppliedReaction = nil diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index 0032c120fe..626dbf77d1 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -157,7 +157,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { strongSelf.createActionDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in - if let strongSelf = self, let (contactPeers, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first { + if let strongSelf = self, let (contactPeers, _, _, _, _) = result, case let .peer(peer, _, _) = contactPeers.first { controller?.dismissSearch() controller?.displayNavigationActivity = true strongSelf.createActionDisposable.set((strongSelf.context.engine.peers.createSecretChat(peerId: peer.id) |> deliverOnMainQueue).start(next: { peerId in diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 8fa2ab08b3..54d8b79e1e 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -43,8 +43,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController return self._ready } - private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?)?>() - var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?)?, NoError> { + fileprivate var caption: NSAttributedString? + + private let _result = Promise<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?>() + var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?)?, NoError> { return self._result.get() } @@ -74,6 +76,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } var requestAttachmentMenuExpansion: () -> Void = {} + var updateNavigationStack: (@escaping ([AttachmentContainable]) -> [AttachmentContainable]) -> Void = { _ in } + var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } init(_ params: ContactSelectionControllerParams) { self.context = params.context @@ -199,6 +203,10 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } } + self.contactsNode.cancelSearch = { [weak self] in + self?.deactivateSearch() + } + self.contactsNode.dismiss = { [weak self] in self?.presentingViewController?.dismiss(animated: true, completion: nil) } @@ -220,7 +228,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.contactsNode.requestMultipleAction = { [weak self] silent, scheduleTime in if let strongSelf = self { let selectedPeers = strongSelf.contactsNode.contactListNode.selectedPeers - strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime))) + strongSelf._result.set(.single((selectedPeers, .generic, silent, scheduleTime, strongSelf.caption))) if strongSelf.autoDismiss { strongSelf.dismiss() } @@ -307,6 +315,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController if let searchContentNode = self.searchContentNode as? NavigationBarSearchContentNode { self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode) } + } else if let searchContentNode = self.searchContentNode as? ContactsSearchNavigationContentNode { + searchContentNode.cancel() } } @@ -315,7 +325,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.confirmationDisposable.set((self.confirmation(peer) |> deliverOnMainQueue).start(next: { [weak self] value in if let strongSelf = self { if value { - strongSelf._result.set(.single(([peer], action, false, nil))) + strongSelf._result.set(.single(([peer], action, false, nil, nil))) if strongSelf.autoDismiss { strongSelf.dismiss() } @@ -377,6 +387,10 @@ final class ContactsSearchNavigationContentNode: NavigationBarContentNode { self.searchBar.deactivate(clear: false) } + func cancel() { + self.searchBar.cancel?() + } + func updateActivity(_ activity: Bool) { self.searchBar.activity = activity } @@ -410,7 +424,7 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { } func setCaption(_ caption: NSAttributedString) { - + self.controller?.caption = caption } func send(silently: Bool, mode: AttachmentMediaPickerSendMode) { diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index d0a672ea99..1c7310f7a9 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -38,6 +38,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)? var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?) -> Void)? var dismiss: (() -> Void)? + var cancelSearch: (() -> Void)? var presentationData: PresentationData { didSet { @@ -216,6 +217,9 @@ final class ContactSelectionControllerNode: ASDisplayNode { } } }, contextAction: nil) + searchContainerNode.cancel = { [weak self] in + self?.cancelSearch?() + } self.insertSubnode(searchContainerNode, belowSubnode: navigationBar) self.searchContainerNode = searchContainerNode diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index a09ea9005c..4b85a465c3 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -10,7 +10,7 @@ import ShareController import LegacyUI import LegacyMediaPickerUI -func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}) { +func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) @@ -94,7 +94,9 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch let screenSize = parentController.view.bounds.size var startFrame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: screenSize.height) if let cameraView = cameraView { - if let menuController = menuController { + if let attachmentController = attachmentController { + startFrame = attachmentController.view.convert(cameraView.previewView()!.frame, from: cameraView) + } else if let menuController = menuController { startFrame = menuController.view.convert(cameraView.previewView()!.frame, from: cameraView) } else { startFrame = parentController.view.convert(cameraView.previewView()!.frame, from: cameraView) @@ -109,7 +111,9 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch controller.view.disablesInteractiveTransitionGestureRecognizer = true } } - + controller.finishedTransitionIn = { + finishedTransitionIn() + } controller.beginTransitionOut = { [weak controller, weak cameraView] in if let controller = controller, let cameraView = cameraView { cameraView.willAttachPreviewView() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index adc4439026..7415d4742a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -8059,7 +8059,7 @@ func presentAddMembers(context: AccountContext, updatedPresentationData: (initia if let contactsController = contactsController as? ContactSelectionController { selectAddMemberDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { [weak contactsController] result in - guard let (peers, _, _, _) = result, let memberPeer = peers.first else { + guard let (peers, _, _, _, _) = result, let memberPeer = peers.first else { return } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index a9445f2316..7f608c8ee2 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -164,6 +164,8 @@ public final class WebSearchController: ViewController { public var dismissed: () -> Void = { } + public var searchingUpdated: (Bool) -> Void = { _ in } + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) { self.context = context self.mode = mode @@ -237,6 +239,7 @@ public final class WebSearchController: ViewController { navigationContentNode.setQueryUpdated { [weak self] query in if let strongSelf = self, strongSelf.isNodeLoaded { strongSelf.updateSearchQuery(query) + strongSelf.searchingUpdated(!query.isEmpty) } } navigationContentNode.cancel = { [weak self] in @@ -354,7 +357,7 @@ public final class WebSearchController: ViewController { if case let .media(attachmentValue, _) = self.mode, attachmentValue { attachment = true } - self.displayNode = WebSearchControllerNode(context: self.context, presentationData: self.interfaceState.presentationData, controllerInteraction: self.controllerInteraction!, peer: self.peer, chatLocation: self.chatLocation, mode: self.mode.mode, attachment: attachment) + self.displayNode = WebSearchControllerNode(controller: self, context: self.context, presentationData: self.interfaceState.presentationData, controllerInteraction: self.controllerInteraction!, peer: self.peer, chatLocation: self.chatLocation, mode: self.mode.mode, attachment: attachment) self.controllerNode.requestUpdateInterfaceState = { [weak self] animated, f in if let strongSelf = self { strongSelf.updateInterfaceState(f) @@ -374,6 +377,8 @@ public final class WebSearchController: ViewController { self._ready.set(.single(true)) self.displayNodeDidLoad() + + self.controllerNode.updateBackgroundAlpha(0.0, transition: .immediate) } private func updateInterfaceState(animated: Bool = true, _ f: (WebSearchInterfaceState) -> WebSearchInterfaceState) { @@ -542,7 +547,8 @@ public final class WebSearchController: ViewController { self.validLayout = layout - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + let navigationBarHeight = self.navigationLayout(layout: layout).navigationFrame.maxY + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } } diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index 79ff528f97..39af94a084 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -64,8 +64,20 @@ private func preparedTransition(from fromEntries: [WebSearchEntry], to toEntries return WebSearchTransition(deleteItems: deleteIndices, insertItems: insertions, updateItems: updates, entryCount: toEntries.count, hasMore: hasMore) } -private func gridNodeLayoutForContainerLayout(size: CGSize, insets: UIEdgeInsets) -> GridNodeLayoutType { - let side = floorToScreenPixels((size.width - insets.left - insets.right - 2.0) / 3.0) +private func gridNodeLayoutForContainerLayout(_ layout: ContainerViewLayout) -> GridNodeLayoutType { + let itemsPerRow: Int + if case .compact = layout.metrics.widthClass { + switch layout.orientation { + case .portrait: + itemsPerRow = 3 + case .landscape: + itemsPerRow = 5 + } + } else { + itemsPerRow = 3 + } + + let side = floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) } @@ -116,6 +128,7 @@ private func preparedWebSearchRecentTransition(from fromEntries: [WebSearchRecen } class WebSearchControllerNode: ASDisplayNode { + private weak var controller: WebSearchController? private let context: AccountContext private let peer: EnginePeer? private let chatLocation: ChatLocation? @@ -129,6 +142,7 @@ class WebSearchControllerNode: ASDisplayNode { private var webSearchInterfaceState: WebSearchInterfaceState private let webSearchInterfaceStatePromise: ValuePromise + private let segmentedContainerNode: ASDisplayNode private let segmentedBackgroundNode: ASDisplayNode private let segmentedSeparatorNode: ASDisplayNode private let segmentedControlNode: SegmentedControlNode @@ -173,7 +187,8 @@ class WebSearchControllerNode: ASDisplayNode { var presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)? var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } - init(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchControllerInteraction, peer: EnginePeer?, chatLocation: ChatLocation?, mode: WebSearchMode, attachment: Bool) { + init(controller: WebSearchController, context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchControllerInteraction, peer: EnginePeer?, chatLocation: ChatLocation?, mode: WebSearchMode, attachment: Bool) { + self.controller = controller self.context = context self.theme = presentationData.theme self.strings = presentationData.strings @@ -187,6 +202,9 @@ class WebSearchControllerNode: ASDisplayNode { self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: context.sharedContext.currentPresentationData.with { $0 }) self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true) + self.segmentedContainerNode = ASDisplayNode() + self.segmentedContainerNode.clipsToBounds = true + self.segmentedBackgroundNode = ASDisplayNode() self.segmentedSeparatorNode = ASDisplayNode() @@ -225,10 +243,11 @@ class WebSearchControllerNode: ASDisplayNode { if !attachment { self.addSubnode(self.recentQueriesNode) } - self.addSubnode(self.segmentedBackgroundNode) - self.addSubnode(self.segmentedSeparatorNode) + self.addSubnode(self.segmentedContainerNode) + self.segmentedContainerNode.addSubnode(self.segmentedBackgroundNode) + self.segmentedContainerNode.addSubnode(self.segmentedSeparatorNode) if case .media = mode { - self.addSubnode(self.segmentedControlNode) + self.segmentedContainerNode.addSubnode(self.segmentedControlNode) } if !attachment { self.addSubnode(self.toolbarBackgroundNode) @@ -339,6 +358,11 @@ class WebSearchControllerNode: ASDisplayNode { self.applyPresentationData(themeUpdated: themeUpdated) } + func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { + self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: transition) + transition.updateAlpha(node: self.segmentedBackgroundNode, alpha: alpha) + } + func applyPresentationData(themeUpdated: Bool = true) { self.cancelButton.setTitle(self.strings.Common_Cancel, with: Font.regular(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal) @@ -349,6 +373,7 @@ class WebSearchControllerNode: ASDisplayNode { } if themeUpdated { + self.gridNode.backgroundColor = self.theme.list.plainBackgroundColor self.segmentedBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.opaqueBackgroundColor self.segmentedSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) @@ -393,14 +418,19 @@ class WebSearchControllerNode: ASDisplayNode { var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight + let hasQuery = !(self.webSearchInterfaceState.state?.query ?? "").isEmpty + let segmentedHeight: CGFloat = self.segmentedControlNode.supernode != nil ? 44.0 : 5.0 let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 - transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) - transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + segmentedHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + transition.updateSublayerTransformOffset(layer: self.segmentedContainerNode.layer, offset: CGPoint(x: 0.0, y: !hasQuery ? -44.0 : 0.0), completion: nil) + transition.updateFrame(node: self.segmentedContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) + + transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: segmentedHeight))) + transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: segmentedHeight - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0), transition: transition) - transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: panelY + 5.0), size: controlSize)) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: 5.0), size: controlSize)) insets.top -= 4.0 @@ -466,7 +496,7 @@ class WebSearchControllerNode: ASDisplayNode { insets.top += segmentedHeight insets.bottom += toolbarHeight - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(size: layout.size, insets: insets)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(layout)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) @@ -541,7 +571,7 @@ class WebSearchControllerNode: ASDisplayNode { if self.recentQueriesNode.supernode != nil { self.insertSubnode(gridNode, belowSubnode: self.recentQueriesNode) } else { - self.addSubnode(gridNode) + self.insertSubnode(gridNode, aboveSubnode: previousNode) } self.gridNode = gridNode self.currentEntries = nil diff --git a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift index 98f5fe6f19..16649aa33e 100644 --- a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift @@ -22,7 +22,7 @@ final class WebSearchNavigationContentNode: NavigationBarContentNode { self.theme = theme self.strings = strings - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern) + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: !attachment) self.searchBar.hasCancelButton = attachment self.searchBar.placeholderString = NSAttributedString(string: attachment ? strings.Attachment_SearchWeb : strings.Common_Search, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) @@ -30,13 +30,8 @@ final class WebSearchNavigationContentNode: NavigationBarContentNode { self.addSubnode(self.searchBar) - self.searchBar.textReturned = { [weak self] query in - self?.queryUpdated?(query) - } self.searchBar.textUpdated = { [weak self] query, _ in - if query.isEmpty { - self?.queryUpdated?(query) - } + self?.queryUpdated?(query) } self.searchBar.cancel = { [weak self] in self?.cancel?()