diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 6e376e79fa..476962bcd9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -11,6 +11,7 @@ "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", @@ -23,7 +24,6 @@ "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", @@ -144,8 +144,8 @@ "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index c40171acd7..562958ea80 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -323,7 +323,7 @@ private final class AttachButtonComponent: CombinedComponent { truncationType: .end, maximumNumberOfLines: 1 ), - availableSize: context.availableSize, + availableSize: CGSize(width: 60.0, height: context.availableSize.height), transition: .immediate ) @@ -1587,7 +1587,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) - if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[i] { + if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[type.key] { strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) } } diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 4872952c3d..f2d5eeb60f 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -1016,7 +1016,7 @@ public final class Camera { } } - public func setPreviewOutput(_ output: CameraVideoOutput?) { + public func setVideoOutput(_ output: CameraVideoOutput?) { let outputRef: Unmanaged? = output.flatMap { Unmanaged.passRetained($0) } self.queue.async { if let context = self.contextRef?.takeUnretainedValue() { diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index b88f2558f7..f77a496c6e 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -1641,10 +1641,10 @@ final class ComposePollScreenComponent: Component { self.scrollView.verticalScrollIndicatorInsets = scrollInsets } - let edgeEffectHeight: CGFloat = 66.0 + let edgeEffectHeight: CGFloat = 80.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) let title = self.isQuiz ? environment.strings.CreatePoll_QuizTitle : environment.strings.CreatePoll_Title let titleSize = self.title.update( diff --git a/submodules/ContactListUI/Sources/ContactListActionItem.swift b/submodules/ContactListUI/Sources/ContactListActionItem.swift index 372ff14006..b6dcf22be5 100644 --- a/submodules/ContactListUI/Sources/ContactListActionItem.swift +++ b/submodules/ContactListUI/Sources/ContactListActionItem.swift @@ -26,23 +26,27 @@ public class ContactListActionItem: ListViewItem, ListViewItemWithHeader { } let presentationData: ItemListPresentationData + let style: ItemListStyle + let systemStyle: ItemListSystemStyle let title: String let subtitle: String? let height: Height let icon: ContactListActionItemIcon - let style: Style + let actionStyle: Style let highlight: ContactListActionItemHighlight let clearHighlightAutomatically: Bool let accessible: Bool let action: () -> Void public let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, title: String, subtitle: String? = nil, icon: ContactListActionItemIcon, style: Style = .accent, height: Height = .generic, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, accessible: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) { + public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, systemStyle: ItemListSystemStyle = .legacy, title: String, subtitle: String? = nil, icon: ContactListActionItemIcon, actionStyle: Style = .accent, height: Height = .generic, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, accessible: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) { self.presentationData = presentationData + self.style = style + self.systemStyle = systemStyle self.title = title self.subtitle = subtitle self.icon = icon - self.style = style + self.actionStyle = actionStyle self.height = height self.highlight = highlight self.header = header @@ -133,6 +137,7 @@ class ContactListActionItemNode: ListViewItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode private let iconNode: ASImageNode private let titleNode: TextNode @@ -153,6 +158,9 @@ class ContactListActionItemNode: ListViewItemNode { self.bottomStripeNode = ASDisplayNode() self.bottomStripeNode.isLayerBacked = true + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false self.titleNode.contentMode = .left @@ -205,7 +213,7 @@ class ContactListActionItemNode: ListViewItemNode { let titleColor: UIColor let titleFont: UIFont - switch item.style { + switch item.actionStyle { case .accent: titleColor = item.presentationData.theme.list.itemAccentColor titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) @@ -229,7 +237,10 @@ class ContactListActionItemNode: ListViewItemNode { } let contentHeight: CGFloat - let verticalInset: CGFloat = subtitleAttributedString != nil ? 6.0 : 12.0 + var verticalInset: CGFloat = subtitleAttributedString != nil ? 6.0 : 12.0 + if case .glass = item.systemStyle { + verticalInset += 4.0 + } if case .tall = item.height { contentHeight = 50.0 } else if case .alpha = item.highlight { @@ -257,7 +268,7 @@ class ContactListActionItemNode: ListViewItemNode { strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor - if case .generic = item.style { + if case .generic = item.actionStyle { strongSelf.iconNode.image = item.icon.image } else { strongSelf.iconNode.image = generateTintedImage(image: item.icon.image, color: item.presentationData.theme.list.itemAccentColor) @@ -307,9 +318,18 @@ class ContactListActionItemNode: ListViewItemNode { 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))) + let hasCorners = itemListHasRoundedBlockLayout(params) + if case .blocks = item.style { + if strongSelf.maskNode.supernode == nil { + strongSelf.addSubnode(strongSelf.maskNode) + } + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: false, bottom: true, glass: item.systemStyle == .glass) : nil + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + } + strongSelf.topStripeNode.isHidden = true strongSelf.bottomStripeNode.isHidden = hideBottomStripe diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 05b60323ca..9dc1ed23ee 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -145,11 +145,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .inline(dropDownIcon, .right), highlight: .alpha, accessible: false, header: nil, action: { }) case let .permissionInfo(_, title, text, suppressed): - return InfoListItem(presentationData: ItemListPresentationData(presentationData), title: title, text: .plain(text), style: .plain, closeAction: suppressed ? nil : { + return InfoListItem(presentationData: ItemListPresentationData(presentationData), systemStyle: listStyle == .blocks ? .glass : .legacy, title: title, text: .plain(text), style: listStyle, closeAction: suppressed ? nil : { interaction.suppressWarning() }) case let .permissionEnable(_, text): - return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .none, header: nil, action: { + return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), style: listStyle, systemStyle: listStyle == .blocks ? .glass : .legacy, title: text, icon: .none, header: nil, action: { interaction.authorize() }) case .permissionLimited: @@ -167,7 +167,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { style = .generic height = .tall } - return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, subtitle: option.subtitle, icon: option.icon, style: style, height: height, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action) + return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, subtitle: option.subtitle, icon: option.icon, actionStyle: style, height: height, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action) case let .header(_, header): var sectionId: Int32 = 0 var text = "" @@ -1218,11 +1218,13 @@ public final class ContactListNode: ASDisplayNode { private var authorizationNode: PermissionContentNode private let displayPermissionPlaceholder: Bool + private var authorizationDisposable: Disposable? + public var authorizationUpdated: ((AccessType) -> Void)? public var multipleSelection = false private let isPeerEnabled: ((EnginePeer) -> Bool)? - + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, @@ -1311,6 +1313,16 @@ public final class ContactListNode: ASDisplayNode { self.addSubnode(self.indexNode) self.addSubnode(self.authorizationNode) + self.authorizationDisposable = (contactsAuthorization.get() + |> deliverOnMainQueue).start(next: { [weak self] authorization in + guard let self else { + return + } + Queue.mainQueue().justDispatch { + self.authorizationUpdated?(authorization) + } + }) + let processingQueue = Queue() let previousEntries = Atomic<[ContactListNodeEntry]?>(value: nil) let previousSelectionState = Atomic(value: nil) @@ -2270,6 +2282,14 @@ public final class ContactListNode: ASDisplayNode { } } + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if self.indexNode.frame.contains(point) { + return self.indexNode.view + } + return result + } + private func dequeueTransitions() { if self.validLayout != nil { while !self.queuedTransitions.isEmpty { diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index a935febec2..0e64b2afe4 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -239,6 +239,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? private let dimNode: ASDisplayNode + private let backgroundNode: ASDisplayNode public let listNode: ListView private let emptyResultsTitleNode: ImmediateTextNode @@ -290,6 +291,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = glass ? .clear : UIColor.black.withAlphaComponent(0.5) + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.backgroundNode.alpha = 0.0 + self.listNode = ListView() self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.listNode.alpha = 0.0 @@ -302,16 +308,19 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.emptyResultsTitleNode.displaysAsynchronously = false self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Contacts_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor) self.emptyResultsTitleNode.textAlignment = .center - self.emptyResultsTitleNode.isHidden = true + self.emptyResultsTitleNode.alpha = 0.0 + self.emptyResultsTitleNode.isUserInteractionEnabled = false self.emptyResultsTextNode = ImmediateTextNode() self.emptyResultsTextNode.displaysAsynchronously = false self.emptyResultsTextNode.maximumNumberOfLines = 0 self.emptyResultsTextNode.textAlignment = .center - self.emptyResultsTextNode.isHidden = true - + self.emptyResultsTextNode.alpha = 0.0 + self.emptyResultsTextNode.isUserInteractionEnabled = false + self.emptyResultsAnimationNode = DefaultAnimatedStickerNodeImpl() - self.emptyResultsAnimationNode.isHidden = true + self.emptyResultsAnimationNode.alpha = 0.0 + self.emptyResultsAnimationNode.isUserInteractionEnabled = false super.init() @@ -322,6 +331,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.isOpaque = false self.addSubnode(self.dimNode) + self.addSubnode(self.backgroundNode) self.addSubnode(self.listNode) self.addSubnode(self.emptyResultsAnimationNode) @@ -649,6 +659,8 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.presentationData = presentationData self.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings))) + + self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor } @@ -676,8 +688,10 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo let topInset = navigationBarHeight transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) - self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.backgroundNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight)) + + self.listNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) let size = layout.size let sideInset = layout.safeInsets.left @@ -783,13 +797,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) + containerTransition.updateAlpha(node: strongSelf.backgroundNode, alpha: isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = isSearching - strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults - strongSelf.emptyResultsTitleNode.isHidden = !emptyResults - strongSelf.emptyResultsTextNode.isHidden = !emptyResults + containerTransition.updateAlpha(node: strongSelf.emptyResultsAnimationNode, alpha: emptyResults ? 1.0 : 0.0) + containerTransition.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0) + containerTransition.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0) strongSelf.emptyResultsAnimationNode.visibility = emptyResults - }) } } diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index bd21c902fc..3e446f63dc 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -1156,6 +1156,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { if case .app = item.peerMode { verticalInset += 2.0 } + if case .glass = item.systemStyle { + verticalInset += 4.0 + } let statusHeightComponent: CGFloat if statusAttributedString == nil { diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 3053ff1154..46657a20bb 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -910,7 +910,11 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let validLayout = self.validLayout { updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) } - self.addSubnode(updatedNode) + if updatedNode.addedUnderNavigationBar { + self.insertSubnode(updatedNode, belowSubnode: self.navigationBar) + } else { + self.addSubnode(updatedNode) + } updatedNode.activate() } } else { diff --git a/submodules/ItemListUI/Sources/ItemListControllerSearch.swift b/submodules/ItemListUI/Sources/ItemListControllerSearch.swift index 214c09cca1..7d8c528ecd 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerSearch.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerSearch.swift @@ -17,6 +17,8 @@ public protocol ItemListControllerSearch { } open class ItemListControllerSearchNode: ASDisplayNode { + open var addedUnderNavigationBar: Bool = false + open func activate() { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index 3ff5e32610..f39f8b3f4f 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -326,6 +326,9 @@ public class InfoItemNode: ListViewItemNode { hasTopCorners = true strongSelf.topStripeNode.isHidden = hasCorners || !item.hasDecorations } + } else if case .blocks = item.style, case .glass = item.systemStyle { + strongSelf.topStripeNode.isHidden = true + hasTopCorners = true } let bottomStripeInset: CGFloat if let neighbors = neighbors { diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index f10b92c778..3c62f0e67e 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -10,7 +10,6 @@ import GlassBackgroundComponent import PlainButtonComponent import BundleIconComponent import MultilineTextComponent -import EdgeEffect private let panelInset: CGFloat = 4.0 private let panelButtonSize = CGSize(width: 46.0, height: 46.0) @@ -55,9 +54,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { public let mapNode: LocationMapNode public var trackingMode: LocationTrackingMode = .none - - private let edgeEffectView: EdgeEffectView - + private let options: ComponentView? private let optionsBackgroundView: GlassBackgroundView? @@ -153,18 +150,13 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.optionsBackgroundView = nil self.placesBackgroundView = nil } - - self.edgeEffectView = EdgeEffectView() - self.edgeEffectView.isUserInteractionEnabled = false - + super.init() self.clipsToBounds = true self.addSubnode(self.mapNode) - - self.view.addSubview(self.edgeEffectView) - + if glass { if let placesBackgroundView = self.placesBackgroundView { self.placesBackgroundNode.view.addSubview(placesBackgroundView) @@ -272,11 +264,6 @@ public final class LocationMapHeaderNode: ASDisplayNode { transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0)) - let edgeEffectHeight: CGFloat = 80.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight)) - transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: self.mapNode.mapMode == .map ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition)) - if let options = self.options { let optionsSize = options.update( transition: ComponentTransition(transition), @@ -314,6 +301,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.view.addSubview(optionsView) } transition.updateFrame(view: optionsView, frame: CGRect(origin: CGPoint(x: size.width - optionsSize.width - inset - 10.0, y: size.height - optionsSize.height - inset - controlsBottomPadding - 10.0), size: optionsSize)) + ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)).setAlpha(view: optionsView, alpha: size.height < 180.0 ? 0.0 : 1.0) } } else { let buttonSize = self.glass ? glassPanelButtonSize : panelButtonSize diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index b62188f445..f233497ba3 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -355,6 +355,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let searchButton = ComponentView() private let title = ComponentView() + fileprivate let topEdgeEffectView: EdgeEffectView fileprivate let bottomEdgeEffectView: EdgeEffectView private var sendButton: ComponentView? @@ -428,6 +429,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.shadeNode.alpha = 0.0 self.innerShadeNode = ASDisplayNode() self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.isUserInteractionEnabled = false self.bottomEdgeEffectView = EdgeEffectView() self.bottomEdgeEffectView.isUserInteractionEnabled = false @@ -1329,6 +1333,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } if glass { + let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 80.0)) + transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + if self.topEdgeEffectView.superview == nil { + self.view.addSubview(self.topEdgeEffectView) + } + let titleSize = self.title.update( transition: ComponentTransition(transition), component: AnyComponent( @@ -1481,7 +1492,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM }) } } - + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 88.0 - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: 88.0)) transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) self.bottomEdgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index debd37981b..3a979c455e 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -42,7 +42,6 @@ swift_library( "//submodules/UndoUI:UndoUI", "//submodules/MoreButtonNode:MoreButtonNode", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", - "//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/MediaEditor", "//submodules/RadialStatusNode", "//submodules/Camera", diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 5fc241f294..5ebed4a5b6 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -22,7 +22,6 @@ import UndoUI import PresentationDataUtils import MoreButtonNode import Camera -import CameraScreen import MediaEditor import ImageObjectSeparation import ChatSendMessageActionUI @@ -2591,7 +2590,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: barButtonSize ) let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset, y: barButtonSideInset), size: cancelButtonSize) if let view = cancelButton.view { @@ -2626,7 +2625,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self?.moreButtonPlayOnce.invoke(Void()) })), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: barButtonSize ) let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset, y: barButtonSideInset), size: moreButtonSize) if let view = moreButton.view { @@ -3712,46 +3711,47 @@ public func stickerMediaPickerController( controller?.dismiss(animated: true) } mediaPickerController.openCamera = { [weak controller] cameraHolder in - guard let cameraHolder = cameraHolder as? CameraHolder else { - return - } - - var returnToCameraImpl: (() -> Void)? - let cameraScreen = CameraScreenImpl( - context: context, - mode: .sticker, - holder: cameraHolder, - transitionIn: CameraScreenImpl.TransitionIn( - sourceView: cameraHolder.parentView, - sourceRect: cameraHolder.parentView.bounds, - sourceCornerRadius: 0.0, - useFillAnimation: false - ), - transitionOut: { _ in - return CameraScreenImpl.TransitionOut( - destinationView: cameraHolder.parentView, - destinationRect: cameraHolder.parentView.bounds, - destinationCornerRadius: 0.0 - ) - }, - completion: { result, _, _, commit in - completion(result, nil, .zero, nil, true, { _ in return nil }, { - returnToCameraImpl?() - }) - } - ) - cameraScreen.transitionedOut = { [weak cameraHolder] in - if let cameraHolder { - cameraHolder.restore() - } - } - controller?.push(cameraScreen) - - returnToCameraImpl = { [weak cameraScreen] in - if let cameraScreen { - cameraScreen.returnFromEditor() - } - } + let _ = controller +// guard let cameraHolder = cameraHolder as? CameraHolder else { +// return +// } +// +// var returnToCameraImpl: (() -> Void)? +// let cameraScreen = CameraScreenImpl( +// context: context, +// mode: .sticker, +// holder: cameraHolder, +// transitionIn: CameraScreenImpl.TransitionIn( +// sourceView: cameraHolder.parentView, +// sourceRect: cameraHolder.parentView.bounds, +// sourceCornerRadius: 0.0, +// useFillAnimation: false +// ), +// transitionOut: { _ in +// return CameraScreenImpl.TransitionOut( +// destinationView: cameraHolder.parentView, +// destinationRect: cameraHolder.parentView.bounds, +// destinationCornerRadius: 0.0 +// ) +// }, +// completion: { result, _, _, commit in +// completion(result, nil, .zero, nil, true, { _ in return nil }, { +// returnToCameraImpl?() +// }) +// } +// ) +// cameraScreen.transitionedOut = { [weak cameraHolder] in +// if let cameraHolder { +// cameraHolder.restore() +// } +// } +// controller?.push(cameraScreen) +// +// returnToCameraImpl = { [weak cameraScreen] in +// if let cameraScreen { +// cameraScreen.returnFromEditor() +// } +// } } present(mediaPickerController, mediaPickerController.mediaPickerContext) } @@ -3839,46 +3839,47 @@ public func avatarMediaPickerController( controller?.dismiss(animated: true) } mediaPickerController.openCamera = { [weak controller] cameraHolder in - guard let cameraHolder = cameraHolder as? CameraHolder else { - return - } - - var returnToCameraImpl: (() -> Void)? - let cameraScreen = CameraScreenImpl( - context: context, - mode: .avatar, - holder: cameraHolder, - transitionIn: CameraScreenImpl.TransitionIn( - sourceView: cameraHolder.parentView, - sourceRect: cameraHolder.parentView.bounds, - sourceCornerRadius: 0.0, - useFillAnimation: false - ), - transitionOut: { _ in - return CameraScreenImpl.TransitionOut( - destinationView: cameraHolder.parentView, - destinationRect: cameraHolder.parentView.bounds, - destinationCornerRadius: 0.0 - ) - }, - completion: { result, _, _, commit in - completion(result, nil, .zero, nil, true, { _ in return nil }, { - returnToCameraImpl?() - }) - } - ) - cameraScreen.transitionedOut = { [weak cameraHolder] in - if let cameraHolder { - cameraHolder.restore() - } - } - controller?.push(cameraScreen) - - returnToCameraImpl = { [weak cameraScreen] in - if let cameraScreen { - cameraScreen.returnFromEditor() - } - } + let _ = controller +// guard let cameraHolder = cameraHolder as? CameraHolder else { +// return +// } +// +// var returnToCameraImpl: (() -> Void)? +// let cameraScreen = CameraScreenImpl( +// context: context, +// mode: .avatar, +// holder: cameraHolder, +// transitionIn: CameraScreenImpl.TransitionIn( +// sourceView: cameraHolder.parentView, +// sourceRect: cameraHolder.parentView.bounds, +// sourceCornerRadius: 0.0, +// useFillAnimation: false +// ), +// transitionOut: { _ in +// return CameraScreenImpl.TransitionOut( +// destinationView: cameraHolder.parentView, +// destinationRect: cameraHolder.parentView.bounds, +// destinationCornerRadius: 0.0 +// ) +// }, +// completion: { result, _, _, commit in +// completion(result, nil, .zero, nil, true, { _ in return nil }, { +// returnToCameraImpl?() +// }) +// } +// ) +// cameraScreen.transitionedOut = { [weak cameraHolder] in +// if let cameraHolder { +// cameraHolder.restore() +// } +// } +// controller?.push(cameraScreen) +// +// returnToCameraImpl = { [weak cameraScreen] in +// if let cameraScreen { +// cameraScreen.returnFromEditor() +// } +// } } present(mediaPickerController, mediaPickerController.mediaPickerContext) } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index fb9030c920..d4f2c23ef0 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -348,7 +348,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { let arguments = arguments as! ChannelAdminControllerArguments switch self { case let .info(_, _, dateTimeFormat, peer, presence): - return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in }, avatarTapped: { }) case let .rankTitle(_, text, count, limit): @@ -358,7 +358,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { } return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: accessoryText, sectionId: self.section) case let .rank(_, _, placeholder, text, enabled): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in arguments.updateRank(text, updatedText) }, shouldUpdateText: { text in if text.containsEmoji { @@ -374,7 +374,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { case let .rankInfo(_, text, trimBottomInset): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section, additionalOuterInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: trimBottomInset ? -44.0 : 0.0, right: 0.0)) case let .adminRights(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .regular, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, type: .regular, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.updateAdminRights(value) }, activatedWhileDisabled: { }) @@ -382,7 +382,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .rightItem(_, _, text, right, flags, value, enabled, subPermissions, isExpanded): if !subPermissions.isEmpty { - return ItemListExpandableSwitchItem(presentationData: presentationData, title: text, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in + return ItemListExpandableSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in return ItemListExpandableSwitchItem.SubItem( id: AnyHashable(item.flags.rawValue), title: item.title, @@ -414,7 +414,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { } }) } else { - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleRight(right, flags, value) }, activatedWhileDisabled: { if case let .direct(right) = right { @@ -425,11 +425,11 @@ private enum ChannelAdminEntry: ItemListNodeEntry { case let .addAdminsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .transfer(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.transferOwnership() }, tag: nil) case let .dismiss(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.dismissAdmin() }, tag: nil) } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index ed6a5c6f47..abcd3e572f 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -130,6 +130,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/SliderComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift index f49b774870..7c46d31f06 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift @@ -9,6 +9,8 @@ import TelegramPresentationData import LegacyComponents import ItemListUI import PresentationDataUtils +import ComponentFlow +import SliderComponent enum AutomaticDownloadDataUsage: Int { case low @@ -95,7 +97,8 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { private let mediumTextNode: TextNode private let highTextNode: TextNode private let customTextNode: TextNode - private var sliderView: TGPhotoEditorSliderView? +// private var sliderView: TGPhotoEditorSliderView? + private let slider = ComponentView() private let activateArea: AccessibilityAreaNode @@ -140,100 +143,95 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { self.addSubnode(self.customTextNode) self.addSubnode(self.activateArea) - self.activateArea.increment = { [weak self] in - if let self { - self.sliderView?.increase() - } - } - - self.activateArea.decrement = { [weak self] in - if let self { - self.sliderView?.decrease() - } - } +// self.activateArea.increment = { [weak self] in +// if let self { +// self.sliderView?.increase() +// } +// } +// +// self.activateArea.decrement = { [weak self] in +// if let self { +// self.sliderView?.decrease() +// } +// } } - func updateSliderView() { - if let sliderView = self.sliderView, let item = self.item { - sliderView.maximumValue = 2.0 + (item.customPosition != nil ? 1 : 0) - sliderView.positionsCount = 3 + (item.customPosition != nil ? 1 : 0) - var value = item.value.rawValue - if let customPosition = item.customPosition { - if case .custom = item.value { - value = customPosition - } else { - if value >= customPosition { - value += 1 - } - } - } - - sliderView.value = CGFloat(value) - - sliderView.isUserInteractionEnabled = item.enabled - sliderView.alpha = item.enabled ? 1.0 : 0.4 - sliderView.layer.allowsGroupOpacity = !item.enabled - - self.updateAccessibilityLabels() - } - } +// func updateSliderView() { +// if let sliderView = self.sliderView, let item = self.item { +// sliderView.maximumValue = 2.0 + (item.customPosition != nil ? 1 : 0) +// sliderView.positionsCount = 3 + (item.customPosition != nil ? 1 : 0) +// var value = item.value.rawValue +// if let customPosition = item.customPosition { +// if case .custom = item.value { +// value = customPosition +// } else { +// if value >= customPosition { +// value += 1 +// } +// } +// } +// +// sliderView.value = CGFloat(value) +// +// sliderView.isUserInteractionEnabled = item.enabled +// sliderView.alpha = item.enabled ? 1.0 : 0.4 +// sliderView.layer.allowsGroupOpacity = !item.enabled +// +// self.updateAccessibilityLabels() +// } +// } override func didLoad() { super.didLoad() - let sliderView = TGPhotoEditorSliderView() - sliderView.enablePanHandling = true - sliderView.trackCornerRadius = 2.0 - sliderView.lineSize = 4.0 - sliderView.dotSize = 5.0 - sliderView.minimumValue = 0.0 - sliderView.maximumValue = 2.0 + (self.item?.customPosition != nil ? 1 : 0) - sliderView.startValue = 0.0 - sliderView.disablesInteractiveTransitionGestureRecognizer = true - sliderView.positionsCount = 3 + (self.item?.customPosition != nil ? 1 : 0) - sliderView.useLinesForPositions = true - if let item = self.item, let params = self.layoutParams { - var value = item.value.rawValue - if let customPosition = item.customPosition { - if case .custom = item.value { - value = customPosition - } else { - if value >= customPosition { - value += 1 - } - } - } - - sliderView.value = CGFloat(value) - sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor - sliderView.backColor = item.theme.list.itemSwitchColors.frameColor - sliderView.startColor = item.theme.list.itemSwitchColors.frameColor - sliderView.trackColor = item.theme.list.itemAccentColor - sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) - - sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) - } - self.view.addSubview(sliderView) - sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) - self.sliderView = sliderView - - self.updateSliderView() +// let sliderView = TGPhotoEditorSliderView() +// sliderView.enablePanHandling = true +// sliderView.trackCornerRadius = 2.0 +// sliderView.lineSize = 4.0 +// sliderView.dotSize = 5.0 +// sliderView.minimumValue = 0.0 +// sliderView.maximumValue = 2.0 + (self.item?.customPosition != nil ? 1 : 0) +// sliderView.startValue = 0.0 +// sliderView.disablesInteractiveTransitionGestureRecognizer = true +// sliderView.positionsCount = 3 + (self.item?.customPosition != nil ? 1 : 0) +// sliderView.useLinesForPositions = true +// if let item = self.item, let params = self.layoutParams { +// var value = item.value.rawValue +// if let customPosition = item.customPosition { +// if case .custom = item.value { +// value = customPosition +// } else { +// if value >= customPosition { +// value += 1 +// } +// } +// } +// +// sliderView.value = CGFloat(value) +// sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor +// sliderView.backColor = item.theme.list.itemSwitchColors.frameColor +// sliderView.startColor = item.theme.list.itemSwitchColors.frameColor +// sliderView.trackColor = item.theme.list.itemAccentColor +// sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) +// +// sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) +// sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) +// } +// self.view.addSubview(sliderView) +// sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) +// self.sliderView = sliderView +// +// self.updateSliderView() } func asyncLayout() -> (_ item: AutodownloadDataUsagePickerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { - let currentItem = self.item +// let currentItem = self.item let makeLowTextLayout = TextNode.asyncLayout(self.lowTextNode) let makeMediumTextLayout = TextNode.asyncLayout(self.mediumTextNode) let makeHighTextLayout = TextNode.asyncLayout(self.highTextNode) let makeCustomTextLayout = TextNode.asyncLayout(self.customTextNode) return { item, params, neighbors in - var themeUpdated = false - if currentItem?.theme !== item.theme { - themeUpdated = true - } - let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel @@ -317,33 +315,87 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { textNodes.insert((strongSelf.customTextNode, customTextLayout.size), at: customPosition) } - let delta = (params.width - params.leftInset - params.rightInset - 18.0 * 2.0) / CGFloat(textNodes.count - 1) + let delta = (params.width - params.leftInset - params.rightInset - 25.0 * 2.0) / CGFloat(textNodes.count - 1) for i in 0 ..< textNodes.count { let (textNode, textSize) = textNodes[i] - var position = params.leftInset + 18.0 + delta * CGFloat(i) - if i == textNodes.count - 1 { - position -= textSize.width - } else if i > 0 { - position -= textSize.width / 2.0 - } + let leftEdge = params.leftInset + 18.0 + let rightEdge = params.width - params.rightInset - 18.0 + let position = params.leftInset + 25.0 + delta * CGFloat(i) + let origin = max(leftEdge, min(rightEdge - textSize.width, position - textSize.width / 2.0)) - textNode.frame = CGRect(origin: CGPoint(x: position, y: 15.0), size: textSize) + textNode.frame = CGRect(origin: CGPoint(x: origin, y: 15.0), size: textSize) } - if let sliderView = strongSelf.sliderView { - if themeUpdated { - sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor - sliderView.backColor = item.theme.list.itemSwitchColors.frameColor - sliderView.trackColor = item.theme.list.itemSwitchColors.frameColor - sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) + + var valueCount = 3 + var value = item.value.rawValue + if let customPosition = item.customPosition { + valueCount += 1 + if case .custom = item.value { + value = customPosition + } else { + if value >= customPosition { + value += 1 + } } - - sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) - - strongSelf.updateSliderView() } + + let sliderSize = strongSelf.slider.update( + transition: .immediate, + component: AnyComponent( + SliderComponent( + content: .discrete(.init( + valueCount: valueCount, + value: value, + markPositions: true, + valueUpdated: { [weak self] position in + guard let self else { + return + } + var value: AutomaticDownloadDataUsage? + if let customPosition = self.item?.customPosition { + if position == customPosition { + value = .custom + } else { + value = AutomaticDownloadDataUsage(rawValue: position > customPosition ? (position - 1) : position) + } + } else { + value = AutomaticDownloadDataUsage(rawValue: position) + } + if let value = value { + self.item?.updated(value) + } + } + )), + useNative: true, + trackBackgroundColor: item.theme.list.itemSwitchColors.frameColor, + trackForegroundColor: item.theme.list.itemAccentColor + ) + ), + environment: {}, + containerSize: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0) + ) + if let sliderView = strongSelf.slider.view { + if sliderView.superview == nil { + strongSelf.view.addSubview(sliderView) + } + sliderView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - sliderSize.width) / 2.0), y: 37.0), size: sliderSize) + } + +// if let sliderView = strongSelf.sliderView { +// if themeUpdated { +// sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor +// sliderView.backColor = item.theme.list.itemSwitchColors.frameColor +// sliderView.trackColor = item.theme.list.itemSwitchColors.frameColor +// sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) +// } +// +// sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) +// sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) +// +// strongSelf.updateSliderView() +// } strongSelf.activateArea.accessibilityLabel = item.strings.AutoDownloadSettings_DataUsage strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) @@ -353,22 +405,22 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { } private func updateAccessibilityLabels() { - guard let item = self.item else { - return - } - var textNodes: [TextNode] = [self.lowTextNode, self.mediumTextNode, self.highTextNode] - if let customPosition = item.customPosition { - textNodes.insert(self.customTextNode, at: customPosition) - } - if let value = self.sliderView?.value { - self.activateArea.accessibilityValue = textNodes[Int(value)].cachedLayout?.attributedString?.string ?? "" - } - var accessibilityTraits: UIAccessibilityTraits = [.adjustable] - if item.enabled { - } else { - accessibilityTraits.insert(.notEnabled) - } - self.activateArea.accessibilityTraits = accessibilityTraits +// guard let item = self.item else { +// return +// } +// var textNodes: [TextNode] = [self.lowTextNode, self.mediumTextNode, self.highTextNode] +// if let customPosition = item.customPosition { +// textNodes.insert(self.customTextNode, at: customPosition) +// } +// if let value = self.sliderView?.value { +// self.activateArea.accessibilityValue = textNodes[Int(value)].cachedLayout?.attributedString?.string ?? "" +// } +// var accessibilityTraits: UIAccessibilityTraits = [.adjustable] +// if item.enabled { +// } else { +// accessibilityTraits.insert(.notEnabled) +// } +// self.activateArea.accessibilityTraits = accessibilityTraits } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -379,27 +431,27 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } - @objc func sliderValueChanged() { - guard let sliderView = self.sliderView else { - return - } - - let position = Int(sliderView.value) - var value: AutomaticDownloadDataUsage? - - if let customPosition = self.item?.customPosition { - if position == customPosition { - value = .custom - } else { - value = AutomaticDownloadDataUsage(rawValue: position > customPosition ? (position - 1) : position) - } - } else { - value = AutomaticDownloadDataUsage(rawValue: position) - } - - if let value = value { - self.item?.updated(value) - } - } +// @objc func sliderValueChanged() { +// guard let sliderView = self.sliderView else { +// return +// } +// +// let position = Int(sliderView.value) +// var value: AutomaticDownloadDataUsage? +// +// if let customPosition = self.item?.customPosition { +// if position == customPosition { +// value = .custom +// } else { +// value = AutomaticDownloadDataUsage(rawValue: position > customPosition ? (position - 1) : position) +// } +// } else { +// value = AutomaticDownloadDataUsage(rawValue: position) +// } +// +// if let value = value { +// self.item?.updated(value) +// } +// } } diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 1d935d2ea6..73f3f1db32 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -40,7 +40,6 @@ swift_library( "//submodules/StickerPeekUI:StickerPeekUI", "//submodules/Pasteboard:Pasteboard", "//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController", - "//submodules/TelegramUI/Components/CameraScreen", "//submodules/TelegramUI/Components/EmojiStatusComponent", ], visibility = [ diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 1ed435d28c..4597ed97d7 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -23,7 +23,7 @@ import MultiAnimationRenderer import Pasteboard import StickerPackEditTitleController import EntityKeyboard -import CameraScreen +//import CameraScreen import ComponentFlow import EmojiStatusComponent diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 2de619c870..f803ba9e5b 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -3344,7 +3344,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - func requestVideo(capturer: OngoingCallVideoCapturer, useFrontCamera: Bool = true) { + public func requestVideo(capturer: OngoingCallVideoCapturer, useFrontCamera: Bool = true) { self.videoCapturer = capturer self.useFrontCamera = useFrontCamera diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index d3f96f7ac9..2d21de2fd2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1112,21 +1112,83 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) { }).start() } -func _internal_beginStoryLivestream(account: Account) -> Signal { - var flags: Int32 = 0 - flags |= 1 << 5 - flags |= 1 << 6 - return account.network.request(Api.functions.stories.startLive(flags: flags, peer: .inputPeerSelf, caption: nil, entities: nil, privacyRules: [.inputPrivacyValueAllowAll], randomId: Int64.random(in: Int64.min ... Int64.max), messagesEnabled: .boolTrue, sendPaidMessagesStars: nil)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { updates -> Signal in - if let updates { - account.stateManager.addUpdates(updates) +func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal { + return account.postbox.transaction { transaction in + var flags: Int32 = 0 + //flags |= 1 << 5 + + if isForwardingDisabled { + flags |= 1 << 4 + } + + let inputPeer: Api.InputPeer + if peerId.namespace == Namespaces.Peer.CloudChannel, let peer = transaction.getPeer(peerId).flatMap(apiInputPeer) { + inputPeer = peer + } else { + inputPeer = .inputPeerSelf + } + + return account.network.request(Api.functions.stories.startLive(flags: flags, peer: inputPeer, caption: nil, entities: nil, privacyRules: [.inputPrivacyValueAllowAll], randomId: Int64.random(in: Int64.min ... Int64.max), messagesEnabled: nil, sendPaidMessagesStars: nil)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates { + account.stateManager.addUpdates(updates) + + for update in updates.allUpdates { + if case let .updateStory(_, apiStory) = update { + return account.postbox.transaction { transaction in + if let storedItem = Stories.StoredItem(apiStoryItem: apiStory, peerId: peerId, transaction: transaction), case let .item(item) = storedItem, let media = item.media { + let mappedItem = EngineStoryItem( + id: item.id, + timestamp: item.timestamp, + expirationTimestamp: item.expirationTimestamp, + media: EngineMedia(media), + alternativeMediaList: item.alternativeMediaList.map(EngineMedia.init), + mediaAreas: item.mediaAreas, + text: item.text, + entities: item.entities, + views: item.views.flatMap { views in + return EngineStoryItem.Views( + seenCount: views.seenCount, + reactedCount: views.reactedCount, + forwardCount: views.forwardCount, + seenPeers: views.seenPeerIds.compactMap { id -> EnginePeer? in + return transaction.getPeer(id).flatMap(EnginePeer.init) + }, + reactions: views.reactions, + hasList: views.hasList + ) + }, + privacy: item.privacy.flatMap(EngineStoryPrivacy.init), + isPinned: item.isPinned, + isExpired: item.isExpired, + isPublic: item.isPublic, + isPending: false, + isCloseFriends: item.isCloseFriends, + isContacts: item.isContacts, + isSelectedContacts: item.isSelectedContacts, + isForwardingDisabled: item.isForwardingDisabled, + isEdited: item.isEdited, + isMy: item.isMy, + myReaction: item.myReaction, + forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) }, + author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) }, + folderIds: item.folderIds + ) + return mappedItem + } + return nil + } + } + } + } + return .single(nil) } - return .complete() } + |> switchToLatest } private struct PendingStoryIdMappingKey: Hashable { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 93d14bae12..b28845b55f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1414,8 +1414,8 @@ public extension TelegramEngine { return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo) } - public func beginStoryLivestream() -> Signal { - return _internal_beginStoryLivestream(account: self.account) + public func beginStoryLivestream(peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal { + return _internal_beginStoryLivestream(account: self.account, peerId: peerId, privacy: privacy, isForwardingDisabled: isForwardingDisabled) } public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> { diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index dbd39e4088..07c787129a 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -204,6 +204,12 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon private var topEdgeEffectView: EdgeEffectView? private var bottomEdgeEffectView: EdgeEffectView? + var isSearching: Bool = false { + didSet { + self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) @@ -234,6 +240,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) transition.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + transition.updateAlpha(layer: bottomEdgeEffectView.layer, alpha: self.isSearching ? 0.0 : 1.0) bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) } } @@ -255,6 +262,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre var expandImpl: (() -> Void)? var dismissImpl: (() -> Void)? var dismissInputImpl: (() -> Void)? + var updateIsSearchingImpl: ((Bool) -> Void)? let arguments = AttachmentFileControllerArguments( context: context, openGallery: { @@ -357,6 +365,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre return updatedState } updateTabBarVisibilityImpl?(false) + updateIsSearchingImpl?(true) } ) let searchButtonComponent = state.searching ? nil : AnyComponentWithIdentity(id: "search", component: AnyComponent(searchButton)) @@ -424,6 +433,7 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre return updatedState } updateTabBarVisibilityImpl?(true) + updateIsSearchingImpl?(false) }, send: { message in arguments.send(message) }, dismissInput: { @@ -463,6 +473,9 @@ public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPre return updatedState } } + updateIsSearchingImpl = { [weak controller] isSearching in + controller?.isSearching = isSearching + } dismissImpl = { [weak controller] in controller?.dismiss(animated: true) } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift index c01a8efea5..d8f20e23bd 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift @@ -122,7 +122,7 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo insets.top += -60.0 imageSize = CGSize(width: 112.0, height: 112.0) } else { - insets.top += -160.0 + insets.top += 30.0 //-160.0 } let imageSpacing: CGFloat = 12.0 diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 2c434c1e96..9b19bbe670 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -94,13 +94,15 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { self.strings = strings self.focus = focus self.cancel = cancel - + self.containerNode = AttachmentFileSearchContainerNode(context: context, forceTheme: nil, send: { message in send(message) }, updateActivity: updateActivity) super.init() + self.addedUnderNavigationBar = true + self.addSubnode(self.containerNode) self.containerNode.cancel = { [weak self] in @@ -278,6 +280,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon private let send: (Message) -> Void private let dimNode: ASDisplayNode + private let backgroundNode: ASDisplayNode private let listNode: ListView private let emptyResultsTitleNode: ImmediateTextNode @@ -317,6 +320,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = .clear + self.backgroundNode = ASDisplayNode() + self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string @@ -336,12 +341,16 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon super.init() + self.backgroundNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.backgroundNode.alpha = 0.0 + self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.listNode.alpha = 0.0 self._hasDim = true self.addSubnode(self.dimNode) + self.addSubnode(self.backgroundNode) self.addSubnode(self.listNode) self.addSubnode(self.emptyResultsTitleNode) @@ -448,6 +457,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.backgroundNode.backgroundColor = theme.chatList.backgroundColor self.listNode.backgroundColor = theme.chatList.backgroundColor } @@ -484,6 +494,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + containerTransition.updateAlpha(node: strongSelf.backgroundNode, alpha: isSearching ? 1.0 : 0.0) containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = transition.isSearching @@ -514,6 +525,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon let topInset = navigationBarHeight transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) + self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -66.0), size: CGSize(width: layout.size.width, height: 66.0)) self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) diff --git a/submodules/TelegramUI/Components/CameraScreen/BUILD b/submodules/TelegramUI/Components/CameraScreen/BUILD index be81f37a3d..19fc7867ba 100644 --- a/submodules/TelegramUI/Components/CameraScreen/BUILD +++ b/submodules/TelegramUI/Components/CameraScreen/BUILD @@ -87,6 +87,12 @@ swift_library( "//submodules/ContextUI", "//submodules/AvatarNode", "//submodules/TelegramUI/Components/Utils/AnimatableProperty", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/ShareWithPeersScreen", + "//submodules/TelegramCallsUI", + "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal b/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal index 745949c73f..e8ab4634fa 100644 --- a/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal +++ b/submodules/TelegramUI/Components/CameraScreen/MetalResources/cameraScreen.metal @@ -28,17 +28,18 @@ float smin(float a, float b, float k) { return mix(a, b, h) - k * h * (1.0 - h); } -float sdfRoundedRectangle(float2 uv, float2 position, float size, float radius) { - float2 q = abs(uv - position) - size + radius; - return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; +float sdfRoundedRectangle(float2 uv, float2 center, float2 halfSize, float radius) { + float r = min(radius, min(halfSize.x, halfSize.y)); + float2 q = abs(uv - center) - (halfSize - float2(r)); + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; } float sdfCircle(float2 uv, float2 position, float radius) { return length(uv - position) - radius; } -float map(float2 uv, float3 primaryParameters, float2 primaryOffset, float3 secondaryParameters, float2 secondaryOffset) { - float primary = sdfRoundedRectangle(uv, primaryOffset, primaryParameters.x, primaryParameters.z); +float map(float2 uv, float4 primaryParameters, float2 primaryOffset, float3 secondaryParameters, float2 secondaryOffset) { + float primary = sdfRoundedRectangle(uv, primaryOffset, primaryParameters.xy, primaryParameters.w); float secondary = sdfCircle(uv, secondaryOffset, secondaryParameters.x); float metaballs = 1.0; metaballs = smin(metaballs, primary, BindingDistance); @@ -48,7 +49,7 @@ float map(float2 uv, float3 primaryParameters, float2 primaryOffset, float3 seco fragment half4 cameraBlobFragment(RasterizerData in[[stage_in]], constant uint2 &resolution[[buffer(0)]], - constant float3 &primaryParameters[[buffer(1)]], + constant float4 &primaryParameters[[buffer(1)]], constant float2 &primaryOffset[[buffer(2)]], constant float3 &secondaryParameters[[buffer(3)]], constant float2 &secondaryOffset[[buffer(4)]]) @@ -67,9 +68,9 @@ fragment half4 cameraBlobFragment(RasterizerData in[[stage_in]], float t = AARadius / resolution.y; - float cAlpha = min(1.0, 1.0 - primaryParameters.y); - float minColor = min(1.0, 1.0 + primaryParameters.y); - float bound = primaryParameters.x + 0.05; + float cAlpha = min(1.0, 1.0 - primaryParameters.z); + float minColor = min(1.0, 1.0 + primaryParameters.z); + float bound = max(primaryParameters.x, primaryParameters.y) + 0.05; if (abs(offset) > bound) { cAlpha = mix(0.0, 1.0, min(1.0, (abs(offset) - bound) * 2.4)); } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCollage.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCollage.swift index e630e84e18..9c13c51196 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCollage.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCollage.swift @@ -903,7 +903,6 @@ final class CameraCollageView: UIView, UIGestureRecognizerDelegate { private let context: AccountContext private let collage: CameraCollage - private weak var camera: Camera? private weak var cameraContainerView: UIView? private var cameraVideoSource: CameraVideoSource? @@ -934,9 +933,10 @@ final class CameraCollageView: UIView, UIGestureRecognizerDelegate { return self.collage.result(itemViews: self.itemViews) } - init(context: AccountContext, collage: CameraCollage, camera: Camera?, cameraContainerView: UIView?) { + init(context: AccountContext, collage: CameraCollage, cameraVideoSource: CameraVideoSource, cameraContainerView: UIView?) { self.context = context self.collage = collage + self.cameraVideoSource = cameraVideoSource self.cameraContainerView = cameraContainerView self.cloneLayers = (0 ..< 6).map { _ in MetalEngineSubjectLayer() } @@ -1009,17 +1009,12 @@ final class CameraCollageView: UIView, UIGestureRecognizerDelegate { self.tapRecognizer = tapRecognizer self.addGestureRecognizer(tapRecognizer) - if let cameraVideoSource = CameraVideoSource() { - self.cameraVideoLayer.video = cameraVideoSource.currentOutput - camera?.setPreviewOutput(cameraVideoSource.cameraVideoOutput) - self.cameraVideoSource = cameraVideoSource - - self.cameraVideoDisposable = cameraVideoSource.addOnUpdated { [weak self] in - guard let self, let videoSource = self.cameraVideoSource, self.isEnabled else { - return - } - self.cameraVideoLayer.video = videoSource.currentOutput + self.cameraVideoLayer.video = cameraVideoSource.currentOutput + self.cameraVideoDisposable = cameraVideoSource.addOnUpdated { [weak self] in + guard let self, let videoSource = self.cameraVideoSource, self.isEnabled else { + return } + self.cameraVideoLayer.video = videoSource.currentOutput } let videoSize = CGSize(width: 160.0 * 2.0, height: 284.0 * 2.0) @@ -1035,7 +1030,6 @@ final class CameraCollageView: UIView, UIGestureRecognizerDelegate { deinit { self.disposable?.dispose() self.cameraVideoDisposable?.dispose() - self.camera?.setPreviewOutput(nil) } func getPreviewLayer() -> PreviewLayer { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift new file mode 100644 index 0000000000..2fda91ee14 --- /dev/null +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -0,0 +1,255 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramCallsUI +import TelegramPresentationData +import StoryContainerScreen +import ChatEntityKeyboardInputNode + +final class CameraLiveStreamComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let peerId: EnginePeer.Id + let story: EngineStoryItem? + let statusBarHeight: CGFloat + let inputHeight: CGFloat + let metrics: LayoutMetrics + let deviceMetrics: DeviceMetrics + let didSetupMediaStream: (PresentationGroupCall) -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + peerId: EnginePeer.Id, + story: EngineStoryItem?, + statusBarHeight: CGFloat, + inputHeight: CGFloat, + metrics: LayoutMetrics, + deviceMetrics: DeviceMetrics, + didSetupMediaStream: @escaping (PresentationGroupCall) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.peerId = peerId + self.story = story + self.statusBarHeight = statusBarHeight + self.inputHeight = inputHeight + self.metrics = metrics + self.deviceMetrics = deviceMetrics + self.didSetupMediaStream = didSetupMediaStream + } + + static func ==(lhs: CameraLiveStreamComponent, rhs: CameraLiveStreamComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.story != rhs.story { + return false + } + if lhs.statusBarHeight != rhs.statusBarHeight { + return false + } + if lhs.inputHeight != rhs.inputHeight { + return false + } + if lhs.metrics != rhs.metrics { + return false + } + if lhs.deviceMetrics != rhs.deviceMetrics { + return false + } + return true + } + + final class View: UIView { + private var liveChat: ComponentView? + + private var storyContent: SingleStoryContentContextImpl? + private var storyContentState: StoryContentContextState? + private var storyContentDisposable: Disposable? + + private let externalState = StoryItemSetContainerComponent.ExternalState() + private let storyItemSharedState = StoryContentItem.SharedState() + + private let inputMediaNodeDataPromise = Promise() + private let closeFriendsPromise = Promise<[EnginePeer]>() + private var blockedPeers: BlockedPeersContext? + + private var component: CameraLiveStreamComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.storyContentDisposable?.dispose() + } + + var mediaStreamCall: PresentationGroupCall? { + if let view = self.liveChat?.view as? StoryItemSetContainerComponent.View { + return view.mediaStreamCall + } + return nil + } + + func update(component: CameraLiveStreamComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + if let story = component.story { + if self.storyContentDisposable == nil { + let storyContent = SingleStoryContentContextImpl(context: component.context, storyId: StoryId(peerId: component.peerId, id: story.id), storyItem: story, readGlobally: false) + self.storyContent = storyContent + self.storyContentDisposable = (storyContent.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.storyContentState = state + self.state?.updated() + }) + } + + if let storyContentState = self.storyContentState, let slice = storyContentState.slice { + var mediaStreamTransition = transition + + let liveChat: ComponentView + if let current = self.liveChat { + liveChat = current + } else { + mediaStreamTransition = .immediate + liveChat = ComponentView() + self.liveChat = liveChat + } + + let itemSetContainerInsets = UIEdgeInsets(top: component.statusBarHeight + 5.0, left: 0.0, bottom: 0.0, right: 0.0) + let itemSetContainerSafeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 20.0, right: 0.0) + + let _ = liveChat.update( + transition: mediaStreamTransition, + component: AnyComponent(StoryItemSetContainerComponent( + context: component.context, + externalState: self.externalState, + storyItemSharedState: self.storyItemSharedState, + availableReactions: nil, + slice: slice, + theme: defaultDarkColorPresentationTheme, + strings: component.strings, + containerInsets: itemSetContainerInsets, + safeInsets: itemSetContainerSafeInsets, + statusBarHeight: component.statusBarHeight, + inputHeight: component.inputHeight, + metrics: component.metrics, + deviceMetrics: component.deviceMetrics, + isEmbeddedInCamera: true, + isProgressPaused: false, + isAudioMuted: false, + audioMode: .off, + hideUI: false, + visibilityFraction: 1.0, + isPanning: false, + pinchState: nil, + presentController: { c, a in + // guard let self, let environment = self.environment else { + // return + // } + // if c is UndoOverlayController || c is TooltipScreen { + // environment.controller()?.present(c, in: .current) + // } else { + // environment.controller()?.present(c, in: .window(.root), with: a) + // } + }, + presentInGlobalOverlay: { c, a in + // guard let self, let environment = self.environment else { + // return + // } + // environment.controller()?.presentInGlobalOverlay(c, with: a) + }, + close: { + // guard let self, let environment = self.environment else { + // return + // } + // environment.controller()?.dismiss() + }, + navigate: { _ in + }, + delete: { + }, + markAsSeen: { _ in + }, + reorder: { + }, + createToFolder: { _, _ in + }, + addToFolder: { _ in + }, + controller: { + return nil //self?.environment?.controller() + }, + toggleAmbientMode: { + }, + keyboardInputData: self.inputMediaNodeDataPromise.get(), + closeFriends: self.closeFriendsPromise, + blockedPeers: self.blockedPeers, + sharedViewListsContext: StoryItemSetViewListComponent.SharedListsContext(), + stealthModeTimeout: nil, + isDismissed: false + )), + environment: {}, + containerSize: availableSize + ) + let liveChatFrame = CGRect(origin: CGPoint(), size: availableSize) + if let liveChatView = liveChat.view as? StoryItemSetContainerComponent.View { + if liveChatView.superview == nil { + liveChatView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + + liveChat.parentState = state + self.addSubview(liveChatView) + } + mediaStreamTransition.setFrame(view: liveChatView, frame: liveChatFrame) + + if let mediaStreamCall = liveChatView.mediaStreamCall { + component.didSetupMediaStream(mediaStreamCall) + } + } + } + } else { + if let liveChat = self.liveChat { + self.liveChat = nil + liveChat.view?.removeFromSuperview() + } + } + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index edeaa6e288..aac00afa46 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -25,6 +25,10 @@ import DeviceAccess import MediaAssetsContext import UndoUI import MetalEngine +import ShareWithPeersScreen +import TelegramVoip +import TelegramCallsUI +import GlassBarButtonComponent let videoRedColor = UIColor(rgb: 0xff3b30) let collageGrids: [Camera.CollageGrid] = [ @@ -43,6 +47,7 @@ let collageGrids: [Camera.CollageGrid] = [ enum CameraMode: Equatable { case photo case video + case live } struct CameraState: Equatable { @@ -80,49 +85,54 @@ struct CameraState: Equatable { let isCollageEnabled: Bool let collageGrid: Camera.CollageGrid let collageProgress: Float + let isStreaming: Bool func updatedMode(_ mode: CameraMode) -> CameraState { - return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedPosition(_ position: Camera.Position) -> CameraState { - return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedFlashMode(_ flashMode: Camera.FlashMode) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedFlashTint(_ flashTint: FlashTint) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedFlashTintSize(_ size: CGFloat) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: size, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: size, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedRecording(_ recording: Recording) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedDuration(_ duration: Double) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedIsDualCameraEnabled(_ isDualCameraEnabled: Bool) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedIsCollageEnabled(_ isCollageEnabled: Bool) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedCollageGrid(_ collageGrid: Camera.CollageGrid) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: collageGrid, collageProgress: self.collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: collageGrid, collageProgress: self.collageProgress, isStreaming: self.isStreaming) } func updatedCollageProgress(_ collageProgress: Float) -> CameraState { - return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress) + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress, isStreaming: self.isStreaming) + } + + func updatedIsStreaming(_ isStreaming: Bool) -> CameraState { + return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: isStreaming) } } @@ -236,6 +246,7 @@ private final class CameraScreenComponent: CombinedComponent { case cancel case flip case flashImage + case topGradient } private var cachedImages: [ImageKey: UIImage] = [:] func image(_ key: ImageKey) -> UIImage { @@ -260,6 +271,12 @@ private final class CameraScreenComponent: CombinedComponent { let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0 - 10.0) context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: size.width, options: .drawsAfterEndLocation) })!.withRenderingMode(.alwaysTemplate) + case .topGradient: + if let gradientImage = UIImage(named: "Stories/PanelGradient"), let cgImage = gradientImage.cgImage { + image = UIImage(cgImage: cgImage, scale: 0.0, orientation: .down).stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(gradientImage.size.height - 1.0)) + } else { + image = UIImage() + } } cachedImages[key] = image return image @@ -334,6 +351,7 @@ private final class CameraScreenComponent: CombinedComponent { deinit { self.lastGalleryAssetsDisposable?.dispose() self.resultDisposable.dispose() + self.liveStreamVideoDisposable?.dispose() } func setupRecentAssetSubscription() { @@ -441,6 +459,8 @@ private final class CameraScreenComponent: CombinedComponent { self.takePhoto() case .video: self.startVideoRecording(pressing: false) + case .live: + break } } else { if self.isPressingButton, case .handsFree = controller.cameraState.recording { @@ -853,6 +873,46 @@ private final class CameraScreenComponent: CombinedComponent { controller.updateCameraState({ $0.updatedRecording(.handsFree) }, transition: .spring(duration: 0.4)) } + private(set) var liveStreamStory: EngineStoryItem? + func startLiveStream() { + guard let controller = self.getController() else { + return + } + + let _ = (self.context.engine.messages.beginStoryLivestream(peerId: self.context.account.peerId, privacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []), isForwardingDisabled: false) + |> deliverOnMainQueue).start(next: { [weak self, weak controller] story in + guard let self else { + return + } + self.liveStreamStory = story + controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) + self.updated(transition: .immediate) + }) + } + + private var liveStreamVideoCapturer: OngoingCallVideoCapturer? + private var liveStreamVideoDisposable: Disposable? + func setupStreamCamera(call: PresentationGroupCall) { + guard self.liveStreamVideoCapturer == nil, let call = call as? PresentationGroupCallImpl, let controller = self.getController() else { + return + } + let cameraVideoSource = controller.node.cameraVideoSource + let videoCapturer = OngoingCallVideoCapturer(keepLandscape: false, isCustom: true) + self.liveStreamVideoCapturer = videoCapturer + + self.liveStreamVideoDisposable = cameraVideoSource.addOnUpdated { [weak self, weak cameraVideoSource] in + guard let self, let cameraVideoSource, let videoCapturer = self.liveStreamVideoCapturer else { + return + } + if let currentOutput = cameraVideoSource.currentOutput, let pixelBuffer = currentOutput.dataBuffer.pixelBuffer, let sampleBuffer = sampleBufferFromPixelBuffer(pixelBuffer: pixelBuffer) { + videoCapturer.injectSampleBuffer(sampleBuffer, rotation: .up, completion: {}) + } + } + Queue.mainQueue().after(1.0) { + call.requestVideo(capturer: videoCapturer, useFrontCamera: false) + } + } + func updateZoom(fraction: CGFloat) { guard let camera = self.getController()?.camera else { return @@ -875,9 +935,11 @@ private final class CameraScreenComponent: CombinedComponent { } static var body: Body { + let topGradient = Child(Image.self) let placeholder = Child(PlaceholderComponent.self) let frontFlash = Child(Image.self) let cancelButton = Child(CameraButton.self) + let endStreamButton = Child(GlassBarButtonComponent.self) let captureControls = Child(CaptureControlsComponent.self) let zoomControl = Child(ZoomComponent.self) let flashButton = Child(CameraButton.self) @@ -889,9 +951,10 @@ private final class CameraScreenComponent: CombinedComponent { let modeControl = Child(ModeComponent.self) let hintLabel = Child(HintLabelComponent.self) let flashTintControl = Child(FlashTintControlComponent.self) - let timeBackground = Child(RoundedRectangle.self) let timeLabel = Child(MultilineTextComponent.self) + + let liveStream = Child(CameraLiveStreamComponent.self) return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value @@ -1030,6 +1093,8 @@ private final class CameraScreenComponent: CombinedComponent { shutterState = .generic case .video: shutterState = .video + case .live: + shutterState = .live(active: component.cameraState.isStreaming) } } } @@ -1064,10 +1129,13 @@ private final class CameraScreenComponent: CombinedComponent { return } if case .none = cameraState.recording { - if cameraState.mode == .photo { + switch cameraState.mode { + case .photo: state.takePhoto() - } else if cameraState.mode == .video { + case .video: state.startVideoRecording(pressing: false) + case .live: + state.startLiveStream() } } else { state.stopVideoRecording() @@ -1105,6 +1173,12 @@ private final class CameraScreenComponent: CombinedComponent { controller.presentGallery() } }, + settingsTapped: { + guard let controller = environment.controller() as? CameraScreenImpl else { + return + } + controller.presentLiveSettings() + }, swipeHintUpdated: { [weak state] hint in if let state { state.updateSwipeHint(hint) @@ -1132,9 +1206,72 @@ private final class CameraScreenComponent: CombinedComponent { .position(captureControlsPosition) ) + if component.cameraState.mode == .live { + let topGradient = topGradient.update( + component: Image(image: state.image(.topGradient)), + availableSize: CGSize(width: availableSize.width, height: 90.0), + transition: context.transition + ) + context.add(topGradient + .position(CGPoint(x: availableSize.width * 0.5, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top) + topGradient.size.height / 2.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } + + if component.cameraState.mode == .live, component.cameraState.isStreaming { + let liveStream = liveStream.update( + component: CameraLiveStreamComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + peerId: component.context.account.peerId, + story: state.liveStreamStory, + statusBarHeight: environment.statusBarHeight, + inputHeight: environment.inputHeight, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + didSetupMediaStream: { [weak state] call in + state?.setupStreamCamera(call: call) + } + ), + availableSize: availableSize, + transition: context.transition + ) + context.add(liveStream + .position(CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) + ) + } + var flashButtonPosition: CGPoint? - let topControlInset: CGFloat = 20.0 - if case .none = component.cameraState.recording, !state.isTransitioning { + let topControlSideInset: CGFloat = 9.0 + let topControlVerticalInset: CGFloat = 12.0 + let topButtonSpacing: CGFloat = 15.0 + + if component.cameraState.isStreaming { + let endStreamButton = endStreamButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 56.0, height: 40.0), + backgroundColor: UIColor.black.withAlphaComponent(0.5), + isDark: true, + state: .glass, + component: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "End", font: Font.semibold(17.0), color: .white))), + action: { _ in + guard let controller = controller() as? CameraScreenImpl else { + return + } + controller.requestDismiss(animated: true) + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: .immediate + ) + context.add(endStreamButton + .position(CGPoint(x: availableSize.width - topControlSideInset - 3.0 - endStreamButton.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + endStreamButton.size.height / 2.0)) + .appear(.default(scale: true)) + .disappear(.default(scale: true)) + ) + } else if case .none = component.cameraState.recording, !state.isTransitioning { if !state.displayingCollageSelection { let cancelButton = cancelButton.update( component: CameraButton( @@ -1159,7 +1296,7 @@ private final class CameraScreenComponent: CombinedComponent { transition: .immediate ) context.add(cancelButton - .position(CGPoint(x: isTablet ? smallPanelWidth / 2.0 : topControlInset + cancelButton.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + cancelButton.size.height / 2.0)) + .position(CGPoint(x: isTablet ? smallPanelWidth / 2.0 : availableSize.width - topControlSideInset - cancelButton.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + cancelButton.size.height / 2.0)) .appear(.default(scale: true)) .disappear(.default(scale: true)) .shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero)) @@ -1217,7 +1354,9 @@ private final class CameraScreenComponent: CombinedComponent { if hasAllRequiredAccess { let rightMostButtonWidth: CGFloat - if state.displayingCollageSelection { + if component.cameraState.mode == .live { + rightMostButtonWidth = -55.0 + } else if state.displayingCollageSelection { let disableCollageButton = disableCollageButton.update( component: CameraButton( content: AnyComponentWithIdentity( @@ -1241,7 +1380,7 @@ private final class CameraScreenComponent: CombinedComponent { transition: .immediate ) context.add(disableCollageButton - .position(CGPoint(x: availableSize.width - topControlInset - disableCollageButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + disableCollageButton.size.height / 2.0 + 2.0)) + .position(CGPoint(x: availableSize.width - topControlSideInset - disableCollageButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + disableCollageButton.size.height / 2.0 + 2.0)) .appear(.default(scale: true)) .disappear(.default(scale: true)) .shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero)) @@ -1268,7 +1407,7 @@ private final class CameraScreenComponent: CombinedComponent { transition: .immediate ) - let position = CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlInset - flashButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + flashButton.size.height / 2.0) + let position = CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlSideInset - flashButton.size.width / 2.0 - 50.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + flashButton.size.height / 2.0) flashButtonPosition = position context.add(flashButton .position(position) @@ -1280,7 +1419,7 @@ private final class CameraScreenComponent: CombinedComponent { } if !isSticker && !isAvatar && !isTablet { - var nextButtonX = availableSize.width - topControlInset - rightMostButtonWidth / 2.0 - 58.0 + var nextButtonX = availableSize.width - topControlSideInset - rightMostButtonWidth / 2.0 - 100.0 if Camera.isDualCameraSupported(forRoundVideo: false) && !component.cameraState.isCollageEnabled { let dualButton = dualButton.update( component: CameraButton( @@ -1303,75 +1442,79 @@ private final class CameraScreenComponent: CombinedComponent { transition: .immediate ) context.add(dualButton - .position(CGPoint(x: nextButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + dualButton.size.height / 2.0 + 2.0)) + .position(CGPoint(x: nextButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + dualButton.size.height / 2.0 + 2.0)) .appear(.default(scale: true)) .disappear(.default(scale: true)) .shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero)) ) - nextButtonX -= dualButton.size.width + 16.0 + nextButtonX -= dualButton.size.width + topButtonSpacing } - let collageButton = collageButton.update( - component: CameraButton( - content: AnyComponentWithIdentity( - id: "collage", - component: AnyComponent( - CollageIconComponent( - grid: component.cameraState.collageGrid, - crossed: false, - isSelected: component.cameraState.isCollageEnabled, - tintColor: controlsTintColor + if component.cameraState.mode == .live { + + } else { + let collageButton = collageButton.update( + component: CameraButton( + content: AnyComponentWithIdentity( + id: "collage", + component: AnyComponent( + CollageIconComponent( + grid: component.cameraState.collageGrid, + crossed: false, + isSelected: component.cameraState.isCollageEnabled, + tintColor: controlsTintColor + ) ) - ) - ), - action: { [weak state] in - if let state { - state.toggleCollageCamera() + ), + action: { [weak state] in + if let state { + state.toggleCollageCamera() + } } - } - ).tagged(collageButtonTag), - availableSize: CGSize(width: 40.0, height: 40.0), - transition: .immediate - ) - var collageButtonX = nextButtonX - if rightMostButtonWidth.isZero { - collageButtonX = availableSize.width - topControlInset - collageButton.size.width / 2.0 - 5.0 - } - context.add(collageButton - .position(CGPoint(x: collageButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageButton.size.height / 2.0 + 2.0)) - .appear(.default(scale: true)) - .disappear(.default(scale: true)) - .shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero)) - ) - nextButtonX -= collageButton.size.width - - if state.displayingCollageSelection { - let collageCarousel = collageCarousel.update( - component: CollageIconCarouselComponent( - grids: collageGrids.filter { $0 != component.cameraState.collageGrid }, - selected: { [weak state] grid in - state?.updateCollageGrid(grid) - } - ), - availableSize: CGSize(width: nextButtonX + 4.0, height: 40.0), + ).tagged(collageButtonTag), + availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) - context.add(collageCarousel - .position(CGPoint(x: collageCarousel.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + collageCarousel.size.height / 2.0 + 2.0)) - .appear(ComponentTransition.Appear({ _, view, transition in - if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate { - view.animateIn() - } - })) - .disappear(ComponentTransition.Disappear({ view, transition, completion in - if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate { - view.animateOut(completion: completion) - } else { - completion() - } - })) + var collageButtonX = nextButtonX + if rightMostButtonWidth.isZero { + collageButtonX = availableSize.width - topControlSideInset - collageButton.size.width / 2.0 - 5.0 + } + context.add(collageButton + .position(CGPoint(x: collageButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + collageButton.size.height / 2.0 + 2.0)) + .appear(.default(scale: true)) + .disappear(.default(scale: true)) + .shadow(Shadow(color: UIColor(white: 0.0, alpha: 0.25), radius: 3.0, offset: .zero)) ) + nextButtonX -= collageButton.size.width + + if state.displayingCollageSelection { + let collageCarousel = collageCarousel.update( + component: CollageIconCarouselComponent( + grids: collageGrids.filter { $0 != component.cameraState.collageGrid }, + selected: { [weak state] grid in + state?.updateCollageGrid(grid) + } + ), + availableSize: CGSize(width: nextButtonX + 4.0, height: 40.0), + transition: .immediate + ) + context.add(collageCarousel + .position(CGPoint(x: collageCarousel.size.width / 2.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + collageCarousel.size.height / 2.0 + 2.0)) + .appear(ComponentTransition.Appear({ _, view, transition in + if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate { + view.animateIn() + } + })) + .disappear(ComponentTransition.Disappear({ view, transition, completion in + if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate { + view.animateOut(completion: completion) + } else { + completion() + } + })) + ) + } } } } @@ -1407,13 +1550,7 @@ private final class CameraScreenComponent: CombinedComponent { ) } - var isVideoRecording = false - if case .video = component.cameraState.mode { - isVideoRecording = true - } else if component.cameraState.recording != .none { - isVideoRecording = true - } - + let isVideoRecording = component.cameraState.recording != .none if isVideoRecording && !state.isTransitioning && !state.displayingCollageSelection { let duration = Int(component.cameraState.duration) let durationString = String(format: "%02d:%02d", (duration / 60) % 60, duration % 60) @@ -1431,7 +1568,7 @@ private final class CameraScreenComponent: CombinedComponent { if isTablet { timePosition = CGPoint(x: availableSize.width - panelWidth / 2.0, y: availableSize.height / 2.0 - 97.0) } else { - timePosition = CGPoint(x: availableSize.width / 2.0, y: max(environment.statusBarHeight + 5.0 + 20.0, environment.safeInsets.top + topControlInset + 20.0)) + timePosition = CGPoint(x: availableSize.width / 2.0, y: max(environment.statusBarHeight + 5.0 + 20.0, environment.safeInsets.top + topControlVerticalInset + 20.0)) } if component.cameraState.recording != .none { @@ -1485,19 +1622,27 @@ private final class CameraScreenComponent: CombinedComponent { } } - if !isSticker, case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne { + if !isSticker, case .none = component.cameraState.recording, !component.cameraState.isStreaming && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne { let availableModeControlSize: CGSize if isTablet { availableModeControlSize = CGSize(width: panelWidth, height: 120.0) } else { availableModeControlSize = availableSize } + + let availableModes: [CameraMode] + #if DEBUG + availableModes = [.photo, .video, .live] + #else + availableModes = [.photo, .video] + #endif + let modeControl = modeControl.update( component: ModeComponent( isTablet: isTablet, strings: environment.strings, tintColor: controlsTintColor, - availableModes: [.photo, .video], + availableModes: availableModes, currentMode: component.cameraState.mode, updatedMode: { [weak state] mode in if let state { @@ -1513,7 +1658,7 @@ private final class CameraScreenComponent: CombinedComponent { if isTablet { modeControlPosition = CGPoint(x: availableSize.width - panelWidth / 2.0, y: availableSize.height / 2.0 + modeControl.size.height + 26.0) } else { - modeControlPosition = CGPoint(x: availableSize.width / 2.0, y: availableSize.height - environment.safeInsets.bottom + modeControl.size.height / 2.0 + controlsBottomInset) + modeControlPosition = CGPoint(x: availableSize.width / 2.0, y: availableSize.height - environment.safeInsets.bottom + modeControl.size.height / 2.0 + controlsBottomInset + 16.0) } context.add(modeControl .clipsToBounds(true) @@ -1548,7 +1693,7 @@ private final class CameraScreenComponent: CombinedComponent { .disappear(.default(alpha: true)) ) } - + return availableSize } } @@ -1770,6 +1915,18 @@ public class CameraScreenImpl: ViewController, CameraScreen { private let completion = ActionSlot>() + private var _cameraVideoSource: CameraVideoSource? + var cameraVideoSource: CameraVideoSource { + if let current = self._cameraVideoSource { + return current + } else { + let cameraVideoSource = CameraVideoSource() + self.camera?.setVideoOutput(cameraVideoSource.cameraVideoOutput) + self._cameraVideoSource = cameraVideoSource + return cameraVideoSource + } + } + var cameraState: CameraState { didSet { let previousPosition = oldValue.position @@ -1900,7 +2057,8 @@ public class CameraScreenImpl: ViewController, CameraScreen { isDualCameraEnabled: isDualCameraEnabled, isCollageEnabled: false, collageGrid: collageGrids[6], - collageProgress: 0.0 + collageProgress: 0.0, + isStreaming: false ) self.previewFrameLeftDimView = UIView() @@ -3161,7 +3319,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { if let current = self.collageView { collageView = current } else { - collageView = CameraCollageView(context: self.context, collage: collage, camera: self.camera, cameraContainerView: self.mainPreviewContainerView) + collageView = CameraCollageView(context: self.context, collage: collage, cameraVideoSource: self.cameraVideoSource, cameraContainerView: self.mainPreviewContainerView) collageView.getOverlayViews = { [weak self] in guard let self, let view = self.componentHost.view else { return [] @@ -3809,6 +3967,41 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.requestLayout(transition: .immediate) } + func presentLiveSettings() { + let stateContext = ShareWithPeersScreen.StateContext( + context: self.context, + subject: .stories(editing: false, count: 1) + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + guard let self else { + return + } + let controller = ShareWithPeersScreen( + context: self.context, + initialPrivacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), + stateContext: stateContext, + completion: { sendAsPeerId, privacy, allowScreenshots, pin, _, folders, completed in + + }, + editCategory: { privacy, allowScreenshots, pin, folders in + }, + editBlockedPeers: { privacy, allowScreenshots, pin, folders in + + } + ) +// controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in +// if let self, let controller { +// let transitionFactor = controller.modalStyleOverlayTransitionFactor +// self.node.updateModalTransitionFactor(transitionFactor, transition: transition) +// } +// } +// controller.dismissed = { +// self.node.mediaEditor?.play() +// } + self.push(controller) + }) + } + public func presentDraftTooltip() { self.node.presentDraftTooltip() } @@ -4134,3 +4327,36 @@ private func generateAddLabel(strings: PresentationStrings, color: UIColor) -> U UIGraphicsPopContext() }) } + +private func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { + var maybeFormat: CMVideoFormatDescription? + let status = CMVideoFormatDescriptionCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescriptionOut: &maybeFormat) + if status != noErr { + return nil + } + guard let format = maybeFormat else { + return nil + } + + var timingInfo = CMSampleTimingInfo( + duration: CMTimeMake(value: 1, timescale: 30), + presentationTimeStamp: CMTimeMake(value: 0, timescale: 30), + decodeTimeStamp: CMTimeMake(value: 0, timescale: 30) + ) + + var maybeSampleBuffer: CMSampleBuffer? + let bufferStatus = CMSampleBufferCreateReadyWithImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, formatDescription: format, sampleTiming: &timingInfo, sampleBufferOut: &maybeSampleBuffer) + + if (bufferStatus != noErr) { + return nil + } + guard let sampleBuffer = maybeSampleBuffer else { + return nil + } + + let attachments: NSArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: true)! as NSArray + let dict: NSMutableDictionary = attachments[0] as! NSMutableDictionary + dict[kCMSampleAttachmentKey_DisplayImmediately as NSString] = true as NSNumber + + return sampleBuffer +} diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift index cefb8dd66d..92bf55b9cd 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift @@ -18,7 +18,7 @@ final class CameraVideoSource: VideoSource { public var sourceId: Int = 0 public var sizeMultiplicator: CGPoint = CGPoint(x: 1.0, y: 1.0) - public init?() { + public init() { self.device = MetalEngine.shared.device self.cameraVideoOutput = CameraVideoOutput(sink: { [weak self] buffer, mirror in diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 841d286538..756d177aea 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -9,6 +9,9 @@ import LocalMediaResources import CameraButtonComponent import UIKitRuntimeUtils import AccountContext +import GlassBackgroundComponent +import GlassBarButtonComponent +import BundleIconComponent enum ShutterButtonState: Equatable { case disabled @@ -17,6 +20,7 @@ enum ShutterButtonState: Equatable { case stopRecording case holdRecording(progress: Float) case transition + case live(active: Bool) } private let maximumShutterSize = CGSize(width: 96.0, height: 96.0) @@ -95,18 +99,25 @@ private final class ShutterButtonContentComponent: Component { final class View: UIView { private var component: ShutterButtonContentComponent? + private let backgroundView = BlurredBackgroundView(color: UIColor(rgb: 0x222222, alpha: 0.3)) + private let underRingLayer = SimpleShapeLayer() private let ringLayer = SimpleShapeLayer() var blobView: ShutterBlobView? private let innerLayer = SimpleShapeLayer() private let progressLayer = SimpleShapeLayer() + private let chromeView = UIImageView() + private let label = ComponentView() + private let checkLayer = SimpleLayer() private let checkLayerMask = SimpleShapeLayer() private let checkLayerLineMask = SimpleShapeLayer() init() { super.init(frame: CGRect()) + + self.addSubview(self.backgroundView) self.layer.allowsGroupOpacity = true @@ -139,9 +150,12 @@ private final class ShutterButtonContentComponent: Component { self.checkLayer.isHidden = true self.layer.addSublayer(self.innerLayer) - self.layer.addSublayer(self.underRingLayer) - self.layer.addSublayer(self.ringLayer) + //self.layer.addSublayer(self.underRingLayer) + //self.layer.addSublayer(self.ringLayer) self.layer.addSublayer(self.progressLayer) + + self.chromeView.alpha = 0.9 + self.chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: false, fillColor: .clear) } required init?(coder aDecoder: NSCoder) { @@ -152,11 +166,26 @@ private final class ShutterButtonContentComponent: Component { guard let blobView = self.blobView, let component = self.component else { return } - let scale: CGFloat = isHighlighted ? 0.8 : 1.0 - let transition = ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)) - transition.setTransform(view: blobView, transform: CATransform3DMakeScale(scale, scale, 1.0)) - if component.collageProgress > 1.0 - .ulpOfOne { - transition.setTransform(layer: self.ringLayer, transform: CATransform3DMakeScale(scale, scale, 1.0)) + + if case .live = component.blobState { + let transition = ComponentTransition(animation: .curve(duration: isHighlighted ? 0.25 : 0.35, curve: .spring)) + let scale: CGFloat = isHighlighted ? 1.05 : 1.0 + transition.setScale(view: blobView, scale: scale) + transition.setScale(view: self.chromeView, scale: scale) + if let labelView = self.label.view { + transition.setScale(view: labelView, scale: scale) + } + } else { + let scale: CGFloat = isHighlighted ? 0.8 : 1.0 + let transition = ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)) + transition.setTransform(view: blobView, transform: CATransform3DMakeScale(scale, scale, 1.0)) + transition.setTransform(view: self.chromeView, transform: CATransform3DMakeScale(scale, scale, 1.0)) + if let labelView = self.label.view { + transition.setTransform(view: labelView, transform: CATransform3DMakeScale(scale, scale, 1.0)) + } + if component.collageProgress > 1.0 - .ulpOfOne { + transition.setTransform(layer: self.ringLayer, transform: CATransform3DMakeScale(scale, scale, 1.0)) + } } } @@ -166,7 +195,8 @@ private final class ShutterButtonContentComponent: Component { if component.hasAppeared && self.blobView == nil { self.blobView = ShutterBlobView(test: false) - self.addSubview(self.blobView!) + self.insertSubview(self.blobView!, aboveSubview: self.backgroundView) + self.insertSubview(self.chromeView, aboveSubview: self.blobView!) self.layer.addSublayer(self.checkLayer) @@ -215,12 +245,35 @@ private final class ShutterButtonContentComponent: Component { } } } + + //TODO:localize + let labelSize = self.label.update( + transition: .immediate, + component: AnyComponent( + Text(text: "Start Live Stream", font: Font.semibold(17.0), color: .white) + ), + environment: {}, + containerSize: availableSize + ) + let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((maximumShutterSize.width - labelSize.width) / 2.0), y: floorToScreenPixels((maximumShutterSize.height - labelSize.height) / 2.0)), size: labelSize) + if let labelView = self.label.view { + if labelView.superview == nil { + labelView.alpha = 0.0 + labelView.isUserInteractionEnabled = false + self.addSubview(labelView) + } + labelView.frame = labelFrame + } var innerColor: UIColor let innerSize: CGSize var ringSize: CGSize var ringWidth: CGFloat = 3.0 var recordingProgress: Float? + var glassAlpha: CGFloat = 1.0 + var chromeAlpha: CGFloat = 0.0 + var chromeSize = CGSize(width: 60.0, height: 60.0) + var labelAlpha: CGFloat = 0.0 switch component.shutterState { case .generic, .disabled: innerColor = component.tintColor @@ -244,6 +297,14 @@ private final class ShutterButtonContentComponent: Component { innerSize = CGSize(width: 60.0, height: 60.0) ringSize = CGSize(width: 68.0, height: 68.0) recordingProgress = 0.0 + case .live: + innerColor = UIColor(rgb: 0xff375f) + innerSize = CGSize(width: 52.0, height: 52.0) + ringSize = CGSize(width: 60.0, height: 60.0) + glassAlpha = 0.0 + chromeAlpha = 0.65 + labelAlpha = 1.0 + chromeSize = CGSize(width: 326.0, height: 53.0 - UIScreenPixel) } if component.collageProgress > 1.0 - .ulpOfOne { @@ -255,6 +316,23 @@ private final class ShutterButtonContentComponent: Component { ringWidth = 5.0 } + transition.setAlpha(view: self.backgroundView, alpha: glassAlpha) + transition.setAlpha(view: self.chromeView, alpha: chromeAlpha) + + if let labelView = self.label.view { + if labelAlpha != labelView.alpha { + if labelAlpha > 0.0 { + transition.animateBlur(layer: labelView.layer, fromRadius: 10.0, toRadius: 0.0) + } else { + transition.animateBlur(layer: labelView.layer, fromRadius: 0.0, toRadius: 10.0) + } + } + transition.setAlpha(view: labelView, alpha: labelAlpha) + } + + let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((maximumShutterSize.width - chromeSize.width) / 2.0), y: floorToScreenPixels((maximumShutterSize.height - chromeSize.height) / 2.0)), size: chromeSize) + transition.setFrame(view: self.chromeView, frame: buttonFrame) + if component.collageProgress > 1.0 - .ulpOfOne { self.blobView?.isHidden = true self.checkLayer.isHidden = false @@ -341,6 +419,10 @@ private final class ShutterButtonContentComponent: Component { self.progressLayer.strokeEnd = CGFloat(recordingProgress ?? 0.0) * totalProgress self.progressLayer.animateStrokeEnd(from: previousValue, to: self.progressLayer.strokeEnd, duration: 0.33) + let backgroundFrame = buttonFrame.insetBy(dx: -6.0, dy: -6.0) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + return maximumShutterSize } } @@ -583,6 +665,7 @@ final class CaptureControlsComponent: Component { let lockRecording: () -> Void let flipTapped: () -> Void let galleryTapped: () -> Void + let settingsTapped: () -> Void let swipeHintUpdated: (SwipeHint) -> Void let zoomUpdated: (CGFloat) -> Void let flipAnimationAction: ActionSlot @@ -610,6 +693,7 @@ final class CaptureControlsComponent: Component { lockRecording: @escaping () -> Void, flipTapped: @escaping () -> Void, galleryTapped: @escaping () -> Void, + settingsTapped: @escaping () -> Void, swipeHintUpdated: @escaping (SwipeHint) -> Void, zoomUpdated: @escaping (CGFloat) -> Void, flipAnimationAction: ActionSlot, @@ -636,6 +720,7 @@ final class CaptureControlsComponent: Component { self.lockRecording = lockRecording self.flipTapped = flipTapped self.galleryTapped = galleryTapped + self.settingsTapped = settingsTapped self.swipeHintUpdated = swipeHintUpdated self.zoomUpdated = zoomUpdated self.flipAnimationAction = flipAnimationAction @@ -738,9 +823,16 @@ final class CaptureControlsComponent: Component { private let zoomView = ComponentView() private let lockView = ComponentView() private let galleryButtonView = ComponentView() + private var galleryButtonChromeView = UIImageView() + private let shutterButtonView = ComponentView() + private let flipButtonView = ComponentView() + private let bottomContainerView = GlassBackgroundContainerView() + private let bottomSettingsButton = ComponentView() + private let bottomFlipButton = ComponentView() + private let leftGuide = SimpleLayer() private let rightGuide = SimpleLayer() @@ -756,6 +848,9 @@ final class CaptureControlsComponent: Component { private var wasBanding: Bool? private var panBlobState: ShutterBlobView.BlobState? + private var panGestureRecognizer: UIPanGestureRecognizer? + private var pressGestureRecognizer: UILongPressGestureRecognizer? + private let hapticFeedback = HapticFeedback() public func matches(tag: Any) -> Bool { @@ -776,6 +871,12 @@ final class CaptureControlsComponent: Component { self.layer.addSublayer(self.leftGuide) self.layer.addSublayer(self.rightGuide) + + self.addSubview(self.bottomContainerView) + + self.galleryButtonChromeView.layer.compositingFilter = "overlayBlendMode" + self.galleryButtonChromeView.alpha = 0.8 + self.galleryButtonChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 48.0, height: 48.0), isDark: false, fillColor: .clear) } required init?(coder aDecoder: NSCoder) { @@ -1019,6 +1120,11 @@ final class CaptureControlsComponent: Component { transition.setScale(view: view, scale: 0.1) transition.setAlpha(view: view, alpha: 0.0) } + + transition.setAlpha(view: self.bottomContainerView, alpha: 0.0) + if let view = self.bottomFlipButton.view { + transition.setScale(view: view, scale: 0.1) + } } func animateInFromEditor(transition: ComponentTransition) { @@ -1046,6 +1152,11 @@ final class CaptureControlsComponent: Component { transition.setScale(view: view, scale: 1.0) transition.setAlpha(view: view, alpha: 1.0) } + + transition.setAlpha(view: self.bottomContainerView, alpha: 1.0) + if let view = self.bottomFlipButton.view { + transition.setScale(view: view, scale: 1.0) + } } func update(component: CaptureControlsComponent, state: State, availableSize: CGSize, transition: ComponentTransition) -> CGSize { @@ -1062,6 +1173,8 @@ final class CaptureControlsComponent: Component { var isTransitioning = false var isRecording = false var isHolding = false + var isLiveStream = false + var isLiveActive = false if case .stopRecording = component.shutterState { isRecording = true } else if case .holdRecording = component.shutterState { @@ -1069,6 +1182,9 @@ final class CaptureControlsComponent: Component { isHolding = true } else if case .transition = component.shutterState { isTransitioning = true + } else if case let .live(active) = component.shutterState { + isLiveStream = true + isLiveActive = active } let hideControls = component.hideControls @@ -1081,8 +1197,8 @@ final class CaptureControlsComponent: Component { gallerySize = CGSize(width: 72.0, height: 72.0) galleryCornerRadius = 16.0 } else { - gallerySize = CGSize(width: 50.0, height: 50.0) - galleryCornerRadius = 10.0 + gallerySize = CGSize(width: 48.0, height: 48.0) + galleryCornerRadius = 24.0 } let galleryButtonId: String if let (identifier, _) = state.cachedAssetImage, identifier == "" { @@ -1116,21 +1232,28 @@ final class CaptureControlsComponent: Component { if component.isTablet { galleryButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - galleryButtonSize.width) / 2.0), y: size.height - galleryButtonSize.height - 56.0), size: galleryButtonSize) } else { - galleryButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize) + if "".isEmpty { + galleryButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: size.height + 21.0), size: galleryButtonSize) + } else { + galleryButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize) + } } if let galleryButtonView = self.galleryButtonView.view as? CameraButton.View { galleryButtonView.contentView.clipsToBounds = true galleryButtonView.contentView.layer.cornerRadius = galleryCornerRadius if galleryButtonView.superview == nil { + galleryButtonView.contentView.addSubview(self.galleryButtonChromeView) self.addSubview(galleryButtonView) } transition.setBounds(view: galleryButtonView, bounds: CGRect(origin: .zero, size: galleryButtonFrame.size)) transition.setPosition(view: galleryButtonView, position: galleryButtonFrame.center) + self.galleryButtonChromeView.frame = CGRect(origin: .zero, size: galleryButtonSize) + let normalAlpha = component.tintColor.rgb == 0xffffff ? 1.0 : 0.6 - transition.setScale(view: galleryButtonView, scale: isRecording || isTransitioning || hideControls ? 0.1 : 1.0) - transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning || hideControls ? 0.0 : normalAlpha) + transition.setScale(view: galleryButtonView, scale: isLiveStream || isRecording || isTransitioning || hideControls ? 0.1 : 1.0) + transition.setAlpha(view: galleryButtonView, alpha: isLiveStream || isRecording || isTransitioning || hideControls ? 0.0 : normalAlpha) } } else { galleryButtonFrame = .zero @@ -1165,6 +1288,13 @@ final class CaptureControlsComponent: Component { containerSize: availableSize ) let flipButtonFrame = CGRect(origin: CGPoint(x: flipButtonOriginX, y: (size.height - flipButtonSize.height) / 2.0), size: flipButtonSize) + //if "".isEmpty { + // flipButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - flipButtonSize.width - 16.0, y: size.height + 21.0), size: flipButtonSize) + //} + + //self.flipButtonBackgroundView.update(size: CGSize(width: 48.0, height: 48.0), cornerRadius: 24.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.06)), transition: .immediate) + //self.flipButtonBackgroundView.frame = flipButtonFrame.insetBy(dx: -2.0, dy: -2.0).offsetBy(dx: 2.0, dy: 2.0) + if let flipButtonView = self.flipButtonView.view { if flipButtonView.superview == nil { self.addSubview(flipButtonView) @@ -1172,11 +1302,84 @@ final class CaptureControlsComponent: Component { transition.setBounds(view: flipButtonView, bounds: CGRect(origin: .zero, size: flipButtonFrame.size)) transition.setPosition(view: flipButtonView, position: flipButtonFrame.center) - transition.setScale(view: flipButtonView, scale: isTransitioning || hideControls ? 0.01 : 1.0) - transition.setAlpha(view: flipButtonView, alpha: isTransitioning || hideControls ? 0.0 : 1.0) + transition.setScale(view: flipButtonView, scale: !isRecording || isTransitioning || hideControls ? 0.01 : 1.0) + transition.setAlpha(view: flipButtonView, alpha: !isRecording || isTransitioning || hideControls ? 0.0 : 1.0) } - } else if let flipButtonView = self.flipButtonView.view { - flipButtonView.removeFromSuperview() + + self.bottomContainerView.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: availableSize.width, height: 21.0 + 64.0)) + + let bottomFlipButtonSize = self.bottomFlipButton.update( + transition: .immediate, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 48.0, height: 48.0), + backgroundColor: UIColor(rgb: 0x212121), + isDark: true, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "flip", component: AnyComponent( + FlipButtonContentComponent( + action: component.flipAnimationAction, + maskFrame: flipButtonMaskFrame, + tintColor: component.tintColor + ) + )), + action: { _ in + component.flipTapped() + } + ) + ), + environment: {}, + containerSize: availableSize + ) + let bottomFlipButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - bottomFlipButtonSize.width - 16.0, y: 21.0), size: bottomFlipButtonSize) + if let bottomFlipButtonView = self.bottomFlipButton.view { + if bottomFlipButtonView.superview == nil { + self.bottomContainerView.contentView.addSubview(bottomFlipButtonView) + } + transition.setBounds(view: bottomFlipButtonView, bounds: CGRect(origin: .zero, size: bottomFlipButtonFrame.size)) + transition.setPosition(view: bottomFlipButtonView, position: bottomFlipButtonFrame.center) + + transition.setScale(view: bottomFlipButtonView, scale: isRecording || isLiveActive || isTransitioning || hideControls ? 0.01 : 1.0) + transition.setAlpha(view: bottomFlipButtonView, alpha: isRecording || isLiveActive || isTransitioning || hideControls ? 0.0 : 1.0) + } + } else { + if let flipButtonView = self.flipButtonView.view { + flipButtonView.removeFromSuperview() + } + if let bottomFlipButtonView = self.bottomFlipButton.view { + bottomFlipButtonView.removeFromSuperview() + } + } + + let bottomSettingsButtonSize = self.bottomSettingsButton.update( + transition: .immediate, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 48.0, height: 48.0), + backgroundColor: UIColor(rgb: 0x212121), + isDark: true, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "settings", component: AnyComponent( + BundleIconComponent(name: "Camera/Settings", tintColor: .white) + )), + action: { _ in + component.settingsTapped() + } + ) + ), + environment: {}, + containerSize: availableSize + ) + let bottomFlipButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 21.0), size: bottomSettingsButtonSize) + if let bottomSettingsButtonView = self.bottomSettingsButton.view { + if bottomSettingsButtonView.superview == nil { + self.bottomContainerView.contentView.addSubview(bottomSettingsButtonView) + } + transition.setBounds(view: bottomSettingsButtonView, bounds: CGRect(origin: .zero, size: bottomFlipButtonFrame.size)) + transition.setPosition(view: bottomSettingsButtonView, position: bottomFlipButtonFrame.center) + + transition.setScale(view: bottomSettingsButtonView, scale: !isLiveStream || isLiveActive || isRecording || isTransitioning || hideControls ? 0.01 : 1.0) + transition.setAlpha(view: bottomSettingsButtonView, alpha: !isLiveStream || isLiveActive || isRecording || isTransitioning || hideControls ? 0.0 : 1.0) } var blobState: ShutterBlobView.BlobState @@ -1189,6 +1392,8 @@ final class CaptureControlsComponent: Component { blobState = .stopVideo case .holdRecording: blobState = self.panBlobState ?? .video + case .live: + blobState = .live } let shutterButtonSize = self.shutterButtonView.update( @@ -1384,11 +1589,13 @@ final class CaptureControlsComponent: Component { if !component.isSticker { let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) panGestureRecognizer.delegate = self + self.panGestureRecognizer = panGestureRecognizer shutterButtonView.addGestureRecognizer(panGestureRecognizer) let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePress(_:))) pressGestureRecognizer.minimumPressDuration = 0.3 pressGestureRecognizer.delegate = self + self.pressGestureRecognizer = pressGestureRecognizer shutterButtonView.addGestureRecognizer(pressGestureRecognizer) } self.addSubview(shutterButtonView) @@ -1396,12 +1603,15 @@ final class CaptureControlsComponent: Component { let alpha: CGFloat = component.hasAccess ? 1.0 : 0.3 transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size)) transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center) - transition.setScale(view: shutterButtonView, scale: isTransitioning ? 0.01 : 1.0) - transition.setAlpha(view: shutterButtonView, alpha: isTransitioning ? 0.0 : alpha) + transition.setScale(view: shutterButtonView, scale: isTransitioning || isLiveActive ? 0.01 : 1.0) + transition.setAlpha(view: shutterButtonView, alpha: isTransitioning || isLiveActive ? 0.0 : alpha) shutterButtonView.isUserInteractionEnabled = component.hasAccess } + self.panGestureRecognizer?.isEnabled = !isLiveStream + self.pressGestureRecognizer?.isEnabled = !isLiveStream + if let buttonView = self.flipButtonView.view as? CameraButton.View, let contentView = buttonView.contentView.componentView as? FlipButtonContentComponent.View { if contentView.maskContainerView.superview == nil { self.addSubview(contentView.maskContainerView) @@ -1423,6 +1633,16 @@ final class CaptureControlsComponent: Component { if let codeResultView = self.codeResultView?.view, codeResultView.frame.contains(point) { return codeResultView.hitTest(self.convert(point, to: codeResultView), with: event) } + if let galleryButtonView = self.galleryButtonView.view, galleryButtonView.alpha > 0.0, galleryButtonView.frame.contains(point) { + return galleryButtonView.hitTest(self.convert(point, to: galleryButtonView), with: event) + } + let bottomPoint = self.convert(point, to: self.bottomContainerView) + if let bottomFlipButtonView = self.bottomFlipButton.view, bottomFlipButtonView.alpha > 0.0, bottomFlipButtonView.frame.contains(bottomPoint) { + return bottomFlipButtonView.hitTest(self.convert(point, to: bottomFlipButtonView), with: event) + } + if let bottomSettingsButtonView = self.bottomSettingsButton.view, bottomSettingsButtonView.alpha > 0.0, bottomSettingsButtonView.frame.contains(bottomPoint) { + return bottomSettingsButtonView.hitTest(self.convert(point, to: bottomSettingsButtonView), with: event) + } return super.hitTest(point, with: event) } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift index cd4a2a6104..be75072bb3 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import MultilineTextComponent import TelegramPresentationData +import GlassBackgroundComponent extension CameraMode { func title(strings: PresentationStrings) -> String { @@ -12,11 +13,14 @@ extension CameraMode { return strings.Story_Camera_Photo case .video: return strings.Story_Camera_Video + case .live: + //TODO:localize + return "LIVE" } } } -private let buttonSize = CGSize(width: 55.0, height: 44.0) +private let buttonSize = CGSize(width: 55.0, height: 48.0) final class ModeComponent: Component { let isTablet: Bool @@ -88,21 +92,29 @@ final class ModeComponent: Component { self.pressed() } - func update(value: String, selected: Bool, tintColor: UIColor) { + func update(value: String, selected: Bool, tintColor: UIColor) -> CGSize { let accentColor: UIColor let normalColor: UIColor if tintColor.rgb == 0xffffff { - accentColor = UIColor(rgb: 0xf8d74a) + accentColor = UIColor(rgb: 0xffd300) normalColor = .white } else { accentColor = tintColor normalColor = tintColor.withAlphaComponent(0.5) } - self.setAttributedTitle(NSAttributedString(string: value.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: selected ? accentColor : normalColor, paragraphAlignment: .center), for: .normal) + + let title = NSMutableAttributedString(string: value.uppercased(), font: Font.with(size: 14.0, design: .regular, weight: .medium), textColor: selected ? accentColor : normalColor, paragraphAlignment: .center) + title.addAttribute(.kern, value: -0.5 as NSNumber, range: NSMakeRange(0, title.length)) + self.setAttributedTitle(title, for: .normal) + self.sizeToFit() + + return CGSize(width: self.titleLabel?.bounds.size.width ?? 0.0, height: buttonSize.height) } } - private var containerView = UIView() + private var backgroundView = UIView() + private var glassContainerView = GlassBackgroundContainerView() + private var selectionView = GlassBackgroundView() private var itemViews: [ItemView] = [] public func matches(tag: Any) -> Bool { @@ -118,9 +130,14 @@ final class ModeComponent: Component { init() { super.init(frame: CGRect()) + self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11) + self.backgroundView.layer.cornerRadius = 24.0 + self.layer.allowsGroupOpacity = true - self.addSubview(self.containerView) + self.addSubview(self.backgroundView) + self.backgroundView.addSubview(self.glassContainerView) + self.glassContainerView.contentView.addSubview(self.selectionView) } required init?(coder aDecoder: NSCoder) { @@ -131,15 +148,19 @@ final class ModeComponent: Component { func animateOutToEditor(transition: ComponentTransition) { self.animatedOut = true - transition.setAlpha(view: self.containerView, alpha: 0.0) - transition.setSublayerTransform(view: self.containerView, transform: CATransform3DMakeTranslation(0.0, -buttonSize.height, 0.0)) + transition.setAlpha(view: self.backgroundView, alpha: 0.0) + transition.setSublayerTransform(view: self, transform: CATransform3DMakeTranslation(0.0, -buttonSize.height, 0.0)) } func animateInFromEditor(transition: ComponentTransition) { self.animatedOut = false - transition.setAlpha(view: self.containerView, alpha: 1.0) - transition.setSublayerTransform(view: self.containerView, transform: CATransform3DIdentity) + transition.setAlpha(view: self.backgroundView, alpha: 1.0) + transition.setSublayerTransform(view: self, transform: CATransform3DIdentity) + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.backgroundView.frame.contains(point) } func update(component: ModeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { @@ -148,17 +169,18 @@ final class ModeComponent: Component { let isTablet = component.isTablet let updatedMode = component.updatedMode - let spacing: CGFloat = isTablet ? 9.0 : 14.0 + let inset: CGFloat = 23.0 + let spacing: CGFloat = isTablet ? 9.0 : 40.0 var i = 0 - var itemFrame = CGRect(origin: .zero, size: buttonSize) + var itemFrame = CGRect(origin: isTablet ? .zero : CGPoint(x: inset, y: 0.0), size: buttonSize) var selectedCenter = itemFrame.minX - - for mode in component.availableModes { + var selectedFrame = itemFrame + for mode in component.availableModes.reversed() { let itemView: ItemView if self.itemViews.count == i { itemView = ItemView() - self.containerView.addSubview(itemView) + self.backgroundView.addSubview(itemView) self.itemViews.append(itemView) } else { itemView = self.itemViews[i] @@ -167,8 +189,13 @@ final class ModeComponent: Component { updatedMode(mode) } - itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor) - itemView.bounds = CGRect(origin: .zero, size: itemFrame.size) + let itemSize = itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor) + itemView.bounds = CGRect(origin: .zero, size: itemSize) + itemFrame = CGRect(origin: itemFrame.origin, size: itemSize) + + if mode == component.currentMode { + selectedFrame = itemFrame + } if isTablet { itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY) @@ -181,9 +208,8 @@ final class ModeComponent: Component { if mode == component.currentMode { selectedCenter = itemFrame.midX } - itemFrame = itemFrame.offsetBy(dx: buttonSize.width + spacing, dy: 0.0) + itemFrame = itemFrame.offsetBy(dx: itemFrame.width + spacing, dy: 0.0) } - i += 1 } @@ -192,12 +218,18 @@ final class ModeComponent: Component { if isTablet { totalSize = CGSize(width: availableSize.width, height: buttonSize.height * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1)) size = CGSize(width: availableSize.width, height: availableSize.height) - transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height / 2.0 - selectedCenter), size: totalSize)) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height / 2.0 - selectedCenter), size: totalSize)) } else { size = CGSize(width: availableSize.width, height: buttonSize.height) - totalSize = CGSize(width: buttonSize.width * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1), height: buttonSize.height) - transition.setFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: availableSize.width / 2.0 - selectedCenter, y: 0.0), size: totalSize)) + totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalSize.width) / 2.0), y: 0.0), size: totalSize)) } + transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: .zero, size: self.backgroundView.frame.size)) + + let selectionFrame = selectedFrame.insetBy(dx: -20.0, dy: 3.0) + self.glassContainerView.alpha = 0.5 + self.selectionView.update(size: selectionFrame.size, cornerRadius: selectionFrame.height * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.16)), transition: transition) + transition.setFrame(view: self.selectionView, frame: selectionFrame) return size } @@ -284,7 +316,7 @@ final class HintLabelComponent: Component { view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textSize.width) / 2.0), y: 0.0), size: textSize) } - + return CGSize(width: availableSize.width, height: textSize.height) } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift index f04b2c3925..a8effb9727 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift @@ -36,13 +36,16 @@ final class ShutterBlobView: UIView { case lock case transientToFlip case stopVideo + case live - var primarySize: CGFloat { + var primarySize: CGSize { switch self { case .generic, .video, .transientToFlip: - return 0.63 + return CGSize(width: 0.63, height: 0.63) + case .live: + return CGSize(width: 3.4, height: 0.55) case .transientToLock, .lock, .stopVideo: - return 0.275 + return CGSize(width: 0.275, height: 0.275) } } @@ -54,6 +57,8 @@ final class ShutterBlobView: UIView { } else { return 0.0 } + case .live: + return 0.7 default: return 1.0 } @@ -63,6 +68,8 @@ final class ShutterBlobView: UIView { switch self { case .generic, .video, .transientToFlip: return 0.63 + case .live: + return 0.55 case .transientToLock, .lock, .stopVideo: return 0.185 } @@ -74,14 +81,14 @@ final class ShutterBlobView: UIView { return 0.335 case .lock: return 0.5 - case .stopVideo: + case .stopVideo, .live: return 0.0 } } var secondaryRedness: CGFloat { switch self { - case .generic, .lock, .transientToLock, .transientToFlip: + case .generic, .lock, .transientToLock, .transientToFlip, .live: return 0.0 default: return 1.0 @@ -94,7 +101,8 @@ final class ShutterBlobView: UIView { private var displayLink: SharedDisplayLinkDriver.Link? - private var primarySize = AnimatableProperty(value: 0.63) + private var primaryWidth = AnimatableProperty(value: 0.63) + private var primaryHeight = AnimatableProperty(value: 0.63) private var primaryOffsetX = AnimatableProperty(value: 0.0) private var primaryOffsetY = AnimatableProperty(value: 0.0) private var primaryRedness = AnimatableProperty(value: 0.0) @@ -174,7 +182,8 @@ final class ShutterBlobView: UIView { } self.state = state - self.primarySize.update(value: state.primarySize, transition: transition) + self.primaryWidth.update(value: state.primarySize.width, transition: transition) + self.primaryHeight.update(value: state.primarySize.height, transition: transition) self.primaryRedness.update(value: state.primaryRedness(tintColor: tintColor), transition: transition) self.primaryCornerRadius.update(value: state.primaryCornerRadius, transition: transition) self.secondarySize.update(value: state.secondarySize, transition: transition) @@ -225,7 +234,8 @@ final class ShutterBlobView: UIView { private func updateAnimations() { let properties = [ - self.primarySize, + self.primaryWidth, + self.primaryHeight, self.primaryOffsetX, self.primaryOffsetY, self.primaryRedness, @@ -303,8 +313,9 @@ final class ShutterBlobView: UIView { var resolution = simd_uint2(UInt32(drawableSize.width), UInt32(drawableSize.height)) renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) - var primaryParameters = simd_float3( - Float(self.primarySize.presentationValue), + var primaryParameters = simd_float4( + Float(self.primaryWidth.presentationValue), + Float(self.primaryHeight.presentationValue), Float(self.primaryRedness.presentationValue), Float(self.primaryCornerRadius.presentationValue) ) diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index 8a1da0c876..258b4baa9a 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -1560,10 +1560,10 @@ final class ComposeTodoScreenComponent: Component { } } - let edgeEffectHeight: CGFloat = 66.0 + let edgeEffectHeight: CGFloat = 80.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) let title: String if !component.initialData.canEdit && component.initialData.existingTodo != nil { diff --git a/submodules/TelegramUI/Components/ContentReportScreen/BUILD b/submodules/TelegramUI/Components/ContentReportScreen/BUILD index 24d0da15cc..4f552545f4 100644 --- a/submodules/TelegramUI/Components/ContentReportScreen/BUILD +++ b/submodules/TelegramUI/Components/ContentReportScreen/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/Components/BundleIconComponent", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/AppBundle", @@ -34,6 +35,7 @@ swift_library( "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift index 5585d5c5d7..78097beae1 100644 --- a/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift +++ b/submodules/TelegramUI/Components/ContentReportScreen/Sources/ContentReportScreen.swift @@ -21,6 +21,8 @@ import LottieComponent import TextFieldComponent import ListMultilineTextFieldItemComponent import ButtonComponent +import BundleIconComponent +import GlassBarButtonComponent private enum ReportResult { case reported @@ -83,8 +85,6 @@ private final class SheetPageContent: CombinedComponent { } final class State: ComponentState { - var backArrowImage: (UIImage, PresentationTheme)? - let playOnce = ActionSlot() private var didPlayAnimation = false @@ -104,8 +104,7 @@ private final class SheetPageContent: CombinedComponent { } static var body: Body { - let background = Child(RoundedRectangle.self) - let back = Child(Button.self) + let background = Child(Rectangle.self) let title = Child(Text.self) let animation = Child(LottieComponent.self) let section = Child(ListSectionComponent.self) @@ -124,10 +123,10 @@ private final class SheetPageContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left - var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + var contentSize = CGSize(width: context.availableSize.width, height: 26.0) let background = background.update( - component: RoundedRectangle(color: theme.list.modalBlocksBackgroundColor, cornerRadius: 8.0), + component: Rectangle(color: theme.list.modalBlocksBackgroundColor), availableSize: CGSize(width: context.availableSize.width, height: 1000.0), transition: .immediate ) @@ -135,40 +134,7 @@ private final class SheetPageContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) ) - let backArrowImage: UIImage - if let (cached, cachedTheme) = state.backArrowImage, cachedTheme === theme { - backArrowImage = cached - } else { - backArrowImage = NavigationBarTheme.generateBackArrowImage(color: theme.list.itemAccentColor)! - state.backArrowImage = (backArrowImage, theme) - } - - let backContents: AnyComponent - if component.isFirst { - backContents = AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.list.itemAccentColor)) - } else { - backContents = AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "arrow", component: AnyComponent(Image(image: backArrowImage, contentMode: .center))), - AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: strings.Common_Back, font: Font.regular(17.0), color: theme.list.itemAccentColor))) - ], spacing: 6.0) - ) - } - let back = back.update( - component: Button( - content: backContents, - action: { - component.pop() - } - ), - availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), - transition: .immediate - ) - context.add(back - .position(CGPoint(x: sideInset + back.size.width / 2.0 - (!component.isFirst ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0)) - ) - - let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0 + let constrainedTitleWidth = context.availableSize.width - 60.0 * 2.0 let titleString: String if let title = component.title { @@ -186,7 +152,7 @@ private final class SheetPageContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) ) contentSize.height += title.size.height - contentSize.height += 24.0 + contentSize.height += 40.0 var items: [AnyComponentWithIdentity] = [] var footer: AnyComponent? @@ -196,6 +162,7 @@ private final class SheetPageContent: CombinedComponent { for item in options { items.append(AnyComponentWithIdentity(id: item.title, component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -274,6 +241,7 @@ private final class SheetPageContent: CombinedComponent { let section = section.update( component: ListSectionComponent( theme: theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: component.subtitle.uppercased(), @@ -294,7 +262,7 @@ private final class SheetPageContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + section.size.height / 2.0)) ) contentSize.height += section.size.height - contentSize.height += 54.0 + contentSize.height += 16.0 if case let .comment(isOptional, option) = component.content { contentSize.height -= 16.0 @@ -303,6 +271,7 @@ private final class SheetPageContent: CombinedComponent { let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -316,12 +285,10 @@ private final class SheetPageContent: CombinedComponent { } ), environment: {}, - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 52.0), transition: context.transition ) context.add(button - .clipsToBounds(true) - .cornerRadius(10.0) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height @@ -427,6 +394,7 @@ private final class SheetContent: CombinedComponent { static var body: Body { let navigation = Child(NavigationStackComponent.self) + let backButton = Child(GlassBarButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self] @@ -541,10 +509,39 @@ private final class SheetContent: CombinedComponent { ) context.add(navigation .position(CGPoint(x: context.availableSize.width / 2.0, y: navigation.size.height / 2.0)) - .clipsToBounds(true) - .cornerRadius(8.0) ) contentSize.height += navigation.size.height + + let isBack = items.count > 1 + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let backButton = backButton.update( + component: GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( + BundleIconComponent( + name: isBack ? "Navigation/Back" : "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak state] _ in + if isBack { + state?.pushedOptions.removeLast() + update(.spring(duration: 0.45)) + } else { + component.dismiss() + } + } + ), + environment: {}, + availableSize: barButtonSize, + transition: context.transition + ) + context.add(backButton + .position(CGPoint(x: 16.0 + backButton.size.width / 2.0, y: 16.0 + backButton.size.height / 2.0)) + ) return contentSize } @@ -638,8 +635,10 @@ private final class SheetContainerComponent: CombinedComponent { }, requestSelectMessages: context.component.requestSelectMessages )), + style: .glass, backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), followContentSizeChanges: true, + clipsContent: true, externalState: sheetExternalState, animateOut: animateOut ), diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 33a929b71c..fbd0fcdd58 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -418,12 +418,9 @@ final class GiftOptionsScreenComponent: Component { let availableWidth = self.scrollView.bounds.width let contentOffset = self.scrollView.contentOffset.y -// -// let topPanelAlpha = min(20.0, max(0.0, contentOffset - 95.0)) / 20.0 -// if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { -// transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) -// transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) -// } + + let topPanelAlpha = min(20.0, contentOffset) / 20.0 + transition.setAlpha(view: self.topEdgeEffectView, alpha: topPanelAlpha) let topInset: CGFloat = 0.0 let headerTopInset: CGFloat = environment.navigationHeight - 56.0 @@ -665,7 +662,7 @@ final class GiftOptionsScreenComponent: Component { let edgeEffectHeight: CGFloat = 88.0 let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableWidth, height: edgeEffectHeight)) transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) - self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) + self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) if self.topEdgeEffectView.superview == nil { if let headerView = self.header.view { self.insertSubview(self.topEdgeEffectView, aboveSubview: headerView) diff --git a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift index 4e9001a40e..27e92b559c 100644 --- a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift +++ b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift @@ -67,6 +67,7 @@ public final class SliderComponent: Component { } public let content: Content + public let useNative: Bool public let trackBackgroundColor: UIColor public let trackForegroundColor: UIColor public let minTrackForegroundColor: UIColor? @@ -76,6 +77,7 @@ public final class SliderComponent: Component { public init( content: Content, + useNative: Bool = false, trackBackgroundColor: UIColor, trackForegroundColor: UIColor, minTrackForegroundColor: UIColor? = nil, @@ -84,6 +86,7 @@ public final class SliderComponent: Component { isTrackingUpdated: ((Bool) -> Void)? = nil ) { self.content = content + self.useNative = useNative self.trackBackgroundColor = trackBackgroundColor self.trackForegroundColor = trackForegroundColor self.minTrackForegroundColor = minTrackForegroundColor @@ -114,7 +117,12 @@ public final class SliderComponent: Component { return true } + final class SliderView: UISlider { + + } + public final class View: UIView { + private var nativeSliderView: SliderView? private var sliderView: TGPhotoEditorSliderView? private var component: SliderComponent? @@ -138,125 +146,168 @@ public final class SliderComponent: Component { let size = CGSize(width: availableSize.width, height: 44.0) - var internalIsTrackingUpdated: ((Bool) -> Void)? - if let isTrackingUpdated = component.isTrackingUpdated { - internalIsTrackingUpdated = { [weak self] isTracking in - if let self { - if !"".isEmpty { - if isTracking { - self.sliderView?.bordered = true - } else { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { [weak self] in - self?.sliderView?.bordered = false - }) + if #available(iOS 26.0, *), component.useNative { + let sliderView: SliderView + if let current = self.nativeSliderView { + sliderView = current + } else { + sliderView = SliderView() + sliderView.disablesInteractiveTransitionGestureRecognizer = true + sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) + sliderView.layer.allowsGroupOpacity = true + + self.addSubview(sliderView) + self.nativeSliderView = sliderView + + switch component.content { + case let .continuous(continuous): + sliderView.minimumValue = Float(continuous.minValue ?? 0.0) + sliderView.maximumValue = 1.0 + case let .discrete(discrete): + sliderView.minimumValue = 0.0 + sliderView.maximumValue = Float(discrete.valueCount - 1) + sliderView.trackConfiguration = .init(numberOfTicks: discrete.valueCount) + } + } + switch component.content { + case let .continuous(continuous): + sliderView.value = Float(continuous.value) + case let .discrete(discrete): + sliderView.value = Float(discrete.value) + } + sliderView.minimumTrackTintColor = component.trackForegroundColor + sliderView.maximumTrackTintColor = component.trackBackgroundColor + + transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: 44.0))) + } else { + var internalIsTrackingUpdated: ((Bool) -> Void)? + if let isTrackingUpdated = component.isTrackingUpdated { + internalIsTrackingUpdated = { [weak self] isTracking in + if let self { + if !"".isEmpty { + if isTracking { + self.sliderView?.bordered = true + } else { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { [weak self] in + self?.sliderView?.bordered = false + }) + } } } + isTrackingUpdated(isTracking) } - isTrackingUpdated(isTracking) } - } - - let sliderView: TGPhotoEditorSliderView - if let current = self.sliderView { - sliderView = current - } else { - sliderView = TGPhotoEditorSliderView() - sliderView.enablePanHandling = true - if let knobSize = component.knobSize { - sliderView.lineSize = knobSize + 4.0 - } else { - sliderView.lineSize = 4.0 - } - sliderView.trackCornerRadius = sliderView.lineSize * 0.5 - sliderView.dotSize = 5.0 - sliderView.minimumValue = 0.0 - sliderView.startValue = 0.0 - sliderView.disablesInteractiveTransitionGestureRecognizer = true + let sliderView: TGPhotoEditorSliderView + if let current = self.sliderView { + sliderView = current + } else { + sliderView = TGPhotoEditorSliderView() + sliderView.enablePanHandling = true + if let knobSize = component.knobSize { + sliderView.lineSize = knobSize + 4.0 + } else { + sliderView.lineSize = 4.0 + } + sliderView.trackCornerRadius = sliderView.lineSize * 0.5 + sliderView.dotSize = 5.0 + sliderView.minimumValue = 0.0 + sliderView.startValue = 0.0 + sliderView.disablesInteractiveTransitionGestureRecognizer = true + + switch component.content { + case let .discrete(discrete): + sliderView.maximumValue = CGFloat(discrete.valueCount - 1) + sliderView.positionsCount = discrete.valueCount + sliderView.useLinesForPositions = true + sliderView.markPositions = discrete.markPositions + case .continuous: + sliderView.maximumValue = 1.0 + } + + sliderView.backgroundColor = nil + sliderView.isOpaque = false + sliderView.backColor = component.trackBackgroundColor + sliderView.startColor = component.trackBackgroundColor + sliderView.trackColor = component.trackForegroundColor + if let knobSize = component.knobSize { + sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + if let knobColor = component.knobColor { + context.setFillColor(knobColor.cgColor) + } else { + context.setFillColor(UIColor.white.cgColor) + } + context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - knobSize) * 0.5), y: floor((size.width - knobSize) * 0.5)), size: CGSize(width: knobSize, height: knobSize))) + }) + } else { + sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) + }) + } + + sliderView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) + + + sliderView.disablesInteractiveTransitionGestureRecognizer = true + sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) + sliderView.layer.allowsGroupOpacity = true + self.sliderView = sliderView + self.addSubview(sliderView) + } + sliderView.lowerBoundTrackColor = component.minTrackForegroundColor switch component.content { case let .discrete(discrete): - sliderView.maximumValue = CGFloat(discrete.valueCount - 1) - sliderView.positionsCount = discrete.valueCount - sliderView.useLinesForPositions = true - sliderView.markPositions = discrete.markPositions - case .continuous: - sliderView.maximumValue = 1.0 + sliderView.value = CGFloat(discrete.value) + if let minValue = discrete.minValue { + sliderView.lowerBoundValue = CGFloat(minValue) + } else { + sliderView.lowerBoundValue = 0.0 + } + case let .continuous(continuous): + sliderView.value = continuous.value + if let minValue = continuous.minValue { + sliderView.lowerBoundValue = minValue + } else { + sliderView.lowerBoundValue = 0.0 + } + } + sliderView.interactionBegan = { + internalIsTrackingUpdated?(true) + } + sliderView.interactionEnded = { + internalIsTrackingUpdated?(false) } - sliderView.backgroundColor = nil - sliderView.isOpaque = false - sliderView.backColor = component.trackBackgroundColor - sliderView.startColor = component.trackBackgroundColor - sliderView.trackColor = component.trackForegroundColor - if let knobSize = component.knobSize { - sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) - if let knobColor = component.knobColor { - context.setFillColor(knobColor.cgColor) - } else { - context.setFillColor(UIColor.white.cgColor) - } - context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - knobSize) * 0.5), y: floor((size.width - knobSize) * 0.5)), size: CGSize(width: knobSize, height: knobSize))) - }) - } else { - sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) - }) - } - - sliderView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) - - - sliderView.disablesInteractiveTransitionGestureRecognizer = true - sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) - sliderView.layer.allowsGroupOpacity = true - self.sliderView = sliderView - self.addSubview(sliderView) + transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: 44.0))) + sliderView.hitTestEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) } - sliderView.lowerBoundTrackColor = component.minTrackForegroundColor - switch component.content { - case let .discrete(discrete): - sliderView.value = CGFloat(discrete.value) - if let minValue = discrete.minValue { - sliderView.lowerBoundValue = CGFloat(minValue) - } else { - sliderView.lowerBoundValue = 0.0 - } - case let .continuous(continuous): - sliderView.value = continuous.value - if let minValue = continuous.minValue { - sliderView.lowerBoundValue = minValue - } else { - sliderView.lowerBoundValue = 0.0 - } - } - sliderView.interactionBegan = { - internalIsTrackingUpdated?(true) - } - sliderView.interactionEnded = { - internalIsTrackingUpdated?(false) - } - - transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: 44.0))) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) return size } @objc private func sliderValueChanged() { - guard let component = self.component, let sliderView = self.sliderView else { + guard let component = self.component else { + return + } + let floatValue: CGFloat + if let sliderView = self.sliderView { + floatValue = sliderView.value + } else if let nativeSliderView = self.nativeSliderView { + floatValue = CGFloat(nativeSliderView.value) + } else { return } switch component.content { case let .discrete(discrete): - discrete.valueUpdated(Int(sliderView.value)) + discrete.valueUpdated(Int(floatValue)) case let .continuous(continuous): - continuous.valueUpdated(sliderView.value) + continuous.valueUpdated(floatValue) } } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index fd1770e759..cd3f1be142 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -466,6 +466,7 @@ final class StarsStatisticsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Stars_BotRevenue_Revenue_Title.uppercased(), @@ -501,6 +502,7 @@ final class StarsStatisticsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Stars_BotRevenue_Proceeds_Title.uppercased(), @@ -656,6 +658,7 @@ final class StarsStatisticsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Stars_BotRevenue_Withdraw_Balance.uppercased(), diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 61ce3c665a..dc32b21d4f 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -28,6 +28,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/BottomButtonPanelComponent", + "//submodules/TelegramUI/Components/SliderComponent", "//submodules/CheckNode", "//submodules/Markdown", "//submodules/ContextUI", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift index 8dc4fc9a1d..4f2cb4878b 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift @@ -3,16 +3,8 @@ import UIKit import Display import AsyncDisplayKit import ComponentFlow -import SwiftSignalKit -import ViewControllerComponent import ComponentDisplayAdapters import TelegramPresentationData -import AccountContext -import TelegramCore -import MultilineTextComponent -import EmojiStatusComponent -import TelegramStringFormatting -import CheckNode import SegmentedControlNode final class SegmentControlComponent: Component { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift index 115020cc8a..3f171dba89 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift @@ -10,10 +10,8 @@ import TelegramPresentationData import AccountContext import TelegramCore import MultilineTextComponent -import EmojiStatusComponent -import CheckNode -import SolidRoundedButtonComponent import LegacyComponents +import SliderComponent private func stringForCacheSize(strings: PresentationStrings, size: Int32) -> String { if size > 100 { @@ -78,13 +76,15 @@ final class StorageKeepSizeComponent: Component { class View: UIView { private let titles: [ComponentView] - private var sliderView: TGPhotoEditorSliderView? + private let slider: ComponentView + //private var sliderView: TGPhotoEditorSliderView? private var component: StorageKeepSizeComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { self.titles = (0 ..< 4).map { _ in ComponentView() } + self.slider = ComponentView() super.init(frame: frame) @@ -137,64 +137,36 @@ final class StorageKeepSizeComponent: Component { } } - var sliderFirstTime = false - let sliderView: TGPhotoEditorSliderView - if let current = self.sliderView { - sliderView = current - } else { - sliderFirstTime = true - sliderView = TGPhotoEditorSliderView() - sliderView.enablePanHandling = true - sliderView.trackCornerRadius = 2.0 - sliderView.lineSize = 4.0 - sliderView.dotSize = 5.0 - sliderView.minimumValue = 0.0 - sliderView.maximumValue = 3.0 - sliderView.startValue = 0.0 - sliderView.disablesInteractiveTransitionGestureRecognizer = true - sliderView.positionsCount = 4 - sliderView.useLinesForPositions = true - sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) - self.sliderView = sliderView - self.addSubview(sliderView) + let sliderSize = self.slider.update( + transition: transition, + component: AnyComponent( + SliderComponent( + content: .discrete(.init( + valueCount: 4, + value: maximumCacheSizeValues.firstIndex(where: { $0 == component.value }) ?? 0, + markPositions: true, + valueUpdated: { value in + let sizeValue = maximumCacheSizeValues[value] + component.updateValue(sizeValue) + } + )), + useNative: true, + trackBackgroundColor: component.theme.list.itemSwitchColors.frameColor, + trackForegroundColor: component.theme.list.itemAccentColor + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - 15.0 * 2.0, height: 44.0) + ) + if let sliderView = self.slider.view { + if sliderView.superview == nil { + self.addSubview(sliderView) + } + transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - sliderSize.width) / 2.0), y: 41.0), size: sliderSize)) } - - if sliderFirstTime || themeUpdated { - sliderView.backgroundColor = component.theme.list.itemBlocksBackgroundColor - sliderView.backColor = component.theme.list.itemSwitchColors.frameColor - sliderView.startColor = component.theme.list.itemSwitchColors.frameColor - sliderView.trackColor = component.theme.list.itemAccentColor - sliderView.knobImage = PresentationResourcesItemList.knobImage(component.theme) - } - - transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 15.0, y: 41.0), size: CGSize(width: availableSize.width - 15.0 * 2.0, height: 44.0))) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) - - self.updateSliderView() - + return CGSize(width: availableSize.width, height: height) } - - private func updateSliderView() { - guard let sliderView = self.sliderView, let component = self.component else { - return - } - sliderView.maximumValue = 3.0 - sliderView.positionsCount = 4 - - let value = maximumCacheSizeValues.firstIndex(where: { $0 == component.value }) ?? 0 - sliderView.value = CGFloat(value) - } - - @objc private func sliderValueChanged() { - guard let component = self.component, let sliderView = self.sliderView else { - return - } - - let position = Int(sliderView.value) - let value = maximumCacheSizeValues[position] - component.updateValue(value) - } } func makeView() -> View { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 2eaca42c2f..b4f24f6ee4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -73,7 +73,6 @@ swift_library( "//submodules/StickerPackPreviewUI", "//submodules/Components/AnimatedStickerComponent", "//submodules/OpenInExternalAppUI", - "//submodules/MediaPasteboardUI", "//submodules/WebPBinding", "//submodules/Utils/RangeSet", "//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index a30e67b74a..17f2680813 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -1616,6 +1616,7 @@ private final class StoryContainerScreenComponent: Component { inputHeight: environment.inputHeight, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, + isEmbeddedInCamera: false, isProgressPaused: isProgressPaused || i != focusedIndex, isAudioMuted: self.audioMode == .off || (self.audioMode == .ambient && !(self.isMuteSwitchOn || self.areHeadphonesConnected)), audioMode: self.audioMode, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index d47f666e01..e13283b9f3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -38,9 +38,10 @@ final class StoryItemContentComponent: Component { let isVideoBuffering: Bool let isCurrent: Bool let preferHighQuality: Bool + let isEmbeddedInCamera: Bool let activateReaction: (UIView, MessageReaction.Reaction) -> Void - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { self.context = context self.strings = strings self.peer = peer @@ -52,6 +53,7 @@ final class StoryItemContentComponent: Component { self.isVideoBuffering = isVideoBuffering self.isCurrent = isCurrent self.preferHighQuality = preferHighQuality + self.isEmbeddedInCamera = isEmbeddedInCamera self.activateReaction = activateReaction } @@ -83,6 +85,9 @@ final class StoryItemContentComponent: Component { if lhs.isCurrent != rhs.isCurrent { return false } + if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera { + return false + } if lhs.preferHighQuality != rhs.preferHighQuality { return false } @@ -861,55 +866,57 @@ final class StoryItemContentComponent: Component { mediaStreamTransition.setFrame(view: liveChatView, frame: liveChatFrame) } - let _ = mediaStream.update( - transition: mediaStreamTransition, - component: AnyComponent(MediaStreamVideoComponent( - call: mediaStreamCall, - hasVideo: true, - isVisible: true, - isAdmin: false, - peerTitle: "", - addInset: false, - isFullscreen: false, - videoLoading: false, - callPeer: nil, - activatePictureInPicture: ActionSlot(), - deactivatePictureInPicture: ActionSlot(), - bringBackControllerForPictureInPictureDeactivation: { f in - f() - }, - pictureInPictureClosed: { - }, - onVideoSizeRetrieved: { _ in - }, - onVideoPlaybackLiveChange: { [weak self] isLive in - guard let self else { - return - } - self.videoPlaybackStatus = MediaPlayerStatus( - generationTimestamp: CACurrentMediaTime(), - duration: .infinity, - dimensions: CGSize(), - timestamp: 0.0, - baseRate: 1.0, - seekId: 0, - status: isLive ? .playing : .buffering(initial: false, whilePlaying: true, progress: 0.0, display: true), - soundEnabled: true - ) - if !self.isSeeking { - self.updateVideoPlaybackProgress() + if !component.isEmbeddedInCamera { + let _ = mediaStream.update( + transition: mediaStreamTransition, + component: AnyComponent(MediaStreamVideoComponent( + call: mediaStreamCall, + hasVideo: true, + isVisible: true, + isAdmin: false, + peerTitle: "", + addInset: false, + isFullscreen: false, + videoLoading: false, + callPeer: nil, + activatePictureInPicture: ActionSlot(), + deactivatePictureInPicture: ActionSlot(), + bringBackControllerForPictureInPictureDeactivation: { f in + f() + }, + pictureInPictureClosed: { + }, + onVideoSizeRetrieved: { _ in + }, + onVideoPlaybackLiveChange: { [weak self] isLive in + guard let self else { + return + } + self.videoPlaybackStatus = MediaPlayerStatus( + generationTimestamp: CACurrentMediaTime(), + duration: .infinity, + dimensions: CGSize(), + timestamp: 0.0, + baseRate: 1.0, + seekId: 0, + status: isLive ? .playing : .buffering(initial: false, whilePlaying: true, progress: 0.0, display: true), + soundEnabled: true + ) + if !self.isSeeking { + self.updateVideoPlaybackProgress() + } } + )), + environment: {}, + containerSize: availableSize + ) + let mediaStreamFrame = CGRect(origin: CGPoint(), size: availableSize) + if let mediaStreamView = mediaStream.view { + if mediaStreamView.superview == nil { + self.insertSubview(mediaStreamView, aboveSubview: self.imageView) } - )), - environment: {}, - containerSize: availableSize - ) - let mediaStreamFrame = CGRect(origin: CGPoint(), size: availableSize) - if let mediaStreamView = mediaStream.view { - if mediaStreamView.superview == nil { - self.insertSubview(mediaStreamView, aboveSubview: self.imageView) + mediaStreamTransition.setFrame(view: mediaStreamView, frame: mediaStreamFrame) } - mediaStreamTransition.setFrame(view: mediaStreamView, frame: mediaStreamFrame) } } else { if let mediaStream = self.mediaStream { @@ -1026,8 +1033,9 @@ final class StoryItemContentComponent: Component { self.unsupportedButton = nil unsupportedButton.view?.removeFromSuperview() } - - self.backgroundColor = .black + if !component.isEmbeddedInCamera { + self.backgroundColor = .black + } default: var unsuportedTransition = transition @@ -1117,7 +1125,7 @@ final class StoryItemContentComponent: Component { #endif } - if !self.contentLoaded || component.isVideoBuffering { + if !component.isEmbeddedInCamera && (!self.contentLoaded || component.isVideoBuffering) { let loadingEffectView: StoryItemLoadingEffectView if let current = self.loadingEffectView { loadingEffectView = current diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 74fdb9f34b..436900514e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -108,6 +108,7 @@ public final class StoryItemSetContainerComponent: Component { public let inputHeight: CGFloat public let metrics: LayoutMetrics public let deviceMetrics: DeviceMetrics + public let isEmbeddedInCamera: Bool public let isProgressPaused: Bool public let isAudioMuted: Bool public let audioMode: StoryContentItem.AudioMode @@ -133,7 +134,7 @@ public final class StoryItemSetContainerComponent: Component { let stealthModeTimeout: Int32? public let isDismissed: Bool - init( + public init( context: AccountContext, externalState: ExternalState, storyItemSharedState: StoryContentItem.SharedState, @@ -147,6 +148,7 @@ public final class StoryItemSetContainerComponent: Component { inputHeight: CGFloat, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, + isEmbeddedInCamera: Bool, isProgressPaused: Bool, isAudioMuted: Bool, audioMode: StoryContentItem.AudioMode, @@ -185,6 +187,7 @@ public final class StoryItemSetContainerComponent: Component { self.inputHeight = inputHeight self.metrics = metrics self.deviceMetrics = deviceMetrics + self.isEmbeddedInCamera = isEmbeddedInCamera self.isProgressPaused = isProgressPaused self.isAudioMuted = isAudioMuted self.audioMode = audioMode @@ -245,6 +248,9 @@ public final class StoryItemSetContainerComponent: Component { if lhs.deviceMetrics != rhs.deviceMetrics { return false } + if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera { + return false + } if lhs.isProgressPaused != rhs.isProgressPaused { return false } @@ -590,6 +596,10 @@ public final class StoryItemSetContainerComponent: Component { return [] } + if let component = self.component, component.isEmbeddedInCamera { + return [] + } + for (_, viewList) in self.viewLists { if let view = viewList.view.view, view.hitTest(self.convert(point, to: view), with: nil) != nil { return [.down] @@ -736,6 +746,19 @@ public final class StoryItemSetContainerComponent: Component { self.updateDisposable.dispose() } + public var mediaStreamCall: PresentationGroupCall? { + guard let component = self.component else { + return nil + } + guard let visibleItem = self.visibleItems[component.slice.item.id] else { + return nil + } + guard let itemView = visibleItem.view.view as? StoryItemContentComponent.View else { + return nil + } + return itemView.mediaStreamCall + } + func allowsExternalGestures(point: CGPoint) -> Bool { if self.viewListDisplayState != .hidden { return false @@ -1599,6 +1622,7 @@ public final class StoryItemSetContainerComponent: Component { isVideoBuffering: visibleItem.isBuffering, isCurrent: index == centralIndex, preferHighQuality: component.slice.additionalPeerData.preferHighQualityStories, + isEmbeddedInCamera: component.isEmbeddedInCamera, activateReaction: { [weak self] reactionView, reaction in guard let self else { return @@ -1644,6 +1668,7 @@ public final class StoryItemSetContainerComponent: Component { itemTransition.setPosition(view: visibleItem.unclippedContainerView, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y)) itemTransition.setBounds(view: visibleItem.unclippedContainerView, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size)) + visibleItem.contentTintLayer.isHidden = component.isEmbeddedInCamera itemTransition.setPosition(layer: visibleItem.contentTintLayer, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y)) itemTransition.setBounds(layer: visibleItem.contentTintLayer, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size)) @@ -3895,13 +3920,14 @@ public final class StoryItemSetContainerComponent: Component { closeButtonFrame.origin.y -= contentBottomInsetOverflow transition.setFrame(view: self.closeButton, frame: closeButtonFrame) - transition.setAlpha(view: self.closeButton, alpha: (component.hideUI || self.isEditingStory) ? 0.0 : 1.0) + transition.setAlpha(view: self.closeButton, alpha: (component.hideUI || self.isEditingStory || component.isEmbeddedInCamera) ? 0.0 : 1.0) transition.setFrame(view: self.closeButtonIconView, frame: CGRect(origin: CGPoint(x: floor((closeButtonFrame.width - image.size.width) * 0.5), y: floor((closeButtonFrame.height - image.size.height) * 0.5)), size: image.size)) headerRightOffset -= 51.0 } var moreButtonSize: CGSize? if case let .user(user) = component.slice.peer, let botInfo = user.botInfo, !botInfo.flags.contains(.canEdit) { + } else if component.isEmbeddedInCamera { } else { moreButtonSize = self.moreButton.update( transition: transition, @@ -4030,7 +4056,7 @@ public final class StoryItemSetContainerComponent: Component { } let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy? - if case .user = component.slice.effectivePeer { + if !component.isEmbeddedInCamera, case .user = component.slice.effectivePeer { if component.slice.item.storyItem.isCloseFriends { storyPrivacyIcon = .closeFriends } else if component.slice.item.storyItem.isContacts { @@ -4284,6 +4310,7 @@ public final class StoryItemSetContainerComponent: Component { let topContentGradientRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: topGradientHeight)) transition.setPosition(view: self.topContentGradientView, position: topContentGradientRect.center) transition.setBounds(view: self.topContentGradientView, bounds: CGRect(origin: CGPoint(), size: topContentGradientRect.size)) + self.topContentGradientView.isHidden = component.isEmbeddedInCamera if let inputPanelFrame = inputPanelFrameValue { var inputPanelAlpha: CGFloat = (component.hideUI || self.isEditingStory || component.slice.item.storyItem.isPending) ? 0.0 : 1.0 @@ -5046,7 +5073,7 @@ public final class StoryItemSetContainerComponent: Component { let navigationStripSideInset: CGFloat = 8.0 let navigationStripTopInset: CGFloat = 8.0 - if let focusedItem, let visibleItem = self.visibleItems[focusedItem.id], let index = focusedItem.position { + if !component.isEmbeddedInCamera, let focusedItem, let visibleItem = self.visibleItems[focusedItem.id], let index = focusedItem.position { var index = max(0, min(index, component.slice.totalCount - 1)) var count = component.slice.totalCount if let dayCounters = focusedItem.dayCounters { @@ -5526,6 +5553,13 @@ public final class StoryItemSetContainerComponent: Component { return } + #if DEBUG + if "".isEmpty { + self.performDeleteAction() + return + } + #endif + self.isEditingStory = true self.updateIsProgressPaused() self.state?.updated(transition: .easeInOut(duration: 0.2)) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index fea168d6e8..99afa17771 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -37,7 +37,7 @@ import TextFieldComponent import StickerPackPreviewUI import OpenInExternalAppUI import SafariServices -import MediaPasteboardUI +//import MediaPasteboardUI import WebPBinding import ContextUI import ChatScheduleTimeController @@ -2403,71 +2403,71 @@ final class StoryItemSetContainerSendMessage { } func presentMediaPasteboard(view: StoryItemSetContainerComponent.View, subjects: [MediaPickerScreenImpl.Subject.Media]) { - guard let component = view.component else { - return - } - let focusedItem = component.slice.item - guard let peerId = focusedItem.peerId else { - return - } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) - guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { - return - } - - var inputText = NSAttributedString(string: "") - switch inputPanelView.getSendMessageInput() { - case let .text(text): - inputText = text - } - - let peer = component.slice.effectivePeer - let theme = defaultDarkPresentationTheme - let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - let controller = mediaPasteboardScreen( - context: component.context, - updatedPresentationData: updatedPresentationData, - peer: peer, - subjects: subjects, - presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in - if let self { - self.presentMediaPicker( - view: view, - peer: peer, - replyToMessageId: nil, - replyToStoryId: focusedStoryId, - subject: subject, - saveEditedPhotos: saveEditedPhotos, - bannedSendPhotos: bannedSendPhotos, - bannedSendVideos: bannedSendVideos, - present: { controller, mediaPickerContext in - if !inputText.string.isEmpty { - mediaPickerContext?.setCaption(inputText) - } - present(controller, mediaPickerContext) - }, - updateMediaPickerContext: { _ in }, - completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in - guard let self, let view else { - return - } - if !inputText.string.isEmpty { - self.clearInputText(view: view) - } - self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in - guard let self else { - return - } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) - }) - } - ) - } - }, - getSourceRect: nil - ) - controller.navigationPresentation = .flatModal - component.controller()?.push(controller) +// guard let component = view.component else { +// return +// } +// let focusedItem = component.slice.item +// guard let peerId = focusedItem.peerId else { +// return +// } +// let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) +// guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { +// return +// } +// +// var inputText = NSAttributedString(string: "") +// switch inputPanelView.getSendMessageInput() { +// case let .text(text): +// inputText = text +// } +// +// let peer = component.slice.effectivePeer +// let theme = defaultDarkPresentationTheme +// let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) +// let controller = mediaPasteboardScreen( +// context: component.context, +// updatedPresentationData: updatedPresentationData, +// peer: peer, +// subjects: subjects, +// presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in +// if let self { +// self.presentMediaPicker( +// view: view, +// peer: peer, +// replyToMessageId: nil, +// replyToStoryId: focusedStoryId, +// subject: subject, +// saveEditedPhotos: saveEditedPhotos, +// bannedSendPhotos: bannedSendPhotos, +// bannedSendVideos: bannedSendVideos, +// present: { controller, mediaPickerContext in +// if !inputText.string.isEmpty { +// mediaPickerContext?.setCaption(inputText) +// } +// present(controller, mediaPickerContext) +// }, +// updateMediaPickerContext: { _ in }, +// completion: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in +// guard let self, let view else { +// return +// } +// if !inputText.string.isEmpty { +// self.clearInputText(view: view) +// } +// self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in +// guard let self else { +// return +// } +// self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) +// }) +// } +// ) +// } +// }, +// getSourceRect: nil +// ) +// controller.navigationPresentation = .flatModal +// component.controller()?.push(controller) } private func enqueueChatContextResult(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, storyId: StoryId?, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 11b8786589..54490dfc18 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -36,7 +36,7 @@ private func cancelContextGestures(view: UIView) { } } -final class StoryItemSetViewListComponent: Component { +public final class StoryItemSetViewListComponent: Component { final class AnimationHint { let synchronous: Bool @@ -45,10 +45,10 @@ final class StoryItemSetViewListComponent: Component { } } - final class SharedListsContext { + public final class SharedListsContext { var viewLists: [StoryId: EngineStoryViewListContext] = [:] - init() { + public init() { } } @@ -129,7 +129,7 @@ final class StoryItemSetViewListComponent: Component { self.controller = controller } - static func ==(lhs: StoryItemSetViewListComponent, rhs: StoryItemSetViewListComponent) -> Bool { + public static func ==(lhs: StoryItemSetViewListComponent, rhs: StoryItemSetViewListComponent) -> Bool { if lhs.theme !== rhs.theme { return false } @@ -1285,7 +1285,7 @@ final class StoryItemSetViewListComponent: Component { } } - final class View: UIView, UIScrollViewDelegate { + public final class View: UIView, UIScrollViewDelegate { private let navigationBarBackground: BlurredBackgroundView private let navigationSearch = ComponentView() private let navigationSeparator: SimpleLayer @@ -1344,7 +1344,7 @@ final class StoryItemSetViewListComponent: Component { self.mainViewListDisposable?.dispose() } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.backgroundView.frame.contains(point) && !self.navigationContainerView.frame.contains(point) { return nil } @@ -1907,11 +1907,11 @@ final class StoryItemSetViewListComponent: Component { } } - func makeView() -> View { + public func makeView() -> View { return View(frame: CGRect()) } - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } diff --git a/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/Contents.json new file mode 100644 index 0000000000..2d9ef0bdf7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "settings_30 (3).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/settings_30 (3).pdf b/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/settings_30 (3).pdf new file mode 100644 index 0000000000..2c491666df Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/settings_30 (3).pdf differ diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 1126c0c18b..96c927354b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -604,7 +604,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { _, f in f(.dismissWithoutContent) - controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context, forceDark: true)) + controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context, forceDark: false)) }))) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 9b9837ea57..e15dafce1e 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -66,6 +66,8 @@ final class ContactSelectionControllerNode: ASDisplayNode { private let topEdgeEffectView: EdgeEffectView private let bottomEdgeEffectView: EdgeEffectView + private var contactsAuthorization: AccessType? + init( context: AccountContext, listStyle: ItemListStyle, @@ -207,6 +209,16 @@ final class ContactSelectionControllerNode: ASDisplayNode { } } + self.contactListNode.authorizationUpdated = { [weak self] authorization in + guard let self else { + return + } + self.contactsAuthorization = authorization + if let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, actualNavigationBarHeight: actualNavigationHeight, transition: .immediate) + } + } + shareImpl = { [weak self] in self?.requestMultipleAction?(false, nil, nil) } @@ -262,7 +274,11 @@ final class ContactSelectionControllerNode: ASDisplayNode { let safeInsets = layout.safeInsets var size = layout.size if case .blocks = self.listStyle { - insets.top -= 25.0 + if let contactsAuthorization = self.contactsAuthorization, contactsAuthorization != .allowed { + insets.top += 23.0 + } else { + insets.top -= 25.0 + } let inset: CGFloat if layout.size.width >= 375.0 { @@ -277,14 +293,6 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.contactListNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: 0.0), size: size) -// let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition) -// if (self.selectionState?.selectedPeerIndices.isEmpty ?? true) { -// transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: layout.size.width, height: countPanelHeight))) -// } else { -// insets.bottom += countPanelHeight -// transition.updateFrame(node: self.countPanelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - countPanelHeight), size: CGSize(width: layout.size.width, height: countPanelHeight))) -// } - if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } @@ -297,7 +305,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { let topEdgeEffectHeight: CGFloat = 80.0 let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topEdgeEffectHeight)) transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) - self.topEdgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + self.topEdgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) let bottomEdgeEffectHeight: CGFloat = 88.0 let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomEdgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: bottomEdgeEffectHeight)) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 6074aaa50a..ee3759df89 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -727,8 +727,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon if let media { #if DEBUG - if "".isEmpty { - let _ = context.engine.messages.beginStoryLivestream().startStandalone() + if !"".isEmpty { + let _ = context.engine.messages.beginStoryLivestream(peerId: context.account.peerId, privacy: result.options.privacy, isForwardingDisabled: false).startStandalone() } #endif diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index b20037d083..84839a9bd3 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -489,7 +489,7 @@ public final class OngoingCallVideoCapturer { if isCustom { self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider() } else { - #if targetEnvironment(simulator) && false + #if targetEnvironment(simulator) self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider() let imageSize = CGSize(width: 600.0, height: 800.0) UIGraphicsBeginImageContextWithOptions(imageSize, true, 1.0)