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
e0c9a3075d
commit
458a153bad
57
submodules/ComponentFlow/Source/Components/HStack.swift
Normal file
57
submodules/ComponentFlow/Source/Components/HStack.swift
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||||
|
public typealias EnvironmentType = ChildEnvironment
|
||||||
|
|
||||||
|
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||||
|
private let spacing: CGFloat
|
||||||
|
|
||||||
|
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], spacing: CGFloat) {
|
||||||
|
self.items = items
|
||||||
|
self.spacing = spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: HStack<ChildEnvironment>, rhs: HStack<ChildEnvironment>) -> Bool {
|
||||||
|
if lhs.items != rhs.items {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.spacing != rhs.spacing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var body: Body {
|
||||||
|
let children = ChildMap(environment: ChildEnvironment.self, keyedBy: AnyHashable.self)
|
||||||
|
|
||||||
|
return { context in
|
||||||
|
let updatedChildren = context.component.items.map { item in
|
||||||
|
return children[item.id].update(
|
||||||
|
component: item.component, environment: {
|
||||||
|
context.environment[ChildEnvironment.self]
|
||||||
|
},
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = CGSize(width: 0.0, height: 0.0)
|
||||||
|
for child in updatedChildren {
|
||||||
|
size.width += child.size.width
|
||||||
|
size.height = max(size.height, child.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextX = 0.0
|
||||||
|
for child in updatedChildren {
|
||||||
|
context.add(child
|
||||||
|
.position(child.size.centered(in: CGRect(origin: CGPoint(x: nextX, y: floor((size.height - child.size.height) * 0.5)), size: child.size)).center)
|
||||||
|
)
|
||||||
|
nextX += child.size.width
|
||||||
|
nextX += context.component.spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -578,7 +578,7 @@ private func privacyAndSecurityControllerEntries(
|
|||||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium))
|
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium))
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
//entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
||||||
} else {
|
} else {
|
||||||
entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, presentationData.strings.Channel_NotificationLoading))
|
||||||
@ -591,7 +591,7 @@ private func privacyAndSecurityControllerEntries(
|
|||||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium))
|
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium))
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
//entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if canAutoarchive {
|
if canAutoarchive {
|
||||||
|
@ -82,17 +82,20 @@ public final class LottieComponent: Component {
|
|||||||
public let color: UIColor?
|
public let color: UIColor?
|
||||||
public let startingPosition: StartingPosition
|
public let startingPosition: StartingPosition
|
||||||
public let size: CGSize?
|
public let size: CGSize?
|
||||||
|
public let loop: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
content: Content,
|
content: Content,
|
||||||
color: UIColor? = nil,
|
color: UIColor? = nil,
|
||||||
startingPosition: StartingPosition = .end,
|
startingPosition: StartingPosition = .end,
|
||||||
size: CGSize? = nil
|
size: CGSize? = nil,
|
||||||
|
loop: Bool = false
|
||||||
) {
|
) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.color = color
|
self.color = color
|
||||||
self.startingPosition = startingPosition
|
self.startingPosition = startingPosition
|
||||||
self.size = size
|
self.size = size
|
||||||
|
self.loop = loop
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: LottieComponent, rhs: LottieComponent) -> Bool {
|
public static func ==(lhs: LottieComponent, rhs: LottieComponent) -> Bool {
|
||||||
@ -108,6 +111,9 @@ public final class LottieComponent: Component {
|
|||||||
if lhs.size != rhs.size {
|
if lhs.size != rhs.size {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.loop != rhs.loop {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +122,8 @@ public final class LottieComponent: Component {
|
|||||||
private var component: LottieComponent?
|
private var component: LottieComponent?
|
||||||
|
|
||||||
private var scheduledPlayOnce: Bool = false
|
private var scheduledPlayOnce: Bool = false
|
||||||
|
private var isPlaying: Bool = false
|
||||||
|
|
||||||
private var playOnceCompletion: (() -> Void)?
|
private var playOnceCompletion: (() -> Void)?
|
||||||
private var animationInstance: LottieInstance?
|
private var animationInstance: LottieInstance?
|
||||||
private var animationFrameRange: Range<Int>?
|
private var animationFrameRange: Range<Int>?
|
||||||
@ -196,6 +204,7 @@ public final class LottieComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.scheduledPlayOnce = false
|
self.scheduledPlayOnce = false
|
||||||
|
self.isPlaying = true
|
||||||
|
|
||||||
if self.currentFrame != animationFrameRange.lowerBound {
|
if self.currentFrame != animationFrameRange.lowerBound {
|
||||||
self.currentFrame = animationFrameRange.lowerBound
|
self.currentFrame = animationFrameRange.lowerBound
|
||||||
@ -284,11 +293,19 @@ public final class LottieComponent: Component {
|
|||||||
advanceFrameCount = 4
|
advanceFrameCount = 4
|
||||||
}
|
}
|
||||||
self.currentFrame += advanceFrameCount
|
self.currentFrame += advanceFrameCount
|
||||||
|
|
||||||
|
if self.currentFrame >= animationFrameRange.upperBound - 1 {
|
||||||
|
if let component = self.component, component.loop {
|
||||||
|
self.currentFrame = animationFrameRange.lowerBound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.currentFrame >= animationFrameRange.upperBound - 1 {
|
if self.currentFrame >= animationFrameRange.upperBound - 1 {
|
||||||
self.currentFrame = animationFrameRange.upperBound - 1
|
self.currentFrame = animationFrameRange.upperBound - 1
|
||||||
self.updateImage()
|
self.updateImage()
|
||||||
self.displayLink?.invalidate()
|
self.displayLink?.invalidate()
|
||||||
self.displayLink = nil
|
self.displayLink = nil
|
||||||
|
self.isPlaying = false
|
||||||
|
|
||||||
if let playOnceCompletion = self.playOnceCompletion {
|
if let playOnceCompletion = self.playOnceCompletion {
|
||||||
self.playOnceCompletion = nil
|
self.playOnceCompletion = nil
|
||||||
@ -362,6 +379,10 @@ public final class LottieComponent: Component {
|
|||||||
transition.setTintColor(view: self, color: color)
|
transition.setTintColor(view: self, color: color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if component.loop && !self.isPlaying {
|
||||||
|
self.playOnce()
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,9 @@ public final class NavigationSearchComponent: Component {
|
|||||||
private let searchIconView: UIImageView
|
private let searchIconView: UIImageView
|
||||||
private let placeholderText = ComponentView<Empty>()
|
private let placeholderText = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private let clearButton: HighlightableButton
|
||||||
|
private let clearIconView: UIImageView
|
||||||
|
|
||||||
private var button: ComponentView<Empty>?
|
private var button: ComponentView<Empty>?
|
||||||
|
|
||||||
private var textField: UITextField?
|
private var textField: UITextField?
|
||||||
@ -85,12 +88,21 @@ public final class NavigationSearchComponent: Component {
|
|||||||
|
|
||||||
self.searchIconView = UIImageView(image: UIImage(bundleImageName: "Components/Search Bar/Loupe")?.withRenderingMode(.alwaysTemplate))
|
self.searchIconView = UIImageView(image: UIImage(bundleImageName: "Components/Search Bar/Loupe")?.withRenderingMode(.alwaysTemplate))
|
||||||
|
|
||||||
|
self.clearButton = HighlightableButton()
|
||||||
|
self.clearIconView = UIImageView(image: UIImage(bundleImageName: "Components/Search Bar/Clear")?.withRenderingMode(.alwaysTemplate))
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
self.addSubview(self.backgroundView)
|
||||||
self.addSubview(self.searchIconView)
|
self.addSubview(self.searchIconView)
|
||||||
|
|
||||||
|
self.addSubview(self.clearButton)
|
||||||
|
self.clearButton.addSubview(self.clearIconView)
|
||||||
|
self.clearButton.isHidden = true
|
||||||
|
|
||||||
self.backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.backgroundTapGesture(_:))))
|
self.backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.backgroundTapGesture(_:))))
|
||||||
|
|
||||||
|
self.clearButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -109,6 +121,7 @@ public final class NavigationSearchComponent: Component {
|
|||||||
textField.addTarget(self, action: #selector(self.textChanged), for: .editingChanged)
|
textField.addTarget(self, action: #selector(self.textChanged), for: .editingChanged)
|
||||||
self.addSubview(textField)
|
self.addSubview(textField)
|
||||||
textField.keyboardAppearance = .dark
|
textField.keyboardAppearance = .dark
|
||||||
|
textField.returnKeyType = .done
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textField?.becomeFirstResponder()
|
self.textField?.becomeFirstResponder()
|
||||||
@ -124,14 +137,26 @@ public final class NavigationSearchComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
textField.resignFirstResponder()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func textChanged() {
|
@objc private func textChanged() {
|
||||||
self.updateText(updateComponent: true)
|
self.updateText(updateComponent: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func clearPressed() {
|
||||||
|
self.textField?.text = ""
|
||||||
|
self.updateText(updateComponent: true)
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func updateText(updateComponent: Bool) {
|
@objc private func updateText(updateComponent: Bool) {
|
||||||
let isEmpty = self.textField?.text?.isEmpty ?? true
|
let isEmpty = self.textField?.text?.isEmpty ?? true
|
||||||
self.placeholderText.view?.isHidden = !isEmpty
|
self.placeholderText.view?.isHidden = !isEmpty
|
||||||
|
|
||||||
|
self.clearButton.isHidden = isEmpty
|
||||||
|
|
||||||
if updateComponent, let component = self.component {
|
if updateComponent, let component = self.component {
|
||||||
component.updateQuery(self.textField?.text ?? "")
|
component.updateQuery(self.textField?.text ?? "")
|
||||||
}
|
}
|
||||||
@ -212,6 +237,7 @@ public final class NavigationSearchComponent: Component {
|
|||||||
}
|
}
|
||||||
if previousComponent?.colors.inactiveForeground != component.colors.inactiveForeground {
|
if previousComponent?.colors.inactiveForeground != component.colors.inactiveForeground {
|
||||||
self.searchIconView.tintColor = component.colors.inactiveForeground
|
self.searchIconView.tintColor = component.colors.inactiveForeground
|
||||||
|
self.clearIconView.tintColor = component.colors.inactiveForeground
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholderSize = self.placeholderText.update(
|
let placeholderSize = self.placeholderText.update(
|
||||||
@ -241,6 +267,15 @@ public final class NavigationSearchComponent: Component {
|
|||||||
let searchIconFrame = CGRect(origin: CGPoint(x: placeholderTextFrame.minX - searchIconSpacing - searchIconSize.width, y: backgroundFrame.minY + floor((fieldHeight - searchIconSize.height) * 0.5)), size: searchIconSize)
|
let searchIconFrame = CGRect(origin: CGPoint(x: placeholderTextFrame.minX - searchIconSpacing - searchIconSize.width, y: backgroundFrame.minY + floor((fieldHeight - searchIconSize.height) * 0.5)), size: searchIconSize)
|
||||||
transition.setFrame(view: self.searchIconView, frame: searchIconFrame)
|
transition.setFrame(view: self.searchIconView, frame: searchIconFrame)
|
||||||
|
|
||||||
|
if let image = self.clearIconView.image {
|
||||||
|
let clearSize = image.size
|
||||||
|
let clearFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 6.0 - clearSize.width, y: backgroundFrame.minY + floor((backgroundFrame.height - clearSize.height) / 2.0)), size: clearSize)
|
||||||
|
|
||||||
|
let clearButtonFrame = CGRect(origin: CGPoint(x: clearFrame.minX - 4.0, y: backgroundFrame.minY), size: CGSize(width: clearFrame.width + 8.0, height: backgroundFrame.height))
|
||||||
|
transition.setFrame(view: self.clearButton, frame: clearButtonFrame)
|
||||||
|
transition.setFrame(view: self.clearIconView, frame: clearFrame.offsetBy(dx: -clearButtonFrame.minX, dy: -clearButtonFrame.minY))
|
||||||
|
}
|
||||||
|
|
||||||
if let textField = self.textField {
|
if let textField = self.textField {
|
||||||
var textFieldTransition = transition
|
var textFieldTransition = transition
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
@ -255,7 +290,7 @@ public final class NavigationSearchComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let textLeftInset: CGFloat = fieldSideInset + searchIconSize.width + searchIconSpacing
|
let textLeftInset: CGFloat = fieldSideInset + searchIconSize.width + searchIconSpacing
|
||||||
let textRightInset: CGFloat = 8.0
|
let textRightInset: CGFloat = 8.0 + 30.0
|
||||||
textFieldTransition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: placeholderTextFrame.minX, y: backgroundFrame.minY - 1.0), size: CGSize(width: backgroundFrame.width - textLeftInset - textRightInset, height: backgroundFrame.height)))
|
textFieldTransition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: placeholderTextFrame.minX, y: backgroundFrame.minY - 1.0), size: CGSize(width: backgroundFrame.width - textLeftInset - textRightInset, height: backgroundFrame.height)))
|
||||||
|
|
||||||
if animateIn {
|
if animateIn {
|
||||||
|
@ -287,7 +287,7 @@ final class StorageUsageScreenComponent: Component {
|
|||||||
case .misc:
|
case .misc:
|
||||||
return UIColor(rgb: 0xFF9500)
|
return UIColor(rgb: 0xFF9500)
|
||||||
case .stories:
|
case .stories:
|
||||||
return UIColor(rgb: 0x3478F6)
|
return UIColor(rgb: 0xFF2D55)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,7 +888,10 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
controller.forEachController { controller in
|
controller.forEachController { controller in
|
||||||
if let controller = controller as? UndoOverlayController {
|
if let controller = controller as? UndoOverlayController {
|
||||||
controller.dismissWithCommitAction()
|
if let tag = controller.tag as? String, tag == "no_auto_dismiss" {
|
||||||
|
} else {
|
||||||
|
controller.dismissWithCommitAction()
|
||||||
|
}
|
||||||
} else if let controller = controller as? TooltipScreen {
|
} else if let controller = controller as? TooltipScreen {
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -348,6 +348,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
weak var scrollView: UIScrollView?
|
weak var scrollView: UIScrollView?
|
||||||
var startContentOffsetY: CGFloat = 0.0
|
var startContentOffsetY: CGFloat = 0.0
|
||||||
var accumulatedOffset: CGFloat = 0.0
|
var accumulatedOffset: CGFloat = 0.0
|
||||||
|
var dismissedTooltips: Bool = false
|
||||||
|
|
||||||
init(fraction: CGFloat, scrollView: UIScrollView?) {
|
init(fraction: CGFloat, scrollView: UIScrollView?) {
|
||||||
self.fraction = fraction
|
self.fraction = fraction
|
||||||
@ -998,6 +999,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let verticalPanState = self.verticalPanState {
|
if let verticalPanState = self.verticalPanState {
|
||||||
|
if abs(verticalPanState.fraction) >= 0.1 && !verticalPanState.dismissedTooltips {
|
||||||
|
verticalPanState.dismissedTooltips = true
|
||||||
|
self.dismissAllTooltips()
|
||||||
|
}
|
||||||
|
|
||||||
if let scrollView = verticalPanState.scrollView {
|
if let scrollView = verticalPanState.scrollView {
|
||||||
let relativeTranslationY = recognizer.translation(in: self).y - verticalPanState.startContentOffsetY
|
let relativeTranslationY = recognizer.translation(in: self).y - verticalPanState.startContentOffsetY
|
||||||
let overflowY = scrollView.contentOffset.y - relativeTranslationY
|
let overflowY = scrollView.contentOffset.y - relativeTranslationY
|
||||||
@ -1061,6 +1067,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
} else if verticalPanState.fraction >= 0.05 && velocity.y >= -80.0 {
|
} else if verticalPanState.fraction >= 0.05 && velocity.y >= -80.0 {
|
||||||
self.viewListDisplayState = .half
|
self.viewListDisplayState = .half
|
||||||
}
|
}
|
||||||
|
self.dismissAllTooltips()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
@ -1073,6 +1080,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if component.slice.peer.id == component.context.account.peerId {
|
if component.slice.peer.id == component.context.account.peerId {
|
||||||
self.viewListDisplayState = self.targetViewListDisplayStateIsFull ? .full : .half
|
self.viewListDisplayState = self.targetViewListDisplayStateIsFull ? .full : .half
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
|
self.dismissAllTooltips()
|
||||||
} else {
|
} else {
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
|
|
||||||
@ -1511,6 +1519,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.preparingToDisplayViewList = false
|
self.preparingToDisplayViewList = false
|
||||||
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
|
||||||
|
self.dismissAllTooltips()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteAction: { [weak self] in
|
deleteAction: { [weak self] in
|
||||||
@ -1627,6 +1637,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if component.slice.peer.id == component.context.account.peerId {
|
if component.slice.peer.id == component.context.account.peerId {
|
||||||
self.viewListDisplayState = .half
|
self.viewListDisplayState = .half
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
|
||||||
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
var canReply = true
|
var canReply = true
|
||||||
@ -2127,9 +2140,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maybeDisplayReactionTooltip() {
|
func maybeDisplayReactionTooltip() {
|
||||||
if "".isEmpty {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2207,7 +2217,10 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let tooltipScreen = self.sendMessageContext.tooltipScreen {
|
if let tooltipScreen = self.sendMessageContext.tooltipScreen {
|
||||||
tooltipScreen.dismiss()
|
if let tooltipScreen = tooltipScreen as? UndoOverlayController, let tag = tooltipScreen.tag as? String, tag == "no_auto_dismiss" {
|
||||||
|
} else {
|
||||||
|
tooltipScreen.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var itemsTransition = transition
|
var itemsTransition = transition
|
||||||
@ -2736,6 +2749,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
var safeInsets = component.safeInsets
|
var safeInsets = component.safeInsets
|
||||||
safeInsets.bottom = max(safeInsets.bottom, component.inputHeight)
|
safeInsets.bottom = max(safeInsets.bottom, component.inputHeight)
|
||||||
|
|
||||||
|
var hasPremium = false
|
||||||
|
if case let .user(user) = component.slice.peer {
|
||||||
|
hasPremium = user.isPremium
|
||||||
|
}
|
||||||
|
|
||||||
viewList.view.parentState = state
|
viewList.view.parentState = state
|
||||||
let viewListSize = viewList.view.update(
|
let viewListSize = viewList.view.update(
|
||||||
transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint(
|
transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint(
|
||||||
@ -2751,6 +2769,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
peerId: component.slice.peer.id,
|
peerId: component.slice.peer.id,
|
||||||
safeInsets: safeInsets,
|
safeInsets: safeInsets,
|
||||||
storyItem: item.storyItem,
|
storyItem: item.storyItem,
|
||||||
|
hasPremium: hasPremium,
|
||||||
effectiveHeight: viewListHeight,
|
effectiveHeight: viewListHeight,
|
||||||
minHeight: midViewListHeight,
|
minHeight: midViewListHeight,
|
||||||
availableReactions: component.availableReactions,
|
availableReactions: component.availableReactions,
|
||||||
@ -3007,7 +3026,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
self.openPeerStories(peer: peer, avatarNode: avatarNode)
|
self.openPeerStories(peer: peer, avatarNode: avatarNode)
|
||||||
},
|
},
|
||||||
openPremiumIntro: {},
|
openPremiumIntro: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentStoriesUpgradeScreen()
|
||||||
|
},
|
||||||
setIsSearchActive: { [weak self] value in
|
setIsSearchActive: { [weak self] value in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -4136,7 +4160,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
component.externalState.derivedMediaSize = contentFrame.size
|
component.externalState.derivedMediaSize = contentFrame.size
|
||||||
if component.slice.peer.id == component.context.account.peerId {
|
if component.slice.peer.id == component.context.account.peerId {
|
||||||
component.externalState.derivedBottomInset = availableSize.height - contentFrame.maxY
|
component.externalState.derivedBottomInset = availableSize.height - itemsContainerFrame.maxY
|
||||||
} else {
|
} else {
|
||||||
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY)
|
component.externalState.derivedBottomInset = availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY)
|
||||||
}
|
}
|
||||||
@ -4674,6 +4698,15 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
), nil)
|
), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentStealthModeUpgradeScreen() {
|
||||||
|
self.sendMessageContext.presentStealthModeUpgrade(view: self, action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.presentStoriesUpgradeScreen()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func presentStoriesUpgradeScreen() {
|
private func presentStoriesUpgradeScreen() {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
@ -5095,7 +5128,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if accountUser.isPremium {
|
if accountUser.isPremium {
|
||||||
self.sendMessageContext.requestStealthMode(view: self)
|
self.sendMessageContext.requestStealthMode(view: self)
|
||||||
} else {
|
} else {
|
||||||
self.presentStoriesUpgradeScreen()
|
self.presentStealthModeUpgradeScreen()
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -5327,7 +5360,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if accountUser.isPremium {
|
if accountUser.isPremium {
|
||||||
self.sendMessageContext.requestStealthMode(view: self)
|
self.sendMessageContext.requestStealthMode(view: self)
|
||||||
} else {
|
} else {
|
||||||
self.presentStoriesUpgradeScreen()
|
self.presentStealthModeUpgradeScreen()
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
@ -3004,6 +3004,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
tooltipScreen.tag = "no_auto_dismiss"
|
||||||
weak var tooltipScreenValue: UndoOverlayController? = tooltipScreen
|
weak var tooltipScreenValue: UndoOverlayController? = tooltipScreen
|
||||||
self.currentTooltipUpdateTimer?.invalidate()
|
self.currentTooltipUpdateTimer?.invalidate()
|
||||||
self.currentTooltipUpdateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self, weak view] _ in
|
self.currentTooltipUpdateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self, weak view] _ in
|
||||||
@ -3046,7 +3047,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
let sheet = StoryStealthModeSheetScreen(
|
let sheet = StoryStealthModeSheetScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp,
|
mode: .control(cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp),
|
||||||
backwardDuration: pastPeriod,
|
backwardDuration: pastPeriod,
|
||||||
forwardDuration: futurePeriod,
|
forwardDuration: futurePeriod,
|
||||||
buttonAction: { [weak self, weak view] in
|
buttonAction: { [weak self, weak view] in
|
||||||
@ -3095,6 +3096,52 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentStealthModeUpgrade(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) {
|
||||||
|
guard let component = view.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (component.context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState(),
|
||||||
|
TelegramEngine.EngineData.Item.Configuration.App()
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak view] config, appConfig in
|
||||||
|
guard let self, let view, let component = view.component, let controller = component.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let pastPeriod: Int32
|
||||||
|
let futurePeriod: Int32
|
||||||
|
if let data = appConfig.data, let futurePeriodF = data["stories_stealth_future_period"] as? Double, let pastPeriodF = data["stories_stealth_past_period"] as? Double {
|
||||||
|
futurePeriod = Int32(futurePeriodF)
|
||||||
|
pastPeriod = Int32(pastPeriodF)
|
||||||
|
} else {
|
||||||
|
pastPeriod = 5 * 60
|
||||||
|
futurePeriod = 25 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = StoryStealthModeSheetScreen(
|
||||||
|
context: component.context,
|
||||||
|
mode: .upgrade,
|
||||||
|
backwardDuration: pastPeriod,
|
||||||
|
forwardDuration: futurePeriod,
|
||||||
|
buttonAction: {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
sheet.wasDismissed = { [weak self, weak view] in
|
||||||
|
guard let self, let view else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.actionSheet = nil
|
||||||
|
view.updateIsProgressPaused()
|
||||||
|
}
|
||||||
|
self.actionSheet = sheet
|
||||||
|
view.updateIsProgressPaused()
|
||||||
|
controller.push(sheet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func activateMediaArea(view: StoryItemSetContainerComponent.View, mediaArea: MediaArea) {
|
func activateMediaArea(view: StoryItemSetContainerComponent.View, mediaArea: MediaArea) {
|
||||||
guard let component = view.component, let controller = component.controller() else {
|
guard let component = view.component, let controller = component.controller() else {
|
||||||
return
|
return
|
||||||
|
@ -59,6 +59,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
let peerId: EnginePeer.Id
|
let peerId: EnginePeer.Id
|
||||||
let safeInsets: UIEdgeInsets
|
let safeInsets: UIEdgeInsets
|
||||||
let storyItem: EngineStoryItem
|
let storyItem: EngineStoryItem
|
||||||
|
let hasPremium: Bool
|
||||||
let effectiveHeight: CGFloat
|
let effectiveHeight: CGFloat
|
||||||
let minHeight: CGFloat
|
let minHeight: CGFloat
|
||||||
let availableReactions: StoryAvailableReactions?
|
let availableReactions: StoryAvailableReactions?
|
||||||
@ -82,6 +83,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
safeInsets: UIEdgeInsets,
|
safeInsets: UIEdgeInsets,
|
||||||
storyItem: EngineStoryItem,
|
storyItem: EngineStoryItem,
|
||||||
|
hasPremium: Bool,
|
||||||
effectiveHeight: CGFloat,
|
effectiveHeight: CGFloat,
|
||||||
minHeight: CGFloat,
|
minHeight: CGFloat,
|
||||||
availableReactions: StoryAvailableReactions?,
|
availableReactions: StoryAvailableReactions?,
|
||||||
@ -104,6 +106,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.safeInsets = safeInsets
|
self.safeInsets = safeInsets
|
||||||
self.storyItem = storyItem
|
self.storyItem = storyItem
|
||||||
|
self.hasPremium = hasPremium
|
||||||
self.effectiveHeight = effectiveHeight
|
self.effectiveHeight = effectiveHeight
|
||||||
self.minHeight = minHeight
|
self.minHeight = minHeight
|
||||||
self.availableReactions = availableReactions
|
self.availableReactions = availableReactions
|
||||||
@ -136,6 +139,9 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
if lhs.storyItem != rhs.storyItem {
|
if lhs.storyItem != rhs.storyItem {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.hasPremium != rhs.hasPremium {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.effectiveHeight != rhs.effectiveHeight {
|
if lhs.effectiveHeight != rhs.effectiveHeight {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -248,6 +254,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
var emptyIcon: ComponentView<Empty>?
|
var emptyIcon: ComponentView<Empty>?
|
||||||
var emptyText: ComponentView<Empty>?
|
var emptyText: ComponentView<Empty>?
|
||||||
|
var emptyButton: ComponentView<Empty>?
|
||||||
|
|
||||||
let scrollView: UIScrollView
|
let scrollView: UIScrollView
|
||||||
var itemLayout: ItemLayout?
|
var itemLayout: ItemLayout?
|
||||||
@ -267,6 +274,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
var contentLoaded: Bool = false
|
var contentLoaded: Bool = false
|
||||||
var contentLoadedUpdated: ((Bool) -> Void)?
|
var contentLoadedUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
var dismissInput: (() -> Void)?
|
||||||
|
|
||||||
init(configuration: ContentConfigurationKey) {
|
init(configuration: ContentConfigurationKey) {
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
@ -317,6 +326,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
|
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
cancelContextGestures(view: scrollView)
|
cancelContextGestures(view: scrollView)
|
||||||
|
|
||||||
|
self.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition) {
|
private func updateScrolling(transition: Transition) {
|
||||||
@ -759,6 +770,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
if let viewListState = self.viewListState, viewListState.loadMoreToken == nil, viewListState.items.isEmpty, viewListState.totalCount == 0 {
|
if let viewListState = self.viewListState, viewListState.loadMoreToken == nil, viewListState.items.isEmpty, viewListState.totalCount == 0 {
|
||||||
|
self.scrollView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
var emptyTransition = transition
|
var emptyTransition = transition
|
||||||
|
|
||||||
let emptyIcon: ComponentView<Empty>
|
let emptyIcon: ComponentView<Empty>
|
||||||
@ -778,6 +791,25 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.emptyText = emptyText
|
self.emptyText = emptyText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyButtonTransition = transition
|
||||||
|
let emptyButton: ComponentView<Empty>?
|
||||||
|
if !component.hasPremium, let views = component.storyItem.views, views.seenCount != 0 {
|
||||||
|
if let current = self.emptyButton {
|
||||||
|
emptyButton = current
|
||||||
|
} else {
|
||||||
|
emptyButtonTransition = emptyButtonTransition.withAnimation(.none)
|
||||||
|
emptyButton = ComponentView()
|
||||||
|
self.emptyButton = emptyButton
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let emptyButton = self.emptyButton {
|
||||||
|
self.emptyButton = nil
|
||||||
|
emptyButton.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyButton = nil
|
||||||
|
}
|
||||||
|
|
||||||
let emptyIconSize = emptyIcon.update(
|
let emptyIconSize = emptyIcon.update(
|
||||||
transition: emptyTransition,
|
transition: emptyTransition,
|
||||||
component: AnyComponent(LottieComponent(
|
component: AnyComponent(LottieComponent(
|
||||||
@ -794,12 +826,17 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: component.theme.list.itemSecondaryTextColor)
|
let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: component.theme.list.itemSecondaryTextColor)
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemSecondaryTextColor)
|
let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemSecondaryTextColor)
|
||||||
let link = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemAccentColor)
|
let link = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemAccentColor)
|
||||||
let attributes = MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in nil })
|
let attributes = MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return ("URL", "") })
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
if self.configuration.listMode == .everyone && (self.query == nil || self.query == "") {
|
if self.configuration.listMode == .everyone && (self.query == nil || self.query == "") {
|
||||||
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
||||||
text = component.strings.Story_Views_ViewsExpired
|
if emptyButton == nil {
|
||||||
|
text = component.strings.Story_Views_ViewsExpired
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
text = "List of viewers isn't available after 24 hours of story expiration.\n\nTo unlock viewers' lists for expired and saved stories, subscribe to [Telegram Premium]()."
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = component.strings.Story_Views_NoViews
|
text = component.strings.Story_Views_NoViews
|
||||||
}
|
}
|
||||||
@ -811,7 +848,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
text = "None of your contacts viewed this story."
|
text = "None of your contacts viewed this story."
|
||||||
} else {
|
} else {
|
||||||
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
||||||
text = component.strings.Story_Views_ViewsExpired
|
if emptyButton == nil {
|
||||||
|
text = component.strings.Story_Views_ViewsExpired
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
text = "List of viewers isn't available after 24 hours of story expiration.\n\nTo unlock viewers' lists for expired and saved stories, subscribe to [Telegram Premium]()."
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
text = component.strings.Story_Views_NoViews
|
text = component.strings.Story_Views_NoViews
|
||||||
}
|
}
|
||||||
@ -822,15 +864,71 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
component: AnyComponent(BalancedTextComponent(
|
component: AnyComponent(BalancedTextComponent(
|
||||||
text: .markdown(text: text, attributes: attributes),
|
text: .markdown(text: text, attributes: attributes),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0,
|
||||||
|
highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.5),
|
||||||
|
highlightAction: { attributes in
|
||||||
|
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||||
|
return NSAttributedString.Key(rawValue: "URL")
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tapAction: { [weak self] _, _ in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.openPremiumIntro()
|
||||||
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: min(220.0, availableSize.width - 16.0 * 2.0), height: 1000.0)
|
containerSize: CGSize(width: min(320.0, availableSize.width - 16.0 * 2.0), height: 1000.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let emptyContentSpacing: CGFloat = 20.0
|
let emptyContentSpacing: CGFloat = 20.0
|
||||||
let emptyContentHeight = emptyIconSize.height + emptyContentSpacing + textSize.height
|
var emptyContentHeight = emptyIconSize.height + emptyContentSpacing + textSize.height
|
||||||
var emptyContentY = navigationMinY + floor((availableSize.height - navigationMinY - emptyContentHeight) * 0.5)
|
|
||||||
|
var emptyButtonSize: CGSize?
|
||||||
|
if let emptyButton {
|
||||||
|
//TODO:localize
|
||||||
|
emptyButtonSize = emptyButton.update(
|
||||||
|
transition: emptyButtonTransition,
|
||||||
|
component: AnyComponent(ButtonComponent(
|
||||||
|
background: ButtonComponent.Background(
|
||||||
|
color: component.theme.list.itemCheckColors.fillColor,
|
||||||
|
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||||
|
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||||
|
),
|
||||||
|
content: AnyComponentWithIdentity(
|
||||||
|
id: AnyHashable(0),
|
||||||
|
component: AnyComponent(ButtonTextContentComponent(
|
||||||
|
text: "Learn More",
|
||||||
|
badge: 0,
|
||||||
|
textColor: component.theme.list.itemCheckColors.foregroundColor,
|
||||||
|
badgeBackground: component.theme.list.itemCheckColors.foregroundColor,
|
||||||
|
badgeForeground: component.theme.list.itemCheckColors.fillColor
|
||||||
|
))
|
||||||
|
),
|
||||||
|
isEnabled: true,
|
||||||
|
displaysProgress: false,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.openPremiumIntro()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: min(availableSize.width, 180.0), height: 50.0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let emptyButtonSpacing: CGFloat = 32.0
|
||||||
|
if let emptyButtonSize {
|
||||||
|
emptyContentHeight += emptyButtonSpacing
|
||||||
|
emptyContentHeight += emptyButtonSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyContentY = navigationMinY + floor((availableSize.height - component.safeInsets.bottom - navigationMinY - emptyContentHeight) * 0.5)
|
||||||
|
|
||||||
if let emptyIconView = emptyIcon.view as? LottieComponent.View {
|
if let emptyIconView = emptyIcon.view as? LottieComponent.View {
|
||||||
if emptyIconView.superview == nil {
|
if emptyIconView.superview == nil {
|
||||||
@ -868,7 +966,17 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
emptyTransition.setFrame(view: emptyTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: emptyContentY), size: textSize))
|
emptyTransition.setFrame(view: emptyTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: emptyContentY), size: textSize))
|
||||||
emptyContentY += textSize.height + emptyContentSpacing * 2.0
|
emptyContentY += textSize.height + emptyContentSpacing * 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let emptyButtonSize, let emptyButton, let emptyButtonView = emptyButton.view {
|
||||||
|
if emptyButtonView.superview == nil {
|
||||||
|
self.insertSubview(emptyButtonView, belowSubview: self.scrollView)
|
||||||
|
}
|
||||||
|
emptyTransition.setFrame(view: emptyButtonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - emptyButtonSize.width) * 0.5), y: emptyContentY), size: emptyButtonSize))
|
||||||
|
emptyContentY += emptyButtonSize.height + emptyButtonSpacing
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
self.scrollView.isUserInteractionEnabled = true
|
||||||
|
|
||||||
if let emptyIcon = self.emptyIcon {
|
if let emptyIcon = self.emptyIcon {
|
||||||
self.emptyIcon = nil
|
self.emptyIcon = nil
|
||||||
emptyIcon.view?.removeFromSuperview()
|
emptyIcon.view?.removeFromSuperview()
|
||||||
@ -877,6 +985,10 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.emptyText = nil
|
self.emptyText = nil
|
||||||
emptyText.view?.removeFromSuperview()
|
emptyText.view?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
if let emptyButton = self.emptyButton {
|
||||||
|
self.emptyButton = nil
|
||||||
|
emptyButton.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1084,9 +1196,22 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let titleText: String
|
||||||
|
if let views = component.storyItem.views, views.seenCount != 0 {
|
||||||
|
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
|
||||||
|
titleText = component.strings.Story_Footer_Views(Int32(views.seenCount))
|
||||||
|
} else {
|
||||||
|
titleText = "All Viewers"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
titleText = "No Views"
|
||||||
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(Text(text: "All Viewers", font: Font.semibold(17.0), color: .white)),
|
component: AnyComponent(Text(text: titleText, font: Font.semibold(17.0), color: .white)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 260.0, height: 100.0)
|
containerSize: CGSize(width: 260.0, height: 100.0)
|
||||||
)
|
)
|
||||||
@ -1265,6 +1390,9 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
navigationHeight: navigationHeight,
|
navigationHeight: navigationHeight,
|
||||||
transition: contentViewTransition
|
transition: contentViewTransition
|
||||||
)
|
)
|
||||||
|
if currentContentView.contentLoaded {
|
||||||
|
currentContentView.isHidden = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.currentSearchQuery.isEmpty {
|
if !self.currentSearchQuery.isEmpty {
|
||||||
@ -1275,6 +1403,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
currentSearchContentView = ContentView(configuration: currentConfiguration)
|
currentSearchContentView = ContentView(configuration: currentConfiguration)
|
||||||
self.currentSearchContentView = currentSearchContentView
|
self.currentSearchContentView = currentSearchContentView
|
||||||
currentSearchContentView.isHidden = true
|
currentSearchContentView.isHidden = true
|
||||||
|
currentSearchContentView.dismissInput = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.navigationSearch.view?.endEditing(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentViewTransition = transition
|
var contentViewTransition = transition
|
||||||
|
@ -284,8 +284,6 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
avatarsAlpha = pow(1.0 - component.expandFraction, 1.0)
|
avatarsAlpha = pow(1.0 - component.expandFraction, 1.0)
|
||||||
baseViewCountAlpha = 1.0
|
baseViewCountAlpha = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:upload
|
|
||||||
let _ = baseViewCountAlpha
|
let _ = baseViewCountAlpha
|
||||||
|
|
||||||
var peers: [EnginePeer] = []
|
var peers: [EnginePeer] = []
|
||||||
@ -306,7 +304,9 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
var regularSegments: [AnimatedCountLabelView.Segment] = []
|
var regularSegments: [AnimatedCountLabelView.Segment] = []
|
||||||
regularSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.regular(15.0), textColor: .white)))
|
if viewCount != 0 {
|
||||||
|
regularSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.regular(15.0), textColor: .white)))
|
||||||
|
}
|
||||||
|
|
||||||
let viewPart: String
|
let viewPart: String
|
||||||
if viewCount == 0 {
|
if viewCount == 0 {
|
||||||
@ -399,11 +399,13 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
|
|
||||||
contentWidth += (avatarsSize.width + avatarViewsSpacing) * (1.0 - component.expandFraction)
|
contentWidth += (avatarsSize.width + avatarViewsSpacing) * (1.0 - component.expandFraction)
|
||||||
if let image = self.viewsIconView.image {
|
if let image = self.viewsIconView.image {
|
||||||
contentWidth += (image.size.width + viewsIconSpacing) * component.expandFraction
|
if viewCount != 0 {
|
||||||
|
contentWidth += (image.size.width + viewsIconSpacing) * component.expandFraction
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if viewCount == 0 {
|
if viewCount == 0 {
|
||||||
contentWidth += viewStatsTextLayout.size.width * component.expandFraction
|
contentWidth += viewStatsTextLayout.size.width * (1.0 - component.expandFraction)
|
||||||
} else {
|
} else {
|
||||||
contentWidth += viewStatsTextLayout.size.width
|
contentWidth += viewStatsTextLayout.size.width
|
||||||
}
|
}
|
||||||
@ -430,7 +432,11 @@ public final class StoryFooterPanelComponent: Component {
|
|||||||
let viewsIconFrame = CGRect(origin: CGPoint(x: contentX, y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
let viewsIconFrame = CGRect(origin: CGPoint(x: contentX, y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
||||||
transition.setPosition(view: self.viewsIconView, position: viewsIconFrame.center)
|
transition.setPosition(view: self.viewsIconView, position: viewsIconFrame.center)
|
||||||
transition.setBounds(view: self.viewsIconView, bounds: CGRect(origin: CGPoint(), size: viewsIconFrame.size))
|
transition.setBounds(view: self.viewsIconView, bounds: CGRect(origin: CGPoint(), size: viewsIconFrame.size))
|
||||||
transition.setAlpha(view: self.viewsIconView, alpha: component.expandFraction)
|
if viewCount == 0 {
|
||||||
|
transition.setAlpha(view: self.viewsIconView, alpha: 0.0)
|
||||||
|
} else {
|
||||||
|
transition.setAlpha(view: self.viewsIconView, alpha: component.expandFraction)
|
||||||
|
}
|
||||||
transition.setScale(view: self.viewsIconView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: 1.0 - component.expandFraction))
|
transition.setScale(view: self.viewsIconView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: 1.0 - component.expandFraction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,17 +15,23 @@ public final class StoryStealthModeInfoContentComponent: Component {
|
|||||||
public let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
public let backwardDuration: Int32
|
public let backwardDuration: Int32
|
||||||
public let forwardDuration: Int32
|
public let forwardDuration: Int32
|
||||||
|
public let mode: StoryStealthModeSheetScreen.Mode
|
||||||
|
public let dismiss: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
backwardDuration: Int32,
|
backwardDuration: Int32,
|
||||||
forwardDuration: Int32
|
forwardDuration: Int32,
|
||||||
|
mode: StoryStealthModeSheetScreen.Mode,
|
||||||
|
dismiss: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.backwardDuration = backwardDuration
|
self.backwardDuration = backwardDuration
|
||||||
self.forwardDuration = forwardDuration
|
self.forwardDuration = forwardDuration
|
||||||
|
self.mode = mode
|
||||||
|
self.dismiss = dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: StoryStealthModeInfoContentComponent, rhs: StoryStealthModeInfoContentComponent) -> Bool {
|
public static func ==(lhs: StoryStealthModeInfoContentComponent, rhs: StoryStealthModeInfoContentComponent) -> Bool {
|
||||||
@ -41,6 +47,9 @@ public final class StoryStealthModeInfoContentComponent: Component {
|
|||||||
if lhs.forwardDuration != rhs.forwardDuration {
|
if lhs.forwardDuration != rhs.forwardDuration {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.mode != rhs.mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +164,13 @@ public final class StoryStealthModeInfoContentComponent: Component {
|
|||||||
contentHeight += 15.0
|
contentHeight += 15.0
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let text: String = "Turn Stealth Mode on to hide the fact that you viewed peoples' stories from them."
|
let text: String
|
||||||
|
switch component.mode {
|
||||||
|
case .control:
|
||||||
|
text = "Turn Stealth Mode on to hide the fact that you viewed peoples' stories from them."
|
||||||
|
case .upgrade:
|
||||||
|
text = "Subscribe to Telegram Premium to hide the fact that you viewed peoples' stories from them."
|
||||||
|
}
|
||||||
let mainText = NSMutableAttributedString()
|
let mainText = NSMutableAttributedString()
|
||||||
mainText.append(parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
|
mainText.append(parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
|
||||||
body: MarkdownAttributeSet(
|
body: MarkdownAttributeSet(
|
||||||
|
@ -15,25 +15,28 @@ import TelegramStringFormatting
|
|||||||
private final class StoryStealthModeSheetContentComponent: Component {
|
private final class StoryStealthModeSheetContentComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let cooldownUntilTimestamp: Int32?
|
let mode: StoryStealthModeSheetScreen.Mode
|
||||||
let backwardDuration: Int32
|
let backwardDuration: Int32
|
||||||
let forwardDuration: Int32
|
let forwardDuration: Int32
|
||||||
|
let action: () -> Void
|
||||||
let dismiss: () -> Void
|
let dismiss: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
cooldownUntilTimestamp: Int32?,
|
mode: StoryStealthModeSheetScreen.Mode,
|
||||||
backwardDuration: Int32,
|
backwardDuration: Int32,
|
||||||
forwardDuration: Int32,
|
forwardDuration: Int32,
|
||||||
|
action: @escaping () -> Void,
|
||||||
dismiss: @escaping () -> Void
|
dismiss: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.cooldownUntilTimestamp = cooldownUntilTimestamp
|
self.mode = mode
|
||||||
self.backwardDuration = backwardDuration
|
self.backwardDuration = backwardDuration
|
||||||
self.forwardDuration = forwardDuration
|
self.forwardDuration = forwardDuration
|
||||||
|
self.action = action
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StoryStealthModeSheetContentComponent, rhs: StoryStealthModeSheetContentComponent) -> Bool {
|
static func ==(lhs: StoryStealthModeSheetContentComponent, rhs: StoryStealthModeSheetContentComponent) -> Bool {
|
||||||
if lhs.cooldownUntilTimestamp != rhs.cooldownUntilTimestamp {
|
if lhs.mode != rhs.mode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.backwardDuration != rhs.backwardDuration {
|
if lhs.backwardDuration != rhs.backwardDuration {
|
||||||
@ -50,6 +53,8 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
private let content = ComponentView<Empty>()
|
private let content = ComponentView<Empty>()
|
||||||
private let button = ComponentView<Empty>()
|
private let button = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var cancelButton: ComponentView<Empty>?
|
||||||
|
|
||||||
private var component: StoryStealthModeSheetContentComponent?
|
private var component: StoryStealthModeSheetContentComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
@ -95,10 +100,13 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
var remainingCooldownSeconds: Int32 = 0
|
var remainingCooldownSeconds: Int32 = 0
|
||||||
if let cooldownUntilTimestamp = component.cooldownUntilTimestamp {
|
if case let .control(cooldownUntilTimestamp) = component.mode {
|
||||||
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
if let cooldownUntilTimestamp {
|
||||||
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if remainingCooldownSeconds > 0 {
|
if remainingCooldownSeconds > 0 {
|
||||||
if self.timer == nil {
|
if self.timer == nil {
|
||||||
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
|
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
|
||||||
@ -170,6 +178,39 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case .upgrade = component.mode {
|
||||||
|
let cancelButton: ComponentView<Empty>
|
||||||
|
if let current = self.cancelButton {
|
||||||
|
cancelButton = current
|
||||||
|
} else {
|
||||||
|
cancelButton = ComponentView()
|
||||||
|
self.cancelButton = cancelButton
|
||||||
|
}
|
||||||
|
let cancelButtonSize = cancelButton.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(Button(
|
||||||
|
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.dismiss()
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 8.0, height: 44.0))),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||||
|
)
|
||||||
|
if let cancelButtonView = cancelButton.view {
|
||||||
|
if cancelButtonView.superview == nil {
|
||||||
|
self.addSubview(cancelButtonView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize))
|
||||||
|
}
|
||||||
|
} else if let cancelButton = self.cancelButton {
|
||||||
|
self.cancelButton = nil
|
||||||
|
cancelButton.view?.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
contentHeight += 32.0
|
contentHeight += 32.0
|
||||||
|
|
||||||
@ -179,7 +220,14 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
backwardDuration: component.backwardDuration,
|
backwardDuration: component.backwardDuration,
|
||||||
forwardDuration: component.forwardDuration
|
forwardDuration: component.forwardDuration,
|
||||||
|
mode: component.mode,
|
||||||
|
dismiss: { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.dismiss()
|
||||||
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||||
@ -195,10 +243,29 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let buttonText: String
|
let buttonText: String
|
||||||
if remainingCooldownSeconds <= 0 {
|
let content: AnyComponentWithIdentity<Empty>
|
||||||
buttonText = "Enable Stealth Mode"
|
switch component.mode {
|
||||||
} else {
|
case .control:
|
||||||
buttonText = "Available in \(stringForDuration(remainingCooldownSeconds))"
|
if remainingCooldownSeconds <= 0 {
|
||||||
|
buttonText = "Enable Stealth Mode"
|
||||||
|
} else {
|
||||||
|
buttonText = "Available in \(stringForDuration(remainingCooldownSeconds))"
|
||||||
|
}
|
||||||
|
content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: buttonText, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)))
|
||||||
|
case .upgrade:
|
||||||
|
buttonText = "Unlock Stealth Mode"
|
||||||
|
content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(
|
||||||
|
HStack([
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: buttonText, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor))),
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "premium_unlock"),
|
||||||
|
color: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
|
startingPosition: .begin,
|
||||||
|
size: CGSize(width: 30.0, height: 30.0),
|
||||||
|
loop: true
|
||||||
|
)))
|
||||||
|
], spacing: 4.0)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
let buttonSize = self.button.update(
|
let buttonSize = self.button.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -208,9 +275,7 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
|
||||||
),
|
),
|
||||||
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
|
content: content,
|
||||||
Text(text: buttonText, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
|
||||||
)),
|
|
||||||
isEnabled: remainingCooldownSeconds <= 0,
|
isEnabled: remainingCooldownSeconds <= 0,
|
||||||
allowActionWhenDisabled: true,
|
allowActionWhenDisabled: true,
|
||||||
displaysProgress: false,
|
displaysProgress: false,
|
||||||
@ -219,16 +284,21 @@ private final class StoryStealthModeSheetContentComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var remainingCooldownSeconds: Int32 = 0
|
switch component.mode {
|
||||||
if let cooldownUntilTimestamp = component.cooldownUntilTimestamp {
|
case let .control(cooldownUntilTimestamp):
|
||||||
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
var remainingCooldownSeconds: Int32 = 0
|
||||||
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
if let cooldownUntilTimestamp {
|
||||||
}
|
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
|
||||||
|
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
|
||||||
if remainingCooldownSeconds > 0 {
|
}
|
||||||
self.displayCooldown()
|
|
||||||
} else {
|
if remainingCooldownSeconds > 0 {
|
||||||
component.dismiss()
|
self.displayCooldown()
|
||||||
|
} else {
|
||||||
|
component.action()
|
||||||
|
}
|
||||||
|
case .upgrade:
|
||||||
|
component.action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
@ -267,20 +337,20 @@ private final class StoryStealthModeSheetScreenComponent: Component {
|
|||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let cooldownUntilTimestamp: Int32?
|
let mode: StoryStealthModeSheetScreen.Mode
|
||||||
let backwardDuration: Int32
|
let backwardDuration: Int32
|
||||||
let forwardDuration: Int32
|
let forwardDuration: Int32
|
||||||
let buttonAction: (() -> Void)?
|
let buttonAction: (() -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
cooldownUntilTimestamp: Int32?,
|
mode: StoryStealthModeSheetScreen.Mode,
|
||||||
backwardDuration: Int32,
|
backwardDuration: Int32,
|
||||||
forwardDuration: Int32,
|
forwardDuration: Int32,
|
||||||
buttonAction: (() -> Void)?
|
buttonAction: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.cooldownUntilTimestamp = cooldownUntilTimestamp
|
self.mode = mode
|
||||||
self.backwardDuration = backwardDuration
|
self.backwardDuration = backwardDuration
|
||||||
self.forwardDuration = forwardDuration
|
self.forwardDuration = forwardDuration
|
||||||
self.buttonAction = buttonAction
|
self.buttonAction = buttonAction
|
||||||
@ -290,7 +360,7 @@ private final class StoryStealthModeSheetScreenComponent: Component {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.cooldownUntilTimestamp != rhs.cooldownUntilTimestamp {
|
if lhs.mode != rhs.mode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.backwardDuration != rhs.backwardDuration {
|
if lhs.backwardDuration != rhs.backwardDuration {
|
||||||
@ -343,10 +413,10 @@ private final class StoryStealthModeSheetScreenComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(SheetComponent(
|
component: AnyComponent(SheetComponent(
|
||||||
content: AnyComponent(StoryStealthModeSheetContentComponent(
|
content: AnyComponent(StoryStealthModeSheetContentComponent(
|
||||||
cooldownUntilTimestamp: component.cooldownUntilTimestamp,
|
mode: component.mode,
|
||||||
backwardDuration: component.backwardDuration,
|
backwardDuration: component.backwardDuration,
|
||||||
forwardDuration: component.forwardDuration,
|
forwardDuration: component.forwardDuration,
|
||||||
dismiss: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -360,9 +430,16 @@ private final class StoryStealthModeSheetScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.component?.buttonAction?()
|
self.component?.buttonAction?()
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
dismiss: {
|
||||||
|
self.sheetAnimateOut.invoke(Action { _ in
|
||||||
|
if let controller = environment.controller() {
|
||||||
|
controller.dismiss(completion: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
backgroundColor: .color(environment.theme.list.plainBackgroundColor),
|
backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor),
|
||||||
animateOut: self.sheetAnimateOut
|
animateOut: self.sheetAnimateOut
|
||||||
)),
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
@ -392,16 +469,21 @@ private final class StoryStealthModeSheetScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class StoryStealthModeSheetScreen: ViewControllerComponentContainer {
|
public class StoryStealthModeSheetScreen: ViewControllerComponentContainer {
|
||||||
|
public enum Mode: Equatable {
|
||||||
|
case control(cooldownUntilTimestamp: Int32?)
|
||||||
|
case upgrade
|
||||||
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
cooldownUntilTimestamp: Int32?,
|
mode: Mode,
|
||||||
backwardDuration: Int32,
|
backwardDuration: Int32,
|
||||||
forwardDuration: Int32,
|
forwardDuration: Int32,
|
||||||
buttonAction: (() -> Void)? = nil
|
buttonAction: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
super.init(context: context, component: StoryStealthModeSheetScreenComponent(
|
super.init(context: context, component: StoryStealthModeSheetScreenComponent(
|
||||||
context: context,
|
context: context,
|
||||||
cooldownUntilTimestamp: cooldownUntilTimestamp,
|
mode: mode,
|
||||||
backwardDuration: backwardDuration,
|
backwardDuration: backwardDuration,
|
||||||
forwardDuration: forwardDuration,
|
forwardDuration: forwardDuration,
|
||||||
buttonAction: buttonAction
|
buttonAction: buttonAction
|
||||||
|
@ -75,6 +75,8 @@ public final class UndoOverlayController: ViewController {
|
|||||||
|
|
||||||
public var keepOnParentDismissal = false
|
public var keepOnParentDismissal = false
|
||||||
|
|
||||||
|
public var tag: Any?
|
||||||
|
|
||||||
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
|
public init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, position: Position = .bottom, animateInAsReplacement: Bool = false, blurred: Bool = false, action: @escaping (UndoOverlayAction) -> Bool) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.content = content
|
self.content = content
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app": "9.6.7",
|
"app": "10.0.0",
|
||||||
"bazel": "6.1.1",
|
"bazel": "6.1.1",
|
||||||
"xcode": "14.2"
|
"xcode": "14.2"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user