mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
c9772c5fd7
commit
28bd7511a6
@ -178,6 +178,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||||||
case storiesDualCameraTooltip = 44
|
case storiesDualCameraTooltip = 44
|
||||||
case displayChatListArchiveTooltip = 45
|
case displayChatListArchiveTooltip = 45
|
||||||
case displayStoryReactionTooltip = 46
|
case displayStoryReactionTooltip = 46
|
||||||
|
case storyStealthModeReplyCount = 47
|
||||||
|
|
||||||
var key: ValueBoxKey {
|
var key: ValueBoxKey {
|
||||||
let v = ValueBoxKey(length: 4)
|
let v = ValueBoxKey(length: 4)
|
||||||
@ -419,6 +420,10 @@ private struct ApplicationSpecificNoticeKeys {
|
|||||||
static func displayStoryReactionTooltip() -> NoticeEntryKey {
|
static func displayStoryReactionTooltip() -> NoticeEntryKey {
|
||||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryReactionTooltip.key)
|
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryReactionTooltip.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func storyStealthModeReplyCount() -> NoticeEntryKey {
|
||||||
|
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storyStealthModeReplyCount.key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ApplicationSpecificNotice {
|
public struct ApplicationSpecificNotice {
|
||||||
@ -1580,4 +1585,30 @@ public struct ApplicationSpecificNotice {
|
|||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func storyStealthModeReplyCount(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int, NoError> {
|
||||||
|
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.storyStealthModeReplyCount())
|
||||||
|
|> map { view -> Int in
|
||||||
|
if let value = view.value?.get(ApplicationSpecificCounterNotice.self) {
|
||||||
|
return Int(value.value)
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func incrementStoryStealthModeReplyCount(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
|
||||||
|
return accountManager.transaction { transaction -> Void in
|
||||||
|
var value: Int32 = 0
|
||||||
|
if let item = transaction.getNotice(ApplicationSpecificNoticeKeys.storyStealthModeReplyCount())?.get(ApplicationSpecificCounterNotice.self) {
|
||||||
|
value = item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value + 1)) {
|
||||||
|
transaction.setNotice(ApplicationSpecificNoticeKeys.storyStealthModeReplyCount(), entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ public final class OptionButtonComponent: Component {
|
|||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
let size = CGSize(width: 52.0, height: 28.0)
|
let size = CGSize(width: 53.0, height: 28.0)
|
||||||
|
|
||||||
if previousComponent?.colors.background != component.colors.background {
|
if previousComponent?.colors.background != component.colors.background {
|
||||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: size.height, color: component.colors.background)
|
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: size.height, color: component.colors.background)
|
||||||
@ -121,8 +121,8 @@ public final class OptionButtonComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let iconSize = self.iconView.image?.size, let arrowSize = self.arrowView.image?.size {
|
if let iconSize = self.iconView.image?.size, let arrowSize = self.arrowView.image?.size {
|
||||||
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: 3.0, y: floor((size.height - iconSize.height) * 0.5)), size: iconSize))
|
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: 4.0, y: floor((size.height - iconSize.height) * 0.5)), size: iconSize))
|
||||||
transition.setFrame(view: self.arrowView, frame: CGRect(origin: CGPoint(x: size.width - 8.0 - arrowSize.width, y: floor((size.height - arrowSize.height) * 0.5)), size: arrowSize))
|
transition.setFrame(view: self.arrowView, frame: CGRect(origin: CGPoint(x: size.width - 8.0 - arrowSize.width, y: 1.0 + floor((size.height - arrowSize.height) * 0.5)), size: arrowSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
@ -541,7 +541,13 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let _ = self
|
|
||||||
|
if self.itemsContainerView.frame.contains(point) {
|
||||||
|
if !self.isPointInsideContentArea(point: point) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [.down]
|
return [.down]
|
||||||
})
|
})
|
||||||
verticalPanRecognizer.delegate = self
|
verticalPanRecognizer.delegate = self
|
||||||
@ -1029,6 +1035,14 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verticalPanState.startContentOffsetY = recognizer.translation(in: self).y
|
verticalPanState.startContentOffsetY = recognizer.translation(in: self).y
|
||||||
|
} else {
|
||||||
|
if verticalPanState.fraction <= -0.15 {
|
||||||
|
if let activate = self.activateInputWhileDragging() {
|
||||||
|
recognizer.state = .cancelled
|
||||||
|
|
||||||
|
activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
@ -1268,7 +1282,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let currentContentScale = itemLayout.contentMinScale * itemLayout.contentScaleFraction + 1.0 * (1.0 - itemLayout.contentScaleFraction)
|
let currentContentScale = itemLayout.contentMinScale * itemLayout.contentScaleFraction + 1.0 * (1.0 - itemLayout.contentScaleFraction)
|
||||||
let scaledCentralVisibleItemWidth = itemLayout.contentFrame.width * currentContentScale
|
let scaledCentralVisibleItemWidth = itemLayout.contentFrame.width * currentContentScale
|
||||||
let scaledSideVisibleItemWidth = scaledCentralVisibleItemWidth - 30.0 * itemLayout.contentScaleFraction
|
let scaledSideVisibleItemWidth = scaledCentralVisibleItemWidth - 54.0 * itemLayout.contentScaleFraction
|
||||||
let scaledFullItemScrollDistance = scaledCentralVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
let scaledFullItemScrollDistance = scaledCentralVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
||||||
let scaledHalfItemScrollDistance = scaledSideVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
let scaledHalfItemScrollDistance = scaledSideVisibleItemWidth * 0.5 + itemLayout.itemSpacing + scaledSideVisibleItemWidth * 0.5
|
||||||
|
|
||||||
@ -3803,135 +3817,154 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.reactionContextNode = reactionContextNode
|
self.reactionContextNode = reactionContextNode
|
||||||
|
|
||||||
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in
|
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in
|
||||||
guard let self, let component = self.component else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let action: () -> Void = { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.displayLikeReactions {
|
||||||
|
if component.slice.item.storyItem.myReaction == updateReaction.reaction {
|
||||||
|
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: nil).start()
|
||||||
|
self.displayLikeReactions = false
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||||
|
} else {
|
||||||
|
if hasFirstResponder(self) {
|
||||||
|
self.sendMessageContext.currentInputMode = .text
|
||||||
|
self.endEditing(true)
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||||
|
|
||||||
|
self.waitingForReactionAnimateOutToLike = updateReaction.reaction
|
||||||
|
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: updateReaction.reaction).start()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = (component.context.engine.stickers.availableReactions()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak reactionContextNode] availableReactions in
|
||||||
|
guard let self, let component = self.component, let availableReactions else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var animation: TelegramMediaFile?
|
||||||
|
for reaction in availableReactions.reactions {
|
||||||
|
if reaction.value == updateReaction.reaction {
|
||||||
|
animation = reaction.centerAnimation
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetView = UIView(frame: CGRect(origin: CGPoint(x: floor((self.bounds.width - 100.0) * 0.5), y: floor((self.bounds.height - 100.0) * 0.5)), size: CGSize(width: 100.0, height: 100.0)))
|
||||||
|
targetView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(targetView)
|
||||||
|
|
||||||
|
if let reactionContextNode {
|
||||||
|
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
||||||
|
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
standaloneReactionAnimation.frame = self.bounds
|
||||||
|
self.addSubview(standaloneReactionAnimation.view)
|
||||||
|
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||||
|
targetView?.removeFromSuperview()
|
||||||
|
if let reactionContextNode {
|
||||||
|
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
||||||
|
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||||
|
reactionContextNode?.view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFirstResponder(self) {
|
||||||
|
self.sendMessageContext.currentInputMode = .text
|
||||||
|
self.endEditing(true)
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||||
|
|
||||||
|
var text = ""
|
||||||
|
var messageAttributes: [MessageAttribute] = []
|
||||||
|
var inlineStickers: [MediaId : Media] = [:]
|
||||||
|
switch updateReaction {
|
||||||
|
case let .builtin(textValue):
|
||||||
|
text = textValue
|
||||||
|
case let .custom(fileId, file):
|
||||||
|
if let file {
|
||||||
|
animation = file
|
||||||
|
loop: for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .CustomEmoji(_, _, displayText, _):
|
||||||
|
text = displayText
|
||||||
|
let length = (text as NSString).length
|
||||||
|
messageAttributes = [TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< length, type: .CustomEmoji(stickerPack: nil, fileId: fileId))])]
|
||||||
|
inlineStickers = [file.fileId: file]
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let message: EnqueueMessage = .message(
|
||||||
|
text: text,
|
||||||
|
attributes: messageAttributes,
|
||||||
|
inlineStickers: inlineStickers,
|
||||||
|
mediaReference: nil,
|
||||||
|
replyToMessageId: nil,
|
||||||
|
replyToStoryId: StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id),
|
||||||
|
localGroupingKey: nil,
|
||||||
|
correlationId: nil,
|
||||||
|
bubbleUpEmojiOrStickersets: []
|
||||||
|
)
|
||||||
|
|
||||||
|
let context = component.context
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
|
let presentController = component.presentController
|
||||||
|
let peer = component.slice.peer
|
||||||
|
|
||||||
|
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
||||||
|
if let animation, let self, let component = self.component {
|
||||||
|
let controller = UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .sticker(context: context, file: animation, loop: false, title: nil, text: component.strings.Story_ToastReactionSent, undoText: component.strings.Story_ToastViewInChat, customAction: { [weak self] in
|
||||||
|
if let messageId = messageIds.first, let self {
|
||||||
|
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
elevatedLayout: false,
|
||||||
|
animateInAsReplacement: false,
|
||||||
|
action: { [weak self] _ in
|
||||||
|
self?.sendMessageContext.tooltipScreen = nil
|
||||||
|
self?.updateIsProgressPaused()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.sendMessageContext.tooltipScreen?.dismiss()
|
||||||
|
self.sendMessageContext.tooltipScreen = controller
|
||||||
|
self.updateIsProgressPaused()
|
||||||
|
presentController(controller, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.displayLikeReactions {
|
if self.displayLikeReactions {
|
||||||
if component.slice.item.storyItem.myReaction == updateReaction.reaction {
|
if component.slice.item.storyItem.myReaction == updateReaction.reaction {
|
||||||
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: nil).start()
|
action()
|
||||||
self.displayLikeReactions = false
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
|
||||||
} else {
|
} else {
|
||||||
if hasFirstResponder(self) {
|
self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: {
|
||||||
self.sendMessageContext.currentInputMode = .text
|
action()
|
||||||
self.endEditing(true)
|
})
|
||||||
}
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
|
||||||
|
|
||||||
self.waitingForReactionAnimateOutToLike = updateReaction.reaction
|
|
||||||
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: updateReaction.reaction).start()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = (component.context.engine.stickers.availableReactions()
|
self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: {
|
||||||
|> take(1)
|
action()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak reactionContextNode] availableReactions in
|
|
||||||
guard let self, let component = self.component, let availableReactions else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var animation: TelegramMediaFile?
|
|
||||||
for reaction in availableReactions.reactions {
|
|
||||||
if reaction.value == updateReaction.reaction {
|
|
||||||
animation = reaction.centerAnimation
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetView = UIView(frame: CGRect(origin: CGPoint(x: floor((self.bounds.width - 100.0) * 0.5), y: floor((self.bounds.height - 100.0) * 0.5)), size: CGSize(width: 100.0, height: 100.0)))
|
|
||||||
targetView.isUserInteractionEnabled = false
|
|
||||||
self.addSubview(targetView)
|
|
||||||
|
|
||||||
if let reactionContextNode {
|
|
||||||
reactionContextNode.willAnimateOutToReaction(value: updateReaction.reaction)
|
|
||||||
reactionContextNode.animateOutToReaction(value: updateReaction.reaction, targetView: targetView, hideNode: false, animateTargetContainer: nil, addStandaloneReactionAnimation: "".isEmpty ? nil : { [weak self] standaloneReactionAnimation in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
standaloneReactionAnimation.frame = self.bounds
|
|
||||||
self.addSubview(standaloneReactionAnimation.view)
|
|
||||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
|
||||||
targetView?.removeFromSuperview()
|
|
||||||
if let reactionContextNode {
|
|
||||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
|
||||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
|
||||||
reactionContextNode?.view.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasFirstResponder(self) {
|
|
||||||
self.sendMessageContext.currentInputMode = .text
|
|
||||||
self.endEditing(true)
|
|
||||||
}
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
|
||||||
|
|
||||||
var text = ""
|
|
||||||
var messageAttributes: [MessageAttribute] = []
|
|
||||||
var inlineStickers: [MediaId : Media] = [:]
|
|
||||||
switch updateReaction {
|
|
||||||
case let .builtin(textValue):
|
|
||||||
text = textValue
|
|
||||||
case let .custom(fileId, file):
|
|
||||||
if let file {
|
|
||||||
animation = file
|
|
||||||
loop: for attribute in file.attributes {
|
|
||||||
switch attribute {
|
|
||||||
case let .CustomEmoji(_, _, displayText, _):
|
|
||||||
text = displayText
|
|
||||||
let length = (text as NSString).length
|
|
||||||
messageAttributes = [TextEntitiesMessageAttribute(entities: [MessageTextEntity(range: 0 ..< length, type: .CustomEmoji(stickerPack: nil, fileId: fileId))])]
|
|
||||||
inlineStickers = [file.fileId: file]
|
|
||||||
break loop
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let message: EnqueueMessage = .message(
|
|
||||||
text: text,
|
|
||||||
attributes: messageAttributes,
|
|
||||||
inlineStickers: inlineStickers,
|
|
||||||
mediaReference: nil,
|
|
||||||
replyToMessageId: nil,
|
|
||||||
replyToStoryId: StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id),
|
|
||||||
localGroupingKey: nil,
|
|
||||||
correlationId: nil,
|
|
||||||
bubbleUpEmojiOrStickersets: []
|
|
||||||
)
|
|
||||||
|
|
||||||
let context = component.context
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
|
||||||
let presentController = component.presentController
|
|
||||||
let peer = component.slice.peer
|
|
||||||
|
|
||||||
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: [message])
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
|
||||||
if let animation, let self, let component = self.component {
|
|
||||||
let controller = UndoOverlayController(
|
|
||||||
presentationData: presentationData,
|
|
||||||
content: .sticker(context: context, file: animation, loop: false, title: nil, text: component.strings.Story_ToastReactionSent, undoText: component.strings.Story_ToastViewInChat, customAction: { [weak self] in
|
|
||||||
if let messageId = messageIds.first, let self {
|
|
||||||
self.navigateToPeer(peer: peer, chat: true, subject: messageId.flatMap { .message(id: .id($0), highlight: false, timecode: nil) })
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
elevatedLayout: false,
|
|
||||||
animateInAsReplacement: false,
|
|
||||||
action: { [weak self] _ in
|
|
||||||
self?.sendMessageContext.tooltipScreen = nil
|
|
||||||
self?.updateIsProgressPaused()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.sendMessageContext.tooltipScreen?.dismiss()
|
|
||||||
self.sendMessageContext.tooltipScreen = controller
|
|
||||||
self.updateIsProgressPaused()
|
|
||||||
presentController(controller, nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4805,59 +4838,73 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let likeButtonView = inputPanelView.likeButtonView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: component.slice.item.storyItem.myReaction == nil ? .builtin("❤") : nil).start()
|
let action: () -> Void = { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let likeButtonView = inputPanelView.likeButtonView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: component.slice.item.storyItem.myReaction == nil ? .builtin("❤") : nil).start()
|
||||||
|
|
||||||
|
if component.slice.item.storyItem.myReaction != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var reactionItem: ReactionItem?
|
||||||
|
guard let availableReactions = component.availableReactions else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for item in availableReactions.reactionItems {
|
||||||
|
if case .builtin("❤") = item.reaction.rawValue {
|
||||||
|
reactionItem = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let reactionItem else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
|
||||||
|
self.componentContainerView.addSubnode(standaloneReactionAnimation)
|
||||||
|
standaloneReactionAnimation.frame = self.bounds
|
||||||
|
standaloneReactionAnimation.animateReactionSelection(
|
||||||
|
context: component.context,
|
||||||
|
theme: component.theme,
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
reaction: reactionItem,
|
||||||
|
avatarPeers: [],
|
||||||
|
playHaptic: true,
|
||||||
|
isLarge: false,
|
||||||
|
hideCenterAnimation: true,
|
||||||
|
targetView: likeButtonView,
|
||||||
|
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneReactionAnimation.frame = self.bounds
|
||||||
|
self.componentContainerView.addSubnode(standaloneReactionAnimation)
|
||||||
|
},
|
||||||
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
|
standaloneReactionAnimation?.removeFromSupernode()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if component.slice.item.storyItem.myReaction != nil {
|
if component.slice.item.storyItem.myReaction != nil {
|
||||||
return
|
action()
|
||||||
|
} else {
|
||||||
|
self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: {
|
||||||
|
action()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var reactionItem: ReactionItem?
|
|
||||||
guard let availableReactions = component.availableReactions else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for item in availableReactions.reactionItems {
|
|
||||||
if case .builtin("❤") = item.reaction.rawValue {
|
|
||||||
reactionItem = item
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let reactionItem else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false)
|
|
||||||
self.componentContainerView.addSubnode(standaloneReactionAnimation)
|
|
||||||
standaloneReactionAnimation.frame = self.bounds
|
|
||||||
standaloneReactionAnimation.animateReactionSelection(
|
|
||||||
context: component.context,
|
|
||||||
theme: component.theme,
|
|
||||||
animationCache: component.context.animationCache,
|
|
||||||
reaction: reactionItem,
|
|
||||||
avatarPeers: [],
|
|
||||||
playHaptic: true,
|
|
||||||
isLarge: false,
|
|
||||||
hideCenterAnimation: true,
|
|
||||||
targetView: likeButtonView,
|
|
||||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
standaloneReactionAnimation.frame = self.bounds
|
|
||||||
self.componentContainerView.addSubnode(standaloneReactionAnimation)
|
|
||||||
},
|
|
||||||
completion: { [weak standaloneReactionAnimation] in
|
|
||||||
standaloneReactionAnimation?.removeFromSupernode()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performLikeOptionsAction(sourceView: UIView) {
|
private func performLikeOptionsAction(sourceView: UIView) {
|
||||||
|
@ -483,125 +483,191 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
view.updateIsProgressPaused()
|
view.updateIsProgressPaused()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func performWithPossibleStealthModeConfirmation(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) {
|
||||||
|
guard let component = view.component, component.stealthModeTimeout != nil else {
|
||||||
|
action()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (combineLatest(
|
||||||
|
component.context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState()
|
||||||
|
),
|
||||||
|
ApplicationSpecificNotice.storyStealthModeReplyCount(accountManager: component.context.sharedContext.accountManager)
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak view] data, noticeCount in
|
||||||
|
let config = data
|
||||||
|
|
||||||
|
guard let self, let view, let component = view.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||||
|
if noticeCount < 3, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp {
|
||||||
|
|
||||||
|
let theme = component.theme
|
||||||
|
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) })
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let alertController = textAlertController(
|
||||||
|
context: component.context,
|
||||||
|
updatedPresentationData: updatedPresentationData,
|
||||||
|
title: "You are in Stealth Mode now",
|
||||||
|
text: "If you send a reply or reaction, the creator of the story will also see you in the list of viewers.",
|
||||||
|
actions: [
|
||||||
|
TextAlertAction(type: .defaultAction, title: "Cancel", action: {}),
|
||||||
|
TextAlertAction(type: .genericAction, title: "Proceed", action: {
|
||||||
|
action()
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
alertController.dismissed = { [weak self, weak view] _ in
|
||||||
|
guard let self, let view else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.actionSheet = nil
|
||||||
|
view.updateIsProgressPaused()
|
||||||
|
}
|
||||||
|
self.actionSheet = alertController
|
||||||
|
view.updateIsProgressPaused()
|
||||||
|
|
||||||
|
component.controller()?.presentInGlobalOverlay(alertController)
|
||||||
|
} else {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func performSendMessageAction(
|
func performSendMessageAction(
|
||||||
view: StoryItemSetContainerComponent.View,
|
view: StoryItemSetContainerComponent.View,
|
||||||
silentPosting: Bool = false,
|
silentPosting: Bool = false,
|
||||||
scheduleTime: Int32? = nil
|
scheduleTime: Int32? = nil
|
||||||
) {
|
) {
|
||||||
guard let component = view.component else {
|
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in
|
||||||
return
|
guard let self, let view else {
|
||||||
}
|
return
|
||||||
let focusedItem = component.slice.item
|
}
|
||||||
guard let peerId = focusedItem.peerId else {
|
guard let component = view.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
|
|
||||||
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let peer = component.slice.peer
|
|
||||||
|
|
||||||
let controller = component.controller() as? StoryContainerScreen
|
|
||||||
|
|
||||||
if let recordedAudioPreview = self.recordedAudioPreview {
|
|
||||||
self.recordedAudioPreview = nil
|
|
||||||
|
|
||||||
let waveformBuffer = recordedAudioPreview.waveform.makeBitstream()
|
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
|
||||||
|
}
|
||||||
|
let peer = component.slice.peer
|
||||||
|
|
||||||
let messages: [EnqueueMessage] = [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: recordedAudioPreview.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(recordedAudioPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedAudioPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
|
|
||||||
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start()
|
if let recordedAudioPreview = self.recordedAudioPreview {
|
||||||
|
self.recordedAudioPreview = nil
|
||||||
view.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
|
||||||
} else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue {
|
let waveformBuffer = recordedAudioPreview.waveform.makeBitstream()
|
||||||
videoRecorderValue.send()
|
|
||||||
self.hasRecordedVideoPreview = false
|
let messages: [EnqueueMessage] = [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: recordedAudioPreview.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(recordedAudioPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedAudioPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
||||||
self.videoRecorder.set(.single(nil))
|
|
||||||
view.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start()
|
||||||
} else {
|
|
||||||
switch inputPanelView.getSendMessageInput() {
|
view.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
case let .text(text):
|
} else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue {
|
||||||
if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
videoRecorderValue.send()
|
||||||
let entities = generateChatInputTextEntities(text)
|
self.hasRecordedVideoPreview = false
|
||||||
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
|
self.videoRecorder.set(.single(nil))
|
||||||
to: peerId,
|
view.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
replyTo: nil,
|
} else {
|
||||||
storyId: focusedStoryId,
|
switch inputPanelView.getSendMessageInput() {
|
||||||
content: .text(text.string, entities),
|
case let .text(text):
|
||||||
silentPosting: silentPosting,
|
if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
scheduleTime: scheduleTime
|
let entities = generateChatInputTextEntities(text)
|
||||||
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
|
||||||
Queue.mainQueue().after(0.3) {
|
to: peerId,
|
||||||
if let self, let view {
|
replyTo: nil,
|
||||||
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
|
storyId: focusedStoryId,
|
||||||
|
content: .text(text.string, entities),
|
||||||
|
silentPosting: silentPosting,
|
||||||
|
scheduleTime: scheduleTime
|
||||||
|
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
|
Queue.mainQueue().after(0.3) {
|
||||||
|
if let self, let view {
|
||||||
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
inputPanelView.clearSendMessageInput()
|
||||||
|
|
||||||
|
self.currentInputMode = .text
|
||||||
|
if hasFirstResponder(view) {
|
||||||
|
view.endEditing(true)
|
||||||
|
} else {
|
||||||
|
view.state?.updated(transition: .spring(duration: 0.3))
|
||||||
}
|
}
|
||||||
})
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
inputPanelView.clearSendMessageInput()
|
|
||||||
|
|
||||||
self.currentInputMode = .text
|
|
||||||
if hasFirstResponder(view) {
|
|
||||||
view.endEditing(true)
|
|
||||||
} else {
|
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
|
||||||
}
|
}
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) {
|
|
||||||
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)
|
|
||||||
let peer = component.slice.peer
|
|
||||||
|
|
||||||
let controller = component.controller() as? StoryContainerScreen
|
|
||||||
|
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
|
||||||
var controllers = navigationController.viewControllers
|
|
||||||
for controller in controllers.reversed() {
|
|
||||||
if !(controller is StoryContainerScreen) {
|
|
||||||
controllers.removeLast()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
|
||||||
|
|
||||||
controller?.window?.forEachController({ controller in
|
|
||||||
if let controller = controller as? StickerPackScreenImpl {
|
|
||||||
controller.dismiss()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
|
|
||||||
to: peerId,
|
|
||||||
replyTo: nil,
|
|
||||||
storyId: focusedStoryId,
|
|
||||||
content: .file(fileReference)
|
|
||||||
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
|
||||||
Queue.mainQueue().after(0.3) {
|
|
||||||
if let self, let view {
|
|
||||||
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
self.currentInputMode = .text
|
|
||||||
if hasFirstResponder(view) {
|
func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) {
|
||||||
view.endEditing(true)
|
self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in
|
||||||
} else {
|
guard let self, let view else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
return
|
||||||
}
|
}
|
||||||
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
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)
|
||||||
|
let peer = component.slice.peer
|
||||||
|
|
||||||
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
|
|
||||||
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
|
var controllers = navigationController.viewControllers
|
||||||
|
for controller in controllers.reversed() {
|
||||||
|
if !(controller is StoryContainerScreen) {
|
||||||
|
controllers.removeLast()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
|
|
||||||
|
controller?.window?.forEachController({ controller in
|
||||||
|
if let controller = controller as? StickerPackScreenImpl {
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (component.context.engine.messages.enqueueOutgoingMessage(
|
||||||
|
to: peerId,
|
||||||
|
replyTo: nil,
|
||||||
|
storyId: focusedStoryId,
|
||||||
|
content: .file(fileReference)
|
||||||
|
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
|
Queue.mainQueue().after(0.3) {
|
||||||
|
if let self, let view {
|
||||||
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.currentInputMode = .text
|
||||||
|
if hasFirstResponder(view) {
|
||||||
|
view.endEditing(true)
|
||||||
|
} else {
|
||||||
|
view.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
}
|
||||||
|
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) {
|
func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) {
|
||||||
|
@ -1139,7 +1139,10 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
let _ = self
|
let _ = self
|
||||||
|
|
||||||
sourceView?.alpha = 1.0
|
if let sourceView {
|
||||||
|
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||||
|
transition.setAlpha(view: sourceView, alpha: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
controller.present(contextController, in: .window(.root))
|
controller.present(contextController, in: .window(.root))
|
||||||
}
|
}
|
||||||
@ -1170,7 +1173,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
component: AnyComponent(TabSelectorComponent(
|
component: AnyComponent(TabSelectorComponent(
|
||||||
colors: TabSelectorComponent.Colors(
|
colors: TabSelectorComponent.Colors(
|
||||||
foreground: .white,
|
foreground: .white,
|
||||||
selection: UIColor(rgb: 0xffffff, alpha: 0.2)
|
selection: UIColor(rgb: 0xffffff, alpha: 0.09)
|
||||||
),
|
),
|
||||||
items: [
|
items: [
|
||||||
TabSelectorComponent.Item(
|
TabSelectorComponent.Item(
|
||||||
@ -1220,7 +1223,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(OptionButtonComponent(
|
component: AnyComponent(OptionButtonComponent(
|
||||||
colors: OptionButtonComponent.Colors(
|
colors: OptionButtonComponent.Colors(
|
||||||
background: UIColor(rgb: 0x767680, alpha: 0.2),
|
background: UIColor(rgb: 0xffffff, alpha: 0.09),
|
||||||
foreground: .white
|
foreground: .white
|
||||||
),
|
),
|
||||||
icon: self.sortMode == .recentFirst ? "Chat/Context Menu/Time" : "Chat/Context Menu/Reactions",
|
icon: self.sortMode == .recentFirst ? "Chat/Context Menu/Time" : "Chat/Context Menu/Reactions",
|
||||||
@ -1281,11 +1284,11 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
if !component.hasPremium, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
||||||
} else {
|
} else {
|
||||||
if let views = component.storyItem.views {
|
if let views = component.storyItem.views {
|
||||||
if views.seenCount >= 20 {
|
if views.seenCount >= 20 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
||||||
displayModeSelector = true
|
displayModeSelector = true
|
||||||
displaySearchBar = true
|
displaySearchBar = true
|
||||||
}
|
}
|
||||||
if views.reactedCount >= 10 {
|
if views.reactedCount >= 10 || component.context.sharedContext.immediateExperimentalUISettings.storiesExperiment {
|
||||||
displaySortSelector = true
|
displaySortSelector = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1299,9 +1302,9 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if component.isSearchActive {
|
if component.isSearchActive {
|
||||||
navigationHeight = navigationSearchSize.height
|
navigationHeight = navigationSearchSize.height
|
||||||
} else if displaySearchBar {
|
} else if displaySearchBar {
|
||||||
navigationHeight = 50.0 + navigationSearchSize.height
|
navigationHeight = 56.0 + navigationSearchSize.height - 6.0
|
||||||
} else {
|
} else {
|
||||||
navigationHeight = 50.0
|
navigationHeight = 56.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - visualHeight + 12.0), size: CGSize(width: availableSize.width, height: navigationHeight))
|
let navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - visualHeight + 12.0), size: CGSize(width: availableSize.width, height: navigationHeight))
|
||||||
@ -1313,7 +1316,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.navigationContainerView.addSubview(tabSelectorView)
|
self.navigationContainerView.addSubview(tabSelectorView)
|
||||||
}
|
}
|
||||||
tabSelectorView.isHidden = !displayModeSelector
|
tabSelectorView.isHidden = !displayModeSelector
|
||||||
transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) * 0.5), y: floor((50.0 - tabSelectorSize.height) * 0.5) + (component.isSearchActive ? (-50.0) : 0.0)), size: tabSelectorSize))
|
transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) * 0.5), y: floor((56.0 - tabSelectorSize.height) * 0.5) + (component.isSearchActive ? (-56.0) : 0.0)), size: tabSelectorSize))
|
||||||
transition.setAlpha(view: tabSelectorView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
transition.setAlpha(view: tabSelectorView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
if let titleView = self.title.view {
|
if let titleView = self.title.view {
|
||||||
@ -1322,7 +1325,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
}
|
}
|
||||||
titleView.isHidden = displayModeSelector
|
titleView.isHidden = displayModeSelector
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((50.0 - titleSize.height) * 0.5) + (component.isSearchActive ? (-50.0) : 0.0)), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((56.0 - titleSize.height) * 0.5) + (component.isSearchActive ? (-56.0) : 0.0)), size: titleSize)
|
||||||
|
|
||||||
transition.setFrame(view: titleView, frame: titleFrame)
|
transition.setFrame(view: titleView, frame: titleFrame)
|
||||||
transition.setAlpha(view: titleView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
transition.setAlpha(view: titleView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
||||||
@ -1332,7 +1335,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if orderSelectorView.superview == nil {
|
if orderSelectorView.superview == nil {
|
||||||
self.navigationContainerView.addSubview(orderSelectorView)
|
self.navigationContainerView.addSubview(orderSelectorView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: orderSelectorView, frame: CGRect(origin: CGPoint(x: availableSize.width - sideInset - orderSelectorSize.width, y: floor((50.0 - orderSelectorSize.height) * 0.5) + (component.isSearchActive ? (-50.0) : 0.0)), size: orderSelectorSize))
|
transition.setFrame(view: orderSelectorView, frame: CGRect(origin: CGPoint(x: availableSize.width - sideInset - orderSelectorSize.width, y: floor((56.0 - orderSelectorSize.height) * 0.5) + (component.isSearchActive ? (-56.0) : 0.0)), size: orderSelectorSize))
|
||||||
transition.setAlpha(view: orderSelectorView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
transition.setAlpha(view: orderSelectorView, alpha: component.isSearchActive ? 0.0 : 1.0)
|
||||||
|
|
||||||
orderSelectorView.isHidden = !displaySortSelector
|
orderSelectorView.isHidden = !displaySortSelector
|
||||||
|
@ -155,7 +155,7 @@ public final class TabSelectorComponent: Component {
|
|||||||
}
|
}
|
||||||
itemTransition.setPosition(view: itemTitleView, position: itemTitleFrame.origin)
|
itemTransition.setPosition(view: itemTitleView, position: itemTitleFrame.origin)
|
||||||
itemTransition.setBounds(view: itemTitleView, bounds: CGRect(origin: CGPoint(), size: itemTitleFrame.size))
|
itemTransition.setBounds(view: itemTitleView, bounds: CGRect(origin: CGPoint(), size: itemTitleFrame.size))
|
||||||
itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId ? 1.0 : 0.6)
|
itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId ? 1.0 : 0.4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user