mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '35a285752f'
This commit is contained in:
commit
eebfe1acae
57 changed files with 1861 additions and 829 deletions
6
MODULE.bazel.lock
generated
6
MODULE.bazel.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
self.clipNode = ASDisplayNode()
|
||||
self.bottomClipNode = ASDisplayNode()
|
||||
self.bottomClipNode.clipsToBounds = true
|
||||
self.bottomClipNode.cornerRadius = 62.0
|
||||
|
||||
var controllerRemovedImpl: ((ViewController) -> Void)?
|
||||
self.container = NavigationContainer(isFlat: false, controllerRemoved: { c in
|
||||
|
|
@ -132,7 +131,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
applySmoothRoundedCorners(self.container.layer)
|
||||
//applySmoothRoundedCorners(self.container.layer)
|
||||
|
||||
controllerRemovedImpl = { [weak self] c in
|
||||
self?.controllerRemoved?(c)
|
||||
|
|
@ -152,6 +151,9 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
if self.glass {
|
||||
self.clipNode.view.addSubview(self.pillView)
|
||||
}
|
||||
|
||||
self.clipNode.layer.cornerCurve = .continuous
|
||||
self.bottomClipNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
|
||||
func cancelPanGesture() {
|
||||
|
|
@ -475,6 +477,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
if self.isDismissed {
|
||||
return
|
||||
}
|
||||
self.bottomClipNode.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0
|
||||
|
||||
self.isUpdatingState = true
|
||||
|
||||
let isFirstTime = self.validLayout == nil
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1016,7 +1016,7 @@ public final class Camera {
|
|||
}
|
||||
}
|
||||
|
||||
public func setPreviewOutput(_ output: CameraVideoOutput?) {
|
||||
public func setVideoOutput(_ output: CameraVideoOutput?) {
|
||||
let outputRef: Unmanaged<CameraVideoOutput>? = output.flatMap { Unmanaged.passRetained($0) }
|
||||
self.queue.async {
|
||||
if let context = self.contextRef?.takeUnretainedValue() {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<PresentationData, NoError>)? = 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<ContactListNodeGroupSelectionState?>(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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Empty>?
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -355,6 +355,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
|||
private let searchButton = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
|
||||
fileprivate let topEdgeEffectView: EdgeEffectView
|
||||
fileprivate let bottomEdgeEffectView: EdgeEffectView
|
||||
|
||||
private var sendButton: ComponentView<Empty>?
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ swift_library(
|
|||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/SliderComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import MultiAnimationRenderer
|
|||
import Pasteboard
|
||||
import StickerPackEditTitleController
|
||||
import EntityKeyboard
|
||||
import CameraScreen
|
||||
//import CameraScreen
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1112,21 +1112,83 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) {
|
|||
}).start()
|
||||
}
|
||||
|
||||
func _internal_beginStoryLivestream(account: Account) -> Signal<Never, NoError> {
|
||||
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<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Never, NoError> in
|
||||
if let updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal<EngineStoryItem?, NoError> {
|
||||
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<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<EngineStoryItem?, NoError> 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 {
|
||||
|
|
|
|||
|
|
@ -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<Never, NoError> {
|
||||
return _internal_beginStoryLivestream(account: self.account)
|
||||
public func beginStoryLivestream(peerId: EnginePeer.Id, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool) -> Signal<EngineStoryItem?, NoError> {
|
||||
return _internal_beginStoryLivestream(account: self.account, peerId: peerId, privacy: privacy, isForwardingDisabled: isForwardingDisabled)
|
||||
}
|
||||
|
||||
public func allStoriesUploadEvents() -> Signal<(Int32, Int32), NoError> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Empty>?
|
||||
|
||||
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<ChatEntityKeyboardInputNode.InputData>()
|
||||
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<EnvironmentType>, 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<Empty>
|
||||
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<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Signal<CameraScreenImpl.Result, NoError>>()
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
|
||||
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<Void>
|
||||
|
|
@ -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<Void>,
|
||||
|
|
@ -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<Empty>()
|
||||
private let lockView = ComponentView<Empty>()
|
||||
private let galleryButtonView = ComponentView<Empty>()
|
||||
private var galleryButtonChromeView = UIImageView()
|
||||
|
||||
private let shutterButtonView = ComponentView<Empty>()
|
||||
|
||||
private let flipButtonView = ComponentView<Empty>()
|
||||
|
||||
private let bottomContainerView = GlassBackgroundContainerView()
|
||||
private let bottomSettingsButton = ComponentView<Empty>()
|
||||
private let bottomFlipButton = ComponentView<Empty>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CGFloat>(value: 0.63)
|
||||
private var primaryWidth = AnimatableProperty<CGFloat>(value: 0.63)
|
||||
private var primaryHeight = AnimatableProperty<CGFloat>(value: 0.63)
|
||||
private var primaryOffsetX = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryOffsetY = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryRedness = AnimatableProperty<CGFloat>(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<simd_uint2>.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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Void>()
|
||||
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<Empty>
|
||||
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<Empty>] = []
|
||||
var footer: AnyComponent<Empty>?
|
||||
|
|
@ -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<EnvironmentType>.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
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Empty>]
|
||||
private var sliderView: TGPhotoEditorSliderView?
|
||||
private let slider: ComponentView<Empty>
|
||||
//private var sliderView: TGPhotoEditorSliderView?
|
||||
|
||||
private var component: StorageKeepSizeComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.titles = (0 ..< 4).map { _ in ComponentView<Empty>() }
|
||||
self.slider = ComponentView<Empty>()
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1617,6 +1617,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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
@ -3889,13 +3914,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,
|
||||
|
|
@ -4024,7 +4050,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 {
|
||||
|
|
@ -4278,6 +4304,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
|
||||
|
|
@ -5040,7 +5067,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 {
|
||||
|
|
@ -5520,6 +5547,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))
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import TextFieldComponent
|
|||
import StickerPackPreviewUI
|
||||
import OpenInExternalAppUI
|
||||
import SafariServices
|
||||
import MediaPasteboardUI
|
||||
//import MediaPasteboardUI
|
||||
import WebPBinding
|
||||
import ContextUI
|
||||
import ChatScheduleTimeController
|
||||
|
|
@ -2450,71 +2450,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<PresentationData, NoError>) = (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<PresentationData, NoError>) = (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) {
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
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<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "settings_30 (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/settings_30 (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Camera/Settings.imageset/settings_30 (3).pdf
vendored
Normal file
Binary file not shown.
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue