mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Stories
This commit is contained in:
parent
99cb9d4832
commit
3d51d83e89
12 changed files with 305 additions and 171 deletions
|
|
@ -525,7 +525,7 @@ public protocol PresentationGroupCall: AnyObject {
|
|||
func disableScreencast()
|
||||
func switchVideoCamera()
|
||||
func updateDefaultParticipantsAreMuted(isMuted: Bool)
|
||||
func updateMessagesEnabled(isEnabled: Bool)
|
||||
func updateMessagesEnabled(isEnabled: Bool, sendPaidMessageStars: Int64?)
|
||||
func setVolume(peerId: EnginePeer.Id, volume: Int32, sync: Bool)
|
||||
func setRequestedVideoList(items: [PresentationGroupCallRequestedVideo])
|
||||
func setSuspendVideoChannelRequests(_ value: Bool)
|
||||
|
|
|
|||
|
|
@ -1156,11 +1156,6 @@ public final class AvatarNode: ASDisplayNode {
|
|||
|
||||
public let contentNode: ContentNode
|
||||
private var storyIndicator: ComponentView<Empty>?
|
||||
private var contentMaskView: UIView?
|
||||
private var storyIndicatorMaskView: UIView?
|
||||
private var liveBadgeMaskView: UIImageView?
|
||||
private var liveBadgeStoryIndicatorMaskView: UIImageView?
|
||||
private var liveBadgeView: UIImageView?
|
||||
public private(set) var storyPresentationParams: StoryPresentationParams?
|
||||
|
||||
private var loadingStatuses = Bag<Disposable>()
|
||||
|
|
@ -1188,7 +1183,6 @@ public final class AvatarNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
public private(set) var storyStats: StoryStats?
|
||||
public var displayLiveBadge: Bool = false
|
||||
|
||||
public var font: UIFont {
|
||||
get {
|
||||
|
|
@ -1504,124 +1498,6 @@ public final class AvatarNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.displayLiveBadge, let storyStats = self.storyStats, storyStats.hasLiveItems {
|
||||
let contentMaskView: UIView
|
||||
let storyIndicatorMaskView: UIView
|
||||
let liveBadgeMaskView: UIImageView
|
||||
let liveBadgeStoryIndicatorMaskView: UIImageView
|
||||
let liveBadgeView: UIImageView
|
||||
|
||||
var liveBadgeTransition = transition
|
||||
|
||||
if let current = self.contentMaskView {
|
||||
contentMaskView = current
|
||||
} else {
|
||||
liveBadgeTransition = liveBadgeTransition.withAnimation(.none)
|
||||
contentMaskView = UIView()
|
||||
contentMaskView.backgroundColor = .white
|
||||
if let filter = CALayer.luminanceToAlpha() {
|
||||
contentMaskView.layer.filters = [filter]
|
||||
}
|
||||
self.contentMaskView = contentMaskView
|
||||
self.contentNode.view.mask = contentMaskView
|
||||
}
|
||||
|
||||
if let current = self.storyIndicatorMaskView {
|
||||
storyIndicatorMaskView = current
|
||||
} else {
|
||||
storyIndicatorMaskView = UIView()
|
||||
storyIndicatorMaskView.backgroundColor = .white
|
||||
if let filter = CALayer.luminanceToAlpha() {
|
||||
storyIndicatorMaskView.layer.filters = [filter]
|
||||
}
|
||||
self.storyIndicatorMaskView = storyIndicatorMaskView
|
||||
self.storyIndicator?.view?.mask = storyIndicatorMaskView
|
||||
}
|
||||
|
||||
if let current = self.liveBadgeView {
|
||||
liveBadgeView = current
|
||||
} else {
|
||||
liveBadgeView = UIImageView()
|
||||
|
||||
//TODO:localize
|
||||
let liveString = NSAttributedString(string: "LIVE", font: Font.semibold(10.0), textColor: .white)
|
||||
let liveStringBounds = liveString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let liveBadgeSize = CGSize(width: ceil(liveStringBounds.width) + 4.0 * 2.0, height: ceil(liveStringBounds.height) + 2.0 * 2.0)
|
||||
liveBadgeView.image = generateImage(liveBadgeSize, rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
defer {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(rgb: 0xFF2D55).cgColor)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
|
||||
context.fillPath()
|
||||
|
||||
liveString.draw(at: CGPoint(x: floorToScreenPixels((size.width - liveStringBounds.width) * 0.5), y: floorToScreenPixels((size.height - liveStringBounds.height) * 0.5)))
|
||||
})
|
||||
|
||||
self.view.addSubview(liveBadgeView)
|
||||
self.liveBadgeView = liveBadgeView
|
||||
}
|
||||
|
||||
if let current = self.liveBadgeMaskView {
|
||||
liveBadgeMaskView = current
|
||||
} else {
|
||||
liveBadgeMaskView = UIImageView()
|
||||
self.liveBadgeMaskView = liveBadgeMaskView
|
||||
contentMaskView.addSubview(liveBadgeMaskView)
|
||||
|
||||
if let image = liveBadgeView.image {
|
||||
liveBadgeMaskView.image = generateStretchableFilledCircleImage(diameter: image.size.height + 2.0 * 2.0, color: .black)
|
||||
}
|
||||
}
|
||||
|
||||
if let current = self.liveBadgeStoryIndicatorMaskView {
|
||||
liveBadgeStoryIndicatorMaskView = current
|
||||
} else {
|
||||
liveBadgeStoryIndicatorMaskView = UIImageView()
|
||||
self.liveBadgeStoryIndicatorMaskView = liveBadgeStoryIndicatorMaskView
|
||||
storyIndicatorMaskView.addSubview(liveBadgeStoryIndicatorMaskView)
|
||||
|
||||
if let image = liveBadgeView.image {
|
||||
liveBadgeStoryIndicatorMaskView.image = generateStretchableFilledCircleImage(diameter: image.size.height + 2.0 * 2.0, color: .black)
|
||||
}
|
||||
}
|
||||
|
||||
liveBadgeTransition.setFrame(view: contentMaskView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
liveBadgeTransition.setFrame(view: storyIndicatorMaskView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -6.0, dy: -6.0))
|
||||
if let image = liveBadgeView.image {
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) * 0.5), y: size.height + 5.0 - image.size.height), size: image.size)
|
||||
liveBadgeTransition.setFrame(view: liveBadgeView, frame: badgeFrame)
|
||||
liveBadgeTransition.setFrame(view: liveBadgeMaskView, frame: self.contentNode.view.convert(badgeFrame.insetBy(dx: -2.0, dy: -2.0), from: self.view))
|
||||
liveBadgeTransition.setFrame(view: liveBadgeStoryIndicatorMaskView, frame: badgeFrame.insetBy(dx: -2.0, dy: -2.0).offsetBy(dx: 1.0, dy: 0.0))
|
||||
}
|
||||
} else {
|
||||
if let contentMaskView = self.contentMaskView {
|
||||
self.contentMaskView = nil
|
||||
contentMaskView.removeFromSuperview()
|
||||
self.contentNode.view.mask = nil
|
||||
}
|
||||
if let storyIndicatorMaskView = self.storyIndicatorMaskView {
|
||||
self.storyIndicatorMaskView = nil
|
||||
storyIndicatorMaskView.removeFromSuperview()
|
||||
self.storyIndicator?.view?.mask = nil
|
||||
}
|
||||
if let liveBadgeMaskView = self.liveBadgeMaskView {
|
||||
self.liveBadgeMaskView = nil
|
||||
liveBadgeMaskView.removeFromSuperview()
|
||||
}
|
||||
if let liveBadgeStoryIndicatorMaskView = self.liveBadgeStoryIndicatorMaskView {
|
||||
self.liveBadgeStoryIndicatorMaskView = nil
|
||||
liveBadgeStoryIndicatorMaskView.removeFromSuperview()
|
||||
}
|
||||
if let liveBadgeView = self.liveBadgeView {
|
||||
self.liveBadgeView = nil
|
||||
liveBadgeView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func cancelLoading() {
|
||||
|
|
|
|||
|
|
@ -1348,6 +1348,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
let onlineNode: PeerOnlineMarkerNode
|
||||
var avatarTimerBadge: AvatarBadgeView?
|
||||
private var starView: StarView?
|
||||
var avatarLiveBadge: (outline: UIImageView, foreground: UIImageView)?
|
||||
let pinnedIconNode: ASImageNode
|
||||
var secretIconNode: ASImageNode?
|
||||
var verifiedIconView: ComponentHostView<Empty>?
|
||||
|
|
@ -1601,7 +1602,6 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
|
||||
self.avatarContainerNode = ASDisplayNode()
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
self.avatarNode.displayLiveBadge = true
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
|
@ -1802,6 +1802,92 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
), transition: .immediate)
|
||||
self.avatarNode.isUserInteractionEnabled = storyState != nil
|
||||
|
||||
if let stats = storyState?.stats, stats.hasLiveItems {
|
||||
if self.avatarLiveBadge == nil {
|
||||
let avatarLiveBadge: (outline: UIImageView, foreground: UIImageView) = (UIImageView(), UIImageView())
|
||||
self.avatarLiveBadge = avatarLiveBadge
|
||||
self.avatarNode.view.addSubview(avatarLiveBadge.outline)
|
||||
self.avatarNode.view.addSubview(avatarLiveBadge.foreground)
|
||||
|
||||
//TODO:localize
|
||||
let liveString = NSAttributedString(string: "LIVE", font: Font.semibold(10.0), textColor: .white)
|
||||
let liveStringBounds = liveString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let liveBadgeSize = CGSize(width: ceil(liveStringBounds.width) + 4.0 * 2.0, height: ceil(liveStringBounds.height) + 2.0 * 2.0)
|
||||
avatarLiveBadge.foreground.image = generateImage(liveBadgeSize, rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
defer {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(rgb: 0xFF2D55).cgColor)
|
||||
|
||||
func roundedRectCgPath(roundRect rect: CGRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat) -> CGPath {
|
||||
let path = CGMutablePath()
|
||||
|
||||
let topLeft = rect.origin
|
||||
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
|
||||
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
|
||||
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
|
||||
|
||||
if topLeftRadius != .zero {
|
||||
path.move(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y))
|
||||
} else {
|
||||
path.move(to: CGPoint(x: topLeft.x, y: topLeft.y))
|
||||
}
|
||||
|
||||
if topRightRadius != .zero {
|
||||
path.addLine(to: CGPoint(x: topRight.x-topRightRadius, y: topRight.y))
|
||||
path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y + topRightRadius))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: topRight.x, y: topRight.y))
|
||||
}
|
||||
|
||||
if bottomRightRadius != .zero {
|
||||
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius))
|
||||
path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius, y: bottomRight.y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y))
|
||||
}
|
||||
|
||||
if bottomLeftRadius != .zero {
|
||||
path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius, y: bottomLeft.y))
|
||||
path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
|
||||
}
|
||||
|
||||
if topLeftRadius != .zero {
|
||||
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius))
|
||||
path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius, y: topLeft.y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
|
||||
}
|
||||
|
||||
path.closeSubpath()
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
let radius = size.height * 0.5
|
||||
context.addPath(roundedRectCgPath(roundRect: CGRect(origin: CGPoint(), size: size), topLeftRadius: radius, topRightRadius: radius, bottomLeftRadius: radius, bottomRightRadius: radius))
|
||||
context.fillPath()
|
||||
|
||||
liveString.draw(at: CGPoint(x: floorToScreenPixels((size.width - liveStringBounds.width) * 0.5), y: floorToScreenPixels((size.height - liveStringBounds.height) * 0.5)))
|
||||
})
|
||||
|
||||
if let image = avatarLiveBadge.foreground.image {
|
||||
avatarLiveBadge.outline.image = generateStretchableFilledCircleImage(diameter: image.size.height + 2.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let avatarLiveBadge = self.avatarLiveBadge {
|
||||
self.avatarLiveBadge = nil
|
||||
avatarLiveBadge.outline.removeFromSuperview()
|
||||
avatarLiveBadge.foreground.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let peer = peer {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if case let .peer(peerData) = item.content, peerData.customMessageListData != nil {
|
||||
|
|
@ -2027,6 +2113,21 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
self.starView?.setOutlineColor(effectiveBackgroundColor, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let item = self.item {
|
||||
if let avatarLiveBadge = self.avatarLiveBadge {
|
||||
let effectiveBackgroundColor: UIColor
|
||||
if item.isPinned {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
}
|
||||
|
||||
let highlightAlpha = self.highlightedBackgroundNode.supernode == nil ? 0.0 : self.highlightedBackgroundNode.alpha
|
||||
let outlineColor = item.presentationData.theme.chatList.itemHighlightedBackgroundColor.mixedWith(effectiveBackgroundColor, alpha: 1.0 - highlightAlpha)
|
||||
transition.updateTintColor(view: avatarLiveBadge.outline, color: outlineColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func tapped() {
|
||||
|
|
@ -4019,6 +4120,13 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
transition.updateFrame(node: strongSelf.onlineNode, frame: onlineFrame)
|
||||
|
||||
if let avatarLiveBadge = strongSelf.avatarLiveBadge, let iconImage = avatarLiveBadge.foreground.image, let outlineImage = avatarLiveBadge.outline.image {
|
||||
let outlineInset = (outlineImage.size.height - iconImage.size.height) * 0.5
|
||||
let liveBadgeFrame = CGRect(origin: CGPoint(x: floor((avatarFrame.width - iconImage.size.width) * 0.5), y: avatarFrame.height + 5.0 - iconImage.size.height), size: iconImage.size)
|
||||
transition.updateFrame(view: avatarLiveBadge.foreground, frame: liveBadgeFrame)
|
||||
transition.updateFrame(view: avatarLiveBadge.outline, frame: liveBadgeFrame.insetBy(dx: -outlineInset, dy: -outlineInset))
|
||||
}
|
||||
|
||||
let onlineInlineNavigationFraction: CGFloat = item.interaction.inlineNavigationLocation?.progress ?? 0.0
|
||||
transition.updateAlpha(node: strongSelf.onlineNode, alpha: 1.0 - onlineInlineNavigationFraction)
|
||||
transition.updateSublayerTransformScale(node: strongSelf.onlineNode, scale: (1.0 - onlineInlineNavigationFraction) * 1.0 + onlineInlineNavigationFraction * 0.00001)
|
||||
|
|
|
|||
|
|
@ -3988,8 +3988,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||
self.participantsContext?.updateDefaultParticipantsAreMuted(isMuted: isMuted)
|
||||
}
|
||||
|
||||
public func updateMessagesEnabled(isEnabled: Bool) {
|
||||
self.participantsContext?.updateMessagesEnabled(isEnabled: isEnabled)
|
||||
public func updateMessagesEnabled(isEnabled: Bool, sendPaidMessageStars: Int64?) {
|
||||
self.participantsContext?.updateMessagesEnabled(isEnabled: isEnabled, sendPaidMessageStars: sendPaidMessageStars)
|
||||
}
|
||||
|
||||
func video(endpointId: String) -> Signal<OngoingGroupCallContext.VideoFrameData, NoError>? {
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ extension VideoChatCall {
|
|||
func setMessagesEnabled(isEnabled: Bool) {
|
||||
switch self {
|
||||
case let .group(group):
|
||||
group.updateMessagesEnabled(isEnabled: isEnabled)
|
||||
group.updateMessagesEnabled(isEnabled: isEnabled, sendPaidMessageStars: nil)
|
||||
case .conferenceSource:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2643,13 +2643,18 @@ public final class GroupCallParticipantsContext {
|
|||
}))
|
||||
}
|
||||
|
||||
public func updateMessagesEnabled(isEnabled: Bool) {
|
||||
if isEnabled == self.stateValue.state.messagesAreEnabled.isEnabled {
|
||||
public func updateMessagesEnabled(isEnabled: Bool, sendPaidMessageStars: Int64?) {
|
||||
if isEnabled == self.stateValue.state.messagesAreEnabled.isEnabled && self.stateValue.state.messagesAreEnabled.sendPaidMessagesStars == sendPaidMessageStars {
|
||||
return
|
||||
}
|
||||
self.stateValue.state.messagesAreEnabled.isEnabled = isEnabled
|
||||
self.stateValue.state.messagesAreEnabled.sendPaidMessagesStars = sendPaidMessageStars
|
||||
|
||||
self.updateMessagesEnabledDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse, sendPaidMessagesStars: nil))
|
||||
var flags: Int32 = 1 << 2
|
||||
if sendPaidMessageStars != nil {
|
||||
flags |= 1 << 3
|
||||
}
|
||||
self.updateMessagesEnabledDisposable.set((self.account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 2, call: self.reference.apiInputGroupCall, joinMuted: nil, messagesEnabled: isEnabled ? .boolTrue : .boolFalse, sendPaidMessagesStars: sendPaidMessageStars))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -349,7 +349,14 @@ final class LiveStreamSettingsScreenComponent: Component {
|
|||
contentHeight += sectionSpacing
|
||||
}
|
||||
|
||||
if screenState.sendAsPeerId?.namespace != Namespaces.Peer.CloudChannel {
|
||||
var displayPrivacy = true
|
||||
if screenState.sendAsPeerId?.namespace == Namespaces.Peer.CloudChannel {
|
||||
displayPrivacy = false
|
||||
} else if screenState.call != nil && screenState.isEdit {
|
||||
displayPrivacy = false
|
||||
}
|
||||
|
||||
if displayPrivacy {
|
||||
var privacySectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
||||
|
|
@ -659,7 +666,7 @@ final class LiveStreamSettingsScreenComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
if !screenState.isEdit {
|
||||
if !screenState.isEdit || (screenState.call != nil && screenState.isEdit) {
|
||||
let externalStreamSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||
ListActionItemComponent(
|
||||
theme: theme,
|
||||
|
|
@ -703,6 +710,7 @@ final class LiveStreamSettingsScreenComponent: Component {
|
|||
}
|
||||
|
||||
|
||||
//TODO:localize
|
||||
var settingsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
settingsSectionItems.append(AnyComponentWithIdentity(id: "comments", component: AnyComponent(ListActionItemComponent(
|
||||
theme: theme,
|
||||
|
|
@ -725,26 +733,29 @@ final class LiveStreamSettingsScreenComponent: Component {
|
|||
action: nil
|
||||
))))
|
||||
|
||||
settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Allow Screenshots",
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
if !(screenState.call != nil && screenState.isEdit) {
|
||||
//TODO:localize
|
||||
settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Allow Screenshots",
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: !screenState.isForwardingDisabled, action: { [weak self] _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.stateContext.isForwardingDisabled = !component.stateContext.isForwardingDisabled
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
})),
|
||||
action: nil
|
||||
))))
|
||||
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: !screenState.isForwardingDisabled, action: { [weak self] _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.stateContext.isForwardingDisabled = !component.stateContext.isForwardingDisabled
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
})),
|
||||
action: nil
|
||||
))))
|
||||
}
|
||||
|
||||
let settingsSectionSize = self.settingsSection.update(
|
||||
transition: transition,
|
||||
|
|
@ -1025,7 +1036,7 @@ final class LiveStreamSettingsScreenComponent: Component {
|
|||
public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
||||
public enum Mode {
|
||||
case create(sendAsPeerId: EnginePeer.Id?, isCustomTarget: Bool, privacy: EngineStoryPrivacy, allowComments: Bool, isForwardingDisabled: Bool, pin: Bool, paidMessageStars: Int64)
|
||||
case edit(PresentationGroupCall)
|
||||
case edit(call: PresentationGroupCall, displayPrivacy: Bool)
|
||||
}
|
||||
|
||||
public struct Result {
|
||||
|
|
@ -1088,6 +1099,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
|
||||
final class State {
|
||||
var call: PresentationGroupCall?
|
||||
var isEdit: Bool
|
||||
var maxPaidMessageStars: Int64
|
||||
var sendAsPeerId: EnginePeer.Id?
|
||||
|
|
@ -1105,6 +1117,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
var grayListPeers: [EnginePeer]
|
||||
|
||||
init(
|
||||
call: PresentationGroupCall?,
|
||||
isEdit: Bool,
|
||||
maxPaidMessageStars: Int64,
|
||||
sendAsPeerId: EnginePeer.Id?,
|
||||
|
|
@ -1121,6 +1134,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
closeFriendsPeers: [EnginePeer],
|
||||
grayListPeers: [EnginePeer]
|
||||
) {
|
||||
self.call = call
|
||||
self.isEdit = isEdit
|
||||
self.maxPaidMessageStars = maxPaidMessageStars
|
||||
self.sendAsPeerId = sendAsPeerId
|
||||
|
|
@ -1140,6 +1154,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
|
||||
public final class StateContext {
|
||||
let mode: LiveStreamSettingsScreen.Mode
|
||||
let blockedPeersContext: BlockedPeersContext?
|
||||
|
||||
var stateValue: State?
|
||||
|
|
@ -1211,6 +1226,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
adminedChannels: Signal<[EnginePeer], NoError>,
|
||||
blockedPeersContext: BlockedPeersContext?
|
||||
) {
|
||||
self.mode = mode
|
||||
self.blockedPeersContext = blockedPeersContext
|
||||
|
||||
let grayListPeers: Signal<[EnginePeer], NoError>
|
||||
|
|
@ -1337,6 +1353,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
savedSelectedPeers[.contacts] = contactsPeers
|
||||
savedSelectedPeers[.nobody] = selectedPeers
|
||||
|
||||
let call: PresentationGroupCall?
|
||||
let isEdit: Bool
|
||||
let maxPaidMessageStars: Int64 = 10000
|
||||
let sendAsPeerId: EnginePeer.Id?
|
||||
|
|
@ -1348,6 +1365,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
let paidMessageStars: Int64
|
||||
|
||||
if let current = self.stateValue {
|
||||
call = current.call
|
||||
isEdit = current.isEdit
|
||||
sendAsPeerId = current.sendAsPeerId
|
||||
isCustomTarget = current.isCustomTarget
|
||||
|
|
@ -1359,6 +1377,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
} else {
|
||||
switch mode {
|
||||
case let .create(sendAsPeerIdValue, isCustomTargetValue, privacyValue, allowCommentsValue, isForwardingDisabledValue, pinValue, paidMessageStarsValue):
|
||||
call = nil
|
||||
isEdit = false
|
||||
sendAsPeerId = sendAsPeerIdValue
|
||||
isCustomTarget = isCustomTargetValue
|
||||
|
|
@ -1367,8 +1386,8 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
isForwardingDisabled = isForwardingDisabledValue
|
||||
pin = pinValue
|
||||
paidMessageStars = paidMessageStarsValue
|
||||
case let .edit(call):
|
||||
let _ = call
|
||||
case let .edit(callValue, _):
|
||||
call = callValue
|
||||
isEdit = true
|
||||
sendAsPeerId = nil
|
||||
isCustomTarget = false
|
||||
|
|
@ -1381,6 +1400,7 @@ public class LiveStreamSettingsScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
|
||||
let state = State(
|
||||
call: call,
|
||||
isEdit: isEdit,
|
||||
maxPaidMessageStars: maxPaidMessageStars,
|
||||
sendAsPeerId: sendAsPeerId,
|
||||
|
|
|
|||
|
|
@ -4249,23 +4249,13 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
var currentLeftInfoItem: InfoItem?
|
||||
if focusedItem != nil {
|
||||
let leftInfoComponent = AnyComponent(StoryAvatarInfoComponent(context: component.context, peer: component.slice.effectivePeer, isLiveStream: isLiveStream))
|
||||
if let leftInfoItem = self.leftInfoItem, leftInfoItem.component == leftInfoComponent {
|
||||
if let leftInfoItem = self.leftInfoItem {
|
||||
currentLeftInfoItem = leftInfoItem
|
||||
} else {
|
||||
currentLeftInfoItem = InfoItem(component: leftInfoComponent)
|
||||
}
|
||||
}
|
||||
|
||||
if let leftInfoItem = self.leftInfoItem, currentLeftInfoItem?.component != leftInfoItem.component {
|
||||
self.leftInfoItem = nil
|
||||
if let view = leftInfoItem.view.view {
|
||||
view.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in
|
||||
view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var currentCenterInfoItem: InfoItem?
|
||||
if let focusedItem {
|
||||
var counters: StoryAuthorInfoComponent.Counters?
|
||||
|
|
@ -4295,7 +4285,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
isLiveStream: isLiveStream,
|
||||
customSubtitle: customSubtitle
|
||||
))
|
||||
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent {
|
||||
if let centerInfoItem = self.centerInfoItem {
|
||||
currentCenterInfoItem = centerInfoItem
|
||||
} else {
|
||||
currentCenterInfoItem = InfoItem(component: centerInfoComponent)
|
||||
|
|
@ -6644,6 +6634,19 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
|
||||
if case .liveStream = component.slice.item.storyItem.media {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Live Settings", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.sendMessageContext.displayLiveStreamSettings(view: self)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, a in
|
||||
|
|
@ -6921,6 +6924,21 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
component.controller()?.present(tooltipScreen, in: .current)
|
||||
})))
|
||||
|
||||
if channel.hasPermission(.postStories) {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Live Settings", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.sendMessageContext.displayLiveStreamSettings(view: self)
|
||||
})))
|
||||
}
|
||||
|
||||
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
|
||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import ChatMessagePaymentAlertController
|
|||
import ChatSendStarsScreen
|
||||
import AnimatedTextComponent
|
||||
import ChatSendAsContextMenu
|
||||
import ShareWithPeersScreen
|
||||
|
||||
private var ObjCKey_DeinitWatcher: Int?
|
||||
|
||||
|
|
@ -4391,6 +4392,39 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
|
|||
self.isSelectingSendAsPeer = true
|
||||
view.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
|
||||
func displayLiveStreamSettings(view: StoryItemSetContainerComponent.View) {
|
||||
Task { @MainActor [weak self, weak view] in
|
||||
guard let self, let view, let component = view.component, let controller = component.controller(), let mediaStreamCall = view.mediaStreamCall else {
|
||||
return
|
||||
}
|
||||
|
||||
let stateContext = LiveStreamSettingsScreen.StateContext(
|
||||
context: component.context,
|
||||
mode: .edit(call: mediaStreamCall, displayPrivacy: false),
|
||||
closeFriends: component.closeFriends.get(),
|
||||
adminedChannels: .single([]),
|
||||
blockedPeersContext: nil
|
||||
)
|
||||
let _ = await (stateContext.ready |> filter { $0 } |> take(1)).get()
|
||||
let settingsScreen = LiveStreamSettingsScreen(
|
||||
context: component.context,
|
||||
stateContext: stateContext,
|
||||
editCategory: { _, _, _, _, _ in
|
||||
},
|
||||
editBlockedPeers: { _, _, _, _, _ in
|
||||
},
|
||||
completion: { [weak self, weak view] result in
|
||||
guard let self, let view, let call = view.mediaStreamCall else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
call.updateMessagesEnabled(isEnabled: result.allowComments, sendPaidMessageStars: result.paidMessageStars)
|
||||
}
|
||||
)
|
||||
controller.push(settingsScreen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class StoryProgressPauseContext {
|
||||
|
|
|
|||
|
|
@ -843,12 +843,22 @@ public final class StoryPeerListItemComponent: Component {
|
|||
if let image = avatarLiveBadgeView.image {
|
||||
let badgeSize = image.size
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - badgeSize.width) * 0.5), y: avatarFrame.height + 5.0 - badgeSize.height), size: badgeSize)
|
||||
avatarLiveBadgeTransition.setFrame(view: avatarLiveBadgeView, frame: badgeFrame)
|
||||
avatarLiveBadgeTransition.setPosition(view: avatarLiveBadgeView, position: badgeFrame.center)
|
||||
avatarLiveBadgeTransition.setBounds(view: avatarLiveBadgeView, bounds: CGRect(origin: CGPoint(), size: badgeFrame.size))
|
||||
|
||||
avatarLiveBadgeTransition.setScale(view: avatarLiveBadgeView, scale: max(0.001, component.expandedAlphaFraction))
|
||||
avatarLiveBadgeTransition.setAlpha(view: avatarLiveBadgeView, alpha: component.expandedAlphaFraction)
|
||||
|
||||
avatarLiveBadgeMaskSeenLayer.frame = badgeFrame.offsetBy(dx: 8.0, dy: 8.0).insetBy(dx: -2.0, dy: -2.0)
|
||||
avatarLiveBadgeMaskSeenLayer.cornerRadius = avatarLiveBadgeMaskSeenLayer.bounds.height * 0.5
|
||||
avatarLiveBadgeMaskUnseenLayer.frame = badgeFrame.offsetBy(dx: 8.0, dy: 8.0).insetBy(dx: -2.0, dy: -2.0)
|
||||
avatarLiveBadgeMaskUnseenLayer.cornerRadius = avatarLiveBadgeMaskUnseenLayer.bounds.height * 0.5
|
||||
|
||||
avatarLiveBadgeTransition.setScale(layer: avatarLiveBadgeMaskSeenLayer, scale: max(0.001, component.expandedAlphaFraction))
|
||||
avatarLiveBadgeTransition.setAlpha(layer: avatarLiveBadgeMaskSeenLayer, alpha: component.expandedAlphaFraction)
|
||||
|
||||
avatarLiveBadgeTransition.setScale(layer: avatarLiveBadgeMaskUnseenLayer, scale: max(0.001, component.expandedAlphaFraction))
|
||||
avatarLiveBadgeTransition.setAlpha(layer: avatarLiveBadgeMaskUnseenLayer, alpha: component.expandedAlphaFraction)
|
||||
}
|
||||
} else {
|
||||
if let avatarLiveBadgeView = self.avatarLiveBadgeView {
|
||||
|
|
|
|||
|
|
@ -254,6 +254,18 @@ static void registerEffectViewOverrides(void) {
|
|||
if (@available(iOS 26.0, *)) {
|
||||
registerEffectViewOverrides();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Class cls = NSClassFromString(@"WKBrowsingContextController");
|
||||
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
|
||||
if ([cls respondsToSelector:sel]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[cls performSelector:sel withObject:@"http"];
|
||||
[cls performSelector:sel withObject:@"https"];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,53 @@ public func generateWebAppThemeParams(_ theme: PresentationTheme) -> [String: An
|
|||
]
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private let registeredProtocols: Void = {
|
||||
/*class AppURLProtocol: URLProtocol {
|
||||
var urlTask: URLSessionDataTask?
|
||||
|
||||
override class func canInit(with request: URLRequest) -> Bool {
|
||||
if request.url?.scheme == "https" {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
||||
return request
|
||||
}
|
||||
|
||||
override func startLoading() {
|
||||
super.startLoading()
|
||||
|
||||
/*if self.urlTask != nil {
|
||||
return
|
||||
}
|
||||
self.urlTask = URLSession.shared.dataTask(with: self.request, completionHandler: { [weak self] _, response, error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let error {
|
||||
self.client?.urlProtocol(self, didFailWithError: error)
|
||||
} else {
|
||||
if let response = response as? HTTPURLResponse {
|
||||
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
||||
} else {
|
||||
}
|
||||
self.client?.urlProtocolDidFinishLoading(self)
|
||||
}
|
||||
})
|
||||
self.urlTask?.resume()*/
|
||||
}
|
||||
|
||||
override func stopLoading() {
|
||||
self.urlTask?.cancel()
|
||||
}
|
||||
}
|
||||
URLProtocol.registerClass(AppURLProtocol.self)*/
|
||||
}()
|
||||
#endif
|
||||
|
||||
public final class WebAppController: ViewController, AttachmentContainable {
|
||||
public var requestAttachmentMenuExpansion: () -> Void = { }
|
||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
|
|
@ -203,6 +250,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
init(context: AccountContext, controller: WebAppController) {
|
||||
#if DEBUG
|
||||
let _ = registeredProtocols
|
||||
#endif
|
||||
|
||||
self.context = context
|
||||
self.controller = controller
|
||||
self.presentationData = controller.presentationData
|
||||
|
|
@ -428,7 +479,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
|
||||
private func load(url: URL) {
|
||||
#if DEBUG
|
||||
/*#if DEBUG
|
||||
if "".isEmpty {
|
||||
if #available(iOS 16.0, *) {
|
||||
let documentsPath = URL.documentsDirectory.path(percentEncoded: false)
|
||||
|
|
@ -464,7 +515,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
|
||||
return
|
||||
}
|
||||
#endif
|
||||
#endif*/
|
||||
self.webView?.load(URLRequest(url: url))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue