Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-07-03 11:52:41 +02:00
commit af6c9a9868
16 changed files with 674 additions and 194 deletions

View File

@ -75,6 +75,7 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
super.init(target: target, action: action)
self.maximumNumberOfTouches = 1
self.delaysTouchesBegan = false
}
override public func reset() {
@ -136,7 +137,9 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
let size = self.view?.bounds.size ?? CGSize()
print("moved: \(CFAbsoluteTimeGetCurrent()) absTranslationX: \(absTranslationX) absTranslationY: \(absTranslationY)")
//print("moved: \(CFAbsoluteTimeGetCurrent()) absTranslationX: \(absTranslationX) absTranslationY: \(absTranslationY)")
var fireBegan = false
if self.currentAllowedDirections.contains(.down) {
if !self.validatedGesture {
@ -177,12 +180,18 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
self.state = .failed
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
self.validatedGesture = true
fireBegan = true
}
}
}
if self.validatedGesture {
super.touchesMoved(touches, with: event)
if fireBegan {
if self.state == .possible {
self.state = .began
}
}
}
}
}

View File

@ -332,7 +332,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(), size: layout.size))
self.ignoreScrolling = true
self.scrollNode.view.isScrollEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0) && self.isInteractiveDimissEnabled
self.scrollNode.view.isScrollEnabled = (layout.inputHeight == nil || layout.inputHeight == 0.0) && self.isInteractiveDimissEnabled && !self.isFlat
let previousBounds = self.scrollNode.bounds
let scrollNodeFrame = CGRect(origin: CGPoint(x: self.horizontalDismissOffset ?? 0.0, y: 0.0), size: layout.size)
self.scrollNode.frame = scrollNodeFrame
@ -348,7 +348,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
}
self.ignoreScrolling = false
self.scrollNode.view.isScrollEnabled = !isStandaloneModal
self.scrollNode.view.isScrollEnabled = !isStandaloneModal && !self.isFlat
let isLandscape = layout.orientation == .landscape
let containerLayout: ContainerViewLayout
@ -515,6 +515,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
if !self.container.bounds.contains(self.view.convert(point, to: self.container.view)) {
return self.dim.view
}
if self.isFlat {
return result
}
var currentParent: UIView? = result
var enableScrolling = true
while true {
@ -562,7 +565,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
enableScrolling = false
}
}
self.scrollNode.view.isScrollEnabled = enableScrolling
self.scrollNode.view.isScrollEnabled = enableScrolling && !self.isFlat
return result
}
}

View File

@ -1669,6 +1669,56 @@ public final class EngineStoryViewListContext {
}
}
if let storedItem = transaction.getStory(id: StoryId(peerId: account.peerId, id: storyId))?.get(Stories.StoredItem.self), case let .item(item) = storedItem, let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(seenCount: Int(count), seenPeerIds: currentViews.seenPeerIds),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
))
if let entry = CodableEntry(updatedItem) {
transaction.setStory(id: StoryId(peerId: account.peerId, id: storyId), value: entry)
}
}
var currentItems = transaction.getStoryItems(peerId: account.peerId)
for i in 0 ..< currentItems.count {
if currentItems[i].id == storyId {
if case let .item(item) = currentItems[i].value.get(Stories.StoredItem.self), let currentViews = item.views {
let updatedItem: Stories.StoredItem = .item(Stories.Item(
id: item.id,
timestamp: item.timestamp,
expirationTimestamp: item.expirationTimestamp,
media: item.media,
text: item.text,
entities: item.entities,
views: Stories.Item.Views(seenCount: Int(count), seenPeerIds: currentViews.seenPeerIds),
privacy: item.privacy,
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled,
isEdited: item.isEdited
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp)
}
}
}
}
transaction.setStoryItems(peerId: account.peerId, items: currentItems)
return InternalState(totalCount: Int(count), items: items, canLoadMore: nextOffset != nil, nextOffset: nextOffset)
case .none:
return InternalState(totalCount: 0, items: [], canLoadMore: false, nextOffset: nil)

View File

@ -23,6 +23,7 @@ swift_library(
"//submodules/TelegramStringFormatting",
"//submodules/AppBundle",
"//submodules/PeerPresenceStatusManager",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -13,6 +13,7 @@ import CheckNode
import TelegramStringFormatting
import AppBundle
import PeerPresenceStatusManager
import EmojiStatusComponent
private let avatarFont = avatarPlaceholderFont(size: 15.0)
private let readIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/MenuReadIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
@ -132,6 +133,7 @@ public final class PeerListItemComponent: Component {
private let label = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private let avatarNode: AvatarNode
private var avatarIcon: ComponentView<Empty>?
private var iconView: UIImageView?
private var checkLayer: CheckLayer?
@ -316,6 +318,8 @@ public final class PeerListItemComponent: Component {
} else {
transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
}
var statusIcon: EmojiStatusComponent.Content?
if let peer = component.peer {
let clipStyle: AvatarNodeClipStyle
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
@ -324,6 +328,18 @@ public final class PeerListItemComponent: Component {
clipStyle = .round
}
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
if peer.isScam {
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_ScamAccount.uppercased())
} else if peer.isFake {
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_FakeAccount.uppercased())
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
statusIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(2))
} else if peer.isVerified {
statusIcon = .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .compact)
} else if peer.isPremium {
statusIcon = .premium(color: component.theme.list.itemAccentColor)
}
}
let labelSize = self.label.update(
@ -350,7 +366,7 @@ public final class PeerListItemComponent: Component {
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleSpacing: CGFloat = 1.0
let titleSpacing: CGFloat = 2.0
let centralContentHeight: CGFloat
if labelSize.height > 0.0, case .generic = component.style {
centralContentHeight = titleSize.height + labelSize.height + titleSpacing
@ -358,7 +374,7 @@ public final class PeerListItemComponent: Component {
centralContentHeight = titleSize.height
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: -1.0 + floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
@ -419,6 +435,48 @@ public final class PeerListItemComponent: Component {
transition.setFrame(view: labelView, frame: labelFrame)
}
if let statusIcon {
let animationCache = component.context.animationCache
let animationRenderer = component.context.animationRenderer
let avatarIcon: ComponentView<Empty>
var avatarIconTransition = transition
if let current = self.avatarIcon {
avatarIcon = current
} else {
avatarIconTransition = transition.withAnimation(.none)
avatarIcon = ComponentView<Empty>()
self.avatarIcon = avatarIcon
}
let avatarIconComponent = EmojiStatusComponent(
context: component.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
content: statusIcon,
isVisibleForAnimations: true,
action: nil,
emojiFileUpdated: nil
)
let iconSize = avatarIcon.update(
transition: avatarIconTransition,
component: AnyComponent(avatarIconComponent),
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
if let avatarIconView = avatarIcon.view {
if avatarIconView.superview == nil {
avatarIconView.isUserInteractionEnabled = false
self.containerButton.addSubview(avatarIconView)
}
avatarIconTransition.setFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
}
} else if let avatarIcon = self.avatarIcon {
self.avatarIcon = nil
avatarIcon.view?.removeFromSuperview()
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}

View File

@ -72,7 +72,7 @@ swift_library(
"//submodules/TinyThumbnail",
"//submodules/ImageBlur",
"//submodules/StickerPackPreviewUI",
"//submodules/Components/AnimatedStickerComponent",
],
visibility = [
"//visibility:public",

View File

@ -398,7 +398,7 @@ public final class StoryContentContextImpl: StoryContentContext {
private var requestStoryDisposables = DisposableSet()
private var preloadStoryResourceDisposables: [MediaId: Disposable] = [:]
private var pollStoryMetadataDisposables = DisposableSet()
private var pollStoryMetadataDisposables: [StoryId: Disposable] = [:]
private var singlePeerListContext: PeerExpiringStoryListContext?
@ -615,7 +615,9 @@ public final class StoryContentContextImpl: StoryContentContext {
for (_, disposable) in self.preloadStoryResourceDisposables {
disposable.dispose()
}
self.pollStoryMetadataDisposables.dispose()
for (_, disposable) in self.pollStoryMetadataDisposables {
disposable.dispose()
}
self.storySubscriptionsDisposable?.dispose()
}
@ -805,7 +807,11 @@ public final class StoryContentContextImpl: StoryContentContext {
}
}
for (peerId, ids) in pollIdByPeerId {
self.pollStoryMetadataDisposables.add(self.context.engine.messages.refreshStoryViews(peerId: peerId, ids: ids).start())
for id in ids {
if self.pollStoryMetadataDisposables[StoryId(peerId: peerId, id: id)] == nil {
self.pollStoryMetadataDisposables[StoryId(peerId: peerId, id: id)] = self.context.engine.messages.refreshStoryViews(peerId: peerId, ids: ids).start()
}
}
}
}

View File

@ -33,6 +33,7 @@ func hasFirstResponder(_ view: UIView) -> Bool {
}
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
var shouldBegin: ((UITouch) -> Bool)?
var updateIsTracking: ((Bool) -> Void)?
override var state: UIGestureRecognizer.State {
@ -50,10 +51,12 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
}
private var isTracking: Bool = false
private var isValidated: Bool = false
override func reset() {
super.reset()
self.isValidated = false
if self.isTracking {
self.isTracking = false
self.updateIsTracking?(false)
@ -61,11 +64,21 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if !self.isValidated, let touch = touches.first {
if let shouldBegin = self.shouldBegin, shouldBegin(touch) {
self.isValidated = true
} else {
return
}
}
if !self.isTracking {
self.isTracking = true
self.updateIsTracking?(true)
if self.isValidated {
super.touchesBegan(touches, with: event)
if !self.isTracking {
self.isTracking = true
self.updateIsTracking?(true)
}
}
}
}
@ -153,9 +166,8 @@ private final class StoryContainerScreenComponent: Component {
self.didBegin = didBegin
}
}
final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
final class View: UIView, UIGestureRecognizerDelegate {
private var component: StoryContainerScreenComponent?
private weak var state: EmptyComponentState?
private var environment: ViewControllerComponentContainer.Environment?
@ -190,6 +202,8 @@ private final class StoryContainerScreenComponent: Component {
var dismissWithoutTransitionOut: Bool = false
var longPressRecognizer: StoryLongPressRecognizer?
override init(frame: CGRect) {
self.backgroundLayer = SimpleLayer()
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
@ -236,7 +250,7 @@ private final class StoryContainerScreenComponent: Component {
return []
}
}
if !itemSetComponentView.allowsInteractiveGestures() {
if !itemSetComponentView.allowsVerticalPanGesture() {
return []
}
@ -253,6 +267,19 @@ private final class StoryContainerScreenComponent: Component {
self.isHoldingTouch = isTracking
self.state?.updated(transition: .immediate)
}
longPressRecognizer.shouldBegin = { [weak self] touch in
guard let self else {
return false
}
guard let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return false
}
if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) {
return false
}
return true
}
self.longPressRecognizer = longPressRecognizer
self.addGestureRecognizer(longPressRecognizer)
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchGesture(_:)))
@ -406,7 +433,7 @@ private final class StoryContainerScreenComponent: Component {
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
print("began: \(CFAbsoluteTimeGetCurrent())")
//print("began: \(CFAbsoluteTimeGetCurrent())")
self.beginHorizontalPan(translation: recognizer.translation(in: self))
case .changed:
self.updateHorizontalPan(translation: recognizer.translation(in: self))
@ -450,7 +477,7 @@ private final class StoryContainerScreenComponent: Component {
self.verticalPanState = nil
var updateState = true
if translation.y > 200.0 || (translation.y > 100.0 && velocity.y > 200.0) {
if translation.y > 200.0 || (translation.y > 5.0 && velocity.y > 200.0) {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
self.environment?.controller()?.dismiss()
} else if translation.y < -200.0 || (translation.y < -100.0 && velocity.y < -100.0) {

View File

@ -218,7 +218,7 @@ final class StoryContentCaptionComponent: Component {
let edgeDistanceFraction = edgeDistance / 7.0
transition.setAlpha(view: self.scrollFullMaskView, alpha: 1.0 - edgeDistanceFraction)
let shadowOverflow: CGFloat = 36.0
let shadowOverflow: CGFloat = 56.0
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
transition.setFrame(layer: self.shadowGradientLayer, frame: shadowFrame)
transition.setFrame(layer: self.shadowPlainLayer, frame: CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.maxY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)))
@ -479,7 +479,7 @@ final class StoryContentCaptionComponent: Component {
var locations: [NSNumber] = []
var colors: [CGColor] = []
let numStops = 10
let baseAlpha: CGFloat = 0.3
let baseAlpha: CGFloat = 0.5
for i in 0 ..< numStops {
let step = 1.0 - CGFloat(i) / CGFloat(numStops - 1)
locations.append((1.0 - step) as NSNumber)

View File

@ -410,10 +410,8 @@ final class StoryItemContentComponent: Component {
var fetchSignal: Signal<Never, NoError>?
switch messageMedia {
case .image:
self.contentLoaded = true
break
case let .file(file):
self.contentLoaded = true
fetchSignal = fetchedMediaResource(
mediaBox: component.context.account.postbox.mediaBox,
userLocation: .other,
@ -446,6 +444,16 @@ final class StoryItemContentComponent: Component {
}
if let messageMedia {
var applyState = false
self.imageView.didLoadContents = { [weak self] in
guard let self else {
return
}
self.contentLoaded = true
if applyState {
self.state?.updated(transition: .immediate)
}
}
self.imageView.update(
context: component.context,
peer: component.peer,
@ -456,6 +464,10 @@ final class StoryItemContentComponent: Component {
attemptSynchronous: synchronousLoad,
transition: transition
)
applyState = true
if self.imageView.isContentLoaded {
self.contentLoaded = true
}
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: availableSize))
var dimensions: CGSize?

View File

@ -16,6 +16,9 @@ final class StoryItemImageView: UIView {
private var disposable: Disposable?
private var fetchDisposable: Disposable?
private(set) var isContentLoaded: Bool = false
var didLoadContents: (() -> Void)?
override init(frame: CGRect) {
self.contentView = UIImageView()
self.contentView.contentMode = .scaleAspectFill
@ -55,6 +58,8 @@ final class StoryItemImageView: UIView {
self.updateImage(image: image)
}
}
self.isContentLoaded = true
self.didLoadContents?()
} else {
if let thumbnailData = image.immediateThumbnailData.flatMap(decodeTinyThumbnail), let thumbnailImage = UIImage(data: thumbnailData) {
self.contentView.image = blurredImage(thumbnailImage, radius: 10.0, iterations: 3)
@ -89,6 +94,8 @@ final class StoryItemImageView: UIView {
}
if let image {
self.updateImage(image: image)
self.isContentLoaded = true
self.didLoadContents?()
}
})
}
@ -110,6 +117,8 @@ final class StoryItemImageView: UIView {
self.updateImage(image: image)
}
}
self.isContentLoaded = true
self.didLoadContents?()
} else {
if let thumbnailData = file.immediateThumbnailData.flatMap(decodeTinyThumbnail), let thumbnailImage = UIImage(data: thumbnailData) {
self.contentView.image = blurredImage(thumbnailImage, radius: 10.0, iterations: 3)
@ -141,6 +150,8 @@ final class StoryItemImageView: UIView {
}
if let image {
self.updateImage(image: image)
self.isContentLoaded = true
self.didLoadContents?()
}
})
}

View File

@ -269,6 +269,14 @@ public final class StoryItemSetContainerComponent: Component {
}
}
private struct PanState: Equatable {
var fraction: CGFloat
init(fraction: CGFloat) {
self.fraction = fraction
}
}
private final class Scroller: UIScrollView {
override init(frame: CGRect) {
super.init(frame: frame)
@ -319,7 +327,7 @@ public final class StoryItemSetContainerComponent: Component {
var preparingToDisplayViewList: Bool = false
var displayViewList: Bool = false
var viewList: ViewList?
var viewLists: [Int32: ViewList] = [:]
var isEditingStory: Bool = false
@ -354,6 +362,11 @@ public final class StoryItemSetContainerComponent: Component {
private var animateNextNavigationId: Int32?
private var initializedOffset: Bool = false
private var viewListPanState: PanState?
private var viewListSwipeRecognizer: InteractiveTransitionGestureRecognizer?
private var verticalPanState: PanState?
override init(frame: CGRect) {
self.sendMessageContext = StoryItemSetContainerSendMessage()
@ -365,6 +378,7 @@ public final class StoryItemSetContainerComponent: Component {
self.scroller.showsVerticalScrollIndicator = false
self.scroller.showsHorizontalScrollIndicator = false
self.scroller.decelerationRate = .fast
self.scroller.delaysContentTouches = false
self.controlsContainerView = SparseContainerView()
self.controlsContainerView.clipsToBounds = true
@ -404,6 +418,33 @@ public final class StoryItemSetContainerComponent: Component {
tapRecognizer.delegate = self
self.itemsContainerView.addGestureRecognizer(tapRecognizer)
let verticalPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.viewListDismissPanGesture(_:)), allowedDirections: { [weak self] point in
guard let self else {
return []
}
if !self.displayViewList {
return []
}
return [.down]
})
self.addGestureRecognizer(verticalPanRecognizer)
let viewListSwipeRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.viewListPanGesture(_:)), allowedDirections: { [weak self] point in
guard let self else {
return []
}
if !self.displayViewList {
return []
}
if self.bounds.contains(point), !self.itemsContainerView.frame.contains(point) {
return [.left, .right]
} else {
return []
}
})
self.viewListSwipeRecognizer = viewListSwipeRecognizer
self.addGestureRecognizer(viewListSwipeRecognizer)
self.audioRecorderDisposable = (self.sendMessageContext.audioRecorder.get()
|> deliverOnMainQueue).start(next: { [weak self] audioRecorder in
guard let self else {
@ -531,6 +572,13 @@ public final class StoryItemSetContainerComponent: Component {
return true
}
func allowsVerticalPanGesture() -> Bool {
if self.displayViewList {
return false
}
return true
}
func rewindCurrentItem() {
guard let component = self.component else {
return
@ -623,6 +671,78 @@ public final class StoryItemSetContainerComponent: Component {
}
}
@objc private func viewListPanGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
if !self.bounds.isEmpty {
let translation = recognizer.translation(in: self)
let fraction: CGFloat = max(-1.0, min(1.0, translation.x / self.bounds.width))
self.viewListPanState = PanState(fraction: fraction)
self.state?.updated(transition: .immediate)
}
case .changed:
if var viewListPanState = self.viewListPanState {
let translation = recognizer.translation(in: self)
let fraction: CGFloat = max(-1.0, min(1.0, translation.x / self.bounds.width))
viewListPanState.fraction = fraction
self.viewListPanState = viewListPanState
self.state?.updated(transition: .immediate)
}
case .cancelled, .ended:
if let viewListPanState = self.viewListPanState {
let velocity = recognizer.velocity(in: self)
var consumed = false
if let component = self.component, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
if (viewListPanState.fraction <= -0.3 || (viewListPanState.fraction <= -0.05 && velocity.x <= -200.0)), currentIndex != component.slice.allItems.count - 1 {
let nextItem = component.slice.allItems[currentIndex + 1]
self.animateNextNavigationId = nextItem.storyItem.id
component.navigate(.id(nextItem.storyItem.id))
consumed = true
} else if (viewListPanState.fraction >= 0.3 || (viewListPanState.fraction >= 0.05 && velocity.x >= 200.0)), currentIndex != 0 {
let previousItem = component.slice.allItems[currentIndex - 1]
self.animateNextNavigationId = previousItem.storyItem.id
component.navigate(.id(previousItem.storyItem.id))
consumed = true
}
}
if !consumed {
self.viewListPanState = nil
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
}
default:
break
}
}
@objc private func viewListDismissPanGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.verticalPanState = PanState(fraction: 0.0)
self.state?.updated(transition: .immediate)
case .changed:
let translation = recognizer.translation(in: self)
self.verticalPanState = PanState(fraction: max(-1.0, min(1.0, translation.y / self.bounds.height)))
self.state?.updated(transition: .immediate)
case .cancelled, .ended:
if let verticalPanState = self.verticalPanState {
self.verticalPanState = nil
let velocity = recognizer.velocity(in: self)
if verticalPanState.fraction >= 0.3 || (verticalPanState.fraction >= 0.05 && velocity.y >= 150.0) {
self.displayViewList = false
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
default:
break
}
}
@objc private func closePressed() {
guard let component = self.component else {
return
@ -814,7 +934,11 @@ public final class StoryItemSetContainerComponent: Component {
for index in 0 ..< component.slice.allItems.count {
let item = component.slice.allItems[index]
let offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance
var offsetFraction: CGFloat = (self.scrollingCenterX - self.scrollingOffsetX) / fullItemScrollDistance
if let viewListPanState = self.viewListPanState {
offsetFraction += viewListPanState.fraction
}
let centerIndexOffset = index - centralIndex
let centerFraction: CGFloat = CGFloat(centerIndexOffset)
@ -996,7 +1120,7 @@ public final class StoryItemSetContainerComponent: Component {
return false
}
if component.slice.peer.id == component.context.account.peerId {
if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
if let _ = component.slice.item.storyItem.views {
self.displayViewList = true
if component.verticalPanFraction == 0.0 {
self.preparingToDisplayViewList = true
@ -1062,15 +1186,17 @@ public final class StoryItemSetContainerComponent: Component {
)
inputPanelView.layer.animateAlpha(from: 0.0, to: inputPanelView.alpha, duration: 0.28)
}
if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition(
from: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY),
to: CGPoint(),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
additive: true
)
viewListView.layer.animateAlpha(from: 0.0, to: viewListView.alpha, duration: 0.28)
for (_, viewList) in self.viewLists {
if let viewListView = viewList.view.view {
viewListView.layer.animatePosition(
from: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY),
to: CGPoint(),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
additive: true
)
viewListView.layer.animateAlpha(from: 0.0, to: viewListView.alpha, duration: 0.28)
}
}
if let captionItemView = self.captionItem?.view.view {
captionItemView.layer.animatePosition(
@ -1177,16 +1303,18 @@ public final class StoryItemSetContainerComponent: Component {
)
inputPanelBackground.layer.animateAlpha(from: inputPanelBackground.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
if let viewListView = self.viewList?.view.view {
viewListView.layer.animatePosition(
from: CGPoint(),
to: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
removeOnCompletion: false,
additive: true
)
viewListView.layer.animateAlpha(from: viewListView.alpha, to: 0.0, duration: 0.28, removeOnCompletion: false)
for (_, viewList) in self.viewLists {
if let viewListView = viewList.view.view {
viewListView.layer.animatePosition(
from: CGPoint(),
to: CGPoint(x: 0.0, y: self.bounds.height - self.controlsContainerView.frame.maxY),
duration: 0.3,
timingFunction: kCAMediaTimingFunctionSpring,
removeOnCompletion: false,
additive: true
)
viewListView.layer.animateAlpha(from: viewListView.alpha, to: 0.0, duration: 0.28, removeOnCompletion: false)
}
}
if let captionItemView = self.captionItem?.view.view {
captionItemView.layer.animatePosition(
@ -1439,12 +1567,12 @@ public final class StoryItemSetContainerComponent: Component {
}
if self.component?.slice.item.storyItem.id != component.slice.item.storyItem.id {
component.markAsSeen(StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id))
self.initializedOffset = false
}
var itemsTransition = transition
if let animateNextNavigationId = self.animateNextNavigationId, animateNextNavigationId == component.slice.item.storyItem.id {
self.animateNextNavigationId = nil
self.viewListPanState = nil
itemsTransition = transition.withAnimation(.curve(duration: 0.3, curve: .spring))
}
@ -1705,144 +1833,213 @@ public final class StoryItemSetContainerComponent: Component {
inputPanelIsOverlay = true
}
if component.slice.peer.id == component.context.account.peerId {
let viewList: ViewList
var viewListTransition = transition
if let current = self.viewList {
viewList = current
} else {
if !transition.animation.isImmediate {
viewListTransition = .immediate
}
viewList = ViewList()
self.viewList = viewList
}
let outerExpansionFraction: CGFloat
var validViewListIds: [Int32] = []
if component.slice.peer.id == component.context.account.peerId, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) {
var visibleViewListIds: [Int32] = [component.slice.item.storyItem.id]
if self.displayViewList {
outerExpansionFraction = 1.0
} else if let views = component.slice.item.storyItem.views, !views.seenPeers.isEmpty {
outerExpansionFraction = component.verticalPanFraction
} else {
outerExpansionFraction = 0.0
if currentIndex != 0 {
visibleViewListIds.append(component.slice.allItems[currentIndex - 1].storyItem.id)
}
if currentIndex != component.slice.allItems.count - 1 {
visibleViewListIds.append(component.slice.allItems[currentIndex + 1].storyItem.id)
}
}
viewList.view.parentState = state
let viewListSize = viewList.view.update(
transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint(
synchronousLoad: false
)).withUserData(StoryItemSetViewListComponent.AnimationHint(
synchronous: false
)),
component: AnyComponent(StoryItemSetViewListComponent(
externalState: viewList.externalState,
context: component.context,
theme: component.theme,
strings: component.strings,
sharedListsContext: component.sharedViewListsContext,
peerId: component.slice.peer.id,
safeInsets: component.safeInsets,
storyItem: component.slice.item.storyItem,
outerExpansionFraction: outerExpansionFraction,
close: { [weak self] in
guard let self else {
return
}
self.displayViewList = false
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
},
expandViewStats: { [weak self] in
guard let self else {
return
}
if !self.displayViewList {
self.displayViewList = true
self.preparingToDisplayViewList = true
self.updateScrolling(transition: .immediate)
self.preparingToDisplayViewList = false
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
},
deleteAction: { [weak self] in
guard let self, let component = self.component else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete Story", color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let self, let component = self.component else {
return
}
component.delete()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
actionSheet.dismissed = { [weak self] _ in
var viewListBaseOffsetX: CGFloat = 0.0
if let viewListPanState = self.viewListPanState {
viewListBaseOffsetX = viewListPanState.fraction * availableSize.width
}
var fixedAnimationOffset: CGFloat = 0.0
var applyFixedAnimationOffsetIds: [Int32] = []
for id in visibleViewListIds {
guard let itemIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == id }) else {
continue
}
let item = component.slice.allItems[itemIndex]
validViewListIds.append(id)
let viewList: ViewList
var viewListTransition = itemsTransition
if let current = self.viewLists[id] {
viewList = current
} else {
if !itemsTransition.animation.isImmediate {
viewListTransition = .immediate
}
viewList = ViewList()
self.viewLists[id] = viewList
applyFixedAnimationOffsetIds.append(id)
}
let outerExpansionFraction: CGFloat
let outerExpansionDirection: Bool
if self.displayViewList {
if let verticalPanState = self.verticalPanState {
outerExpansionFraction = max(0.0, min(1.0, 1.0 - verticalPanState.fraction))
} else {
outerExpansionFraction = 1.0
}
outerExpansionDirection = false
} else if let _ = item.storyItem.views {
outerExpansionFraction = component.verticalPanFraction
outerExpansionDirection = true
} else {
outerExpansionFraction = 0.0
outerExpansionDirection = true
}
viewList.view.parentState = state
let viewListSize = viewList.view.update(
transition: viewListTransition.withUserData(PeerListItemComponent.TransitionHint(
synchronousLoad: false
)).withUserData(StoryItemSetViewListComponent.AnimationHint(
synchronous: false
)),
component: AnyComponent(StoryItemSetViewListComponent(
externalState: viewList.externalState,
context: component.context,
theme: component.theme,
strings: component.strings,
sharedListsContext: component.sharedViewListsContext,
peerId: component.slice.peer.id,
safeInsets: component.safeInsets,
storyItem: item.storyItem,
outerExpansionFraction: outerExpansionFraction,
outerExpansionDirection: outerExpansionDirection,
close: { [weak self] in
guard let self else {
return
}
self.sendMessageContext.actionSheet = nil
self.displayViewList = false
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
},
expandViewStats: { [weak self] in
guard let self else {
return
}
if !self.displayViewList {
self.displayViewList = true
self.preparingToDisplayViewList = true
self.updateScrolling(transition: .immediate)
self.preparingToDisplayViewList = false
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
}
},
deleteAction: { [weak self] in
guard let self, let component = self.component else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete Story", color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let self, let component = self.component else {
return
}
component.delete()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
actionSheet.dismissed = { [weak self] _ in
guard let self else {
return
}
self.sendMessageContext.actionSheet = nil
self.updateIsProgressPaused()
}
self.sendMessageContext.actionSheet = actionSheet
self.updateIsProgressPaused()
component.presentController(actionSheet, nil)
},
moreAction: { [weak self] sourceView, gesture in
guard let self else {
return
}
self.performMoreAction(sourceView: sourceView, gesture: gesture)
},
openPeer: { [weak self] peer in
guard let self else {
return
}
self.navigateToPeer(peer: peer, chat: false)
}
self.sendMessageContext.actionSheet = actionSheet
self.updateIsProgressPaused()
component.presentController(actionSheet, nil)
},
moreAction: { [weak self] sourceView, gesture in
guard let self else {
return
}
self.performMoreAction(sourceView: sourceView, gesture: gesture)
},
openPeer: { [weak self] peer in
guard let self else {
return
}
self.navigateToPeer(peer: peer, chat: false)
}
)),
environment: {},
containerSize: availableSize
)
let viewListFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - viewListSize.height), size: viewListSize)
if let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
var animateIn = false
if viewListView.superview == nil {
self.addSubview(viewListView)
animateIn = true
}
viewListTransition.setFrame(view: viewListView, frame: viewListFrame)
viewListTransition.setAlpha(view: viewListView, alpha: component.hideUI || self.isEditingStory ? 0.0 : 1.0)
)),
environment: {},
containerSize: availableSize
)
if animateIn, !transition.animation.isImmediate {
viewListView.animateIn(transition: transition)
var viewListFrame = CGRect(origin: CGPoint(x: viewListBaseOffsetX, y: availableSize.height - viewListSize.height), size: viewListSize)
let indexDistance = CGFloat(max(-1, min(1, itemIndex - currentIndex)))
viewListFrame.origin.x += indexDistance * availableSize.width
if let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
var animateIn = false
if viewListView.superview == nil {
self.addSubview(viewListView)
animateIn = true
} else {
fixedAnimationOffset = viewListFrame.minX - viewListView.frame.minX
}
viewListTransition.setFrame(view: viewListView, frame: viewListFrame)
viewListTransition.setAlpha(view: viewListView, alpha: component.hideUI || self.isEditingStory ? 0.0 : 1.0)
if animateIn, !transition.animation.isImmediate {
viewListView.animateIn(transition: transition)
}
}
if id == component.slice.item.storyItem.id {
viewListInset = viewList.externalState.effectiveHeight
inputPanelBottomInset = viewListInset
}
}
viewListInset = viewList.externalState.effectiveHeight
inputPanelBottomInset = viewListInset
} else if let viewList = self.viewList {
self.viewList = nil
if let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View {
viewListView.animateOut(transition: transition, completion: { [weak viewListView] in
viewListView?.removeFromSuperview()
})
if fixedAnimationOffset == 0.0 {
for (id, viewList) in self.viewLists {
if let viewListView = viewList.view.view, !visibleViewListIds.contains(id), let itemIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == id }) {
let viewListSize = viewListView.bounds.size
var viewListFrame = CGRect(origin: CGPoint(x: viewListBaseOffsetX, y: availableSize.height - viewListSize.height), size: viewListSize)
let indexDistance = CGFloat(max(-1, min(1, itemIndex - currentIndex)))
viewListFrame.origin.x += indexDistance * availableSize.width
fixedAnimationOffset = viewListFrame.minX - viewListView.frame.minX
}
}
}
if fixedAnimationOffset != 0.0 {
for id in applyFixedAnimationOffsetIds {
if let viewListView = self.viewLists[id]?.view.view {
itemsTransition.animatePosition(view: viewListView, from: CGPoint(x: -fixedAnimationOffset, y: 0.0), to: CGPoint(), additive: true)
}
}
}
}
var removeViewListIds: [Int32] = []
for (id, viewList) in self.viewLists {
if !validViewListIds.contains(id) {
removeViewListIds.append(id)
viewList.view.view?.removeFromSuperview()
}
}
for id in removeViewListIds {
self.viewLists.removeValue(forKey: id)
}
let itemSize = CGSize(width: availableSize.width, height: ceil(availableSize.width * 1.77778))
@ -2000,7 +2197,7 @@ public final class StoryItemSetContainerComponent: Component {
let tooltipScreen = TooltipScreen(
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .plain(text: "This video has no sound"), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: self).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .manual, shouldDismissOnTouch: { _, _ in
text: .plain(text: "This video has no sound"), style: .default, location: TooltipScreen.Location.point(soundButtonView.convert(soundButtonView.bounds, to: self).offsetBy(dx: 1.0, dy: -10.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: true)
}
)

View File

@ -13,6 +13,7 @@ import TelegramStringFormatting
import ShimmerEffect
import StoryFooterPanelComponent
import PeerListItemComponent
import AnimatedStickerComponent
final class StoryItemSetViewListComponent: Component {
final class AnimationHint {
@ -47,6 +48,7 @@ final class StoryItemSetViewListComponent: Component {
let safeInsets: UIEdgeInsets
let storyItem: EngineStoryItem
let outerExpansionFraction: CGFloat
let outerExpansionDirection: Bool
let close: () -> Void
let expandViewStats: () -> Void
let deleteAction: () -> Void
@ -63,6 +65,7 @@ final class StoryItemSetViewListComponent: Component {
safeInsets: UIEdgeInsets,
storyItem: EngineStoryItem,
outerExpansionFraction: CGFloat,
outerExpansionDirection: Bool,
close: @escaping () -> Void,
expandViewStats: @escaping () -> Void,
deleteAction: @escaping () -> Void,
@ -78,6 +81,7 @@ final class StoryItemSetViewListComponent: Component {
self.safeInsets = safeInsets
self.storyItem = storyItem
self.outerExpansionFraction = outerExpansionFraction
self.outerExpansionDirection = outerExpansionDirection
self.close = close
self.expandViewStats = expandViewStats
self.deleteAction = deleteAction
@ -104,6 +108,9 @@ final class StoryItemSetViewListComponent: Component {
if lhs.outerExpansionFraction != rhs.outerExpansionFraction {
return false
}
if lhs.outerExpansionDirection != rhs.outerExpansionDirection {
return false
}
return true
}
@ -194,6 +201,9 @@ final class StoryItemSetViewListComponent: Component {
private var visibleItems: [EnginePeer.Id: ComponentView<Empty>] = [:]
private var visiblePlaceholderViews: [Int: UIImageView] = [:]
private var emptyIcon: ComponentView<Empty>?
private var emptyText: ComponentView<Empty>?
private var component: StoryItemSetViewListComponent?
private weak var state: EmptyComponentState?
@ -231,7 +241,9 @@ final class StoryItemSetViewListComponent: Component {
self.addSubview(self.navigationBarBackground)
self.layer.addSublayer(self.navigationSeparator)
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { _ in
return [.down]
})
panRecognizer.delegate = self
self.addGestureRecognizer(panRecognizer)
}
@ -245,7 +257,11 @@ final class StoryItemSetViewListComponent: Component {
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
if otherGestureRecognizer === self.scrollView.panGestureRecognizer {
return true
} else {
return false
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
@ -321,7 +337,10 @@ final class StoryItemSetViewListComponent: Component {
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let navigationPanelView = self.navigationPanel.view {
if let navigationPanelView = self.navigationPanel.view as? StoryFooterPanelComponent.View {
if navigationPanelView.frame.contains(point), let result = navigationPanelView.externalContainerView.hitTest(self.convert(point, to: navigationPanelView.externalContainerView), with: event), result !== navigationPanelView.externalContainerView {
return result
}
if let result = navigationPanelView.hitTest(self.convert(point, to: navigationPanelView), with: event) {
if result !== navigationPanelView {
return result
@ -455,7 +474,7 @@ final class StoryItemSetViewListComponent: Component {
theme: component.theme,
strings: component.strings,
style: .generic,
sideInset: itemLayout.sideInset,
sideInset: 0.0,
title: item.peer.displayTitle(strings: component.strings, displayOrder: .firstLast),
peer: item.peer,
subtitle: dateText,
@ -530,6 +549,7 @@ final class StoryItemSetViewListComponent: Component {
func update(component: StoryItemSetViewListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
let itemUpdated = self.component?.storyItem.id != component.storyItem.id
let viewsNilUpdated = (self.component?.storyItem.views == nil) != (component.storyItem.views == nil)
self.component = component
self.state = state
@ -539,7 +559,7 @@ final class StoryItemSetViewListComponent: Component {
synchronous = animationHint.synchronous
}
let minimizedHeight = min(availableSize.height, 500.0)
let minimizedHeight = max(100.0, availableSize.height - (325.0 + 12.0))
if themeUpdated {
self.backgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor
@ -547,7 +567,7 @@ final class StoryItemSetViewListComponent: Component {
self.navigationSeparator.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
}
if itemUpdated {
if itemUpdated || viewsNilUpdated {
self.viewListState = nil
self.viewListDisposable?.dispose()
@ -606,7 +626,7 @@ final class StoryItemSetViewListComponent: Component {
environment: {},
containerSize: CGSize(width: 120.0, height: 100.0)
)
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: navigationBarFrame.minY), size: navigationLeftButtonSize)
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: navigationBarFrame.minY + 1.0), size: navigationLeftButtonSize)
if let navigationLeftButtonView = self.navigationLeftButton.view {
if navigationLeftButtonView.superview == nil {
self.addSubview(navigationLeftButtonView)
@ -621,7 +641,17 @@ final class StoryItemSetViewListComponent: Component {
dismissOffsetY = -dismissPanState.accumulatedOffset
}
dismissOffsetY -= (1.0 - component.outerExpansionFraction) * expansionOffset
let selfFraction = expansionOffset / availableSize.height
var mappedOuterExpansionFraction: CGFloat
if component.outerExpansionDirection {
mappedOuterExpansionFraction = component.outerExpansionFraction / (1.0 - selfFraction)
} else {
mappedOuterExpansionFraction = 1.0 - (1.0 - component.outerExpansionFraction) / (1.0 - selfFraction)
}
mappedOuterExpansionFraction = max(0.0, min(1.0, mappedOuterExpansionFraction))
dismissOffsetY -= (1.0 - mappedOuterExpansionFraction) * expansionOffset
let dismissFraction: CGFloat = 1.0 - max(0.0, min(1.0, -dismissOffsetY / expansionOffset))
@ -744,6 +774,82 @@ final class StoryItemSetViewListComponent: Component {
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
if let viewListState = self.viewListState, viewListState.loadMoreToken == nil, viewListState.items.isEmpty, viewListState.totalCount == 0 {
var emptyTransition = transition
let emptyIcon: ComponentView<Empty>
if let current = self.emptyIcon {
emptyIcon = current
} else {
emptyTransition = emptyTransition.withAnimation(.none)
emptyIcon = ComponentView()
self.emptyIcon = emptyIcon
}
let emptyText: ComponentView<Empty>
if let current = self.emptyText {
emptyText = current
} else {
emptyText = ComponentView()
self.emptyText = emptyText
}
let emptyIconSize = emptyIcon.update(
transition: emptyTransition,
component: AnyComponent(AnimatedStickerComponent(
account: component.context.account,
animation: AnimatedStickerComponent.Animation(source: .bundle(name: "ChatListNoResults"), loop: true),
size: CGSize(width: 140.0, height: 140.0)
)),
environment: {},
containerSize: CGSize(width: 140.0, height: 140.0)
)
let text: String
if component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) {
text = "List of viewers isnt available after\n24 hours of story expiration."
} else {
text = "Nobody has viewed\nyour story yet."
}
let textSize = emptyText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: min(300.0, availableSize.width - 16.0 * 2.0), height: 1000.0)
)
let emptyContentSpacing: CGFloat = 20.0
var emptyContentY = navigationBarFrame.minY + floor((availableSize.height - navigationBarFrame.minY - (emptyIconSize.height - emptyContentSpacing - textSize.height)) * 0.5) - 60.0
if let emptyIconView = emptyIcon.view {
if emptyIconView.superview == nil {
self.insertSubview(emptyIconView, belowSubview: self.scrollView)
}
emptyTransition.setFrame(view: emptyIconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - emptyIconSize.width) * 0.5), y: emptyContentY), size: emptyIconSize))
emptyContentY += emptyIconSize.height + emptyContentSpacing
}
if let emptyTextView = emptyText.view {
if emptyTextView.superview == nil {
self.insertSubview(emptyTextView, belowSubview: self.scrollView)
}
emptyTransition.setFrame(view: emptyTextView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: emptyContentY), size: textSize))
}
} else {
if let emptyIcon = self.emptyIcon {
self.emptyIcon = nil
emptyIcon.view?.removeFromSuperview()
}
if let emptyText = self.emptyText {
self.emptyText = nil
emptyText.view?.removeFromSuperview()
}
}
transition.setBoundsOrigin(view: self, origin: CGPoint(x: 0.0, y: dismissOffsetY))
component.externalState.minimizedHeight = minimizedHeight

View File

@ -277,7 +277,7 @@ public final class StoryFooterPanelComponent: Component {
)
let viewStatsCollapsedFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - viewStatsTextSize.height) * 0.5)), size: viewStatsTextSize)
let viewStatsExpandedFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - viewStatsExpandedTextSize.width) * 0.5), y: 2.0 + floor((size.height - viewStatsExpandedTextSize.height) * 0.5)), size: viewStatsExpandedTextSize)
let viewStatsExpandedFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - viewStatsExpandedTextSize.width) * 0.5), y: 3.0 + floor((size.height - viewStatsExpandedTextSize.height) * 0.5)), size: viewStatsExpandedTextSize)
let viewStatsCurrentFrame = viewStatsCollapsedFrame.interpolate(to: viewStatsExpandedFrame, amount: component.expandFraction)
let viewStatsTextCenter = viewStatsCollapsedFrame.center.interpolate(to: viewStatsExpandedFrame.center, amount: component.expandFraction)

View File

@ -721,7 +721,7 @@ public final class StoryPeerListComponent: Component {
let centralContentWidth: CGFloat = collapsedContentWidth + titleContentSpacing + collapsedState.titleWidth
collapsedContentOrigin = floor((itemLayout.containerSize.width - centralContentWidth) * 0.5)
collapsedContentOrigin = (itemLayout.containerSize.width - centralContentWidth) * 0.5
collapsedContentOrigin = min(collapsedContentOrigin, component.maxTitleX - centralContentWidth - 4.0)
@ -1135,7 +1135,7 @@ public final class StoryPeerListComponent: Component {
let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset
let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction * (1.0 - collapsedState.activityFraction))
var titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: floor((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: collapsedState.maxFraction * (1.0 - collapsedState.activityFraction))
var titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: ((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: collapsedState.maxFraction * (1.0 - collapsedState.activityFraction))
var titleIndicatorSize: CGSize?
if collapsedState.activityFraction != 0.0 {

View File

@ -49,7 +49,7 @@ private final class ShapeImageView: UIView {
context.setBlendMode(.sourceIn)
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: params.borderColors.map {
UIColor(rgb: $0).cgColor
UIColor(argb: $0).cgColor
} as CFArray, locations: nil)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 50.0), options: [])
@ -344,25 +344,25 @@ public final class StorySetIndicatorComponent: Component {
if component.theme.overallDarkAppearance {
if component.hasUnseen {
borderColors = [
0x34C76F,
0x3DA1FD
0xFF34C76F,
0xFF3DA1FD
]
} else {
borderColors = [
0x48484A,
0x48484A
UIColor(white: 1.0, alpha: 0.3).argb,
UIColor(white: 1.0, alpha: 0.3).argb
]
}
} else {
if component.hasUnseen {
borderColors = [
0x34C76F,
0x3DA1FD
0xFF34C76F,
0xFF3DA1FD
]
} else {
borderColors = [
0xD8D8E1,
0xD8D8E1
UIColor(white: 1.0, alpha: 0.3).argb,
UIColor(white: 1.0, alpha: 0.3).argb
]
}
}