Story improvements

This commit is contained in:
Ali 2023-09-08 19:22:12 +04:00
parent 53f6d62e8b
commit 6910ec9334
22 changed files with 511 additions and 208 deletions

View File

@ -795,7 +795,7 @@ public struct StoryCameraTransitionInCoordinator {
public protocol TelegramRootControllerInterface: NavigationController { public protocol TelegramRootControllerInterface: NavigationController {
@discardableResult @discardableResult
func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
func getContactsController() -> ViewController? func getContactsController() -> ViewController?
func getChatsController() -> ViewController? func getChatsController() -> ViewController?

View File

@ -1366,9 +1366,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return return
} }
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
storyPeerListView.cancelLoadingItem()
}
switch subject { switch subject {
case .archive: case .archive:
StoryContainerScreen.openArchivedStories(context: self.context, parentController: self, avatarNode: itemNode.avatarNode) StoryContainerScreen.openArchivedStories(context: self.context, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable)
case let .peer(peerId): case let .peer(peerId):
StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable) StoryContainerScreen.openPeerStories(context: self.context, peerId: peerId, parentController: self, avatarNode: itemNode.avatarNode, sharedProgressDisposable: self.sharedOpenStoryProgressDisposable)
} }
@ -2487,6 +2491,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.fullScreenEffectView = nil self.fullScreenEffectView = nil
fullScreenEffectView.removeFromSuperview() fullScreenEffectView.removeFromSuperview()
} }
self.sharedOpenStoryProgressDisposable.set(nil)
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
storyPeerListView.cancelLoadingItem()
}
} }
func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) { func updateHeaderContent() -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) {
@ -2692,39 +2702,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: { [weak self] target in let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
guard let self, let target else {
return nil
}
if let componentView = self.chatListHeaderView() {
let peerId: EnginePeer.Id
switch target {
case .myStories:
peerId = self.context.account.peerId
case let .peer(id):
peerId = id
}
if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
} else if let rightButtonView = componentView.rightButtonViews["story"] {
return StoryCameraTransitionOut(
destinationView: rightButtonView,
destinationRect: rightButtonView.bounds,
destinationCornerRadius: rightButtonView.bounds.height * 0.5
)
}
}
return nil
})
coordinator?.animateIn() coordinator?.animateIn()
} }
} }
public func storyCameraTransitionOut() -> (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut? {
return { [weak self] target, isArchived in
guard let self, let target else {
return nil
}
if let componentView = self.chatListHeaderView() {
let peerId: EnginePeer.Id
switch target {
case .myStories:
peerId = self.context.account.peerId
case let .peer(id):
peerId = id
}
if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
return StoryCameraTransitionOut(
destinationView: transitionView,
destinationRect: transitionView.bounds,
destinationCornerRadius: transitionView.bounds.height * 0.5
)
}
}
return nil
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
@ -3947,6 +3955,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return return
} }
if let componentView = self.chatListHeaderView() { if let componentView = self.chatListHeaderView() {
self.sharedOpenStoryProgressDisposable.set(nil)
componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal) componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal)
} }
} }
@ -5667,7 +5676,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let current = self.storyCameraTransitionInCoordinator { if let current = self.storyCameraTransitionInCoordinator {
coordinator = current coordinator = current
} else { } else {
coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target in coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
guard let self, let target else { guard let self, let target else {
return nil return nil
} }

View File

@ -260,6 +260,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var isLeftAligned: Bool = true private var isLeftAligned: Bool = true
private var itemLayout: ItemLayout? private var itemLayout: ItemLayout?
public var centerAligned: Bool {
return self.validLayout?.4 ?? false
}
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)? private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)? public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?

View File

@ -4579,7 +4579,7 @@ func replayFinalState(
mediaAreas: item.mediaAreas, mediaAreas: item.mediaAreas,
text: item.text, text: item.text,
entities: item.entities, entities: item.entities,
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction), views: _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
privacy: item.privacy, privacy: item.privacy,
isPinned: item.isPinned, isPinned: item.isPinned,
isExpired: item.isExpired, isExpired: item.isExpired,
@ -4610,7 +4610,7 @@ func replayFinalState(
mediaAreas: item.mediaAreas, mediaAreas: item.mediaAreas,
text: item.text, text: item.text,
entities: item.entities, entities: item.entities,
views: _internal_updateStoryViewsForMyReaction(views: item.views, previousReaction: item.myReaction, reaction: updatedReaction), views: _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: item.views, previousReaction: item.myReaction, reaction: updatedReaction),
privacy: item.privacy, privacy: item.privacy,
isPinned: item.isPinned, isPinned: item.isPinned,
isExpired: item.isExpired, isExpired: item.isExpired,

View File

@ -23,10 +23,10 @@ public struct CachedChannelFlags: OptionSet {
} }
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable { public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
public let memberCount: Int32? public var memberCount: Int32?
public let adminCount: Int32? public var adminCount: Int32?
public let bannedCount: Int32? public var bannedCount: Int32?
public let kickedCount: Int32? public var kickedCount: Int32?
public init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) { public init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) {
self.memberCount = memberCount self.memberCount = memberCount

View File

@ -16,6 +16,6 @@ public final class CachedLocalizationInfos: Codable {
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self) var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.list, forKey: "l") try container.encode(self.list, forKey: "t")
} }
} }

View File

@ -109,6 +109,7 @@ public struct Namespaces {
public static let cachedEmojiQueryResults: Int8 = 26 public static let cachedEmojiQueryResults: Int8 = 26
public static let cachedPeerStoryListHeads: Int8 = 27 public static let cachedPeerStoryListHeads: Int8 = 27
public static let displayedStoryNotifications: Int8 = 28 public static let displayedStoryNotifications: Int8 = 28
public static let storySendAsPeerIds: Int8 = 29
} }
public struct UnorderedItemList { public struct UnorderedItemList {

View File

@ -1047,7 +1047,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
if !peerIds.contains(toPeerId) { if !peerIds.contains(toPeerId) {
peerIds.append(toPeerId) peerIds.append(toPeerId)
} }
transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) transaction.replaceAllStorySubscriptions(key: subscriptionsKey, state: state, peerIds: peerIds)
} }
} }
@ -1976,7 +1976,11 @@ public func _internal_setStoryNotificationWasDisplayed(transaction: Transaction,
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data())) transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.displayedStoryNotifications, key: key), entry: CodableEntry(data: Data()))
} }
func _internal_updateStoryViewsForMyReaction(views: Stories.Item.Views?, previousReaction: MessageReaction.Reaction?, reaction: MessageReaction.Reaction?) -> Stories.Item.Views? { func _internal_updateStoryViewsForMyReaction(isChannel: Bool, views: Stories.Item.Views?, previousReaction: MessageReaction.Reaction?, reaction: MessageReaction.Reaction?) -> Stories.Item.Views? {
if !isChannel {
return views
}
var views = views ?? Stories.Item.Views(seenCount: 0, reactedCount: 0, forwardCount: 0, seenPeerIds: [], reactions: [], hasList: false) var views = views ?? Stories.Item.Views(seenCount: 0, reactedCount: 0, forwardCount: 0, seenPeerIds: [], reactions: [], hasList: false)
if let reaction { if let reaction {
@ -2040,7 +2044,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
var updatedItemValue: Stories.StoredItem? var updatedItemValue: Stories.StoredItem?
let updateViews: (Stories.Item.Views?, MessageReaction.Reaction?) -> Stories.Item.Views? = { views, previousReaction in let updateViews: (Stories.Item.Views?, MessageReaction.Reaction?) -> Stories.Item.Views? = { views, previousReaction in
return _internal_updateStoryViewsForMyReaction(views: views, previousReaction: previousReaction, reaction: reaction) return _internal_updateStoryViewsForMyReaction(isChannel: peerId.namespace == Namespaces.Peer.CloudChannel, views: views, previousReaction: previousReaction, reaction: reaction)
} }
var currentItems = transaction.getStoryItems(peerId: peerId) var currentItems = transaction.getStoryItems(peerId: peerId)

View File

@ -839,6 +839,17 @@ public extension TelegramEngine {
guard let peer = peerView.peer else { guard let peer = peerView.peer else {
continue continue
} }
var isPeerHidden = false
if let user = peer as? TelegramUser {
isPeerHidden = user.storiesHidden ?? false
} else if let channel = peer as? TelegramChannel {
isPeerHidden = channel.storiesHidden ?? false
}
if isPeerHidden != isHidden {
continue
}
guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else { guard let itemsView = views.views[PostboxViewKey.storyItems(peerId: peerId)] as? StoryItemsView else {
continue continue
} }
@ -912,6 +923,16 @@ public extension TelegramEngine {
continue continue
} }
var isPeerHidden = false
if let user = peer as? TelegramUser {
isPeerHidden = user.storiesHidden ?? false
} else if let channel = peer as? TelegramChannel {
isPeerHidden = channel.storiesHidden ?? false
}
if isPeerHidden != isHidden {
continue
}
let item = EngineStorySubscriptions.Item( let item = EngineStorySubscriptions.Item(
peer: EnginePeer(peer), peer: EnginePeer(peer),
hasUnseen: false, hasUnseen: false,

View File

@ -546,29 +546,81 @@ func _internal_adminedPublicChannels(account: Account, scope: AdminedPublicChann
} }
} }
final class CachedStorySendAsPeers: Codable {
public let peerIds: [PeerId]
public init(peerIds: [PeerId]) {
self.peerIds = peerIds
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.peerIds = try container.decode([Int64].self, forKey: "l").map(PeerId.init)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: "l")
}
}
func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> { func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> {
let accountPeerId = account.peerId let accountPeerId = account.peerId
return account.network.request(Api.functions.stories.getChatsToSend()) return account.postbox.transaction { transaction -> [Peer]? in
|> retryRequest if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)))?.get(CachedStorySendAsPeers.self) {
|> mapToSignal { result -> Signal<[Peer], NoError> in return entry.peerIds.compactMap(transaction.getPeer)
return account.postbox.transaction { transaction -> [Peer] in } else {
let chats: [Api.Chat] return nil
let parsedPeers: AccumulatedPeers }
switch result { }
case let .chats(apiChats): |> mapToSignal { cachedPeers in
chats = apiChats let remote: Signal<[Peer], NoError> = account.network.request(Api.functions.stories.getChatsToSend())
case let .chatsSlice(_, apiChats): |> retryRequest
chats = apiChats |> mapToSignal { result -> Signal<[Peer], NoError> in
} return account.postbox.transaction { transaction -> [Peer] in
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: []) let chats: [Api.Chat]
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) let parsedPeers: AccumulatedPeers
var peers: [Peer] = [] switch result {
for chat in chats { case let .chats(apiChats):
if let peer = transaction.getPeer(chat.peerId) { chats = apiChats
peers.append(peer) case let .chatsSlice(_, apiChats):
chats = apiChats
} }
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: [])
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
var peers: [Peer] = []
for chat in chats {
if let peer = transaction.getPeer(chat.peerId) {
peers.append(peer)
if case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _) = chat, let participantsCount = participantsCount {
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
var current = current as? CachedChannelData ?? CachedChannelData()
var participantsSummary = current.participantsSummary
participantsSummary.memberCount = participantsCount
current = current.withUpdatedParticipantsSummary(participantsSummary)
return current
})
}
}
}
if let entry = CodableEntry(CachedStorySendAsPeers(peerIds: peers.map(\.id))) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)), entry: entry)
}
return peers
} }
return peers }
if let cachedPeers {
return .single(cachedPeers) |> then(remote)
} else {
return remote
} }
} }
} }

View File

@ -809,6 +809,7 @@ private final class GroupHeaderLayer: UIView {
func update( func update(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
forceNeedsVibrancy: Bool,
layoutType: EmojiPagerContentComponent.ItemLayoutType, layoutType: EmojiPagerContentComponent.ItemLayoutType,
hasTopSeparator: Bool, hasTopSeparator: Bool,
actionButtonTitle: String?, actionButtonTitle: String?,
@ -830,7 +831,7 @@ private final class GroupHeaderLayer: UIView {
themeUpdated = true themeUpdated = true
} }
let needsVibrancy = !theme.overallDarkAppearance let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy
let textOffsetY: CGFloat let textOffsetY: CGFloat
if hasTopSeparator { if hasTopSeparator {
@ -839,16 +840,22 @@ private final class GroupHeaderLayer: UIView {
textOffsetY = 0.0 textOffsetY = 0.0
} }
let subtitleColor: UIColor
if theme.overallDarkAppearance && forceNeedsVibrancy {
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
} else {
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
}
let color: UIColor let color: UIColor
let needsTintText: Bool let needsTintText: Bool
if subtitle != nil { if subtitle != nil {
color = theme.chat.inputPanel.primaryTextColor color = theme.chat.inputPanel.primaryTextColor
needsTintText = false needsTintText = false
} else { } else {
color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor color = subtitleColor
needsTintText = true needsTintText = true
} }
let subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
let titleHorizontalOffset: CGFloat let titleHorizontalOffset: CGFloat
if isPremiumLocked { if isPremiumLocked {
@ -903,7 +910,7 @@ private final class GroupHeaderLayer: UIView {
tintClearIconLayer.isHidden = !needsVibrancy tintClearIconLayer.isHidden = !needsVibrancy
clearSize = clearIconLayer.bounds.size clearSize = clearIconLayer.bounds.size
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor) { if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) {
clearSize = image.size clearSize = image.size
clearIconLayer.contents = image.cgImage clearIconLayer.contents = image.cgImage
} }
@ -1144,7 +1151,7 @@ private final class GroupHeaderLayer: UIView {
self.separatorLayer = separatorLayer self.separatorLayer = separatorLayer
self.layer.addSublayer(separatorLayer) self.layer.addSublayer(separatorLayer)
} }
separatorLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.cgColor separatorLayer.backgroundColor = subtitleColor.cgColor
separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
let tintSeparatorLayer: SimpleLayer let tintSeparatorLayer: SimpleLayer
@ -1517,6 +1524,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
private struct Params: Equatable { private struct Params: Equatable {
var context: AccountContext var context: AccountContext
var theme: PresentationTheme var theme: PresentationTheme
var forceNeedsVibrancy: Bool
var strings: PresentationStrings var strings: PresentationStrings
var text: String var text: String
var useOpaqueTheme: Bool var useOpaqueTheme: Bool
@ -1535,6 +1543,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
return false
}
if lhs.strings !== rhs.strings { if lhs.strings !== rhs.strings {
return false return false
} }
@ -1823,10 +1834,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
return return
} }
self.params = nil self.params = nil
self.update(context: params.context, theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition) self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition)
} }
public func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) { public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) {
let textInputState: EmojiSearchSearchBarComponent.TextInputState let textInputState: EmojiSearchSearchBarComponent.TextInputState
if let textField = self.textField { if let textField = self.textField {
textInputState = .active(hasText: !(textField.text ?? "").isEmpty) textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
@ -1837,6 +1848,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
let params = Params( let params = Params(
context: context, context: context,
theme: theme, theme: theme,
forceNeedsVibrancy: forceNeedsVibrancy,
strings: strings, strings: strings,
text: text, text: text,
useOpaqueTheme: useOpaqueTheme, useOpaqueTheme: useOpaqueTheme,
@ -1880,7 +1892,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
let sideTextInset: CGFloat = sideInset + 4.0 + 24.0 let sideTextInset: CGFloat = sideInset + 4.0 + 24.0
if useOpaqueTheme { if theme.overallDarkAppearance && forceNeedsVibrancy {
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor
self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor
} else if useOpaqueTheme {
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
} else { } else {
@ -1891,12 +1906,19 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
self.backgroundLayer.cornerRadius = inputHeight * 0.5 self.backgroundLayer.cornerRadius = inputHeight * 0.5
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5 self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
let cancelColor: UIColor
if theme.overallDarkAppearance && forceNeedsVibrancy {
cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
} else {
cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
}
let cancelTextSize = self.cancelButtonTitle.update( let cancelTextSize = self.cancelButtonTitle.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(Text( component: AnyComponent(Text(
text: strings.Common_Cancel, text: strings.Common_Cancel,
font: Font.regular(17.0), font: Font.regular(17.0),
color: useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor color: cancelColor
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: size.width - 32.0, height: 100.0) containerSize: CGSize(width: size.width - 32.0, height: 100.0)
@ -1942,6 +1964,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
transition: transition, transition: transition,
component: AnyComponent(EmojiSearchStatusComponent( component: AnyComponent(EmojiSearchStatusComponent(
theme: theme, theme: theme,
forceNeedsVibrancy: forceNeedsVibrancy,
strings: strings, strings: strings,
useOpaqueTheme: useOpaqueTheme, useOpaqueTheme: useOpaqueTheme,
content: statusContent content: statusContent
@ -1990,6 +2013,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
component: AnyComponent(EmojiSearchSearchBarComponent( component: AnyComponent(EmojiSearchSearchBarComponent(
context: context, context: context,
theme: theme, theme: theme,
forceNeedsVibrancy: forceNeedsVibrancy,
strings: strings, strings: strings,
useOpaqueTheme: useOpaqueTheme, useOpaqueTheme: useOpaqueTheme,
textInputState: textInputState, textInputState: textInputState,
@ -5426,6 +5450,7 @@ public final class EmojiPagerContentComponent: Component {
let (groupHeaderSize, centralContentWidth) = groupHeaderView.update( let (groupHeaderSize, centralContentWidth) = groupHeaderView.update(
context: component.context, context: component.context,
theme: keyboardChildEnvironment.theme, theme: keyboardChildEnvironment.theme,
forceNeedsVibrancy: component.inputInteractionHolder.inputInteraction?.externalBackground != nil,
layoutType: itemLayout.layoutType, layoutType: itemLayout.layoutType,
hasTopSeparator: hasTopSeparator, hasTopSeparator: hasTopSeparator,
actionButtonTitle: actionButtonTitle, actionButtonTitle: actionButtonTitle,
@ -5467,7 +5492,14 @@ public final class EmojiPagerContentComponent: Component {
self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0) self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0)
self.mirrorContentScrollView.layer.addSublayer(groupBorderLayer.tintContainerLayer) self.mirrorContentScrollView.layer.addSublayer(groupBorderLayer.tintContainerLayer)
groupBorderLayer.strokeColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.cgColor let borderColor: UIColor
if keyboardChildEnvironment.theme.overallDarkAppearance && component.inputInteractionHolder.inputInteraction?.externalBackground != nil {
borderColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
} else {
borderColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
}
groupBorderLayer.strokeColor = borderColor.cgColor
groupBorderLayer.tintContainerLayer.strokeColor = UIColor.white.cgColor groupBorderLayer.tintContainerLayer.strokeColor = UIColor.white.cgColor
groupBorderLayer.lineWidth = 1.6 groupBorderLayer.lineWidth = 1.6
groupBorderLayer.lineCap = .round groupBorderLayer.lineCap = .round
@ -6854,7 +6886,7 @@ public final class EmojiPagerContentComponent: Component {
} }
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight)) let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition) visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, forceNeedsVibrancy: component.inputInteractionHolder.inputInteraction?.externalBackground != nil, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame) transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
// Temporary workaround for status selection; use a separate search container (see GIF) // Temporary workaround for status selection; use a separate search container (see GIF)

View File

@ -99,6 +99,7 @@ final class EmojiSearchSearchBarComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let forceNeedsVibrancy: Bool
let strings: PresentationStrings let strings: PresentationStrings
let useOpaqueTheme: Bool let useOpaqueTheme: Bool
let textInputState: TextInputState let textInputState: TextInputState
@ -109,6 +110,7 @@ final class EmojiSearchSearchBarComponent: Component {
init( init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
forceNeedsVibrancy: Bool,
strings: PresentationStrings, strings: PresentationStrings,
useOpaqueTheme: Bool, useOpaqueTheme: Bool,
textInputState: TextInputState, textInputState: TextInputState,
@ -118,6 +120,7 @@ final class EmojiSearchSearchBarComponent: Component {
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.forceNeedsVibrancy = forceNeedsVibrancy
self.strings = strings self.strings = strings
self.useOpaqueTheme = useOpaqueTheme self.useOpaqueTheme = useOpaqueTheme
self.textInputState = textInputState self.textInputState = textInputState
@ -130,6 +133,9 @@ final class EmojiSearchSearchBarComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
return false
}
if lhs.strings !== rhs.strings { if lhs.strings !== rhs.strings {
return false return false
} }
@ -456,7 +462,10 @@ final class EmojiSearchSearchBarComponent: Component {
} }
let color: UIColor let color: UIColor
if component.useOpaqueTheme { if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
let tempColor = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
color = tempColor.withMultipliedAlpha(0.3)
} else if component.useOpaqueTheme {
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
} else { } else {
color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor color = self.selectedItem == AnyHashable(item.id) ? component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlaySelectedColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
@ -541,10 +550,18 @@ final class EmojiSearchSearchBarComponent: Component {
self.visibleItemViews.removeValue(forKey: id) self.visibleItemViews.removeValue(forKey: id)
} }
let selectedColor: UIColor
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
let tempColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor
selectedColor = tempColor.withMultipliedAlpha(0.3)
} else {
selectedColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor
}
if let selectedItem = self.selectedItem, let index = items.firstIndex(where: { AnyHashable($0.id) == selectedItem }) { if let selectedItem = self.selectedItem, let index = items.firstIndex(where: { AnyHashable($0.id) == selectedItem }) {
let selectedItemCenter = itemLayout.frame(at: index).center let selectedItemCenter = itemLayout.frame(at: index).center
let selectionSize = CGSize(width: 28.0, height: 28.0) let selectionSize = CGSize(width: 28.0, height: 28.0)
self.selectedItemBackground.backgroundColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayHighlightColor.cgColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayHighlightColor.cgColor self.selectedItemBackground.backgroundColor = selectedColor.cgColor
self.selectedItemTintBackground.backgroundColor = UIColor(white: 1.0, alpha: 0.15).cgColor self.selectedItemTintBackground.backgroundColor = UIColor(white: 1.0, alpha: 0.15).cgColor
self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5 self.selectedItemBackground.cornerRadius = selectionSize.height * 0.5
self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5 self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5
@ -609,12 +626,19 @@ final class EmojiSearchSearchBarComponent: Component {
self.component = component self.component = component
self.componentState = state self.componentState = state
let textColor: UIColor
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
textColor = component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
} else {
textColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
}
let textSize = self.textView.update( let textSize = self.textView.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(Text( component: AnyComponent(Text(
text: component.strings.Common_Search, text: component.strings.Common_Search,
font: Font.regular(17.0), font: Font.regular(17.0),
color: component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor color: textColor
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0) containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0)

View File

@ -70,17 +70,20 @@ final class EmojiSearchStatusComponent: Component {
} }
let theme: PresentationTheme let theme: PresentationTheme
let forceNeedsVibrancy: Bool
let strings: PresentationStrings let strings: PresentationStrings
let useOpaqueTheme: Bool let useOpaqueTheme: Bool
let content: Content let content: Content
init( init(
theme: PresentationTheme, theme: PresentationTheme,
forceNeedsVibrancy: Bool,
strings: PresentationStrings, strings: PresentationStrings,
useOpaqueTheme: Bool, useOpaqueTheme: Bool,
content: Content content: Content
) { ) {
self.theme = theme self.theme = theme
self.forceNeedsVibrancy = forceNeedsVibrancy
self.strings = strings self.strings = strings
self.useOpaqueTheme = useOpaqueTheme self.useOpaqueTheme = useOpaqueTheme
self.content = content self.content = content
@ -90,6 +93,9 @@ final class EmojiSearchStatusComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
return false
}
if lhs.strings !== rhs.strings { if lhs.strings !== rhs.strings {
return false return false
} }
@ -430,7 +436,13 @@ final class EmojiSearchStatusComponent: Component {
let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale) let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale)
self.displaySize = displaySize self.displaySize = displaySize
let overlayColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor let overlayColor: UIColor
if component.theme.overallDarkAppearance && component.forceNeedsVibrancy {
overlayColor = component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
} else {
overlayColor = component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
}
let baseColor: UIColor = .white let baseColor: UIColor = .white
if self.contentView.tintColor != overlayColor { if self.contentView.tintColor != overlayColor {

View File

@ -1090,7 +1090,7 @@ public final class GifPagerContentComponent: Component {
} }
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight)) let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition) visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, forceNeedsVibrancy: false, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, searchCategories: component.searchCategories, searchState: component.searchState, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
let _ = self let _ = self
let _ = completed let _ = completed

View File

@ -291,7 +291,7 @@ public final class LottieComponent: Component {
} }
} }
if self.scheduledPlayOnce { if self.scheduledPlayOnce && self.isEffectivelyVisible {
self.scheduledPlayOnce = false self.scheduledPlayOnce = false
self.playOnce() self.playOnce()
} else { } else {

View File

@ -718,7 +718,6 @@ final class ShareWithPeersScreenComponent: Component {
return result return result
} }
} else { } else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
return .complete() return .complete()
} }
} }
@ -2693,12 +2692,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
) )
self.stateValue = state self.stateValue = state
self.stateSubject.set(.single(state)) self.stateSubject.set(.single(state))
for peer in peers {
if case let .channel(channel) = peer, participants[channel.id] == nil {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
}
}
self.readySubject.set(true) self.readySubject.set(true)
}) })
@ -2811,7 +2804,9 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
} }
for channel in adminedChannels { for channel in adminedChannels {
if case let .channel(channel) = channel, channel.hasPermission(.postStories) { if case let .channel(channel) = channel, channel.hasPermission(.postStories) {
sendAsPeers.append(contentsOf: adminedChannels) if !sendAsPeers.contains(where: { $0.id == channel.id }) {
sendAsPeers.append(contentsOf: adminedChannels)
}
} }
} }
@ -2832,12 +2827,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
) )
self.stateValue = state self.stateValue = state
self.stateSubject.set(.single(state)) self.stateSubject.set(.single(state))
for peer in adminedChannels {
if case let .channel(channel) = peer, participants[channel.id] == nil {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
}
}
self.readySubject.set(true) self.readySubject.set(true)
}) })
@ -2958,12 +2947,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
self.stateValue = state self.stateValue = state
self.stateSubject.set(.single(state)) self.stateSubject.set(.single(state))
for peer in state.peers {
if case let .channel(channel) = peer, participants[channel.id] == nil {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
}
}
self.readySubject.set(true) self.readySubject.set(true)
}) })
case let .contacts(base): case let .contacts(base):
@ -3107,12 +3090,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
self.stateValue = state self.stateValue = state
self.stateSubject.set(.single(state)) self.stateSubject.set(.single(state))
for peer in state.peers {
if case let .channel(channel) = peer, participants[channel.id] == nil {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: channel.id).start()
}
}
self.readySubject.set(true) self.readySubject.set(true)
}) })
} }

View File

@ -8,7 +8,7 @@ import Postbox
import AvatarNode import AvatarNode
public extension StoryContainerScreen { public extension StoryContainerScreen {
static func openArchivedStories(context: AccountContext, parentController: ViewController, avatarNode: AvatarNode) { static func openArchivedStories(context: AccountContext, parentController: ViewController, avatarNode: AvatarNode, sharedProgressDisposable: MetaDisposable?) {
let storyContent = StoryContentContextImpl(context: context, isHidden: true, focusedPeerId: nil, singlePeer: false) let storyContent = StoryContentContextImpl(context: context, isHidden: true, focusedPeerId: nil, singlePeer: false)
let signal = storyContent.state let signal = storyContent.state
|> take(1) |> take(1)
@ -82,7 +82,10 @@ public extension StoryContainerScreen {
} }
|> ignoreValues |> ignoreValues
let _ = avatarNode.pushLoadingStatus(signal: signal) let disposable = avatarNode.pushLoadingStatus(signal: signal)
if let sharedProgressDisposable {
sharedProgressDisposable.set(disposable)
}
} }
static func openPeerStories(context: AccountContext, peerId: EnginePeer.Id, parentController: ViewController, avatarNode: AvatarNode?, sharedProgressDisposable: MetaDisposable? = nil) { static func openPeerStories(context: AccountContext, peerId: EnginePeer.Id, parentController: ViewController, avatarNode: AvatarNode?, sharedProgressDisposable: MetaDisposable? = nil) {

View File

@ -91,7 +91,7 @@ private final class MuteMonitor {
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer { private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
var shouldBegin: ((UITouch) -> Bool)? var shouldBegin: ((UITouch) -> Bool)?
var updateIsTracking: ((Bool) -> Void)? var updateIsTracking: ((CGPoint?) -> Void)?
override var state: UIGestureRecognizer.State { override var state: UIGestureRecognizer.State {
didSet { didSet {
@ -116,7 +116,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
self.isValidated = false self.isValidated = false
if self.isTracking { if self.isTracking {
self.isTracking = false self.isTracking = false
self.updateIsTracking?(false) self.updateIsTracking?(nil)
} }
} }
@ -134,7 +134,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
if !self.isTracking { if !self.isTracking {
self.isTracking = true self.isTracking = true
self.updateIsTracking?(true) self.updateIsTracking?(touches.first?.location(in: self.view))
} }
} }
} }
@ -450,12 +450,38 @@ private final class StoryContainerScreenComponent: Component {
let longPressRecognizer = StoryLongPressRecognizer(target: self, action: #selector(self.longPressGesture(_:))) let longPressRecognizer = StoryLongPressRecognizer(target: self, action: #selector(self.longPressGesture(_:)))
longPressRecognizer.delegate = self longPressRecognizer.delegate = self
longPressRecognizer.updateIsTracking = { [weak self] isTracking in longPressRecognizer.updateIsTracking = { [weak self] point in
guard let self else { guard let self else {
return return
} }
self.isHoldingTouch = isTracking guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
self.state?.updated(transition: .immediate) return
}
var point = point
if let pointValue = point {
if !itemSetComponentView.allowsInstantPauseOnTouch(point: self.convert(pointValue, to: itemSetComponentView)) {
point = nil
}
}
if point != nil {
if !self.isHoldingTouch {
self.isHoldingTouch = true
self.state?.updated(transition: .immediate)
}
} else {
DispatchQueue.main.async { [weak self] in
guard let self else {
return
}
if self.isHoldingTouch {
self.isHoldingTouch = false
self.state?.updated(transition: .immediate)
}
}
}
} }
longPressRecognizer.shouldBegin = { [weak self] touch in longPressRecognizer.shouldBegin = { [weak self] touch in
guard let self else { guard let self else {

View File

@ -162,6 +162,13 @@ final class StoryItemContentComponent: Component {
self.currentFetchPriority?.disposable.dispose() self.currentFetchPriority?.disposable.dispose()
} }
func allowsInstantPauseOnTouch(point: CGPoint) -> Bool {
if let _ = self.overlaysView.hitTest(self.convert(self.convert(point, to: self.overlaysView), to: self.overlaysView), with: nil) {
return false
}
return true
}
private func performActionAfterImageContentLoaded(update: Bool) { private func performActionAfterImageContentLoaded(update: Bool) {
self.initializeVideoIfReady(update: update) self.initializeVideoIfReady(update: update)
} }

View File

@ -449,6 +449,7 @@ public final class StoryItemSetContainerComponent: Component {
var reactionContextNode: ReactionContextNode? var reactionContextNode: ReactionContextNode?
weak var disappearingReactionContextNode: ReactionContextNode? weak var disappearingReactionContextNode: ReactionContextNode?
weak var willDismissReactionContextNode: ReactionContextNode?
var displayLikeReactions: Bool = false var displayLikeReactions: Bool = false
var tempReactionsGesture: ContextGesture? var tempReactionsGesture: ContextGesture?
var waitingForReactionAnimateOutToLike: MessageReaction.Reaction? var waitingForReactionAnimateOutToLike: MessageReaction.Reaction?
@ -711,7 +712,32 @@ public final class StoryItemSetContainerComponent: Component {
return true return true
} }
func allowsInstantPauseOnTouch(point: CGPoint) -> Bool {
guard let component = self.component else {
return false
}
guard let visibleItem = self.visibleItems[component.slice.item.storyItem.id] else {
return false
}
guard let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
return false
}
let localPoint = self.convert(point, to: itemView)
if itemView.bounds.contains(localPoint) {
if !itemView.allowsInstantPauseOnTouch(point: localPoint) {
return false
}
}
return true
}
func isPointInsideContentArea(point: CGPoint) -> Bool { func isPointInsideContentArea(point: CGPoint) -> Bool {
if self.reactionContextNode != nil {
return false
}
if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 { if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 {
if inputPanelView.frame.contains(point) { if inputPanelView.frame.contains(point) {
return false return false
@ -874,6 +900,13 @@ public final class StoryItemSetContainerComponent: Component {
} }
if self.displayLikeReactions { if self.displayLikeReactions {
self.displayLikeReactions = false self.displayLikeReactions = false
self.sendMessageContext.currentInputMode = .text
self.willDismissReactionContextNode = self.reactionContextNode
if hasFirstResponder(self) {
self.endEditing(true)
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
self.updateIsProgressPaused() self.updateIsProgressPaused()
} else if self.hasActiveDeactivateableInput() { } else if self.hasActiveDeactivateableInput() {
@ -1240,6 +1273,8 @@ public final class StoryItemSetContainerComponent: Component {
return self return self
} }
return self.itemsContainerView return self.itemsContainerView
} else if self.viewListDisplayState == .half && result.isDescendant(of: self.itemsContainerView) {
return self.itemsContainerView
} }
return result return result
@ -4077,18 +4112,17 @@ public final class StoryItemSetContainerComponent: Component {
} }
let reactionsAnchorRect: CGRect let reactionsAnchorRect: CGRect
if self.displayLikeReactions, let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View, let likeButtonView = inputPanelView.likeButtonView {
if self.inputPanelExternalState.isEditing, let inputPanelFrameValue {
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrameValue.maxX - 40.0, y: inputPanelFrameValue.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
} else if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View, let likeButtonView = inputPanelView.likeButtonView {
var likeRect = likeButtonView.convert(likeButtonView.bounds, to: self) var likeRect = likeButtonView.convert(likeButtonView.bounds, to: self)
likeRect.origin.y -= 15.0 likeRect.origin.y -= 15.0
likeRect.size.height += 15.0 likeRect.size.height += 15.0
likeRect.origin.x -= 30.0 likeRect.origin.x -= 30.0
reactionsAnchorRect = likeRect reactionsAnchorRect = likeRect
} else { } else {
if let inputPanelFrameValue { reactionsAnchorRect = CGRect()
reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrameValue.maxX - 40.0, y: inputPanelFrameValue.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
} else {
reactionsAnchorRect = CGRect()
}
} }
var effectiveDisplayReactions = false var effectiveDisplayReactions = false
@ -4114,7 +4148,7 @@ public final class StoryItemSetContainerComponent: Component {
effectiveDisplayReactions = false effectiveDisplayReactions = false
} }
if let reactionContextNode = self.reactionContextNode, (reactionContextNode.isReactionSearchActive && !reactionContextNode.isAnimatingOutToReaction && !reactionContextNode.isAnimatingOut) { if let reactionContextNode = self.reactionContextNode, self.willDismissReactionContextNode !== reactionContextNode, (reactionContextNode.isReactionSearchActive && !reactionContextNode.isAnimatingOutToReaction && !reactionContextNode.isAnimatingOut) {
effectiveDisplayReactions = true effectiveDisplayReactions = true
} }
@ -4203,11 +4237,11 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in reactionContextNode.reactionSelected = { [weak self, weak reactionContextNode] updateReaction, _ in
guard let self else { guard let self, let reactionContextNode else {
return return
} }
let action: () -> Void = { [weak self] in let action: () -> Void = { [weak self, weak reactionContextNode] in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
@ -4265,7 +4299,6 @@ public final class StoryItemSetContainerComponent: Component {
}, completion: { [weak targetView, weak reactionContextNode] in }, completion: { [weak targetView, weak reactionContextNode] in
targetView?.removeFromSuperview() targetView?.removeFromSuperview()
if let reactionContextNode { if let reactionContextNode {
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
reactionContextNode?.view.removeFromSuperview() reactionContextNode?.view.removeFromSuperview()
}) })
@ -4486,7 +4519,7 @@ public final class StoryItemSetContainerComponent: Component {
if let reactionContextNode = self.disappearingReactionContextNode { if let reactionContextNode = self.disappearingReactionContextNode {
if !reactionContextNode.isAnimatingOutToReaction { if !reactionContextNode.isAnimatingOutToReaction {
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition) reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: reactionContextNode.centerAligned, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
} }
} }
@ -5852,6 +5885,51 @@ public final class StoryItemSetContainerComponent: Component {
}))) })))
} }
var isHidden = false
if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden {
isHidden = storiesHidden
}
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self, let component = self.component else {
return
}
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string
let tooltipScreen = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .markdown(text: text),
style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0),
icon: .peer(peer: component.slice.peer, isStory: true),
action: TooltipScreen.Action(
title: component.strings.Undo_Undo,
action: {
component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden)
}
),
location: .bottom,
shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) }
)
tooltipScreen.willBecomeDismissed = { [weak self] _ in
guard let self else {
return
}
self.sendMessageContext.tooltipScreen = nil
self.updateIsProgressPaused()
}
self.sendMessageContext.tooltipScreen?.dismiss()
self.sendMessageContext.tooltipScreen = tooltipScreen
self.updateIsProgressPaused()
component.controller()?.present(tooltipScreen, in: .current)
})))
if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) { if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
@ -6004,14 +6082,27 @@ public final class StoryItemSetContainerComponent: Component {
), nil) ), nil)
} }
}))) })))
}
var isHidden = false
if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden { var isHidden = false
isHidden = storiesHidden if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden {
} else if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { isHidden = storiesHidden
isHidden = storiesHidden } else if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden {
isHidden = storiesHidden
}
var canArchive = false
if isHidden {
canArchive = true
} else {
if case .user = component.slice.peer, !component.slice.peer.isService {
canArchive = true
} else if case .channel = component.slice.peer {
canArchive = true
} }
}
if canArchive {
items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in

View File

@ -472,10 +472,21 @@ public final class StoryPeerListComponent: Component {
} }
} }
public func cancelLoadingItem() {
self.loadingItemDisposable?.dispose()
self.loadingItemDisposable = nil
if self.loadingItemId != nil {
self.loadingItemId = nil
self.state?.updated(transition: .immediate)
}
}
public func setLoadingItem(peerId: EnginePeer.Id, signal: Signal<Never, NoError>) { public func setLoadingItem(peerId: EnginePeer.Id, signal: Signal<Never, NoError>) {
var applyLoadingItem = true var applyLoadingItem = true
self.loadingItemDisposable?.dispose() self.loadingItemDisposable?.dispose()
self.loadingItemDisposable = (signal |> deliverOnMainQueue).start(completed: { [weak self] in let loadingItemDisposable = (signal |> deliverOnMainQueue).start(completed: { [weak self] in
guard let self else { guard let self else {
return return
} }
@ -483,6 +494,7 @@ public final class StoryPeerListComponent: Component {
applyLoadingItem = false applyLoadingItem = false
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
}) })
self.loadingItemDisposable = loadingItemDisposable
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
guard let self else { guard let self else {
@ -1285,10 +1297,9 @@ public final class StoryPeerListComponent: Component {
titleContentOffset = collapsedTitleOffset titleContentOffset = collapsedTitleOffset
} else { } else {
titleContentOffset = titleMinContentOffset.interpolate(to: ((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: min(1.0, collapsedState.maxFraction) * (1.0 - collapsedState.activityFraction)) titleContentOffset = titleMinContentOffset.interpolate(to: ((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: min(1.0, collapsedState.maxFraction) * (1.0 - collapsedState.activityFraction))
titleContentOffset += -expandBoundsFraction * 4.0
} }
titleContentOffset += -expandBoundsFraction * 4.0
var titleIndicatorSize: CGSize? var titleIndicatorSize: CGSize?
if collapsedState.activityFraction != 0.0 { if collapsedState.activityFraction != 0.0 {
let collapsedItemMinX = collapsedContentOrigin - collapsedItemWidth * 0.5 let collapsedItemMinX = collapsedContentOrigin - collapsedItemWidth * 0.5

View File

@ -266,7 +266,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
@discardableResult @discardableResult
public func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? { public func openStoryCamera(transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
guard let controller = self.viewControllers.last as? ViewController else { guard let controller = self.viewControllers.last as? ViewController else {
return nil return nil
} }
@ -275,6 +275,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
let context = self.context let context = self.context
var storyTarget: Stories.PendingTarget? var storyTarget: Stories.PendingTarget?
var isPeerArchived = false
var updatedTransitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?
var presentImpl: ((ViewController) -> Void)? var presentImpl: ((ViewController) -> Void)?
var returnToCameraImpl: (() -> Void)? var returnToCameraImpl: (() -> Void)?
@ -295,7 +297,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
}, },
transitionOut: { finished in transitionOut: { finished in
if let transitionOut = transitionOut(finished ? storyTarget : nil), let destinationView = transitionOut.destinationView { if let transitionOut = (updatedTransitionOut ?? transitionOut)(finished ? storyTarget : nil, isPeerArchived), let destinationView = transitionOut.destinationView {
return CameraScreen.TransitionOut( return CameraScreen.TransitionOut(
destinationView: destinationView, destinationView: destinationView,
destinationRect: transitionOut.destinationRect, destinationRect: transitionOut.destinationRect,
@ -353,7 +355,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
isEditing: false, isEditing: false,
transitionIn: transitionIn, transitionIn: transitionIn,
transitionOut: { finished, isNew in transitionOut: { finished, isNew in
if finished, let transitionOut = transitionOut(storyTarget), let destinationView = transitionOut.destinationView { if finished, let transitionOut = (updatedTransitionOut ?? transitionOut)(storyTarget, false), let destinationView = transitionOut.destinationView {
return MediaEditorScreen.TransitionOut( return MediaEditorScreen.TransitionOut(
destinationView: destinationView, destinationView: destinationView,
destinationRect: transitionOut.destinationRect, destinationRect: transitionOut.destinationRect,
@ -375,13 +377,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return return
} }
let context = self.context
if let rootTabController = self.rootTabController {
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
rootTabController.selectedIndex = index
}
}
let target: Stories.PendingTarget let target: Stories.PendingTarget
let targetPeerId: EnginePeer.Id let targetPeerId: EnginePeer.Id
if let sendAsPeerId = options.sendAsPeerId { if let sendAsPeerId = options.sendAsPeerId {
@ -393,84 +388,118 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
} }
storyTarget = target storyTarget = target
let completionImpl: () -> Void = { [weak self] in let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
guard let self else { |> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return return
} }
if let chatListController = self.chatListController as? ChatListControllerImpl { if case let .user(user) = peer {
let _ = (chatListController.hasPendingStories isPeerArchived = user.storiesHidden ?? false
|> filter { $0 } } else if case let .channel(channel) = peer {
|> take(1) isPeerArchived = channel.storiesHidden ?? false
|> timeout(0.25, queue: .mainQueue(), alternate: .single(true)) }
|> deliverOnMainQueue).start(completed: { [weak chatListController] in
guard let chatListController else { let context = self.context
return if let rootTabController = self.rootTabController {
} if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController}) {
rootTabController.selectedIndex = index
}
}
let completionImpl: () -> Void = { [weak self] in
guard let self else {
return
}
var chatListController: ChatListControllerImpl?
if isPeerArchived {
var viewControllers = self.viewControllers
chatListController.scrollToStories(peerId: targetPeerId) let archiveController = ChatListControllerImpl(context: context, location: .chatList(groupId: .archive), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false)
updatedTransitionOut = archiveController.storyCameraTransitionOut()
chatListController = archiveController
viewControllers.insert(archiveController, at: 1)
self.setViewControllers(viewControllers, animated: false)
} else {
chatListController = self.chatListController as? ChatListControllerImpl
}
if let chatListController {
let _ = (chatListController.hasPendingStories
|> filter { $0 }
|> take(1)
|> timeout(isPeerArchived ? 0.5 : 0.25, queue: .mainQueue(), alternate: .single(true))
|> deliverOnMainQueue).start(completed: { [weak chatListController] in
guard let chatListController else {
return
}
chatListController.scrollToStories(peerId: targetPeerId)
Queue.mainQueue().justDispatch {
commit({})
}
})
} else {
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
commit({}) commit({})
} }
})
} else {
Queue.mainQueue().justDispatch {
commit({})
} }
} }
}
if let _ = self.chatListController as? ChatListControllerImpl {
if let _ = self.chatListController as? ChatListControllerImpl { switch mediaResult {
switch mediaResult { case let .image(image, dimensions):
case let .image(image, dimensions): if let imageData = compressImageToJPEG(image, quality: 0.7) {
if let imageData = compressImageToJPEG(image, quality: 0.7) { let entities = generateChatInputTextEntities(caption)
let entities = generateChatInputTextEntities(caption) Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)")
Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)") let _ = (context.engine.messages.uploadStory(target: target, media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId)
let _ = (context.engine.messages.uploadStory(target: target, media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId) |> deliverOnMainQueue).start(next: { stableId in
|> deliverOnMainQueue).start(next: { stableId in moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId)) })
})
completionImpl()
completionImpl()
}
case let .video(content, firstFrameImage, values, duration, dimensions):
let adjustments: VideoMediaResourceAdjustments
if let valuesData = try? JSONEncoder().encode(values) {
let data = MemoryBuffer(data: valuesData)
let digest = MemoryBuffer(data: data.md5Digest())
adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
let resource: TelegramMediaResource
switch content {
case let .imageFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .videoFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .asset(localIdentifier):
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
} }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } case let .video(content, firstFrameImage, values, duration, dimensions):
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in let adjustments: VideoMediaResourceAdjustments
let file = TempBox.shared.tempFile(fileName: "image.jpg") if let valuesData = try? JSONEncoder().encode(values) {
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) { let data = MemoryBuffer(data: valuesData)
return file let digest = MemoryBuffer(data: data.md5Digest())
} else { adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
return nil
let resource: TelegramMediaResource
switch content {
case let .imageFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .videoFile(path):
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
case let .asset(localIdentifier):
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
} }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
let file = TempBox.shared.tempFile(fileName: "image.jpg")
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
return file
} else {
return nil
}
}
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
let entities = generateChatInputTextEntities(caption)
let _ = (context.engine.messages.uploadStory(target: target, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId)
|> deliverOnMainQueue).start(next: { stableId in
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
})
completionImpl()
} }
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
let entities = generateChatInputTextEntities(caption)
let _ = (context.engine.messages.uploadStory(target: target, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId)
|> deliverOnMainQueue).start(next: { stableId in
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
})
completionImpl()
} }
} }
}
dismissCameraImpl?()
dismissCameraImpl?() })
} as (Int64, MediaEditorScreen.Result?, [MediaArea], NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void } as (Int64, MediaEditorScreen.Result?, [MediaArea], NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void
) )
controller.cancelled = { showDraftTooltip in controller.cancelled = { showDraftTooltip in