Various improvements

This commit is contained in:
Ilya Laktyushin 2025-10-22 21:12:38 +04:00
parent 118d475e16
commit c014d0b333
56 changed files with 1855 additions and 827 deletions

6
MODULE.bazel.lock generated
View file

@ -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",

View file

@ -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)
}
}

View file

@ -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() {

View file

@ -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(

View file

@ -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

View file

@ -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 {

View file

@ -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
})
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}

View file

@ -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 {

View file

@ -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

View file

@ -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))

View file

@ -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",

View file

@ -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)
}

View file

@ -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)
}

View file

@ -130,6 +130,7 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/SliderComponent",
],
visibility = [
"//visibility:public",

View file

@ -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)
// }
// }
}

View file

@ -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 = [

View file

@ -23,7 +23,7 @@ import MultiAnimationRenderer
import Pasteboard
import StickerPackEditTitleController
import EntityKeyboard
import CameraScreen
//import CameraScreen
import ComponentFlow
import EmojiStatusComponent

View file

@ -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

View file

@ -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 {

View file

@ -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> {

View file

@ -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)
}

View file

@ -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

View file

@ -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 })

View file

@ -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",

View file

@ -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));
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
)

View file

@ -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 {

View file

@ -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",

View file

@ -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
),

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -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(),

View file

@ -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",

View file

@ -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 {

View file

@ -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 {

View file

@ -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",

View file

@ -1616,6 +1616,7 @@ private final class StoryContainerScreenComponent: Component {
inputHeight: environment.inputHeight,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
isEmbeddedInCamera: false,
isProgressPaused: isProgressPaused || i != focusedIndex,
isAudioMuted: self.audioMode == .off || (self.audioMode == .ambient && !(self.isMuteSwitchOn || self.areHeadphonesConnected)),
audioMode: self.audioMode,

View file

@ -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

View file

@ -108,6 +108,7 @@ public final class StoryItemSetContainerComponent: Component {
public let inputHeight: CGFloat
public let metrics: LayoutMetrics
public let deviceMetrics: DeviceMetrics
public let isEmbeddedInCamera: Bool
public let isProgressPaused: Bool
public let isAudioMuted: Bool
public let audioMode: StoryContentItem.AudioMode
@ -133,7 +134,7 @@ public final class StoryItemSetContainerComponent: Component {
let stealthModeTimeout: Int32?
public let isDismissed: Bool
init(
public init(
context: AccountContext,
externalState: ExternalState,
storyItemSharedState: StoryContentItem.SharedState,
@ -147,6 +148,7 @@ public final class StoryItemSetContainerComponent: Component {
inputHeight: CGFloat,
metrics: LayoutMetrics,
deviceMetrics: DeviceMetrics,
isEmbeddedInCamera: Bool,
isProgressPaused: Bool,
isAudioMuted: Bool,
audioMode: StoryContentItem.AudioMode,
@ -185,6 +187,7 @@ public final class StoryItemSetContainerComponent: Component {
self.inputHeight = inputHeight
self.metrics = metrics
self.deviceMetrics = deviceMetrics
self.isEmbeddedInCamera = isEmbeddedInCamera
self.isProgressPaused = isProgressPaused
self.isAudioMuted = isAudioMuted
self.audioMode = audioMode
@ -245,6 +248,9 @@ public final class StoryItemSetContainerComponent: Component {
if lhs.deviceMetrics != rhs.deviceMetrics {
return false
}
if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera {
return false
}
if lhs.isProgressPaused != rhs.isProgressPaused {
return false
}
@ -590,6 +596,10 @@ public final class StoryItemSetContainerComponent: Component {
return []
}
if let component = self.component, component.isEmbeddedInCamera {
return []
}
for (_, viewList) in self.viewLists {
if let view = viewList.view.view, view.hitTest(self.convert(point, to: view), with: nil) != nil {
return [.down]
@ -736,6 +746,19 @@ public final class StoryItemSetContainerComponent: Component {
self.updateDisposable.dispose()
}
public var mediaStreamCall: PresentationGroupCall? {
guard let component = self.component else {
return nil
}
guard let visibleItem = self.visibleItems[component.slice.item.id] else {
return nil
}
guard let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
return nil
}
return itemView.mediaStreamCall
}
func allowsExternalGestures(point: CGPoint) -> Bool {
if self.viewListDisplayState != .hidden {
return false
@ -1599,6 +1622,7 @@ public final class StoryItemSetContainerComponent: Component {
isVideoBuffering: visibleItem.isBuffering,
isCurrent: index == centralIndex,
preferHighQuality: component.slice.additionalPeerData.preferHighQualityStories,
isEmbeddedInCamera: component.isEmbeddedInCamera,
activateReaction: { [weak self] reactionView, reaction in
guard let self else {
return
@ -1644,6 +1668,7 @@ public final class StoryItemSetContainerComponent: Component {
itemTransition.setPosition(view: visibleItem.unclippedContainerView, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y))
itemTransition.setBounds(view: visibleItem.unclippedContainerView, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
visibleItem.contentTintLayer.isHidden = component.isEmbeddedInCamera
itemTransition.setPosition(layer: visibleItem.contentTintLayer, position: CGPoint(x: itemPositionX, y: itemLayout.contentFrame.center.y))
itemTransition.setBounds(layer: visibleItem.contentTintLayer, bounds: CGRect(origin: CGPoint(), size: itemLayout.contentFrame.size))
@ -3895,13 +3920,14 @@ public final class StoryItemSetContainerComponent: Component {
closeButtonFrame.origin.y -= contentBottomInsetOverflow
transition.setFrame(view: self.closeButton, frame: closeButtonFrame)
transition.setAlpha(view: self.closeButton, alpha: (component.hideUI || self.isEditingStory) ? 0.0 : 1.0)
transition.setAlpha(view: self.closeButton, alpha: (component.hideUI || self.isEditingStory || component.isEmbeddedInCamera) ? 0.0 : 1.0)
transition.setFrame(view: self.closeButtonIconView, frame: CGRect(origin: CGPoint(x: floor((closeButtonFrame.width - image.size.width) * 0.5), y: floor((closeButtonFrame.height - image.size.height) * 0.5)), size: image.size))
headerRightOffset -= 51.0
}
var moreButtonSize: CGSize?
if case let .user(user) = component.slice.peer, let botInfo = user.botInfo, !botInfo.flags.contains(.canEdit) {
} else if component.isEmbeddedInCamera {
} else {
moreButtonSize = self.moreButton.update(
transition: transition,
@ -4030,7 +4056,7 @@ public final class StoryItemSetContainerComponent: Component {
}
let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy?
if case .user = component.slice.effectivePeer {
if !component.isEmbeddedInCamera, case .user = component.slice.effectivePeer {
if component.slice.item.storyItem.isCloseFriends {
storyPrivacyIcon = .closeFriends
} else if component.slice.item.storyItem.isContacts {
@ -4284,6 +4310,7 @@ public final class StoryItemSetContainerComponent: Component {
let topContentGradientRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentFrame.width, height: topGradientHeight))
transition.setPosition(view: self.topContentGradientView, position: topContentGradientRect.center)
transition.setBounds(view: self.topContentGradientView, bounds: CGRect(origin: CGPoint(), size: topContentGradientRect.size))
self.topContentGradientView.isHidden = component.isEmbeddedInCamera
if let inputPanelFrame = inputPanelFrameValue {
var inputPanelAlpha: CGFloat = (component.hideUI || self.isEditingStory || component.slice.item.storyItem.isPending) ? 0.0 : 1.0
@ -5046,7 +5073,7 @@ public final class StoryItemSetContainerComponent: Component {
let navigationStripSideInset: CGFloat = 8.0
let navigationStripTopInset: CGFloat = 8.0
if let focusedItem, let visibleItem = self.visibleItems[focusedItem.id], let index = focusedItem.position {
if !component.isEmbeddedInCamera, let focusedItem, let visibleItem = self.visibleItems[focusedItem.id], let index = focusedItem.position {
var index = max(0, min(index, component.slice.totalCount - 1))
var count = component.slice.totalCount
if let dayCounters = focusedItem.dayCounters {
@ -5526,6 +5553,13 @@ public final class StoryItemSetContainerComponent: Component {
return
}
#if DEBUG
if "".isEmpty {
self.performDeleteAction()
return
}
#endif
self.isEditingStory = true
self.updateIsProgressPaused()
self.state?.updated(transition: .easeInOut(duration: 0.2))

View file

@ -37,7 +37,7 @@ import TextFieldComponent
import StickerPackPreviewUI
import OpenInExternalAppUI
import SafariServices
import MediaPasteboardUI
//import MediaPasteboardUI
import WebPBinding
import ContextUI
import ChatScheduleTimeController
@ -2403,71 +2403,71 @@ final class StoryItemSetContainerSendMessage {
}
func presentMediaPasteboard(view: StoryItemSetContainerComponent.View, subjects: [MediaPickerScreenImpl.Subject.Media]) {
guard let component = view.component else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
return
}
var inputText = NSAttributedString(string: "")
switch inputPanelView.getSendMessageInput() {
case let .text(text):
inputText = text
}
let peer = component.slice.effectivePeer
let theme = defaultDarkPresentationTheme
let updatedPresentationData: (initial: PresentationData, signal: Signal<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) {

View file

@ -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)
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "settings_30 (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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 })

View file

@ -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))

View file

@ -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

View file

@ -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)