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

This commit is contained in:
Ilya Laktyushin 2023-06-27 16:29:11 +02:00
commit b10ae1b12a
36 changed files with 1198 additions and 257 deletions

View File

@ -2604,6 +2604,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
})))
} else {
items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self, let navigationController = self.navigationController as? NavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
})
})))
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
@ -2671,7 +2683,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
})))
items.append(.action(ContextMenuActionItem(text: "Hide", icon: { theme in
items.append(.action(ContextMenuActionItem(text: "Move to Contacts", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
@ -3727,7 +3739,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
searchContentNode.placeholderNode.frame = previousFrame
}
self.chatListDisplayNode.tempAllowAvatarExpansion = true
self.requestLayout(transition: .animated(duration: 0.5, curve: .spring))
self.chatListDisplayNode.tempAllowAvatarExpansion = false
//TODO:swap tabs

View File

@ -1690,7 +1690,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var didBeginSelectingChatsWhileEditing: Bool = false
var isEditing: Bool = false
private var tempAllowAvatarExpansion: Bool = false
var tempAllowAvatarExpansion: Bool = false
private var tempDisableStoriesAnimations: Bool = false
private var tempNavigationScrollingTransition: ContainedViewLayoutTransition?

View File

@ -2807,7 +2807,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyState.hasUnseen,
hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends,
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
theme: item.presentationData.theme,
activeLineWidth: 2.0,
inactiveLineWidth: 1.0 + UIScreenPixel,
counters: nil

View File

@ -43,6 +43,7 @@ swift_library(
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
"//submodules/ComponentFlow",
"//submodules/TooltipUI",
"//submodules/UndoUI",
],
visibility = [
"//visibility:public",

View File

@ -9,6 +9,7 @@ import AlertUI
import PresentationDataUtils
import OverlayStatusController
import LocalizedPeerData
import UndoUI
import TooltipUI
func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, contactsController: ContactsController?, isStories: Bool) -> Signal<[ContextMenuItem], NoError> {
@ -17,16 +18,76 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
return context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceCallsAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVideoCallsAvailable(id: peerId)
TelegramEngine.EngineData.Item.Peer.AreVideoCallsAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId)
)
|> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable -> [ContextMenuItem] in
|> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable, notificationSettings -> [ContextMenuItem] in
var items: [ContextMenuItem] = []
if isStories {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Unhide", icon: { theme in
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { c, _ in
c.dismiss(completion: {
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
(contactsController?.navigationController as? NavigationController)?.pushViewController(controller)
})
})
})))
let isMuted = notificationSettings.storiesMuted == true
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Not Notify", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
let _ = context.engine.peers.togglePeerStoriesMuted(peerId: peerId).start()
if let peer {
let iconColor = UIColor.white
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if isMuted {
contactsController?.present(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will now get a notification whenever **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
} else {
contactsController?.present(UndoOverlayController(
presentationData: presentationData,
content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
], title: nil, text: "You will no longer receive a notification when **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))** posts a story.", customUndoText: nil, timeout: nil),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
}
}
})))
items.append(.action(ContextMenuActionItem(text: "Move to chats", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor)
}, action: { [weak contactsController] _, f in
}, action: { _, f in
f(.default)
context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false)
@ -45,6 +106,8 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
)
contactsController?.present(tooltipController, in: .window(.root))
})))
return items
}
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_SendMessage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.contextMenu.primaryColor) }, action: { _, f in

View File

@ -559,67 +559,6 @@ public class ContactsController: ViewController {
self.push(storyContainerScreen)
})
}
/*componentView.storyContextPeerAction = { [weak self] sourceNode, gesture, peer in
guard let self else {
return
}
var items: [ContextMenuItem] = []
//TODO:localize
if peer.id == self.context.account.peerId {
} else {
items.append(.action(ContextMenuActionItem(text: "View Profile", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c.dismiss(completion: {
guard let self else {
return
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)
)
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self else {
return
}
guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
(self.navigationController as? NavigationController)?.pushViewController(controller)
})
})
})))
/*items.append(.action(ContextMenuActionItem(text: "Mute", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.default)
})))*/
if case let .user(user) = peer, let storiesHidden = user.storiesHidden, storiesHidden {
items.append(.action(ContextMenuActionItem(text: "Unarchive", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false)
})))
}
}
if items.isEmpty {
return
}
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
}*/
}
@objc private func sortPressed() {

View File

@ -527,10 +527,18 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
guard let contactsController = self.controller else {
return
}
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController, isStories: isStories) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
contactsController.presentInGlobalOverlay(contextController)
let items = contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController, isStories: isStories) |> map { ContextController.Items(content: .list($0)) }
if isStories, let node = node?.subnodes?.first(where: { $0 is ContextExtractedContentContainingNode }) as? ContextExtractedContentContainingNode {
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ContactContextExtractedContentSource(sourceNode: node, shouldBeDismissed: .single(false))), items: items, recognizer: nil, gesture: gesture)
contactsController.presentInGlobalOverlay(controller)
} else {
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: items, gesture: gesture)
contactsController.presentInGlobalOverlay(contextController)
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
@ -616,3 +624,26 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
}
}
private final class ContactContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = false
let ignoreContentTouches: Bool = true
let blurBackground: Bool = true
let shouldBeDismissed: Signal<Bool, NoError>
private let sourceNode: ContextExtractedContentContainingNode
init(sourceNode: ContextExtractedContentContainingNode, shouldBeDismissed: Signal<Bool, NoError>? = nil) {
self.sourceNode = sourceNode
self.shouldBeDismissed = shouldBeDismissed ?? .single(false)
}
func takeView() -> ContextControllerTakeViewInfo? {
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds)
}
func putBack() -> ContextControllerPutBackViewInfo? {
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -1115,7 +1115,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyStats.unseen != 0,
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
theme: item.presentationData.theme,
activeLineWidth: 1.0 + UIScreenPixel,
inactiveLineWidth: 1.0 + UIScreenPixel,
counters: AvatarStoryIndicatorComponent.Counters(totalCount: storyStats.total, unseenCount: storyStats.unseen)

View File

@ -105,6 +105,9 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt
if (flags & (1 << 1)) != 0 {
videoFlags.insert(.supportsStreaming)
}
if (flags & (1 << 3)) != 0 {
videoFlags.insert(.isSilent)
}
result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize))
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
let isVoice = (flags & (1 << 10)) != 0

View File

@ -569,6 +569,9 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF
if preloadSize != nil {
flags |= (1 << 2)
}
if videoFlags.contains(.isSilent) {
flags |= (1 << 3)
}
attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize))
case let .Audio(isVoice, duration, title, performer, waveform):

View File

@ -188,6 +188,7 @@ public struct TelegramMediaVideoFlags: OptionSet {
public static let instantRoundVideo = TelegramMediaVideoFlags(rawValue: 1 << 0)
public static let supportsStreaming = TelegramMediaVideoFlags(rawValue: 1 << 1)
public static let isSilent = TelegramMediaVideoFlags(rawValue: 1 << 3)
}
public struct StickerMaskCoords: PostboxCoding, Equatable {

View File

@ -13,6 +13,7 @@ public extension Stories {
case entities
case pin
case privacy
case isForwardingDisabled
case period
case randomId
}
@ -24,6 +25,7 @@ public extension Stories {
public let entities: [MessageTextEntity]
public let pin: Bool
public let privacy: EngineStoryPrivacy
public let isForwardingDisabled: Bool
public let period: Int32
public let randomId: Int64
@ -35,6 +37,7 @@ public extension Stories {
entities: [MessageTextEntity],
pin: Bool,
privacy: EngineStoryPrivacy,
isForwardingDisabled: Bool,
period: Int32,
randomId: Int64
) {
@ -45,6 +48,7 @@ public extension Stories {
self.entities = entities
self.pin = pin
self.privacy = privacy
self.isForwardingDisabled = isForwardingDisabled
self.period = period
self.randomId = randomId
}
@ -62,6 +66,7 @@ public extension Stories {
self.entities = try container.decode([MessageTextEntity].self, forKey: .entities)
self.pin = try container.decode(Bool.self, forKey: .pin)
self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy)
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
self.period = try container.decode(Int32.self, forKey: .period)
self.randomId = try container.decode(Int64.self, forKey: .randomId)
}
@ -80,6 +85,7 @@ public extension Stories {
try container.encode(self.entities, forKey: .entities)
try container.encode(self.pin, forKey: .pin)
try container.encode(self.privacy, forKey: .privacy)
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
try container.encode(self.period, forKey: .period)
try container.encode(self.randomId, forKey: .randomId)
}
@ -106,6 +112,9 @@ public extension Stories {
if lhs.privacy != rhs.privacy {
return false
}
if lhs.isForwardingDisabled != rhs.isForwardingDisabled {
return false
}
if lhs.period != rhs.period {
return false
}
@ -260,7 +269,7 @@ final class PendingStoryManager {
self.currentPendingItemContext = pendingItemContext
let stableId = firstItem.stableId
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, pin: firstItem.pin, privacy: firstItem.privacy, period: Int(firstItem.period), randomId: firstItem.randomId)
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId)
|> deliverOn(self.queue)).start(next: { [weak self] event in
guard let `self` = self else {
return

View File

@ -125,6 +125,7 @@ public enum Stories {
case isExpired
case isPublic
case isCloseFriends
case isForwardingDisabled
}
public let id: Int32
@ -139,6 +140,7 @@ public enum Stories {
public let isExpired: Bool
public let isPublic: Bool
public let isCloseFriends: Bool
public let isForwardingDisabled: Bool
public init(
id: Int32,
@ -152,7 +154,8 @@ public enum Stories {
isPinned: Bool,
isExpired: Bool,
isPublic: Bool,
isCloseFriends: Bool
isCloseFriends: Bool,
isForwardingDisabled: Bool
) {
self.id = id
self.timestamp = timestamp
@ -166,6 +169,7 @@ public enum Stories {
self.isExpired = isExpired
self.isPublic = isPublic
self.isCloseFriends = isCloseFriends
self.isForwardingDisabled = isForwardingDisabled
}
public init(from decoder: Decoder) throws {
@ -189,6 +193,7 @@ public enum Stories {
self.isExpired = try container.decodeIfPresent(Bool.self, forKey: .isExpired) ?? false
self.isPublic = try container.decodeIfPresent(Bool.self, forKey: .isPublic) ?? false
self.isCloseFriends = try container.decodeIfPresent(Bool.self, forKey: .isCloseFriends) ?? false
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -213,6 +218,7 @@ public enum Stories {
try container.encode(self.isExpired, forKey: .isExpired)
try container.encode(self.isPublic, forKey: .isPublic)
try container.encode(self.isCloseFriends, forKey: .isCloseFriends)
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -260,6 +266,9 @@ public enum Stories {
if lhs.isCloseFriends != rhs.isCloseFriends {
return false
}
if lhs.isForwardingDisabled != rhs.isForwardingDisabled {
return false
}
return true
}
@ -680,7 +689,7 @@ private func apiInputPrivacyRules(privacy: EngineStoryPrivacy, transaction: Tran
return privacyRules
}
func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, period: Int, randomId: Int64) {
func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) {
let inputMedia = prepareUploadStoryContent(account: account, media: media)
let _ = (account.postbox.transaction { transaction in
@ -702,6 +711,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
entities: entities,
pin: pin,
privacy: privacy,
isForwardingDisabled: isForwardingDisabled,
period: Int32(period),
randomId: randomId
))
@ -747,7 +757,7 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId:
}
}
func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods)
return contentSignal
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
@ -787,6 +797,10 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
flags |= 1 << 3
if isForwardingDisabled {
flags |= 1 << 4
}
return network.request(Api.functions.stories.sendStory(
flags: flags,
media: inputMedia,
@ -835,7 +849,8 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp))
@ -983,7 +998,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
transaction.setStory(id: storyId, value: entry)
@ -1005,7 +1021,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp)
@ -1136,7 +1153,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
isPinned: isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp)
@ -1157,7 +1175,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
isPinned: isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
updatedItems.append(updatedItem)
}
@ -1253,6 +1272,7 @@ extension Stories.StoredItem {
let isExpired = (flags & (1 << 6)) != 0
let isPublic = (flags & (1 << 7)) != 0
let isCloseFriends = (flags & (1 << 8)) != 0
let isForwardingDisabled = (flags & (1 << 10)) != 0
let item = Stories.Item(
id: id,
@ -1266,7 +1286,8 @@ extension Stories.StoredItem {
isPinned: isPinned,
isExpired: isExpired,
isPublic: isPublic,
isCloseFriends: isCloseFriends
isCloseFriends: isCloseFriends,
isForwardingDisabled: isForwardingDisabled
)
self = .item(item)
} else {

View File

@ -44,8 +44,9 @@ public final class EngineStoryItem: Equatable {
public let isPublic: Bool
public let isPending: Bool
public let isCloseFriends: Bool
public let isForwardingDisabled: Bool
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool) {
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isForwardingDisabled: Bool) {
self.id = id
self.timestamp = timestamp
self.expirationTimestamp = expirationTimestamp
@ -59,6 +60,7 @@ public final class EngineStoryItem: Equatable {
self.isPublic = isPublic
self.isPending = isPending
self.isCloseFriends = isCloseFriends
self.isForwardingDisabled = isForwardingDisabled
}
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
@ -101,6 +103,9 @@ public final class EngineStoryItem: Equatable {
if lhs.isCloseFriends != rhs.isCloseFriends {
return false
}
if lhs.isForwardingDisabled != rhs.isForwardingDisabled {
return false
}
return true
}
}
@ -129,7 +134,8 @@ extension EngineStoryItem {
isPinned: self.isPinned,
isExpired: self.isExpired,
isPublic: self.isPublic,
isCloseFriends: self.isCloseFriends
isCloseFriends: self.isCloseFriends,
isForwardingDisabled: self.isForwardingDisabled
)
}
}
@ -487,7 +493,8 @@ public final class PeerStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
items.append(mappedItem)
}
@ -593,7 +600,8 @@ public final class PeerStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
storyItems.append(mappedItem)
}
@ -726,7 +734,8 @@ public final class PeerStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
finalUpdatedState = updatedState
}
@ -764,7 +773,8 @@ public final class PeerStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
))
updatedState.items.sort(by: { lhs, rhs in
return lhs.timestamp > rhs.timestamp
@ -911,7 +921,8 @@ public final class PeerExpiringStoryListContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
items.append(.item(mappedItem))
}

View File

@ -966,7 +966,8 @@ public extension TelegramEngine {
isPinned: item.isPinned,
isExpired: item.isExpired,
isPublic: item.isPublic,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
))
if let entry = CodableEntry(updatedItem) {
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp)
@ -980,8 +981,8 @@ public extension TelegramEngine {
}
}
public func uploadStory(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, period: Int, randomId: Int64) {
_internal_uploadStory(account: self.account, media: media, text: text, entities: entities, pin: pin, privacy: privacy, period: period, randomId: randomId)
public func uploadStory(media: EngineStoryInputMedia, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) {
_internal_uploadStory(account: self.account, media: media, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId)
}
public func lookUpPendingStoryIdMapping(stableId: Int32) -> Int32? {

View File

@ -214,7 +214,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyData.hasUnseen,
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
isDarkTheme: theme.overallDarkAppearance,
theme: theme,
activeLineWidth: 1.0,
inactiveLineWidth: 1.0,
counters: nil

View File

@ -312,6 +312,7 @@ public final class ChatListHeaderComponent: Component {
var contentOffsetFraction: CGFloat = 0.0
private(set) var centerContentWidth: CGFloat = 0.0
private(set) var centerContentLeftInset: CGFloat = 0.0
private(set) var centerContentRightInset: CGFloat = 0.0
private(set) var centerContentOffsetX: CGFloat = 0.0
@ -639,8 +640,11 @@ public final class ChatListHeaderComponent: Component {
}
}
var centerContentLeftInset: CGFloat = 0.0
centerContentLeftInset = leftOffset - 4.0
var centerContentRightInset: CGFloat = 0.0
centerContentRightInset = size.width - rightOffset + 16.0
centerContentRightInset = size.width - rightOffset - 8.0
var centerContentWidth: CGFloat = 0.0
var centerContentOffsetX: CGFloat = 0.0
@ -705,6 +709,7 @@ public final class ChatListHeaderComponent: Component {
self.centerContentOffsetX = centerContentOffsetX
self.centerContentOrigin = centerContentOrigin
self.centerContentRightInset = centerContentRightInset
self.centerContentLeftInset = centerContentLeftInset
}
}
@ -815,7 +820,7 @@ public final class ChatListHeaderComponent: Component {
let previousComponent = self.component
self.component = component
if let primaryContent = component.primaryContent {
if var primaryContent = component.primaryContent {
var primaryContentTransition = transition
let primaryContentView: ContentView
if let current = self.primaryContentView {
@ -848,6 +853,19 @@ public final class ChatListHeaderComponent: Component {
let sideContentWidth: CGFloat = 0.0
if component.storySubscriptions != nil {
primaryContent = Content(
title: "",
navigationBackTitle: primaryContent.navigationBackTitle,
titleComponent: nil,
chatListTitle: nil,
leftButton: primaryContent.leftButton,
rightButtons: primaryContent.rightButtons,
backTitle: primaryContent.backTitle,
backPressed: primaryContent.backPressed
)
}
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
@ -868,6 +886,28 @@ public final class ChatListHeaderComponent: Component {
self.storyPeerList = storyPeerList
}
var primaryTitle = ""
var primaryTitleHasLock = false
var primaryTitleHasActivity = false
var primaryTitlePeerStatus: StoryPeerListComponent.PeerStatus?
if let primaryContent = component.primaryContent {
if let chatListTitle = primaryContent.chatListTitle {
primaryTitle = chatListTitle.text
primaryTitleHasLock = chatListTitle.isPasscodeSet
primaryTitleHasActivity = chatListTitle.activity
if let peerStatus = chatListTitle.peerStatus {
switch peerStatus {
case .premium:
primaryTitlePeerStatus = .premium
case let .emoji(status):
primaryTitlePeerStatus = .emoji(status)
}
}
} else {
primaryTitle = primaryContent.title
}
}
let _ = storyPeerList.update(
transition: storyListTransition,
component: AnyComponent(StoryPeerListComponent(
@ -876,7 +916,11 @@ public final class ChatListHeaderComponent: Component {
theme: component.theme,
strings: component.strings,
sideInset: component.sideInset,
titleContentWidth: self.primaryContentView?.centerContentWidth ?? 0.0,
title: primaryTitle,
titleHasLock: primaryTitleHasLock,
titleHasActivity: primaryTitleHasActivity,
titlePeerStatus: primaryTitlePeerStatus,
minTitleX: self.primaryContentView?.centerContentLeftInset ?? 0.0,
maxTitleX: availableSize.width - (self.primaryContentView?.centerContentRightInset ?? 0.0),
useHiddenList: component.storiesIncludeHidden,
storySubscriptions: storySubscriptions,
@ -895,14 +939,11 @@ public final class ChatListHeaderComponent: Component {
}
self.storyContextPeerAction?(sourceNode, gesture, peer)
},
updateTitleContentOffset: { [weak self] offset, transition in
guard let self, let primaryContentView = self.primaryContentView else {
openStatusSetup: { [weak self] sourceView in
guard let self else {
return
}
guard let chatListTitleView = primaryContentView.chatListTitleView else {
return
}
transition.setSublayerTransform(view: chatListTitleView, transform: CATransform3DMakeTranslation(offset, 0.0, 0.0))
self.component?.openStatusSetup(sourceView)
}
)),
environment: {},
@ -1002,12 +1043,7 @@ public final class ChatListHeaderComponent: Component {
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: -1.0 * availableSize.width * component.secondaryTransition + 0.0, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
var storyListNormalAlpha: CGFloat = 1.0
if let chatListTitle = component.primaryContent?.chatListTitle {
if chatListTitle.activity {
storyListNormalAlpha = component.storiesFraction
}
}
let storyListNormalAlpha: CGFloat = 1.0
let storyListAlpha: CGFloat = (1.0 - component.secondaryTransition) * storyListNormalAlpha
storyListTransition.setAlpha(view: storyPeerListComponentView, alpha: storyListAlpha)

View File

@ -0,0 +1,24 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "EmptyStateIndicatorComponent",
module_name = "EmptyStateIndicatorComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/Components/AnimatedStickerComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,183 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import AnimatedStickerComponent
import ButtonComponent
import TelegramPresentationData
import AccountContext
import MultilineTextComponent
public final class EmptyStateIndicatorComponent: Component {
public let context: AccountContext
public let theme: PresentationTheme
public let animationName: String
public let title: String
public let text: String
public let actionTitle: String
public let action: () -> Void
public init(
context: AccountContext,
theme: PresentationTheme,
animationName: String,
title: String,
text: String,
actionTitle: String,
action: @escaping () -> Void
) {
self.context = context
self.theme = theme
self.animationName = animationName
self.title = title
self.text = text
self.actionTitle = actionTitle
self.action = action
}
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.animationName != rhs.animationName {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.actionTitle != rhs.actionTitle {
return false
}
return true
}
public final class View: UIView {
private var component: EmptyStateIndicatorComponent?
private weak var componentState: EmptyComponentState?
private let animation = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let text = ComponentView<Empty>()
private let button = ComponentView<Empty>()
override public init(frame: CGRect) {
super.init(frame: frame)
}
required public init(coder: NSCoder) {
preconditionFailure()
}
public func update(component: EmptyStateIndicatorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.componentState = state
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(AnimatedStickerComponent(
account: component.context.account,
animation: AnimatedStickerComponent.Animation(source: .bundle(name: component.animationName), loop: true),
size: CGSize(width: 120.0, height: 120.0)
)),
environment: {},
containerSize: CGSize(width: 120.0, height: 120.0)
)
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: min(300.0, availableSize.width - 16.0 * 2.0), height: 1000.0)
)
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.text, font: Font.regular(15.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 buttonSize = self.button.update(
transition: transition,
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: 0, component: AnyComponent(
Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor)
)),
isEnabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.action()
}
)),
environment: {},
containerSize: CGSize(width: 240.0, height: 50.0)
)
let animationSpacing: CGFloat = 11.0
let titleSpacing: CGFloat = 17.0
let buttonSpacing: CGFloat = 17.0
let totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height + buttonSpacing + buttonSize.height
var contentY = floor((availableSize.height - totalHeight) * 0.5)
if let animationView = self.animation.view {
if animationView.superview == nil {
self.addSubview(animationView)
}
transition.setFrame(view: animationView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) * 0.5), y: contentY), size: animationSize))
contentY += animationSize.height + animationSpacing
}
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentY), size: titleSize))
contentY += titleSize.height + titleSpacing
}
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
}
transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: contentY), size: textSize))
contentY += textSize.height + buttonSpacing
}
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize))
contentY += buttonSize.height
}
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -131,31 +131,33 @@ final class PeerInfoStoryGridScreenComponent: Component {
paneNode.clearSelection()
})))
} else {
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement
let canZoom: Bool = nextZoomLevel != nil
} else if let paneNode = self.paneNode {
if !paneNode.isEmpty {
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
let nextZoomLevel = isZoomIn ? pane?.availableZoomLevels().increment : pane?.availableZoomLevels().decrement
let canZoom: Bool = nextZoomLevel != nil
return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
}, action: canZoom ? { action in
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
return
}
pane.updateZoomLevel(level: zoomLevel)
if let recurseGenerateAction = recurseGenerateAction {
action.updateAction(0, recurseGenerateAction(true))
action.updateAction(1, recurseGenerateAction(false))
}
} : nil)
}
recurseGenerateAction = { isZoomIn in
return generateAction(isZoomIn)
}
return ContextMenuActionItem(id: isZoomIn ? 0 : 1, text: isZoomIn ? strings.SharedMedia_ZoomIn : strings.SharedMedia_ZoomOut, textColor: canZoom ? .primary : .disabled, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
}, action: canZoom ? { action in
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
return
}
pane.updateZoomLevel(level: zoomLevel)
if let recurseGenerateAction = recurseGenerateAction {
action.updateAction(0, recurseGenerateAction(true))
action.updateAction(1, recurseGenerateAction(false))
}
} : nil)
items.append(.action(generateAction(true)))
items.append(.action(generateAction(false)))
}
recurseGenerateAction = { isZoomIn in
return generateAction(isZoomIn)
}
items.append(.action(generateAction(true)))
items.append(.action(generateAction(false)))
if component.peerId == component.context.account.peerId, case .saved = component.scope {
var ignoreNextActions = false
@ -392,6 +394,13 @@ final class PeerInfoStoryGridScreenComponent: Component {
self.paneNode = paneNode
self.addSubview(paneNode.view)
paneNode.emptyAction = { [weak self] in
guard let self, let component = self.component else {
return
}
self.environment?.controller()?.push(PeerInfoStoryGridScreen(context: component.context, peerId: component.peerId, scope: .archive))
}
self.paneStatusDisposable = (paneNode.status
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let self else {

View File

@ -36,6 +36,7 @@ swift_library(
"//submodules/InvisibleInkDustNode",
"//submodules/MediaPickerUI",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/EmptyStateIndicatorComponent",
],
visibility = [
"//visibility:public",

View File

@ -26,6 +26,7 @@ import AppBundle
import InvisibleInkDustNode
import MediaPickerUI
import StoryContainerScreen
import EmptyStateIndicatorComponent
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
private let mediaBadgeTextColor = UIColor.white
@ -849,6 +850,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
return result
}
public var isEmpty: Bool {
if let items = self.items, items.items.count != 0 {
return false
} else {
return true
}
}
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)?
private let ready = Promise<Bool>()
@ -883,6 +892,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
public var openCurrentDate: (() -> Void)?
public var paneDidScroll: (() -> Void)?
public var emptyAction: (() -> Void)?
private weak var currentGestureItem: SparseItemGridDisplayItem?
@ -892,6 +902,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private weak var pendingOpenListContext: PeerStoryListContentContextImpl?
private var preloadArchiveListContext: PeerStoryListContext?
private var emptyStateView: ComponentView<Empty>?
public init(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, navigationController: @escaping () -> NavigationController?, listContext: PeerStoryListContext?) {
self.context = context
@ -1863,6 +1875,67 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData)
transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
if let items = self.items, items.items.isEmpty, items.count == 0, !self.isArchive {
let emptyStateView: ComponentView<Empty>
var emptyStateTransition = Transition(transition)
if let current = self.emptyStateView {
emptyStateView = current
} else {
emptyStateTransition = .immediate
emptyStateView = ComponentView()
self.emptyStateView = emptyStateView
}
//TODO:localize
let emptyStateSize = emptyStateView.update(
transition: emptyStateTransition,
component: AnyComponent(EmptyStateIndicatorComponent(
context: self.context,
theme: presentationData.theme,
animationName: "StoryListEmpty",
title: "No saved stories",
text: "Open the Archive to select stories you\nwant to be displayed in your profile.",
actionTitle: "Open Archive",
action: { [weak self] in
guard let self else {
return
}
self.emptyAction?()
}
)),
environment: {},
containerSize: CGSize(width: size.width, height: size.height - topInset - bottomInset)
)
if let emptyStateComponentView = emptyStateView.view {
if emptyStateComponentView.superview == nil {
self.view.addSubview(emptyStateComponentView)
if self.didUpdateItemsOnce {
emptyStateComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
emptyStateTransition.setFrame(view: emptyStateComponentView, frame: CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: topInset), size: emptyStateSize))
}
if self.didUpdateItemsOnce {
Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
} else {
self.view.backgroundColor = presentationData.theme.list.blocksBackgroundColor
}
} else {
if let emptyStateView = self.emptyStateView {
let subTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
self.emptyStateView = nil
if let emptyStateComponentView = emptyStateView.view {
subTransition.setAlpha(view: emptyStateComponentView, alpha: 0.0, completion: { [weak emptyStateComponentView] _ in
emptyStateComponentView?.removeFromSuperview()
})
}
subTransition.setBackgroundColor(view: self.view, color: presentationData.theme.list.blocksBackgroundColor)
} else {
self.view.backgroundColor = .clear
}
}
transition.updateFrame(node: self.itemGrid, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
if let items = self.items {

View File

@ -17,7 +17,7 @@ public final class AvatarStoryIndicatorComponent: Component {
public let hasUnseen: Bool
public let hasUnseenCloseFriendsItems: Bool
public let isDarkTheme: Bool
public let theme: PresentationTheme
public let activeLineWidth: CGFloat
public let inactiveLineWidth: CGFloat
public let counters: Counters?
@ -25,14 +25,14 @@ public final class AvatarStoryIndicatorComponent: Component {
public init(
hasUnseen: Bool,
hasUnseenCloseFriendsItems: Bool,
isDarkTheme: Bool,
theme: PresentationTheme,
activeLineWidth: CGFloat,
inactiveLineWidth: CGFloat,
counters: Counters?
) {
self.hasUnseen = hasUnseen
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
self.isDarkTheme = isDarkTheme
self.theme = theme
self.activeLineWidth = activeLineWidth
self.inactiveLineWidth = inactiveLineWidth
self.counters = counters
@ -45,7 +45,7 @@ public final class AvatarStoryIndicatorComponent: Component {
if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems {
return false
}
if lhs.isDarkTheme != rhs.isDarkTheme {
if lhs.theme !== rhs.theme {
return false
}
if lhs.activeLineWidth != rhs.activeLineWidth {
@ -112,8 +112,8 @@ public final class AvatarStoryIndicatorComponent: Component {
]
}
if component.isDarkTheme {
inactiveColors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
if component.theme.overallDarkAppearance {
inactiveColors = [component.theme.rootController.tabBar.textColor.cgColor, component.theme.rootController.tabBar.textColor.cgColor]
} else {
inactiveColors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
}

View File

@ -138,7 +138,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isExpired: item.isExpired,
isPublic: item.isPublic,
isPending: false,
isCloseFriends: item.isCloseFriends
isCloseFriends: item.isCloseFriends,
isForwardingDisabled: item.isForwardingDisabled
)
}
if peerId == context.account.peerId, let stateView = views.views[PostboxViewKey.storiesState(key: .local)] as? StoryStatesView, let localState = stateView.value?.get(Stories.LocalState.self) {
@ -156,7 +157,8 @@ public final class StoryContentContextImpl: StoryContentContext {
isExpired: false,
isPublic: false,
isPending: true,
isCloseFriends: false
isCloseFriends: false,
isForwardingDisabled: false
))
}
}
@ -954,7 +956,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
isExpired: itemValue.isExpired,
isPublic: itemValue.isPublic,
isPending: false,
isCloseFriends: itemValue.isCloseFriends
isCloseFriends: itemValue.isCloseFriends,
isForwardingDisabled: itemValue.isForwardingDisabled
)
let mainItem = StoryContentItem(

View File

@ -392,30 +392,46 @@ private final class StoryContainerScreenComponent: Component {
let translation = recognizer.translation(in: self)
self.verticalPanState = ItemSetPanState(fraction: max(-1.0, min(1.0, translation.y / self.bounds.height)), didBegin: true)
self.state?.updated(transition: .immediate)
case .cancelled, .ended:
let translation = recognizer.translation(in: self)
let velocity = recognizer.velocity(in: self)
self.verticalPanState = nil
var updateState = true
if translation.y > 100.0 || velocity.y > 10.0 {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
self.environment?.controller()?.dismiss()
} else if translation.y < -100.0 || velocity.y < -40.0 {
if translation.y < -40.0 {
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
if itemSetComponentView.activateInput() {
updateState = false
if let activateInputWhileDragging = itemSetComponentView.activateInputWhileDragging() {
activateInputWhileDragging()
self.verticalPanState = nil
recognizer.state = .cancelled
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
}
}
}
}
case .cancelled, .ended:
if self.verticalPanState != nil {
let translation = recognizer.translation(in: self)
let velocity = recognizer.velocity(in: self)
if updateState || "".isEmpty {
self.verticalPanState = nil
var updateState = true
if translation.y > 100.0 || velocity.y > 10.0 {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
self.environment?.controller()?.dismiss()
} else if translation.y < -100.0 || velocity.y < -40.0 {
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
if itemSetComponentView.activateInput() {
updateState = false
}
}
}
if updateState || "".isEmpty {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
}
} else {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
}
} else {
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
}
default:
break

View File

@ -141,7 +141,7 @@ final class StoryItemContentComponent: Component {
useLargeThumbnail: true,
autoFetchFullSizeThumbnail: true,
tempFilePath: nil,
captureProtected: false,
captureProtected: component.item.isForwardingDisabled,
hintDimensions: file.dimensions?.cgSize,
storeAfterDownload: nil,
displayImage: false
@ -487,9 +487,17 @@ final class StoryItemContentComponent: Component {
}
if let dimensions {
var imageSize = dimensions.aspectFilled(availableSize)
if imageSize.width < availableSize.width && imageSize.width >= availableSize.width - 5.0 {
imageSize.width = availableSize.width
}
if imageSize.height < availableSize.height && imageSize.height >= availableSize.height - 5.0 {
imageSize.height = availableSize.height
}
self.imageNode.captureProtected = component.item.isForwardingDisabled
let apply = self.imageNode.asyncLayout()(TransformImageArguments(
corners: ImageCorners(),
imageSize: dimensions.aspectFilled(availableSize),
imageSize: imageSize,
boundingSize: availableSize,
intrinsicInsets: UIEdgeInsets()
))

View File

@ -30,6 +30,7 @@ import LocalMediaResources
import SaveToCameraRoll
import BundleIconComponent
import PeerListItemComponent
import PremiumUI
public final class StoryItemSetContainerComponent: Component {
public final class ExternalState {
@ -182,16 +183,16 @@ public final class StoryItemSetContainerComponent: Component {
}
struct ItemLayout {
var size: CGSize
var containerSize: CGSize
var contentFrame: CGRect
var contentVisualScale: CGFloat
init(
size: CGSize,
containerSize: CGSize,
contentFrame: CGRect,
contentVisualScale: CGFloat
) {
self.size = size
self.containerSize = containerSize
self.contentFrame = contentFrame
self.contentVisualScale = contentVisualScale
}
@ -256,10 +257,6 @@ public final class StoryItemSetContainerComponent: Component {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
/*@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}*/
}
public final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
@ -567,7 +564,7 @@ public final class StoryItemSetContainerComponent: Component {
let point = recognizer.location(in: self)
var direction: NavigationDirection?
if point.x < itemLayout.size.width * 0.25 {
if point.x < itemLayout.containerSize.width * 0.25 {
direction = .previous
} else {
direction = .next
@ -670,7 +667,7 @@ public final class StoryItemSetContainerComponent: Component {
}
self.scrollingCenterX = leftWidth
self.scroller.contentSize = CGSize(width: leftWidth + itemLayout.size.width + rightWidth, height: 1.0)
self.scroller.contentSize = CGSize(width: leftWidth + itemLayout.containerSize.width + rightWidth, height: 1.0)
if !self.initializedOffset {
self.initializedOffset = true
@ -862,7 +859,7 @@ public final class StoryItemSetContainerComponent: Component {
environment: {
itemEnvironment
},
containerSize: itemLayout.size
containerSize: itemLayout.contentFrame.size
)
if let view = visibleItem.view.view {
if visibleItem.contentContainerView.superview == nil {
@ -969,6 +966,22 @@ public final class StoryItemSetContainerComponent: Component {
return false
}
func activateInputWhileDragging() -> (() -> Void)? {
guard let component = self.component else {
return nil
}
if component.slice.peer.id == component.context.account.peerId {
} else {
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
return { [weak inputPanelView] in
inputPanelView?.activateInput()
}
}
}
return nil
}
func animateIn(transitionIn: StoryContainerScreen.TransitionIn) {
self.closeButton.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, delay: 0.12, timingFunction: kCAMediaTimingFunctionSpring)
@ -1510,7 +1523,7 @@ public final class StoryItemSetContainerComponent: Component {
self.state?.updated(transition: .immediate)
},
timeoutAction: nil,
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
forwardAction: component.slice.item.storyItem.isPublic && !component.slice.item.storyItem.isForwardingDisabled ? { [weak self] in
guard let self else {
return
}
@ -2048,7 +2061,7 @@ public final class StoryItemSetContainerComponent: Component {
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: component.containerInsets.top - (contentSize.height - contentVisualHeight) * 0.5), size: contentSize)
let itemLayout = ItemLayout(
size: contentFrame.size,
containerSize: availableSize,
contentFrame: contentFrame,
contentVisualScale: contentVisualScale
)
@ -2539,6 +2552,32 @@ public final class StoryItemSetContainerComponent: Component {
})
})
}
reactionContextNode.premiumReactionsSelected = { [weak self] file in
guard let self, let file, let component = self.component else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: file, loop: true, title: nil, text: presentationData.strings.Chat_PremiumReactionToastTitle, undoText: presentationData.strings.Chat_PremiumReactionToastAction, customAction: { [weak self] in
guard let self, let component = self.component else {
return
}
let context = component.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
let controller = PremiumIntroScreen(context: context, source: .reactions)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
component.controller()?.push(controller)
}), elevatedLayout: false, animateInAsReplacement: false, action: { _ in true })
//strongSelf.currentUndoController = undoController
component.controller()?.present(undoController, in: .current)
}
}
var animateReactionsIn = false

View File

@ -22,6 +22,8 @@ swift_library(
"//submodules/ContextUI",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/Components/MultilineTextComponent",
"//submodules/ActivityIndicator",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
],
visibility = [
"//visibility:public",

View File

@ -10,6 +10,7 @@ import Postbox
import SwiftSignalKit
import TelegramPresentationData
import StoryContainerScreen
import EmojiStatusComponent
public func shouldDisplayStoriesInChatListHeader(storySubscriptions: EngineStorySubscriptions) -> Bool {
if !storySubscriptions.items.isEmpty {
@ -48,6 +49,11 @@ private let modelSpringAnimation: CABasicAnimation = {
}()
public final class StoryPeerListComponent: Component {
public enum PeerStatus: Equatable {
case premium
case emoji(PeerEmojiStatus)
}
public final class ExternalState {
public fileprivate(set) var collapsedWidth: CGFloat = 0.0
@ -74,7 +80,11 @@ public final class StoryPeerListComponent: Component {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let sideInset: CGFloat
public let titleContentWidth: CGFloat
public let title: String
public let titleHasLock: Bool
public let titleHasActivity: Bool
public let titlePeerStatus: PeerStatus?
public let minTitleX: CGFloat
public let maxTitleX: CGFloat
public let useHiddenList: Bool
public let storySubscriptions: EngineStorySubscriptions?
@ -83,7 +93,7 @@ public final class StoryPeerListComponent: Component {
public let uploadProgress: Float?
public let peerAction: (EnginePeer?) -> Void
public let contextPeerAction: (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void
public let updateTitleContentOffset: (CGFloat, Transition) -> Void
public let openStatusSetup: (UIView) -> Void
public init(
externalState: ExternalState,
@ -91,7 +101,11 @@ public final class StoryPeerListComponent: Component {
theme: PresentationTheme,
strings: PresentationStrings,
sideInset: CGFloat,
titleContentWidth: CGFloat,
title: String,
titleHasLock: Bool,
titleHasActivity: Bool,
titlePeerStatus: PeerStatus?,
minTitleX: CGFloat,
maxTitleX: CGFloat,
useHiddenList: Bool,
storySubscriptions: EngineStorySubscriptions?,
@ -100,14 +114,18 @@ public final class StoryPeerListComponent: Component {
uploadProgress: Float?,
peerAction: @escaping (EnginePeer?) -> Void,
contextPeerAction: @escaping (ContextExtractedContentContainingNode, ContextGesture, EnginePeer) -> Void,
updateTitleContentOffset: @escaping (CGFloat, Transition) -> Void
openStatusSetup: @escaping (UIView) -> Void
) {
self.externalState = externalState
self.context = context
self.theme = theme
self.strings = strings
self.sideInset = sideInset
self.titleContentWidth = titleContentWidth
self.title = title
self.titleHasLock = titleHasLock
self.titleHasActivity = titleHasActivity
self.titlePeerStatus = titlePeerStatus
self.minTitleX = minTitleX
self.maxTitleX = maxTitleX
self.useHiddenList = useHiddenList
self.storySubscriptions = storySubscriptions
@ -116,7 +134,7 @@ public final class StoryPeerListComponent: Component {
self.uploadProgress = uploadProgress
self.peerAction = peerAction
self.contextPeerAction = contextPeerAction
self.updateTitleContentOffset = updateTitleContentOffset
self.openStatusSetup = openStatusSetup
}
public static func ==(lhs: StoryPeerListComponent, rhs: StoryPeerListComponent) -> Bool {
@ -132,7 +150,19 @@ public final class StoryPeerListComponent: Component {
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.titleContentWidth != rhs.titleContentWidth {
if lhs.title != rhs.title {
return false
}
if lhs.titleHasLock != rhs.titleHasLock {
return false
}
if lhs.titleHasActivity != rhs.titleHasActivity {
return false
}
if lhs.titlePeerStatus != rhs.titlePeerStatus {
return false
}
if lhs.minTitleX != rhs.minTitleX {
return false
}
if lhs.maxTitleX != rhs.maxTitleX {
@ -195,7 +225,18 @@ public final class StoryPeerListComponent: Component {
}
func frame(at index: Int) -> CGRect {
return CGRect(origin: CGPoint(x: self.containerInsets.left + (self.itemSize.width + self.itemSpacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
if self.itemCount <= 1 {
return CGRect(origin: CGPoint(x: floor((self.containerSize.width - self.itemSize.width) * 0.5), y: self.containerInsets.top), size: self.itemSize)
} else if self.contentSize.width < self.containerSize.width {
let usableWidth = self.containerSize.width - self.containerInsets.left - self.containerInsets.right
let usableSpacingWidth = usableWidth - self.itemSize.width * CGFloat(self.itemCount)
var spacing = floor(usableSpacingWidth / CGFloat(self.itemCount + 1))
spacing = min(120.0, spacing)
return CGRect(origin: CGPoint(x: self.containerInsets.left + spacing + (self.itemSize.width + spacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
} else {
return CGRect(origin: CGPoint(x: self.containerInsets.left + (self.itemSize.width + self.itemSpacing) * CGFloat(index), y: self.containerInsets.top), size: self.itemSize)
}
}
}
@ -240,12 +281,19 @@ public final class StoryPeerListComponent: Component {
public final class View: UIView, UIScrollViewDelegate {
private let collapsedButton: HighlightableButton
private let scrollView: ScrollView
private let scrollContainerView: UIView
private var ignoreScrolling: Bool = false
private var itemLayout: ItemLayout?
private var sortedItems: [EngineStorySubscriptions.Item] = []
private var visibleItems: [EnginePeer.Id: VisibleItem] = [:]
private var visibleCollapsableItems: [EnginePeer.Id: VisibleItem] = [:]
private var titleIndicatorView: ComponentView<Empty>?
private let titleView = ComponentView<Empty>()
private var titleIconView: ComponentView<Empty>?
private var component: StoryPeerListComponent?
private weak var state: EmptyComponentState?
@ -275,10 +323,15 @@ public final class StoryPeerListComponent: Component {
self.scrollView.alwaysBounceHorizontal = true
self.scrollView.clipsToBounds = false
self.scrollContainerView = UIView()
super.init(frame: frame)
self.scrollView.delegate = self
self.scrollView.alpha = 0.0
self.scrollContainerView.addGestureRecognizer(self.scrollView.panGestureRecognizer)
self.addSubview(self.scrollView)
self.addSubview(self.scrollContainerView)
self.addSubview(self.collapsedButton)
self.collapsedButton.highligthedChanged = { [weak self] highlighted in
@ -373,20 +426,116 @@ public final class StoryPeerListComponent: Component {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate, keepVisibleUntilCompletion: false)
self.updateScrolling(transition: .immediate)
}
}
private func updateScrolling(transition: Transition, keepVisibleUntilCompletion: Bool) {
private func updateScrolling(transition: Transition) {
guard let component = self.component, let itemLayout = self.itemLayout else {
return
}
var hasStories: Bool = false
if let storySubscriptions = component.storySubscriptions, shouldDisplayStoriesInChatListHeader(storySubscriptions: storySubscriptions) {
hasStories = true
let titleIconSpacing: CGFloat = 4.0
let titleIndicatorSpacing: CGFloat = 8.0
var titleContentWidth: CGFloat = 0.0
var titleIndicatorSize: CGSize?
if component.titleHasActivity {
let titleIndicatorView: ComponentView<Empty>
if let current = self.titleIndicatorView {
titleIndicatorView = current
} else {
titleIndicatorView = ComponentView()
self.titleIndicatorView = titleIndicatorView
}
let titleIndicatorSizeValue = titleIndicatorView.update(
transition: .immediate,
component: AnyComponent(TitleActivityIndicatorComponent(
color: component.theme.rootController.navigationBar.accentTextColor
)),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleIndicatorSize = titleIndicatorSizeValue
titleContentWidth += titleIndicatorSizeValue.width + titleIndicatorSpacing
} else {
if let titleIndicatorView = self.titleIndicatorView {
self.titleIndicatorView = nil
titleIndicatorView.view?.removeFromSuperview()
}
}
let titleSize = self.titleView.update(
transition: .immediate,
component: AnyComponent(Text(text: component.title, font: Font.semibold(17.0), color: component.theme.rootController.navigationBar.primaryTextColor)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
titleContentWidth += titleSize.width
var titleIconSize: CGSize?
if let peerStatus = component.titlePeerStatus {
let statusContent: EmojiStatusComponent.Content
switch peerStatus {
case .premium:
statusContent = .premium(color: component.theme.list.itemAccentColor)
case let .emoji(emoji):
statusContent = .animation(content: .customEmoji(fileId: emoji.fileId), size: CGSize(width: 22.0, height: 22.0), placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(2))
}
var animateStatusTransition = false
let titleIconView: ComponentView<Empty>
if let current = self.titleIconView {
animateStatusTransition = true
titleIconView = current
} else {
titleIconView = ComponentView()
self.titleIconView = titleIconView
}
var titleIconTransition: Transition
if animateStatusTransition {
titleIconTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
} else {
titleIconTransition = .immediate
}
let titleIconSizeValue = titleIconView.update(
transition: titleIconTransition,
component: AnyComponent(EmojiStatusComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
content: statusContent,
isVisibleForAnimations: true,
action: { [weak self] in
guard let self, let component = self.component, let titleIconView = self.titleIconView?.view else {
return
}
component.openStatusSetup(titleIconView)
}
)),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleIconSize = titleIconSizeValue
if let titleIconComponentView = titleIconView.view {
titleIconComponentView.isHidden = component.titleHasActivity
}
if !component.titleHasActivity {
titleContentWidth += titleIconSpacing + titleIconSizeValue.width
}
} else {
if let titleIconView = self.titleIconView {
self.titleIconView = nil
titleIconView.view?.removeFromSuperview()
}
}
let _ = hasStories
let collapseStartIndex: Int
if component.useHiddenList {
@ -415,13 +564,28 @@ public final class StoryPeerListComponent: Component {
let collapsedItemOffsetY: CGFloat
let titleContentSpacing: CGFloat = 8.0
var combinedTitleContentWidth = component.titleContentWidth
var combinedTitleContentWidth = titleContentWidth
if !combinedTitleContentWidth.isZero {
combinedTitleContentWidth += titleContentSpacing
}
let centralContentWidth: CGFloat = collapsedContentWidth + combinedTitleContentWidth
let centralContentWidth: CGFloat
centralContentWidth = collapsedContentWidth + combinedTitleContentWidth
collapsedContentOrigin = floor((itemLayout.containerSize.width - centralContentWidth) * 0.5)
if component.titleHasActivity {
collapsedContentOrigin -= (collapsedContentWidth + titleContentSpacing) * 0.5
}
collapsedContentOrigin = min(collapsedContentOrigin, component.maxTitleX - centralContentWidth - 4.0)
var collapsedContentOriginOffset: CGFloat = 0.0
if itemLayout.itemCount == 1 && collapsedContentWidth <= 0.1 {
collapsedContentOriginOffset += 4.0
}
collapsedContentOrigin -= collapsedContentOriginOffset
collapsedItemOffsetY = -59.0
struct CollapseState {
@ -432,11 +596,6 @@ public final class StoryPeerListComponent: Component {
var sideAlphaFraction: CGFloat
}
/*let calculateCollapedFraction: (CGFloat) -> CGFloat = { t in
let offset = scrollingRubberBandingOffset(offset: (1.0 - t) * 94.0, bandingStart: 0.0, range: 400.0, coefficient: 0.4)
return 1.0 - max(0.0, min(1.0, offset / 94.0))
}*/
let targetExpandedFraction = component.collapseFraction
let targetFraction: CGFloat = component.collapseFraction
@ -504,7 +663,7 @@ public final class StoryPeerListComponent: Component {
var rawProgress = CGFloat((timestamp - animationState.startTime) / animationState.duration)
rawProgress = max(0.0, min(1.0, rawProgress))
if !animationState.fromIsUnlocked && animationState.bounce {
if !animationState.fromIsUnlocked && animationState.bounce && itemLayout.itemCount > 3 {
expandBoundsFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: 1.0, toFraction: 0.0)
} else {
expandBoundsFraction = 0.0
@ -520,15 +679,6 @@ public final class StoryPeerListComponent: Component {
expandBoundsFraction = 0.0
}
let defaultCollapsedTitleOffset = floor((itemLayout.containerSize.width - component.titleContentWidth) * 0.5)
let targetCollapsedTitleOffset: CGFloat = collapsedContentOrigin + collapsedContentWidth + titleContentSpacing
let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset
let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction)
let titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: 0.0 as CGFloat, amount: collapsedState.maxFraction)
component.updateTitleContentOffset(titleContentOffset, transition)
self.currentFraction = collapsedState.globalFraction
component.externalState.collapsedWidth = collapsedContentWidth
@ -536,32 +686,47 @@ public final class StoryPeerListComponent: Component {
let effectiveVisibleBounds = self.scrollView.bounds
let visibleBounds = effectiveVisibleBounds.insetBy(dx: -200.0, dy: 0.0)
var effectiveFirstVisibleIndex = 0
for i in 0 ..< self.sortedItems.count {
let regularItemFrame = itemLayout.frame(at: i)
let isReallyVisible = effectiveVisibleBounds.intersects(regularItemFrame)
if isReallyVisible {
effectiveFirstVisibleIndex = i
break
}
}
struct MeasuredItem {
var itemFrame: CGRect
var itemScale: CGFloat
}
let calculateItem: (Int) -> MeasuredItem = { i in
let regularItemFrame = itemLayout.frame(at: i)
let calculateItem: (Int) -> MeasuredItem = { index in
let frameIndex = index
let regularItemFrame = itemLayout.frame(at: frameIndex)
let isReallyVisible = effectiveVisibleBounds.intersects(regularItemFrame)
let collapseIndex = index - effectiveFirstVisibleIndex
let collapsedItemX: CGFloat
if i < collapseStartIndex {
if collapseIndex < collapseStartIndex {
collapsedItemX = collapsedContentOrigin
} else if i > collapseEndIndex {
} else if collapseIndex > collapseEndIndex {
collapsedItemX = collapsedContentOrigin + CGFloat(collapseEndIndex) * collapsedItemDistance - collapsedItemWidth * 0.5
} else {
collapsedItemX = collapsedContentOrigin + CGFloat(i - collapseStartIndex) * collapsedItemDistance
collapsedItemX = collapsedContentOrigin + CGFloat(collapseIndex - collapseStartIndex) * collapsedItemDistance
}
let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedItemX, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height))
var collapsedMaxItemFrame = collapsedItemFrame
var collapseDistance: CGFloat = CGFloat(i - collapseStartIndex) / CGFloat(collapseEndIndex - collapseStartIndex)
collapseDistance = max(0.0, min(1.0, collapseDistance))
collapsedMaxItemFrame.origin.x -= collapsedState.minFraction * 4.0
collapsedMaxItemFrame.origin.x += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapsedState.minFraction * 10.0
if itemLayout.itemCount > 1 {
var collapseDistance: CGFloat = CGFloat(collapseIndex - collapseStartIndex) / CGFloat(collapseEndIndex - collapseStartIndex)
collapseDistance = max(0.0, min(1.0, collapseDistance))
collapsedMaxItemFrame.origin.x -= collapsedState.minFraction * 4.0
collapsedMaxItemFrame.origin.x += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapseDistance * 20.0
collapsedMaxItemFrame.origin.y += collapsedState.minFraction * 10.0
}
let minimizedItemScale: CGFloat = 24.0 / 52.0
let minimizedMaxItemScale: CGFloat = (24.0 + 4.0) / 52.0
@ -574,11 +739,12 @@ public final class StoryPeerListComponent: Component {
let itemFrame: CGRect
if isReallyVisible {
var adjustedRegularFrame = regularItemFrame
if i < collapseStartIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseStartIndex), amount: 0.0)
} else if i > collapseEndIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: collapseEndIndex), amount: 0.0)
if index < collapseStartIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: effectiveFirstVisibleIndex + collapseStartIndex), amount: 0.0)
} else if index > collapseEndIndex {
adjustedRegularFrame = adjustedRegularFrame.interpolate(to: itemLayout.frame(at: effectiveFirstVisibleIndex + collapseEndIndex), amount: 0.0)
}
adjustedRegularFrame.origin.x -= effectiveVisibleBounds.minX
let collapsedItemPosition: CGPoint = collapsedItemFrame.center.interpolate(to: collapsedMaxItemFrame.center, amount: collapsedState.minFraction)
@ -588,13 +754,11 @@ public final class StoryPeerListComponent: Component {
bounceOffsetFraction = max(-1.0, min(1.0, bounceOffsetFraction))
itemPosition.x += min(10.0, expandBoundsFraction * collapsedState.maxFraction * 1200.0) * bounceOffsetFraction
//let itemPosition = solveParabolicMotion(from: collapsedItemPosition, to: adjustedRegularFrame.center, progress: collapsedState.maxFraction)
let itemSize = CGSize(width: adjustedRegularFrame.width * itemScale, height: adjustedRegularFrame.height)
itemFrame = itemSize.centered(around: itemPosition)
} else {
itemFrame = regularItemFrame
itemFrame = regularItemFrame.offsetBy(dx: -effectiveVisibleBounds.minX, dy: 0.0)
}
return MeasuredItem(
@ -604,12 +768,20 @@ public final class StoryPeerListComponent: Component {
}
var validIds: [EnginePeer.Id] = []
var validCollapsableIds: [EnginePeer.Id] = []
for i in 0 ..< self.sortedItems.count {
let itemSet = self.sortedItems[i]
let peer = itemSet.peer
let regularItemFrame = itemLayout.frame(at: i)
var isItemVisible = true
if !visibleBounds.intersects(regularItemFrame) {
isItemVisible = false
}
if !isItemVisible {
continue
}
@ -654,18 +826,34 @@ public final class StoryPeerListComponent: Component {
var itemAlpha: CGFloat = 1.0
var isCollapsable: Bool = false
var itemScale = measuredItem.itemScale
if itemLayout.itemCount == 1 {
let singleScaleFactor = min(1.0, collapsedState.minFraction + collapsedState.maxFraction)
itemScale = 0.001 * (1.0 - singleScaleFactor) + itemScale * singleScaleFactor
}
if i >= collapseStartIndex && i <= collapseEndIndex {
let collapseIndex = i - effectiveFirstVisibleIndex
if collapseIndex >= collapseStartIndex && collapseIndex <= collapseEndIndex {
isCollapsable = true
if i != collapseStartIndex {
if collapseIndex != collapseStartIndex {
leftItemFrame = calculateItem(i - 1).itemFrame
}
if i != collapseEndIndex {
if collapseIndex != collapseEndIndex {
rightItemFrame = calculateItem(i + 1).itemFrame
}
if effectiveFirstVisibleIndex == 0 && !component.titleHasActivity {
itemAlpha = 1.0
} else {
itemAlpha = collapsedState.sideAlphaFraction
}
} else {
itemAlpha = collapsedState.sideAlphaFraction
if itemLayout.itemCount == 1 {
itemAlpha = min(1.0, (collapsedState.minFraction + collapsedState.maxFraction) * 4.0)
} else {
itemAlpha = collapsedState.sideAlphaFraction
}
}
var leftNeighborDistance: CGPoint?
@ -690,7 +878,7 @@ public final class StoryPeerListComponent: Component {
hasItems: hasItems,
ringAnimation: itemRingAnimation,
collapseFraction: isReallyVisible ? (1.0 - collapsedState.maxFraction) : 0.0,
scale: measuredItem.itemScale,
scale: itemScale,
collapsedWidth: collapsedItemWidth,
expandedAlphaFraction: collapsedState.sideAlphaFraction,
leftNeighborDistance: leftNeighborDistance,
@ -704,8 +892,151 @@ public final class StoryPeerListComponent: Component {
if let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
if itemView.superview == nil {
self.scrollView.addSubview(itemView)
self.scrollView.addSubview(itemView.backgroundContainer)
self.scrollContainerView.addSubview(itemView)
self.scrollContainerView.addSubview(itemView.backgroundContainer)
}
if isCollapsable {
itemView.layer.zPosition = 1000.0 - CGFloat(i) * 0.01
itemView.backgroundContainer.layer.zPosition = 1.0
} else {
itemView.layer.zPosition = 0.5
itemView.backgroundContainer.layer.zPosition = 0.0
}
itemTransition.setFrame(view: itemView, frame: measuredItem.itemFrame)
itemTransition.setAlpha(view: itemView, alpha: itemAlpha)
itemTransition.setScale(view: itemView, scale: 1.0)
itemTransition.setFrame(view: itemView.backgroundContainer, frame: measuredItem.itemFrame)
itemTransition.setAlpha(view: itemView.backgroundContainer, alpha: itemAlpha)
itemTransition.setScale(view: itemView.backgroundContainer, scale: 1.0)
itemView.updateIsPreviewing(isPreviewing: self.previewedItemId == itemSet.peer.id)
}
}
for i in 0 ..< self.sortedItems.count {
let itemSet = self.sortedItems[i]
let peer = itemSet.peer
if i >= collapseStartIndex && i <= collapseEndIndex {
} else {
continue
}
validCollapsableIds.append(itemSet.peer.id)
let visibleItem: VisibleItem
var itemTransition = transition
if let current = self.visibleCollapsableItems[itemSet.peer.id] {
visibleItem = current
} else {
itemTransition = .immediate
visibleItem = VisibleItem()
self.visibleCollapsableItems[itemSet.peer.id] = visibleItem
}
var hasUnseen = false
hasUnseen = itemSet.hasUnseen
var hasUnseenCloseFriendsItems = itemSet.hasUnseenCloseFriends
var hasItems = true
var itemRingAnimation: StoryPeerListItemComponent.RingAnimation?
if peer.id == component.context.account.peerId {
if let storySubscriptions = component.storySubscriptions, let accountItem = storySubscriptions.accountItem {
hasItems = accountItem.storyCount != 0
} else {
hasItems = false
}
if let uploadProgress = component.uploadProgress {
itemRingAnimation = .progress(uploadProgress)
}
hasUnseenCloseFriendsItems = false
}
let collapseIndex = i + effectiveFirstVisibleIndex
let measuredItem = calculateItem(collapseIndex)
var leftItemFrame: CGRect?
var rightItemFrame: CGRect?
var itemAlpha: CGFloat = 1.0
var isCollapsable: Bool = false
var itemScale = measuredItem.itemScale
if itemLayout.itemCount == 1 {
let singleScaleFactor = min(1.0, collapsedState.minFraction + collapsedState.maxFraction)
itemScale = 0.001 * (1.0 - singleScaleFactor) + itemScale * singleScaleFactor
}
if i >= collapseStartIndex && i <= collapseEndIndex {
isCollapsable = true
if i != collapseStartIndex {
leftItemFrame = calculateItem(collapseIndex - 1).itemFrame
}
if i != collapseEndIndex {
rightItemFrame = calculateItem(collapseIndex + 1).itemFrame
}
if effectiveFirstVisibleIndex == 0 {
itemAlpha = 0.0
} else {
itemAlpha = 1.0 - collapsedState.sideAlphaFraction
}
} else {
if itemLayout.itemCount == 1 {
itemAlpha = min(1.0, (collapsedState.minFraction + collapsedState.maxFraction) * 4.0)
} else {
itemAlpha = collapsedState.sideAlphaFraction
}
}
if component.titleHasActivity {
itemAlpha = 0.0
}
var leftNeighborDistance: CGPoint?
var rightNeighborDistance: CGPoint?
if let leftItemFrame {
leftNeighborDistance = CGPoint(x: abs(leftItemFrame.midX - measuredItem.itemFrame.midX), y: leftItemFrame.minY - measuredItem.itemFrame.minY)
}
if let rightItemFrame {
rightNeighborDistance = CGPoint(x: abs(rightItemFrame.midX - measuredItem.itemFrame.midX), y: rightItemFrame.minY - measuredItem.itemFrame.minY)
}
let _ = visibleItem.view.update(
transition: itemTransition,
component: AnyComponent(StoryPeerListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
peer: peer,
hasUnseen: hasUnseen,
hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems,
hasItems: hasItems,
ringAnimation: itemRingAnimation,
collapseFraction: 1.0 - collapsedState.maxFraction,
scale: itemScale,
collapsedWidth: collapsedItemWidth,
expandedAlphaFraction: collapsedState.sideAlphaFraction,
leftNeighborDistance: leftNeighborDistance,
rightNeighborDistance: rightNeighborDistance,
action: component.peerAction,
contextGesture: component.contextPeerAction
)),
environment: {},
containerSize: itemLayout.itemSize
)
if let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
if itemView.superview == nil {
itemView.isUserInteractionEnabled = false
self.scrollContainerView.addSubview(itemView)
self.scrollContainerView.addSubview(itemView.backgroundContainer)
}
if isCollapsable {
@ -733,16 +1064,8 @@ public final class StoryPeerListComponent: Component {
if !validIds.contains(id) {
removedIds.append(id)
if let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
if keepVisibleUntilCompletion && !transition.animation.isImmediate {
let backgroundContainer = itemView.backgroundContainer
transition.attachAnimation(view: itemView, id: "keep", completion: { [weak itemView, weak backgroundContainer] _ in
itemView?.removeFromSuperview()
backgroundContainer?.removeFromSuperview()
})
} else {
itemView.backgroundContainer.removeFromSuperview()
itemView.removeFromSuperview()
}
itemView.backgroundContainer.removeFromSuperview()
itemView.removeFromSuperview()
}
}
}
@ -750,13 +1073,77 @@ public final class StoryPeerListComponent: Component {
self.visibleItems.removeValue(forKey: id)
}
transition.setFrame(view: self.collapsedButton, frame: CGRect(origin: CGPoint(x: collapsedContentOrigin - 4.0, y: 6.0 - 59.0), size: CGSize(width: collapsedContentWidth + 4.0, height: 44.0)))
var removedCollapsableIds: [EnginePeer.Id] = []
for (id, visibleItem) in self.visibleCollapsableItems {
if !validCollapsableIds.contains(id) {
removedCollapsableIds.append(id)
if let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
itemView.backgroundContainer.removeFromSuperview()
itemView.removeFromSuperview()
}
}
}
for id in removedCollapsableIds {
self.visibleCollapsableItems.removeValue(forKey: id)
}
transition.setFrame(view: self.collapsedButton, frame: CGRect(origin: CGPoint(x: component.minTitleX, y: 6.0 - 59.0), size: CGSize(width: max(0.0, component.maxTitleX - component.minTitleX), height: 44.0)))
let defaultCollapsedTitleOffset: CGFloat = 0.0
var targetCollapsedTitleOffset: CGFloat = collapsedContentOrigin + collapsedContentOriginOffset + collapsedContentWidth + titleContentSpacing
if itemLayout.itemCount == 1 && collapsedContentWidth <= 0.1 {
let singleScaleFactor = min(1.0, collapsedState.minFraction)
targetCollapsedTitleOffset += singleScaleFactor * 4.0
}
let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset
let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction)
var titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: floor((itemLayout.containerSize.width - titleContentWidth) * 0.5) as CGFloat, amount: collapsedState.maxFraction)
if let titleIndicatorSize, let titleIndicatorView = self.titleIndicatorView?.view {
let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize)
if titleIndicatorView.superview == nil {
self.addSubview(titleIndicatorView)
}
titleIndicatorView.frame = titleIndicatorFrame
titleContentOffset += titleIndicatorSize.width + titleIndicatorSpacing
}
let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize)
if let titleComponentView = self.titleView.view {
if titleComponentView.superview == nil {
titleComponentView.isUserInteractionEnabled = false
self.addSubview(titleComponentView)
}
titleComponentView.frame = titleFrame
}
titleContentOffset += titleSize.width
if let titleIconSize, let titleIconView = self.titleIconView?.view {
titleContentOffset += titleIconSpacing
let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize)
if titleIconView.superview == nil {
self.addSubview(titleIconView)
}
titleIconView.frame = titleIconFrame
}
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero {
return nil
}
if let titleIconView = self.titleIconView?.view {
if let result = titleIconView.hitTest(self.convert(point, to: titleIconView), with: event) {
return result
}
}
var result: UIView?
for view in self.subviews.reversed() {
if let resultValue = view.hitTest(self.convert(point, to: view), with: event), resultValue.isUserInteractionEnabled {
@ -773,7 +1160,7 @@ public final class StoryPeerListComponent: Component {
return nil
}
} else {
if !result.isDescendant(of: self.scrollView) {
if !result.isDescendant(of: self.scrollContainerView) {
return nil
}
}
@ -784,20 +1171,6 @@ public final class StoryPeerListComponent: Component {
var transition = transition
transition.animation = .none
if self.component != nil {
if !component.unlocked && self.scrollView.bounds.minX != 0.0 {
self.ignoreScrolling = true
let scrollingDistance = self.scrollView.bounds.minX
self.scrollView.bounds = CGRect(origin: CGPoint(), size: self.scrollView.bounds.size)
let tempTransition = Transition(animation: .curve(duration: 0.3, curve: .spring))
self.updateScrolling(transition: tempTransition, keepVisibleUntilCompletion: true)
tempTransition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: scrollingDistance, y: 0.0), to: CGPoint(), additive: true)
self.ignoreScrolling = false
}
}
let animationHint = transition.userData(AnimationHint.self)
var useAnimation = false
if let previousComponent = self.component, component.unlocked != previousComponent.unlocked {
@ -892,12 +1265,13 @@ public final class StoryPeerListComponent: Component {
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: -4.0), size: CGSize(width: availableSize.width, height: availableSize.height + 4.0)))
transition.setFrame(view: self.scrollContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: -4.0), size: CGSize(width: availableSize.width, height: availableSize.height + 4.0)))
if self.scrollView.contentSize != itemLayout.contentSize {
self.scrollView.contentSize = itemLayout.contentSize
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition, keepVisibleUntilCompletion: false)
self.updateScrolling(transition: transition)
return availableSize
}

View File

@ -665,7 +665,7 @@ public final class StoryPeerListItemComponent: Component {
}
} else {
if component.theme.overallDarkAppearance {
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
colors = [component.theme.rootController.tabBar.textColor.cgColor, component.theme.rootController.tabBar.textColor.cgColor]
} else {
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
}

View File

@ -0,0 +1,61 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import ActivityIndicator
public final class TitleActivityIndicatorComponent: Component {
let color: UIColor
public init(
color: UIColor
) {
self.color = color
}
public static func ==(lhs: TitleActivityIndicatorComponent, rhs: TitleActivityIndicatorComponent) -> Bool {
if lhs.color != rhs.color {
return false
}
return true
}
public final class View: UIView {
private var activityIndicator: ActivityIndicator?
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder: NSCoder) {
preconditionFailure()
}
deinit {
}
func update(component: TitleActivityIndicatorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(component.color, availableSize.width, 2.0, true))
self.activityIndicator = activityIndicator
self.addSubview(activityIndicator.view)
}
activityIndicator.frame = CGRect(origin: CGPoint(), size: availableSize)
activityIndicator.isHidden = false
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "messagebubble.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4.66502C7.88795 4.66502 4.66502 7.68163 4.66502 11.2727C4.66502 13.329 5.61495 15.016 7.28375 16.2378C7.50459 16.3995 7.61824 16.6447 7.67716 16.8096C7.74628 17.003 7.78941 17.2274 7.80119 17.4635C7.82475 17.9355 7.72531 18.5159 7.37618 19.0511C7.32109 19.1355 7.26431 19.2162 7.20737 19.2928C7.45008 19.2413 7.68743 19.1624 7.88779 19.05C8.43439 18.7435 8.78169 18.4401 9.03437 18.2083C9.05375 18.1905 9.07401 18.1718 9.0949 18.1524C9.19597 18.0589 9.3118 17.9516 9.41317 17.8755C9.51328 17.8003 9.80413 17.5892 10.1854 17.6767C10.7649 17.8096 11.3728 17.8805 12 17.8805C16.1121 17.8805 19.335 14.8639 19.335 11.2727C19.335 7.68163 16.1121 4.66502 12 4.66502ZM6.17873 19.351C6.17875 19.3509 6.18007 19.3512 6.18254 19.3518C6.17992 19.3513 6.1787 19.351 6.17873 19.351ZM6.49475 20.1245C6.49316 20.1271 6.49223 20.1284 6.49219 20.1284C6.49214 20.1284 6.49293 20.1271 6.49475 20.1245ZM3.33502 11.2727C3.33502 6.83064 7.27554 3.33502 12 3.33502C16.7245 3.33502 20.665 6.83064 20.665 11.2727C20.665 15.7149 16.7245 19.2105 12 19.2105C11.3532 19.2105 10.7221 19.1455 10.1145 19.022C10.0893 19.0448 10.0598 19.0721 10.0238 19.1052C9.99742 19.1295 9.9676 19.157 9.93357 19.1883C9.64507 19.453 9.2118 19.8324 8.53835 20.21C8.00052 20.5117 7.38987 20.636 6.91077 20.6797C6.66653 20.702 6.44116 20.7047 6.25548 20.6946C6.16291 20.6897 6.07427 20.6812 5.9943 20.6688C5.92695 20.6584 5.82187 20.6389 5.72037 20.5963C5.60837 20.5493 5.41843 20.4446 5.30536 20.2201C5.18416 19.9794 5.22417 19.7499 5.27201 19.6141C5.31662 19.4875 5.38558 19.3872 5.43085 19.3263C5.48061 19.2594 5.53686 19.1946 5.58714 19.1386C5.62942 19.0915 5.6727 19.0445 5.71667 18.9967C5.89442 18.8038 6.08334 18.5986 6.26224 18.3244C6.43214 18.0639 6.48498 17.7728 6.47285 17.5298C6.46678 17.4083 6.44507 17.3141 6.42472 17.2571C6.42464 17.2569 6.42456 17.2567 6.42448 17.2565C4.49565 15.8148 3.33502 13.7665 3.33502 11.2727ZM6.40689 17.2161C6.407 17.2159 6.40865 17.2184 6.41149 17.2244C6.4082 17.2193 6.40678 17.2163 6.40689 17.2161ZM10.2318 18.9257C10.2316 18.9262 10.2262 18.9301 10.2162 18.9357C10.2269 18.9279 10.2319 18.9251 10.2318 18.9257Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -469,7 +469,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
component: AnyComponent(AvatarStoryIndicatorComponent(
hasUnseen: storyData.hasUnseen,
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
isDarkTheme: theme.overallDarkAppearance,
theme: theme,
activeLineWidth: 3.0,
inactiveLineWidth: 2.0,
counters: nil

View File

@ -369,7 +369,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
case let .image(image, dimensions):
if let imageData = compressImageToJPEG(image, quality: 0.7) {
let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, isForwardingDisabled: false, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch {
commit({})
}
@ -392,7 +392,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let entities = generateChatInputTextEntities(caption)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, period: privacy.timeout, randomId: randomId)
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.archive, privacy: privacy.privacy, isForwardingDisabled: false, period: privacy.timeout, randomId: randomId)
Queue.mainQueue().justDispatch {
commit({})
}