mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Story improvements
This commit is contained in:
parent
53f6d62e8b
commit
6910ec9334
@ -795,7 +795,7 @@ public struct StoryCameraTransitionInCoordinator {
|
||||
|
||||
public protocol TelegramRootControllerInterface: NavigationController {
|
||||
@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 getChatsController() -> ViewController?
|
||||
|
@ -1366,9 +1366,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
|
||||
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
|
||||
storyPeerListView.cancelLoadingItem()
|
||||
}
|
||||
|
||||
switch subject {
|
||||
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):
|
||||
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
|
||||
fullScreenEffectView.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.sharedOpenStoryProgressDisposable.set(nil)
|
||||
|
||||
if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() {
|
||||
storyPeerListView.cancelLoadingItem()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: { [weak self] target 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
|
||||
)
|
||||
} else if let rightButtonView = componentView.rightButtonViews["story"] {
|
||||
return StoryCameraTransitionOut(
|
||||
destinationView: rightButtonView,
|
||||
destinationRect: rightButtonView.bounds,
|
||||
destinationCornerRadius: rightButtonView.bounds.height * 0.5
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
let coordinator = rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
|
||||
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) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
@ -3947,6 +3955,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
if let componentView = self.chatListHeaderView() {
|
||||
self.sharedOpenStoryProgressDisposable.set(nil)
|
||||
componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal)
|
||||
}
|
||||
}
|
||||
@ -5667,7 +5676,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
if let current = self.storyCameraTransitionInCoordinator {
|
||||
coordinator = current
|
||||
} 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 {
|
||||
return nil
|
||||
}
|
||||
|
@ -260,6 +260,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var isLeftAligned: Bool = true
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
public var centerAligned: Bool {
|
||||
return self.validLayout?.4 ?? false
|
||||
}
|
||||
|
||||
private var customReactionSource: (view: UIView, rect: CGRect, layer: CALayer, item: ReactionItem)?
|
||||
|
||||
public var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)?
|
||||
|
@ -4579,7 +4579,7 @@ func replayFinalState(
|
||||
mediaAreas: item.mediaAreas,
|
||||
text: item.text,
|
||||
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,
|
||||
isPinned: item.isPinned,
|
||||
isExpired: item.isExpired,
|
||||
@ -4610,7 +4610,7 @@ func replayFinalState(
|
||||
mediaAreas: item.mediaAreas,
|
||||
text: item.text,
|
||||
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,
|
||||
isPinned: item.isPinned,
|
||||
isExpired: item.isExpired,
|
||||
|
@ -23,10 +23,10 @@ public struct CachedChannelFlags: OptionSet {
|
||||
}
|
||||
|
||||
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
||||
public let memberCount: Int32?
|
||||
public let adminCount: Int32?
|
||||
public let bannedCount: Int32?
|
||||
public let kickedCount: Int32?
|
||||
public var memberCount: Int32?
|
||||
public var adminCount: Int32?
|
||||
public var bannedCount: Int32?
|
||||
public var kickedCount: Int32?
|
||||
|
||||
public init(memberCount: Int32?, adminCount: Int32?, bannedCount: Int32?, kickedCount: Int32?) {
|
||||
self.memberCount = memberCount
|
||||
|
@ -16,6 +16,6 @@ public final class CachedLocalizationInfos: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode(self.list, forKey: "l")
|
||||
try container.encode(self.list, forKey: "t")
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ public struct Namespaces {
|
||||
public static let cachedEmojiQueryResults: Int8 = 26
|
||||
public static let cachedPeerStoryListHeads: Int8 = 27
|
||||
public static let displayedStoryNotifications: Int8 = 28
|
||||
public static let storySendAsPeerIds: Int8 = 29
|
||||
}
|
||||
|
||||
public struct UnorderedItemList {
|
||||
|
@ -1047,7 +1047,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
||||
if !peerIds.contains(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()))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if let reaction {
|
||||
@ -2040,7 +2044,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
||||
var updatedItemValue: Stories.StoredItem?
|
||||
|
||||
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)
|
||||
|
@ -839,6 +839,17 @@ public extension TelegramEngine {
|
||||
guard let peer = peerView.peer else {
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
@ -912,6 +923,16 @@ public extension TelegramEngine {
|
||||
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(
|
||||
peer: EnginePeer(peer),
|
||||
hasUnseen: false,
|
||||
|
@ -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> {
|
||||
let accountPeerId = account.peerId
|
||||
return account.network.request(Api.functions.stories.getChatsToSend())
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||
return account.postbox.transaction { transaction -> [Peer] in
|
||||
let chats: [Api.Chat]
|
||||
let parsedPeers: AccumulatedPeers
|
||||
switch result {
|
||||
case let .chats(apiChats):
|
||||
chats = apiChats
|
||||
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)
|
||||
return account.postbox.transaction { transaction -> [Peer]? in
|
||||
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.storySendAsPeerIds, key: ValueBoxKey(length: 0)))?.get(CachedStorySendAsPeers.self) {
|
||||
return entry.peerIds.compactMap(transaction.getPeer)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> mapToSignal { cachedPeers in
|
||||
let remote: Signal<[Peer], NoError> = account.network.request(Api.functions.stories.getChatsToSend())
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[Peer], NoError> in
|
||||
return account.postbox.transaction { transaction -> [Peer] in
|
||||
let chats: [Api.Chat]
|
||||
let parsedPeers: AccumulatedPeers
|
||||
switch result {
|
||||
case let .chats(apiChats):
|
||||
chats = apiChats
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -809,6 +809,7 @@ private final class GroupHeaderLayer: UIView {
|
||||
func update(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
forceNeedsVibrancy: Bool,
|
||||
layoutType: EmojiPagerContentComponent.ItemLayoutType,
|
||||
hasTopSeparator: Bool,
|
||||
actionButtonTitle: String?,
|
||||
@ -830,7 +831,7 @@ private final class GroupHeaderLayer: UIView {
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
let needsVibrancy = !theme.overallDarkAppearance
|
||||
let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy
|
||||
|
||||
let textOffsetY: CGFloat
|
||||
if hasTopSeparator {
|
||||
@ -839,16 +840,22 @@ private final class GroupHeaderLayer: UIView {
|
||||
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 needsTintText: Bool
|
||||
if subtitle != nil {
|
||||
color = theme.chat.inputPanel.primaryTextColor
|
||||
needsTintText = false
|
||||
} else {
|
||||
color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||
color = subtitleColor
|
||||
needsTintText = true
|
||||
}
|
||||
let subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||
|
||||
let titleHorizontalOffset: CGFloat
|
||||
if isPremiumLocked {
|
||||
@ -903,7 +910,7 @@ private final class GroupHeaderLayer: UIView {
|
||||
tintClearIconLayer.isHidden = !needsVibrancy
|
||||
|
||||
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
|
||||
clearIconLayer.contents = image.cgImage
|
||||
}
|
||||
@ -1144,7 +1151,7 @@ private final class GroupHeaderLayer: UIView {
|
||||
self.separatorLayer = 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))
|
||||
|
||||
let tintSeparatorLayer: SimpleLayer
|
||||
@ -1517,6 +1524,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
private struct Params: Equatable {
|
||||
var context: AccountContext
|
||||
var theme: PresentationTheme
|
||||
var forceNeedsVibrancy: Bool
|
||||
var strings: PresentationStrings
|
||||
var text: String
|
||||
var useOpaqueTheme: Bool
|
||||
@ -1535,6 +1543,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
@ -1823,10 +1834,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
return
|
||||
}
|
||||
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
|
||||
if let textField = self.textField {
|
||||
textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
|
||||
@ -1837,6 +1848,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
let params = Params(
|
||||
context: context,
|
||||
theme: theme,
|
||||
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||
strings: strings,
|
||||
text: text,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
@ -1880,7 +1892,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
|
||||
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.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
|
||||
} else {
|
||||
@ -1891,12 +1906,19 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
self.backgroundLayer.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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: strings.Common_Cancel,
|
||||
font: Font.regular(17.0),
|
||||
color: useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
color: cancelColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
||||
@ -1942,6 +1964,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
transition: transition,
|
||||
component: AnyComponent(EmojiSearchStatusComponent(
|
||||
theme: theme,
|
||||
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||
strings: strings,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
content: statusContent
|
||||
@ -1990,6 +2013,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||
component: AnyComponent(EmojiSearchSearchBarComponent(
|
||||
context: context,
|
||||
theme: theme,
|
||||
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||
strings: strings,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
textInputState: textInputState,
|
||||
@ -5426,6 +5450,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let (groupHeaderSize, centralContentWidth) = groupHeaderView.update(
|
||||
context: component.context,
|
||||
theme: keyboardChildEnvironment.theme,
|
||||
forceNeedsVibrancy: component.inputInteractionHolder.inputInteraction?.externalBackground != nil,
|
||||
layoutType: itemLayout.layoutType,
|
||||
hasTopSeparator: hasTopSeparator,
|
||||
actionButtonTitle: actionButtonTitle,
|
||||
@ -5467,7 +5492,14 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.scrollView.layer.insertSublayer(groupBorderLayer, at: 0)
|
||||
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.lineWidth = 1.6
|
||||
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))
|
||||
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)
|
||||
// Temporary workaround for status selection; use a separate search container (see GIF)
|
||||
|
@ -99,6 +99,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let forceNeedsVibrancy: Bool
|
||||
let strings: PresentationStrings
|
||||
let useOpaqueTheme: Bool
|
||||
let textInputState: TextInputState
|
||||
@ -109,6 +110,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
forceNeedsVibrancy: Bool,
|
||||
strings: PresentationStrings,
|
||||
useOpaqueTheme: Bool,
|
||||
textInputState: TextInputState,
|
||||
@ -118,6 +120,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.forceNeedsVibrancy = forceNeedsVibrancy
|
||||
self.strings = strings
|
||||
self.useOpaqueTheme = useOpaqueTheme
|
||||
self.textInputState = textInputState
|
||||
@ -130,6 +133,9 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
@ -456,7 +462,10 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
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 }) {
|
||||
let selectedItemCenter = itemLayout.frame(at: index).center
|
||||
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.selectedItemBackground.cornerRadius = selectionSize.height * 0.5
|
||||
self.selectedItemTintBackground.cornerRadius = selectionSize.height * 0.5
|
||||
@ -609,12 +626,19 @@ final class EmojiSearchSearchBarComponent: Component {
|
||||
self.component = component
|
||||
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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: component.strings.Common_Search,
|
||||
font: Font.regular(17.0),
|
||||
color: component.useOpaqueTheme ? component.theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : component.theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||
color: textColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 32.0, height: 100.0)
|
||||
|
@ -70,17 +70,20 @@ final class EmojiSearchStatusComponent: Component {
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let forceNeedsVibrancy: Bool
|
||||
let strings: PresentationStrings
|
||||
let useOpaqueTheme: Bool
|
||||
let content: Content
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
forceNeedsVibrancy: Bool,
|
||||
strings: PresentationStrings,
|
||||
useOpaqueTheme: Bool,
|
||||
content: Content
|
||||
) {
|
||||
self.theme = theme
|
||||
self.forceNeedsVibrancy = forceNeedsVibrancy
|
||||
self.strings = strings
|
||||
self.useOpaqueTheme = useOpaqueTheme
|
||||
self.content = content
|
||||
@ -90,6 +93,9 @@ final class EmojiSearchStatusComponent: Component {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
@ -430,7 +436,13 @@ final class EmojiSearchStatusComponent: Component {
|
||||
let displaySize = CGSize(width: availableSize.width * UIScreenScale, height: availableSize.height * UIScreenScale)
|
||||
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
|
||||
|
||||
if self.contentView.tintColor != overlayColor {
|
||||
|
@ -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))
|
||||
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
|
||||
let _ = self
|
||||
let _ = completed
|
||||
|
@ -291,7 +291,7 @@ public final class LottieComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if self.scheduledPlayOnce {
|
||||
if self.scheduledPlayOnce && self.isEffectivelyVisible {
|
||||
self.scheduledPlayOnce = false
|
||||
self.playOnce()
|
||||
} else {
|
||||
|
@ -718,7 +718,6 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
@ -2693,12 +2692,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
)
|
||||
self.stateValue = 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)
|
||||
})
|
||||
@ -2811,7 +2804,9 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
for channel in adminedChannels {
|
||||
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.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)
|
||||
})
|
||||
@ -2958,12 +2947,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
self.stateValue = 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)
|
||||
})
|
||||
case let .contacts(base):
|
||||
@ -3107,12 +3090,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
self.stateValue = 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)
|
||||
})
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import Postbox
|
||||
import AvatarNode
|
||||
|
||||
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 signal = storyContent.state
|
||||
|> take(1)
|
||||
@ -82,7 +82,10 @@ public extension StoryContainerScreen {
|
||||
}
|
||||
|> 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) {
|
||||
|
@ -91,7 +91,7 @@ private final class MuteMonitor {
|
||||
|
||||
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
||||
var shouldBegin: ((UITouch) -> Bool)?
|
||||
var updateIsTracking: ((Bool) -> Void)?
|
||||
var updateIsTracking: ((CGPoint?) -> Void)?
|
||||
|
||||
override var state: UIGestureRecognizer.State {
|
||||
didSet {
|
||||
@ -116,7 +116,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
||||
self.isValidated = false
|
||||
if self.isTracking {
|
||||
self.isTracking = false
|
||||
self.updateIsTracking?(false)
|
||||
self.updateIsTracking?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
||||
|
||||
if !self.isTracking {
|
||||
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(_:)))
|
||||
longPressRecognizer.delegate = self
|
||||
longPressRecognizer.updateIsTracking = { [weak self] isTracking in
|
||||
longPressRecognizer.updateIsTracking = { [weak self] point in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isHoldingTouch = isTracking
|
||||
self.state?.updated(transition: .immediate)
|
||||
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 {
|
||||
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
|
||||
guard let self else {
|
||||
|
@ -162,6 +162,13 @@ final class StoryItemContentComponent: Component {
|
||||
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) {
|
||||
self.initializeVideoIfReady(update: update)
|
||||
}
|
||||
|
@ -449,6 +449,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
var reactionContextNode: ReactionContextNode?
|
||||
weak var disappearingReactionContextNode: ReactionContextNode?
|
||||
weak var willDismissReactionContextNode: ReactionContextNode?
|
||||
var displayLikeReactions: Bool = false
|
||||
var tempReactionsGesture: ContextGesture?
|
||||
var waitingForReactionAnimateOutToLike: MessageReaction.Reaction?
|
||||
@ -711,7 +712,32 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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 {
|
||||
if self.reactionContextNode != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 {
|
||||
if inputPanelView.frame.contains(point) {
|
||||
return false
|
||||
@ -874,6 +900,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
if self.displayLikeReactions {
|
||||
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.updateIsProgressPaused()
|
||||
} else if self.hasActiveDeactivateableInput() {
|
||||
@ -1240,6 +1273,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return self
|
||||
}
|
||||
return self.itemsContainerView
|
||||
} else if self.viewListDisplayState == .half && result.isDescendant(of: self.itemsContainerView) {
|
||||
return self.itemsContainerView
|
||||
}
|
||||
|
||||
return result
|
||||
@ -4077,18 +4112,17 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
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)
|
||||
likeRect.origin.y -= 15.0
|
||||
likeRect.size.height += 15.0
|
||||
likeRect.origin.x -= 30.0
|
||||
reactionsAnchorRect = likeRect
|
||||
} else {
|
||||
if 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 {
|
||||
reactionsAnchorRect = CGRect()
|
||||
}
|
||||
reactionsAnchorRect = CGRect()
|
||||
}
|
||||
|
||||
var effectiveDisplayReactions = false
|
||||
@ -4114,7 +4148,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
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
|
||||
}
|
||||
|
||||
@ -4203,11 +4237,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in
|
||||
guard let self else {
|
||||
reactionContextNode.reactionSelected = { [weak self, weak reactionContextNode] updateReaction, _ in
|
||||
guard let self, let reactionContextNode else {
|
||||
return
|
||||
}
|
||||
let action: () -> Void = { [weak self] in
|
||||
let action: () -> Void = { [weak self, weak reactionContextNode] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -4265,7 +4299,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}, completion: { [weak targetView, weak reactionContextNode] in
|
||||
targetView?.removeFromSuperview()
|
||||
if let reactionContextNode {
|
||||
reactionContextNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, removeOnCompletion: false)
|
||||
reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak reactionContextNode] _ in
|
||||
reactionContextNode?.view.removeFromSuperview()
|
||||
})
|
||||
@ -4486,7 +4519,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if let reactionContextNode = self.disappearingReactionContextNode {
|
||||
if !reactionContextNode.isAnimatingOutToReaction {
|
||||
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) {
|
||||
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)
|
||||
@ -6004,14 +6082,27 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
), nil)
|
||||
}
|
||||
})))
|
||||
|
||||
var isHidden = false
|
||||
if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden {
|
||||
isHidden = storiesHidden
|
||||
} else if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden {
|
||||
isHidden = storiesHidden
|
||||
}
|
||||
|
||||
var isHidden = false
|
||||
if case let .user(user) = component.slice.peer, let storiesHidden = user.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
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
|
@ -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>) {
|
||||
var applyLoadingItem = true
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -483,6 +494,7 @@ public final class StoryPeerListComponent: Component {
|
||||
applyLoadingItem = false
|
||||
self.state?.updated(transition: .immediate)
|
||||
})
|
||||
self.loadingItemDisposable = loadingItemDisposable
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in
|
||||
guard let self else {
|
||||
@ -1285,10 +1297,9 @@ public final class StoryPeerListComponent: Component {
|
||||
titleContentOffset = collapsedTitleOffset
|
||||
} 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 += -expandBoundsFraction * 4.0
|
||||
}
|
||||
|
||||
titleContentOffset += -expandBoundsFraction * 4.0
|
||||
|
||||
var titleIndicatorSize: CGSize?
|
||||
if collapsedState.activityFraction != 0.0 {
|
||||
let collapsedItemMinX = collapsedContentOrigin - collapsedItemWidth * 0.5
|
||||
|
@ -266,7 +266,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
}
|
||||
|
||||
@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 {
|
||||
return nil
|
||||
}
|
||||
@ -275,6 +275,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
let context = self.context
|
||||
|
||||
var storyTarget: Stories.PendingTarget?
|
||||
var isPeerArchived = false
|
||||
var updatedTransitionOut: ((Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?)?
|
||||
|
||||
var presentImpl: ((ViewController) -> Void)?
|
||||
var returnToCameraImpl: (() -> Void)?
|
||||
@ -295,7 +297,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
}
|
||||
},
|
||||
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(
|
||||
destinationView: destinationView,
|
||||
destinationRect: transitionOut.destinationRect,
|
||||
@ -353,7 +355,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
isEditing: false,
|
||||
transitionIn: transitionIn,
|
||||
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(
|
||||
destinationView: destinationView,
|
||||
destinationRect: transitionOut.destinationRect,
|
||||
@ -375,13 +377,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
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 targetPeerId: EnginePeer.Id
|
||||
if let sendAsPeerId = options.sendAsPeerId {
|
||||
@ -393,84 +388,118 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
}
|
||||
storyTarget = target
|
||||
|
||||
let completionImpl: () -> Void = { [weak self] in
|
||||
guard let self else {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
|
||||
if let chatListController = self.chatListController as? ChatListControllerImpl {
|
||||
let _ = (chatListController.hasPendingStories
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> timeout(0.25, queue: .mainQueue(), alternate: .single(true))
|
||||
|> deliverOnMainQueue).start(completed: { [weak chatListController] in
|
||||
guard let chatListController else {
|
||||
return
|
||||
}
|
||||
if case let .user(user) = peer {
|
||||
isPeerArchived = user.storiesHidden ?? false
|
||||
} else if case let .channel(channel) = peer {
|
||||
isPeerArchived = channel.storiesHidden ?? false
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
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 {
|
||||
commit({})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = self.chatListController as? ChatListControllerImpl {
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
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)
|
||||
|> deliverOnMainQueue).start(next: { stableId in
|
||||
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
|
||||
})
|
||||
|
||||
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))
|
||||
|
||||
if let _ = self.chatListController as? ChatListControllerImpl {
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
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)
|
||||
|> deliverOnMainQueue).start(next: { stableId in
|
||||
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId))
|
||||
})
|
||||
|
||||
completionImpl()
|
||||
}
|
||||
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
|
||||
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) }
|
||||
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
|
||||
)
|
||||
controller.cancelled = { showDraftTooltip in
|
||||
|
Loading…
x
Reference in New Issue
Block a user