mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '0c091f6c1b8d1ced7e95e4c53b5525bf62aa0557'
# Conflicts: # submodules/ContactListUI/Sources/ContactsControllerNode.swift # submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift
This commit is contained in:
commit
313fe3a509
@ -1074,3 +1074,42 @@ public struct AntiSpamBotConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct StoriesConfiguration {
|
||||
public enum PostingAvailability {
|
||||
case enabled
|
||||
case premium
|
||||
case disabled
|
||||
}
|
||||
|
||||
static var defaultValue: StoriesConfiguration {
|
||||
return StoriesConfiguration(posting: .disabled)
|
||||
}
|
||||
|
||||
public let posting: PostingAvailability
|
||||
|
||||
fileprivate init(posting: PostingAvailability) {
|
||||
self.posting = posting
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> StoriesConfiguration {
|
||||
//#if DEBUG
|
||||
// return StoriesConfiguration(posting: .premium)
|
||||
//#else
|
||||
if let data = appConfiguration.data, let postingString = data["stories_posting"] as? String {
|
||||
let posting: PostingAvailability
|
||||
switch postingString {
|
||||
case "enabled":
|
||||
posting = .enabled
|
||||
case "premium":
|
||||
posting = .premium
|
||||
default:
|
||||
posting = .disabled
|
||||
}
|
||||
return StoriesConfiguration(posting: posting)
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
@ -159,6 +159,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
private var clearUnseenDownloadsTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private(set) var isPremium: Bool = false
|
||||
private(set) var storyPostingAvailability: StoriesConfiguration.PostingAvailability = .disabled
|
||||
private var storiesPostingAvailabilityDisposable: Disposable?
|
||||
private let storyPostingAvailabilityValue = ValuePromise<StoriesConfiguration.PostingAvailability>(.disabled)
|
||||
|
||||
private var didSetupTabs = false
|
||||
|
||||
@ -245,7 +248,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
parentController: self,
|
||||
hideNetworkActivityStatus: self.hideNetworkActivityStatus,
|
||||
containerNode: self.chatListDisplayNode.mainContainerNode,
|
||||
isReorderingTabs: self.isReorderingTabsValue.get()
|
||||
isReorderingTabs: self.isReorderingTabsValue.get(),
|
||||
storyPostingAvailable: self.storyPostingAvailabilityValue.get() |> map { availability -> Bool in
|
||||
switch availability {
|
||||
case .enabled, .premium:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
self.primaryContext = primaryContext
|
||||
self.primaryInfoReady.set(primaryContext.ready.get())
|
||||
@ -697,6 +708,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.reloadFilters()
|
||||
}
|
||||
|
||||
self.storiesPostingAvailabilityDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> AppConfiguration in
|
||||
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
return appConfiguration
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> map { appConfiguration -> StoriesConfiguration.PostingAvailability in
|
||||
let storiesConfiguration = StoriesConfiguration.with(appConfiguration: appConfiguration)
|
||||
return storiesConfiguration.posting
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
).start(next: { [weak self] postingAvailability in
|
||||
if let self {
|
||||
self.storyPostingAvailability = postingAvailability
|
||||
self.storyPostingAvailabilityValue.set(postingAvailability)
|
||||
}
|
||||
})
|
||||
|
||||
self.updateNavigationMetadata()
|
||||
}
|
||||
|
||||
@ -725,6 +754,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.storySubscriptionsDisposable?.dispose()
|
||||
self.preloadStorySubscriptionsDisposable?.dispose()
|
||||
self.storyProgressDisposable?.dispose()
|
||||
self.storiesPostingAvailabilityDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateNavigationMetadata() {
|
||||
@ -2341,19 +2371,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
fileprivate func openStoryCamera() {
|
||||
guard self.isPremium else {
|
||||
let context = self.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: self.context, subject: .stories, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
switch self.storyPostingAvailability {
|
||||
case .premium:
|
||||
guard self.isPremium else {
|
||||
let context = self.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: self.context, subject: .stories, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
self.push(controller)
|
||||
return
|
||||
}
|
||||
self.push(controller)
|
||||
case .disabled:
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var cameraTransitionIn: StoryCameraTransitionIn?
|
||||
if let componentView = self.chatListHeaderView() {
|
||||
if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
|
||||
@ -2780,7 +2818,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
parentController: self,
|
||||
hideNetworkActivityStatus: false,
|
||||
containerNode: inlineNode,
|
||||
isReorderingTabs: .single(false)
|
||||
isReorderingTabs: .single(false),
|
||||
storyPostingAvailable: .single(false)
|
||||
)
|
||||
self.pendingSecondaryContext = pendingSecondaryContext
|
||||
let _ = (pendingSecondaryContext.ready.get()
|
||||
@ -4770,7 +4809,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
let (accountPeer, limits, _) = result
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
|
||||
|
||||
let _ = strongSelf.context.engine.peers.markChatListFeaturedFiltersAsSeen().start()
|
||||
let (_, filterItems) = filterItemsAndTotalCount
|
||||
|
||||
@ -4923,6 +4961,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.storyCameraTransitionInCoordinator = nil
|
||||
}
|
||||
}
|
||||
|
||||
var isStoryPostingAvailable: Bool {
|
||||
switch self.storyPostingAvailability {
|
||||
case .enabled:
|
||||
return true
|
||||
case .premium:
|
||||
return self.isPremium
|
||||
case .disabled:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||
@ -5047,7 +5096,8 @@ private final class ChatListLocationContext {
|
||||
parentController: ChatListControllerImpl,
|
||||
hideNetworkActivityStatus: Bool,
|
||||
containerNode: ChatListContainerNode,
|
||||
isReorderingTabs: Signal<Bool, NoError>
|
||||
isReorderingTabs: Signal<Bool, NoError>,
|
||||
storyPostingAvailable: Signal<Bool, NoError>
|
||||
) {
|
||||
self.context = context
|
||||
self.location = location
|
||||
@ -5102,8 +5152,9 @@ private final class ChatListLocationContext {
|
||||
containerNode.currentItemState,
|
||||
isReorderingTabs,
|
||||
peerStatus,
|
||||
parentController.updatedPresentationData.1
|
||||
).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus, presentationData in
|
||||
parentController.updatedPresentationData.1,
|
||||
storyPostingAvailable
|
||||
).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus, presentationData, storyPostingAvailable in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -5114,7 +5165,8 @@ private final class ChatListLocationContext {
|
||||
stateAndFilterId: stateAndFilterId,
|
||||
isReorderingTabs: isReorderingTabs,
|
||||
peerStatus: peerStatus,
|
||||
presentationData: presentationData
|
||||
presentationData: presentationData,
|
||||
storyPostingAvailable: storyPostingAvailable
|
||||
)
|
||||
})
|
||||
} else {
|
||||
@ -5324,7 +5376,8 @@ private final class ChatListLocationContext {
|
||||
stateAndFilterId: (state: ChatListNodeState, filterId: Int32?),
|
||||
isReorderingTabs: Bool,
|
||||
peerStatus: NetworkStatusTitle.Status?,
|
||||
presentationData: PresentationData
|
||||
presentationData: PresentationData,
|
||||
storyPostingAvailable: Bool
|
||||
) {
|
||||
let defaultTitle: String
|
||||
switch location {
|
||||
@ -5431,15 +5484,19 @@ private final class ChatListLocationContext {
|
||||
}
|
||||
}
|
||||
|
||||
self.storyButton = AnyComponentWithIdentity(id: "story", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .icon(imageName: "Chat List/AddStoryIcon"),
|
||||
pressed: { [weak self] _ in
|
||||
guard let self, let parentController = self.parentController else {
|
||||
return
|
||||
if storyPostingAvailable {
|
||||
self.storyButton = AnyComponentWithIdentity(id: "story", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .icon(imageName: "Chat List/AddStoryIcon"),
|
||||
pressed: { [weak self] _ in
|
||||
guard let self, let parentController = self.parentController else {
|
||||
return
|
||||
}
|
||||
parentController.openStoryCamera()
|
||||
}
|
||||
parentController.openStoryCamera()
|
||||
}
|
||||
)))
|
||||
)))
|
||||
} else {
|
||||
self.storyButton = nil
|
||||
}
|
||||
} else {
|
||||
self.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .text(title: presentationData.strings.Common_Edit, isBold: false),
|
||||
|
@ -1214,7 +1214,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
|
||||
}
|
||||
|
||||
if case .compact = layout.metrics.widthClass, self.controller?.isPremium == true {
|
||||
if case .compact = layout.metrics.widthClass, self.controller?.isStoryPostingAvailable == true {
|
||||
let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false
|
||||
if selectedIndex <= 0 && translation.x > 0.0 {
|
||||
transitionFraction = 0.0
|
||||
|
@ -69,8 +69,8 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let stringsPromise = Promise<PresentationStrings>()
|
||||
|
||||
private var isPremium = false
|
||||
private var isPremiumDisposable: Disposable?
|
||||
private var isStoryPostingAvailable = false
|
||||
private var storiesPostingAvailabilityDisposable: Disposable?
|
||||
|
||||
weak var controller: ContactsController?
|
||||
|
||||
@ -244,21 +244,48 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
|
||||
self.storiesReady.set(.single(true))
|
||||
})
|
||||
|
||||
|
||||
self.contactListNode.openStories = { [weak self] peer, sourceNode in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openStories?(peer, sourceNode)
|
||||
}
|
||||
|
||||
self.isPremiumDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> map {
|
||||
return $0?.isPremium ?? false
|
||||
|
||||
let storiesPostingAvailability = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> AppConfiguration in
|
||||
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
return appConfiguration
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isPremium in
|
||||
|> distinctUntilChanged
|
||||
|> map { appConfiguration -> StoriesConfiguration.PostingAvailability in
|
||||
let storiesConfiguration = StoriesConfiguration.with(appConfiguration: appConfiguration)
|
||||
return storiesConfiguration.posting
|
||||
}
|
||||
|
||||
self.storiesPostingAvailabilityDisposable = combineLatest(queue: Queue.mainQueue(),
|
||||
storiesPostingAvailability,
|
||||
self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> map { peer -> Bool in
|
||||
if case let .user(user) = peer, user.isPremium {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
).start(next: { [weak self] postingAvailability, isPremium in
|
||||
if let self {
|
||||
self.isPremium = isPremium
|
||||
let isStoryPostingAvailable: Bool
|
||||
switch postingAvailability {
|
||||
case .enabled:
|
||||
isStoryPostingAvailable = true
|
||||
case .premium:
|
||||
isStoryPostingAvailable = isPremium
|
||||
case .disabled:
|
||||
isStoryPostingAvailable = false
|
||||
}
|
||||
self.isStoryPostingAvailable = isStoryPostingAvailable
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -266,7 +293,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.storySubscriptionsDisposable?.dispose()
|
||||
self.isPremiumDisposable?.dispose()
|
||||
self.storiesPostingAvailabilityDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -298,7 +325,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return self.isPremium
|
||||
return self.isStoryPostingAvailable
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
|
@ -5,6 +5,7 @@ import TelegramApi
|
||||
|
||||
public enum EngineOutgoingMessageContent {
|
||||
case text(String, [MessageTextEntity])
|
||||
case file(FileMediaReference)
|
||||
}
|
||||
|
||||
public final class StoryPreloadInfo {
|
||||
@ -221,29 +222,35 @@ public extension TelegramEngine {
|
||||
storyId: StoryId? = nil,
|
||||
content: EngineOutgoingMessageContent
|
||||
) {
|
||||
var attributes: [MessageAttribute] = []
|
||||
var text: String = ""
|
||||
var mediaReference: AnyMediaReference?
|
||||
|
||||
switch content {
|
||||
case let .text(text, entities):
|
||||
var attributes: [MessageAttribute] = []
|
||||
case let .text(textValue, entities):
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
let message: EnqueueMessage = .message(
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
inlineStickers: [:],
|
||||
mediaReference: nil,
|
||||
replyToMessageId: replyToMessageId,
|
||||
replyToStoryId: storyId,
|
||||
localGroupingKey: nil,
|
||||
correlationId: nil,
|
||||
bubbleUpEmojiOrStickersets: []
|
||||
)
|
||||
let _ = enqueueMessages(
|
||||
account: self.account,
|
||||
peerId: peerId,
|
||||
messages: [message]
|
||||
).start()
|
||||
text = textValue
|
||||
case let .file(fileReference):
|
||||
mediaReference = fileReference.abstract
|
||||
}
|
||||
let message: EnqueueMessage = .message(
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
inlineStickers: [:],
|
||||
mediaReference: mediaReference,
|
||||
replyToMessageId: replyToMessageId,
|
||||
replyToStoryId: storyId,
|
||||
localGroupingKey: nil,
|
||||
correlationId: nil,
|
||||
bubbleUpEmojiOrStickersets: []
|
||||
)
|
||||
let _ = enqueueMessages(
|
||||
account: self.account,
|
||||
peerId: peerId,
|
||||
messages: [message]
|
||||
).start()
|
||||
}
|
||||
|
||||
public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId? = nil, replyToStoryId: StoryId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool {
|
||||
|
@ -183,18 +183,18 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return hasPremium
|
||||
}
|
||||
|
||||
public static func inputData(context: AccountContext, chatPeerId: PeerId?, areCustomEmojiEnabled: Bool, sendGif: ((FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool)?) -> Signal<InputData, NoError> {
|
||||
public static func inputData(context: AccountContext, chatPeerId: PeerId?, areCustomEmojiEnabled: Bool, hasSearch: Bool = true, hideBackground: Bool = false, sendGif: ((FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool)?) -> Signal<InputData, NoError> {
|
||||
let animationCache = context.animationCache
|
||||
let animationRenderer = context.animationRenderer
|
||||
|
||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: true, hasTrending: true, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
|
||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: true, hasTrending: true, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId, hasSearch: hasSearch, hideBackground: hideBackground)
|
||||
|
||||
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
||||
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers]
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: true, hasTrending: true, forceHasPremium: false)
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: hasSearch, hasTrending: true, forceHasPremium: false, hideBackground: hideBackground)
|
||||
|
||||
let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|
||||
|> map { appConfiguration -> [String] in
|
||||
@ -240,7 +240,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
openSearch: {
|
||||
},
|
||||
updateSearchQuery: { _ in
|
||||
}
|
||||
},
|
||||
hideBackground: hideBackground,
|
||||
hasSearch: hasSearch
|
||||
)
|
||||
|
||||
// We are going to subscribe to the actual data when the view is loaded
|
||||
@ -256,7 +258,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
displaySearchWithPlaceholder: nil,
|
||||
searchCategories: nil,
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty(hasResults: false)
|
||||
searchState: .empty(hasResults: false),
|
||||
hideBackground: hideBackground
|
||||
)
|
||||
))
|
||||
|
||||
@ -429,6 +432,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
self.subject = subject
|
||||
self.gifInputInteraction = gifInputInteraction
|
||||
|
||||
let hideBackground = gifInputInteraction.hideBackground
|
||||
|
||||
let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs))
|
||||
|> map { savedGifs -> Bool in
|
||||
return !savedGifs.isEmpty
|
||||
@ -461,10 +466,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
items: items,
|
||||
isLoading: false,
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
displaySearchWithPlaceholder: gifInputInteraction.hasSearch ? presentationData.strings.Common_Search : nil,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty(hasResults: false)
|
||||
searchState: .empty(hasResults: false),
|
||||
hideBackground: hideBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -494,10 +500,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: nil,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
displaySearchWithPlaceholder: gifInputInteraction.hasSearch ? presentationData.strings.Common_Search : nil,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .empty(hasResults: false)
|
||||
searchState: .empty(hasResults: false),
|
||||
hideBackground: hideBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -533,10 +540,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
displaySearchWithPlaceholder: gifInputInteraction.hasSearch ? presentationData.strings.Common_Search : nil,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .active
|
||||
searchState: .active,
|
||||
hideBackground: gifInputInteraction.hideBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -619,10 +627,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
items: items,
|
||||
isLoading: isLoading,
|
||||
loadMoreToken: loadMoreToken,
|
||||
displaySearchWithPlaceholder: presentationData.strings.Common_Search,
|
||||
displaySearchWithPlaceholder: gifInputInteraction.hasSearch ? presentationData.strings.Common_Search : nil,
|
||||
searchCategories: searchCategories,
|
||||
searchInitiallyHidden: true,
|
||||
searchState: .active
|
||||
searchState: .active,
|
||||
hideBackground: gifInputInteraction.hideBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -1738,7 +1747,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
} else {
|
||||
self.gifMode = .recent
|
||||
}
|
||||
}
|
||||
},
|
||||
hideBackground: currentInputData.gifs?.component.hideBackground ?? false,
|
||||
hasSearch: currentInputData.gifs?.component.inputInteraction.hasSearch ?? false
|
||||
)
|
||||
|
||||
self.switchToTextInput = { [weak self] in
|
||||
|
@ -148,19 +148,25 @@ public final class GifPagerContentComponent: Component {
|
||||
public let loadMore: (String) -> Void
|
||||
public let openSearch: () -> Void
|
||||
public let updateSearchQuery: ([String]?) -> Void
|
||||
public let hideBackground: Bool
|
||||
public let hasSearch: Bool
|
||||
|
||||
public init(
|
||||
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
|
||||
openGifContextMenu: @escaping (Item, UIView, CGRect, ContextGesture, Bool) -> Void,
|
||||
loadMore: @escaping (String) -> Void,
|
||||
openSearch: @escaping () -> Void,
|
||||
updateSearchQuery: @escaping ([String]?) -> Void
|
||||
updateSearchQuery: @escaping ([String]?) -> Void,
|
||||
hideBackground: Bool,
|
||||
hasSearch: Bool
|
||||
) {
|
||||
self.performItemAction = performItemAction
|
||||
self.openGifContextMenu = openGifContextMenu
|
||||
self.loadMore = loadMore
|
||||
self.openSearch = openSearch
|
||||
self.updateSearchQuery = updateSearchQuery
|
||||
self.hideBackground = hideBackground
|
||||
self.hasSearch = hasSearch
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +204,7 @@ public final class GifPagerContentComponent: Component {
|
||||
public let searchCategories: EmojiSearchCategories?
|
||||
public let searchInitiallyHidden: Bool
|
||||
public let searchState: EmojiPagerContentComponent.SearchState
|
||||
public let hideBackground: Bool
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -209,7 +216,8 @@ public final class GifPagerContentComponent: Component {
|
||||
displaySearchWithPlaceholder: String?,
|
||||
searchCategories: EmojiSearchCategories?,
|
||||
searchInitiallyHidden: Bool,
|
||||
searchState: EmojiPagerContentComponent.SearchState
|
||||
searchState: EmojiPagerContentComponent.SearchState,
|
||||
hideBackground: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.inputInteraction = inputInteraction
|
||||
@ -221,6 +229,7 @@ public final class GifPagerContentComponent: Component {
|
||||
self.searchCategories = searchCategories
|
||||
self.searchInitiallyHidden = searchInitiallyHidden
|
||||
self.searchState = searchState
|
||||
self.hideBackground = hideBackground
|
||||
}
|
||||
|
||||
public static func ==(lhs: GifPagerContentComponent, rhs: GifPagerContentComponent) -> Bool {
|
||||
@ -254,6 +263,9 @@ public final class GifPagerContentComponent: Component {
|
||||
if lhs.searchState != rhs.searchState {
|
||||
return false
|
||||
}
|
||||
if lhs.hideBackground != rhs.hideBackground {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1103,6 +1115,8 @@ public final class GifPagerContentComponent: Component {
|
||||
transition.setPosition(view: self.scrollClippingView, position: clippingFrame.center)
|
||||
transition.setBounds(view: self.scrollClippingView, bounds: clippingFrame)
|
||||
|
||||
self.backgroundView.isHidden = component.hideBackground
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
private var isDismissed = false
|
||||
|
||||
private var isEditingCaption = false
|
||||
private var currentInputMode: MessageInputPanelComponent.InputMode = .keyboard
|
||||
private var currentInputMode: MessageInputPanelComponent.InputMode = .text
|
||||
|
||||
private var didInitializeInputMediaNodeDataPromise = false
|
||||
private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
||||
@ -337,7 +337,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
updateChoosingSticker: { _ in },
|
||||
switchToTextInput: { [weak self] in
|
||||
if let self {
|
||||
self.currentInputMode = .keyboard
|
||||
self.currentInputMode = .text
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
},
|
||||
@ -373,7 +373,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
}
|
||||
|
||||
@objc private func fadePressed() {
|
||||
self.currentInputMode = .keyboard
|
||||
self.currentInputMode = .text
|
||||
self.endEditing(true)
|
||||
}
|
||||
|
||||
@ -900,10 +900,10 @@ final class MediaEditorScreenComponent: Component {
|
||||
|
||||
let nextInputMode: MessageInputPanelComponent.InputMode
|
||||
switch self.currentInputMode {
|
||||
case .keyboard:
|
||||
case .text:
|
||||
nextInputMode = .emoji
|
||||
case .emoji:
|
||||
nextInputMode = .keyboard
|
||||
nextInputMode = .text
|
||||
default:
|
||||
nextInputMode = .emoji
|
||||
}
|
||||
@ -918,7 +918,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
style: .editor,
|
||||
placeholder: "Add a caption...",
|
||||
alwaysDarkWhenHasText: false,
|
||||
nextInputMode: nextInputMode,
|
||||
nextInputMode: { _ in return nextInputMode },
|
||||
areVoiceMessagesAvailable: false,
|
||||
presentController: { [weak self] c in
|
||||
guard let self, let _ = self.component else {
|
||||
@ -930,7 +930,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.currentInputMode = .keyboard
|
||||
self.currentInputMode = .text
|
||||
self.endEditing(true)
|
||||
},
|
||||
setMediaRecordingActive: nil,
|
||||
@ -941,10 +941,10 @@ final class MediaEditorScreenComponent: Component {
|
||||
inputModeAction: { [weak self] in
|
||||
if let self {
|
||||
switch self.currentInputMode {
|
||||
case .keyboard:
|
||||
case .text:
|
||||
self.currentInputMode = .emoji
|
||||
case .emoji:
|
||||
self.currentInputMode = .keyboard
|
||||
self.currentInputMode = .text
|
||||
default:
|
||||
self.currentInputMode = .emoji
|
||||
}
|
||||
@ -1029,8 +1029,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
transition.setFrame(view: inputPanelBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: isVisible ? availableSize.height - inputPanelBackgroundSize.height : availableSize.height), size: inputPanelBackgroundSize))
|
||||
transition.setAlpha(view: inputPanelBackgroundView, alpha: isVisible ? 1.0 : 0.0, delay: isVisible ? 0.0 : 0.4)
|
||||
}
|
||||
|
||||
|
||||
|
||||
var isEditingTextEntity = false
|
||||
var sizeSliderVisible = false
|
||||
var sizeValue: CGFloat?
|
||||
@ -1386,7 +1385,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
stateContext: self.inputMediaNodeStateContext
|
||||
)
|
||||
inputMediaNode.externalTopPanelContainerImpl = nil
|
||||
if let inputPanelView = self.inputPanel.view {
|
||||
if let inputPanelView = self.inputPanel.view, inputMediaNode.view.superview == nil {
|
||||
self.insertSubview(inputMediaNode.view, belowSubview: inputPanelView)
|
||||
}
|
||||
self.inputMediaNode = inputMediaNode
|
||||
@ -3834,13 +3833,16 @@ public final class BlurredGradientComponent: Component {
|
||||
}
|
||||
|
||||
let position: Position
|
||||
let dark: Bool
|
||||
let tag: AnyObject?
|
||||
|
||||
public init(
|
||||
position: Position,
|
||||
dark: Bool = false,
|
||||
tag: AnyObject?
|
||||
) {
|
||||
self.position = position
|
||||
self.dark = dark
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
@ -3848,6 +3850,9 @@ public final class BlurredGradientComponent: Component {
|
||||
if lhs.position != rhs.position {
|
||||
return false
|
||||
}
|
||||
if lhs.dark != rhs.dark {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -3883,7 +3888,12 @@ public final class BlurredGradientComponent: Component {
|
||||
direction: .vertical
|
||||
)
|
||||
|
||||
self.gradientForeground.colors = [UIColor(rgb: 0x000000, alpha: 0.35).cgColor, UIColor(rgb: 0x000000, alpha: 0.0).cgColor]
|
||||
if component.dark {
|
||||
self.gradientForeground.colors = [UIColor(rgb: 0x000000, alpha: 0.6).cgColor, UIColor(rgb: 0x000000, alpha: 0.6).cgColor, UIColor(rgb: 0x000000, alpha: 0.0).cgColor]
|
||||
self.gradientForeground.locations = [0.0, 0.8, 1.0]
|
||||
} else {
|
||||
self.gradientForeground.colors = [UIColor(rgb: 0x000000, alpha: 0.35).cgColor, UIColor(rgb: 0x000000, alpha: 0.0).cgColor]
|
||||
}
|
||||
self.gradientForeground.startPoint = CGPoint(x: 0.5, y: component.position == .top ? 0.0 : 1.0)
|
||||
self.gradientForeground.endPoint = CGPoint(x: 0.5, y: component.position == .top ? 1.0 : 0.0)
|
||||
|
||||
|
@ -251,7 +251,7 @@ final class StoryPreviewComponent: Component {
|
||||
style: .story,
|
||||
placeholder: "Reply Privately...",
|
||||
alwaysDarkWhenHasText: false,
|
||||
nextInputMode: nil,
|
||||
nextInputMode: { _ in return nil },
|
||||
areVoiceMessagesAvailable: false,
|
||||
presentController: { _ in
|
||||
},
|
||||
|
@ -20,7 +20,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
}
|
||||
|
||||
public enum InputMode: Hashable {
|
||||
case keyboard
|
||||
case text
|
||||
case stickers
|
||||
case emoji
|
||||
}
|
||||
@ -28,6 +28,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
public final class ExternalState {
|
||||
public fileprivate(set) var isEditing: Bool = false
|
||||
public fileprivate(set) var hasText: Bool = false
|
||||
public fileprivate(set) var isKeyboardHidden: Bool = false
|
||||
|
||||
public fileprivate(set) var insertText: (NSAttributedString) -> Void = { _ in }
|
||||
public fileprivate(set) var deleteBackward: () -> Void = { }
|
||||
@ -43,7 +44,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
public let style: Style
|
||||
public let placeholder: String
|
||||
public let alwaysDarkWhenHasText: Bool
|
||||
public let nextInputMode: InputMode?
|
||||
public let nextInputMode: (Bool) -> InputMode?
|
||||
public let areVoiceMessagesAvailable: Bool
|
||||
public let presentController: (ViewController) -> Void
|
||||
public let sendMessageAction: () -> Void
|
||||
@ -77,7 +78,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
style: Style,
|
||||
placeholder: String,
|
||||
alwaysDarkWhenHasText: Bool,
|
||||
nextInputMode: InputMode?,
|
||||
nextInputMode: @escaping (Bool) -> InputMode?,
|
||||
areVoiceMessagesAvailable: Bool,
|
||||
presentController: @escaping (ViewController) -> Void,
|
||||
sendMessageAction: @escaping () -> Void,
|
||||
@ -153,9 +154,6 @@ public final class MessageInputPanelComponent: Component {
|
||||
if lhs.style != rhs.style {
|
||||
return false
|
||||
}
|
||||
if lhs.nextInputMode != rhs.nextInputMode {
|
||||
return false
|
||||
}
|
||||
if lhs.placeholder != rhs.placeholder {
|
||||
return false
|
||||
}
|
||||
@ -246,6 +244,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
private var contextQueryResultPanel: ComponentView<Empty>?
|
||||
private var contextQueryResultPanelExternalState: ContextResultPanelComponent.ExternalState?
|
||||
|
||||
private var currentInputMode: InputMode?
|
||||
|
||||
private var component: MessageInputPanelComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
@ -370,7 +370,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
|
||||
let baseFieldHeight: CGFloat = 40.0
|
||||
|
||||
let previousComponent = self.component
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
@ -799,10 +799,15 @@ public final class MessageInputPanelComponent: Component {
|
||||
let animationName: String
|
||||
var animationPlay = false
|
||||
|
||||
if let inputMode = component.nextInputMode {
|
||||
let previousInputMode = self.currentInputMode
|
||||
let inputMode = component.nextInputMode(self.textFieldExternalState.hasText)
|
||||
self.currentInputMode = inputMode
|
||||
|
||||
if let inputMode {
|
||||
self.currentInputMode = inputMode
|
||||
switch inputMode {
|
||||
case .keyboard:
|
||||
if let previousInputMode = previousComponent?.nextInputMode {
|
||||
case .text:
|
||||
if let previousInputMode {
|
||||
if case .stickers = previousInputMode {
|
||||
animationName = "input_anim_stickerToKey"
|
||||
animationPlay = true
|
||||
@ -816,8 +821,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
animationName = "input_anim_stickerToKey"
|
||||
}
|
||||
case .stickers:
|
||||
if let previousInputMode = previousComponent?.nextInputMode {
|
||||
if case .keyboard = previousInputMode {
|
||||
if let previousInputMode {
|
||||
if case .text = previousInputMode {
|
||||
animationName = "input_anim_keyToSticker"
|
||||
animationPlay = true
|
||||
} else if case .emoji = previousInputMode {
|
||||
@ -830,8 +835,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
animationName = "input_anim_keyToSticker"
|
||||
}
|
||||
case .emoji:
|
||||
if let previousInputMode = previousComponent?.nextInputMode {
|
||||
if case .keyboard = previousInputMode {
|
||||
if let previousInputMode {
|
||||
if case .text = previousInputMode {
|
||||
animationName = "input_anim_keyToSmile"
|
||||
animationPlay = true
|
||||
} else if case .stickers = previousInputMode {
|
||||
@ -967,6 +972,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
view.deleteBackward()
|
||||
}
|
||||
}
|
||||
component.externalState.isKeyboardHidden = component.hideKeyboard
|
||||
|
||||
if hasMediaRecording {
|
||||
if let dismissingMediaRecordingPanel = self.dismissingMediaRecordingPanel {
|
||||
|
@ -778,6 +778,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
safeInsets: itemSetContainerSafeInsets,
|
||||
inputHeight: environment.inputHeight,
|
||||
metrics: environment.metrics,
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
isProgressPaused: isProgressPaused || i != focusedIndex,
|
||||
hideUI: (i == focusedIndex && (self.itemSetPanState?.didBegin == false || self.itemSetPinchState != nil)),
|
||||
visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction),
|
||||
|
@ -23,6 +23,8 @@ import PlainButtonComponent
|
||||
import TooltipUI
|
||||
import PresentationDataUtils
|
||||
import PeerReportScreen
|
||||
import ChatEntityKeyboardInputNode
|
||||
import TextFieldComponent
|
||||
|
||||
public final class StoryItemSetContainerComponent: Component {
|
||||
public final class ExternalState {
|
||||
@ -60,6 +62,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
public let safeInsets: UIEdgeInsets
|
||||
public let inputHeight: CGFloat
|
||||
public let metrics: LayoutMetrics
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let isProgressPaused: Bool
|
||||
public let hideUI: Bool
|
||||
public let visibilityFraction: CGFloat
|
||||
@ -84,6 +87,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
safeInsets: UIEdgeInsets,
|
||||
inputHeight: CGFloat,
|
||||
metrics: LayoutMetrics,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
isProgressPaused: Bool,
|
||||
hideUI: Bool,
|
||||
visibilityFraction: CGFloat,
|
||||
@ -107,6 +111,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.safeInsets = safeInsets
|
||||
self.inputHeight = inputHeight
|
||||
self.metrics = metrics
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.isProgressPaused = isProgressPaused
|
||||
self.hideUI = hideUI
|
||||
self.visibilityFraction = visibilityFraction
|
||||
@ -146,6 +151,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if lhs.metrics != rhs.metrics {
|
||||
return false
|
||||
}
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
if lhs.isProgressPaused != rhs.isProgressPaused {
|
||||
return false
|
||||
}
|
||||
@ -245,6 +253,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let inputBackground = ComponentView<Empty>()
|
||||
let inputPanel = ComponentView<Empty>()
|
||||
let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
|
||||
private let inputPanelBackground = ComponentView<Empty>()
|
||||
|
||||
var displayViewList: Bool = false
|
||||
var viewList: ViewList?
|
||||
@ -494,6 +503,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout {
|
||||
if hasFirstResponder(self) {
|
||||
self.sendMessageContext.currentInputMode = .text
|
||||
self.endEditing(true)
|
||||
} else if self.displayViewList {
|
||||
self.displayViewList = false
|
||||
@ -567,6 +577,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if self.sendMessageContext.shareController != nil {
|
||||
return true
|
||||
}
|
||||
if let navigationController = component.controller()?.navigationController as? NavigationController {
|
||||
if !(navigationController.topViewController is StoryContainerScreen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if let captionItem = self.captionItem, captionItem.externalState.isExpanded {
|
||||
return true
|
||||
}
|
||||
@ -765,6 +780,8 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
func animateOut(transitionOut: StoryContainerScreen.TransitionOut, transitionCloneMasterView: UIView, completion: @escaping () -> Void) {
|
||||
var cleanups: [() -> Void] = []
|
||||
|
||||
self.sendMessageContext.animateOut(bounds: self.bounds)
|
||||
|
||||
if let inputPanelView = self.inputPanel.view {
|
||||
inputPanelView.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
@ -776,6 +793,17 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
)
|
||||
inputPanelView.layer.animateAlpha(from: inputPanelView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
}
|
||||
if let inputPanelBackground = self.inputPanelBackground.view {
|
||||
inputPanelBackground.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
to: CGPoint(x: 0.0, y: self.bounds.height - inputPanelBackground.frame.minY),
|
||||
duration: 0.3,
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
removeOnCompletion: false,
|
||||
additive: true
|
||||
)
|
||||
inputPanelBackground.layer.animateAlpha(from: inputPanelBackground.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
}
|
||||
if let viewListView = self.viewList?.view.view {
|
||||
viewListView.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
@ -1007,7 +1035,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let isFirstTime = self.component == nil
|
||||
|
||||
if let hint = transition.userData(TextFieldComponent.AnimationHint.self), case .textFocusChanged = hint.kind, !hasFirstResponder(self) {
|
||||
self.sendMessageContext.currentInputMode = .text
|
||||
}
|
||||
|
||||
if self.component == nil {
|
||||
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState)
|
||||
|
||||
let _ = (allowedStoryReactions(context: component.context)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] reactionItems in
|
||||
guard let self, let component = self.component else {
|
||||
@ -1095,14 +1129,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nextInputMode: MessageInputPanelComponent.InputMode
|
||||
if self.inputPanelExternalState.hasText {
|
||||
nextInputMode = .emoji
|
||||
} else {
|
||||
nextInputMode = .stickers
|
||||
}
|
||||
|
||||
|
||||
let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden
|
||||
let inputNodeVisible = self.sendMessageContext.currentInputMode == .media || hasFirstResponder(self)
|
||||
self.inputPanel.parentState = state
|
||||
let inputPanelSize = self.inputPanel.update(
|
||||
transition: inputPanelTransition,
|
||||
@ -1114,7 +1143,13 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
style: .story,
|
||||
placeholder: "Reply Privately...",
|
||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||
nextInputMode: nextInputMode,
|
||||
nextInputMode: { [weak self] hasText in
|
||||
if case .media = self?.sendMessageContext.currentInputMode {
|
||||
return .text
|
||||
} else {
|
||||
return hasText ? .emoji : .stickers
|
||||
}
|
||||
},
|
||||
areVoiceMessagesAvailable: component.slice.additionalPeerData.areVoiceMessagesAvailable,
|
||||
presentController: { [weak self] c in
|
||||
guard let self, let component = self.component else {
|
||||
@ -1164,7 +1199,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
self.sendMessageContext.toggleInputMode()
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)))
|
||||
self.state?.updated(transition: .immediate)
|
||||
},
|
||||
timeoutAction: nil,
|
||||
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
||||
@ -1213,7 +1248,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: true)
|
||||
let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Report", icon: { theme in
|
||||
@ -1292,21 +1327,45 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
wasRecordingDismissed: self.sendMessageContext.wasRecordingDismissed,
|
||||
timeoutValue: nil,
|
||||
timeoutSelected: false,
|
||||
displayGradient: component.inputHeight != 0.0 && component.metrics.widthClass != .regular,
|
||||
bottomInset: component.inputHeight != 0.0 ? 0.0 : bottomContentInset,
|
||||
hideKeyboard: false,
|
||||
displayGradient: false, //(component.inputHeight != 0.0 || inputNodeVisible) && component.metrics.widthClass != .regular,
|
||||
bottomInset: component.inputHeight != 0.0 || inputNodeVisible ? 0.0 : bottomContentInset,
|
||||
hideKeyboard: self.sendMessageContext.currentInputMode == .media,
|
||||
disabledPlaceholder: component.slice.peer.isService ? "You can't reply to this story" : nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)
|
||||
)
|
||||
|
||||
var inputHeight = component.inputHeight
|
||||
if self.inputPanelExternalState.isEditing {
|
||||
if self.sendMessageContext.currentInputMode == .media || (inputHeight.isZero && keyboardWasHidden) {
|
||||
inputHeight = component.deviceMetrics.standardInputHeight(inLandscape: false)
|
||||
}
|
||||
}
|
||||
|
||||
let inputPanelBackgroundSize = self.inputPanelBackground.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BlurredGradientComponent(position: .bottom, dark: true, tag: nil)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: component.deviceMetrics.standardInputHeight(inLandscape: false) + 100.0)
|
||||
)
|
||||
if let inputPanelBackgroundView = self.inputPanelBackground.view {
|
||||
if inputPanelBackgroundView.superview == nil {
|
||||
self.addSubview(inputPanelBackgroundView)
|
||||
}
|
||||
let isVisible = inputHeight > 44.0
|
||||
transition.setFrame(view: inputPanelBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: isVisible ? availableSize.height - inputPanelBackgroundSize.height : availableSize.height), size: inputPanelBackgroundSize))
|
||||
transition.setAlpha(view: inputPanelBackgroundView, alpha: isVisible ? 1.0 : 0.0, delay: isVisible ? 0.0 : 0.4)
|
||||
}
|
||||
|
||||
self.sendMessageContext.updateInputMediaNode(inputPanel: self.inputPanel, availableSize: availableSize, bottomInset: component.safeInsets.bottom, inputHeight: component.inputHeight, effectiveInputHeight: inputHeight, metrics: component.metrics, deviceMetrics: component.deviceMetrics, transition: transition)
|
||||
|
||||
let bottomContentInsetWithoutInput = bottomContentInset
|
||||
var viewListInset: CGFloat = 0.0
|
||||
|
||||
var inputPanelBottomInset: CGFloat
|
||||
let inputPanelIsOverlay: Bool
|
||||
if component.inputHeight == 0.0 {
|
||||
if inputHeight == 0.0 {
|
||||
inputPanelBottomInset = bottomContentInset
|
||||
if case .regular = component.metrics.widthClass {
|
||||
bottomContentInset += 60.0
|
||||
@ -1316,7 +1375,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
inputPanelIsOverlay = false
|
||||
} else {
|
||||
bottomContentInset += 44.0
|
||||
inputPanelBottomInset = component.inputHeight - component.containerInsets.bottom
|
||||
inputPanelBottomInset = inputHeight - component.containerInsets.bottom
|
||||
inputPanelIsOverlay = true
|
||||
}
|
||||
|
||||
@ -1884,6 +1943,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if self.voiceMessagesRestrictedTooltipController != nil {
|
||||
effectiveDisplayReactions = false
|
||||
}
|
||||
// if self.sendMessageContext.currentInputMode != .text {
|
||||
// effectiveDisplayReactions = false
|
||||
// }
|
||||
|
||||
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
||||
effectiveDisplayReactions = true
|
||||
@ -1993,6 +2055,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
|
||||
if hasFirstResponder(self) {
|
||||
self.sendMessageContext.currentInputMode = .text
|
||||
self.endEditing(true)
|
||||
}
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||
@ -2103,7 +2166,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
let bottomGradientHeight = inputPanelSize.height + 32.0
|
||||
transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - component.inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight)))
|
||||
transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight)))
|
||||
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
|
||||
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
|
||||
|
||||
|
@ -34,23 +34,36 @@ import ChatPresentationInterfaceState
|
||||
import Postbox
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import TextFieldComponent
|
||||
|
||||
final class StoryItemSetContainerSendMessage {
|
||||
enum InputMode {
|
||||
case text
|
||||
case emoji
|
||||
case sticker
|
||||
case media
|
||||
}
|
||||
|
||||
private var context: AccountContext?
|
||||
private weak var view: StoryItemSetContainerComponent.View?
|
||||
private var inputPanelExternalState: MessageInputPanelComponent.ExternalState?
|
||||
|
||||
weak var attachmentController: AttachmentController?
|
||||
weak var shareController: ShareController?
|
||||
|
||||
var inputMode: InputMode = .text
|
||||
var currentInputMode: InputMode = .text
|
||||
private var needsInputActivation = false
|
||||
|
||||
var audioRecorderValue: ManagedAudioRecorder?
|
||||
var audioRecorder = Promise<ManagedAudioRecorder?>()
|
||||
var recordedAudioPreview: ChatRecordedMediaPreview?
|
||||
|
||||
var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
||||
var inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||
var inputMediaNodeDataDisposable: Disposable?
|
||||
var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext()
|
||||
var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction?
|
||||
var inputMediaNode: ChatEntityKeyboardInputNode?
|
||||
var inputMediaNodeBackground = SimpleLayer()
|
||||
|
||||
var videoRecorderValue: InstantVideoController?
|
||||
var tempVideoRecorderValue: InstantVideoController?
|
||||
var videoRecorder = Promise<InstantVideoController?>()
|
||||
@ -62,14 +75,308 @@ final class StoryItemSetContainerSendMessage {
|
||||
private(set) var isMediaRecordingLocked: Bool = false
|
||||
var wasRecordingDismissed: Bool = false
|
||||
|
||||
init() {
|
||||
self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.inputMediaNodeData = value
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.controllerNavigationDisposable.dispose()
|
||||
self.enqueueMediaMessageDisposable.dispose()
|
||||
self.navigationActionDisposable.dispose()
|
||||
self.resolvePeerByNameDisposable.dispose()
|
||||
self.inputMediaNodeDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
func setup(context: AccountContext, view: StoryItemSetContainerComponent.View, inputPanelExternalState: MessageInputPanelComponent.ExternalState) {
|
||||
self.context = context
|
||||
self.inputPanelExternalState = inputPanelExternalState
|
||||
self.view = view
|
||||
|
||||
self.inputMediaNodeDataPromise.set(
|
||||
ChatEntityKeyboardInputNode.inputData(
|
||||
context: context,
|
||||
chatPeerId: nil,
|
||||
areCustomEmojiEnabled: true,
|
||||
hasSearch: false,
|
||||
hideBackground: true,
|
||||
sendGif: nil
|
||||
)
|
||||
)
|
||||
|
||||
self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction(
|
||||
sendSticker: { [weak self] fileReference, _, _, _, _, _, _, _, _ in
|
||||
if let view = self?.view {
|
||||
self?.performSendStickerAction(view: view, fileReference: fileReference)
|
||||
}
|
||||
return false
|
||||
},
|
||||
sendEmoji: { [weak self] text, attribute, bool1 in
|
||||
if let self {
|
||||
let _ = self
|
||||
}
|
||||
},
|
||||
sendGif: { [weak self] fileReference, _, _, _, _ in
|
||||
if let view = self?.view {
|
||||
self?.performSendGifAction(view: view, fileReference: fileReference)
|
||||
}
|
||||
return false
|
||||
},
|
||||
sendBotContextResultAsGif: { _, _, _, _, _, _ in
|
||||
return false
|
||||
},
|
||||
updateChoosingSticker: { _ in },
|
||||
switchToTextInput: { [weak self] in
|
||||
if let self {
|
||||
self.currentInputMode = .text
|
||||
self.view?.state?.updated(transition: .immediate)
|
||||
}
|
||||
},
|
||||
dismissTextInput: {
|
||||
|
||||
},
|
||||
insertText: { [weak self] text in
|
||||
if let self {
|
||||
self.inputPanelExternalState?.insertText(text)
|
||||
}
|
||||
},
|
||||
backwardsDeleteText: { [weak self] in
|
||||
if let self {
|
||||
self.inputPanelExternalState?.deleteBackward()
|
||||
}
|
||||
},
|
||||
presentController: { [weak self] c, a in
|
||||
if let self {
|
||||
self.view?.component?.controller()?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
},
|
||||
presentGlobalOverlayController: { [weak self] c, a in
|
||||
if let self {
|
||||
self.view?.component?.controller()?.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
},
|
||||
getNavigationController: {
|
||||
return self.view?.component?.controller()?.navigationController as? NavigationController
|
||||
},
|
||||
requestLayout: { [weak self] _ in
|
||||
if let self {
|
||||
self.view?.state?.updated()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func toggleInputMode() {
|
||||
guard let view = self.view else {
|
||||
return
|
||||
}
|
||||
if case .text = self.currentInputMode {
|
||||
if !hasFirstResponder(view) {
|
||||
self.needsInputActivation = true
|
||||
}
|
||||
self.currentInputMode = .media
|
||||
} else {
|
||||
self.currentInputMode = .text
|
||||
}
|
||||
}
|
||||
|
||||
func updateInputMediaNode(inputPanel: ComponentView<Empty>, availableSize: CGSize, bottomInset: CGFloat, inputHeight: CGFloat, effectiveInputHeight: CGFloat, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: Transition) {
|
||||
guard let context = self.context, let inputPanelView = inputPanel.view as? MessageInputPanelComponent.View else {
|
||||
return
|
||||
}
|
||||
|
||||
if case .media = self.currentInputMode, let inputData = self.inputMediaNodeData {
|
||||
let inputMediaNode: ChatEntityKeyboardInputNode
|
||||
if let current = self.inputMediaNode {
|
||||
inputMediaNode = current
|
||||
} else {
|
||||
inputMediaNode = ChatEntityKeyboardInputNode(
|
||||
context: context,
|
||||
currentInputData: inputData,
|
||||
updatedInputData: self.inputMediaNodeDataPromise.get(),
|
||||
defaultToEmojiTab: self.inputPanelExternalState?.hasText ?? false,
|
||||
opaqueTopPanelBackground: false,
|
||||
interaction: self.inputMediaInteraction,
|
||||
chatPeerId: nil,
|
||||
stateContext: self.inputMediaNodeStateContext
|
||||
)
|
||||
inputMediaNode.externalTopPanelContainerImpl = nil
|
||||
if inputMediaNode.view.superview == nil {
|
||||
self.inputMediaNodeBackground.removeAllAnimations()
|
||||
self.inputMediaNodeBackground.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.7).cgColor
|
||||
// inputPanelView.superview?.layer.insertSublayer(self.inputMediaNodeBackground, below: inputPanelView.layer)
|
||||
inputPanelView.superview?.insertSubview(inputMediaNode.view, belowSubview: inputPanelView)
|
||||
}
|
||||
self.inputMediaNode = inputMediaNode
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
|
||||
let presentationInterfaceState = ChatPresentationInterfaceState(
|
||||
chatWallpaper: .builtin(WallpaperSettings()),
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
limitsConfiguration: context.currentLimitsConfiguration.with { $0 },
|
||||
fontSize: presentationData.chatFontSize,
|
||||
bubbleCorners: presentationData.chatBubbleCorners,
|
||||
accountPeerId: context.account.peerId,
|
||||
mode: .standard(previewing: false),
|
||||
chatLocation: .peer(id: context.account.peerId),
|
||||
subject: nil,
|
||||
peerNearbyData: nil,
|
||||
greetingData: nil,
|
||||
pendingUnpinnedAllMessages: false,
|
||||
activeGroupCallInfo: nil,
|
||||
hasActiveGroupCall: false,
|
||||
importState: nil,
|
||||
threadData: nil,
|
||||
isGeneralThreadClosed: nil
|
||||
)
|
||||
|
||||
let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false)
|
||||
let inputNodeHeight = heightAndOverflow.0
|
||||
var inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
|
||||
if self.needsInputActivation {
|
||||
inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight)
|
||||
}
|
||||
transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
|
||||
transition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeFrame)
|
||||
} else if let inputMediaNode = self.inputMediaNode {
|
||||
self.inputMediaNode = nil
|
||||
|
||||
var targetFrame = inputMediaNode.frame
|
||||
if effectiveInputHeight > 0.0 {
|
||||
targetFrame.origin.y = availableSize.height - effectiveInputHeight
|
||||
} else {
|
||||
targetFrame.origin.y = availableSize.height
|
||||
}
|
||||
transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in
|
||||
if let inputMediaNode {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
inputMediaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in
|
||||
inputMediaNode?.view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
transition.setFrame(layer: self.inputMediaNodeBackground, frame: targetFrame, completion: { _ in
|
||||
Queue.mainQueue().after(0.3) {
|
||||
if self.currentInputMode == .text {
|
||||
self.inputMediaNodeBackground.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { finished in
|
||||
if finished {
|
||||
self.inputMediaNodeBackground.removeFromSuperlayer()
|
||||
}
|
||||
self.inputMediaNodeBackground.removeAllAnimations()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if self.needsInputActivation {
|
||||
self.needsInputActivation = false
|
||||
Queue.mainQueue().justDispatch {
|
||||
inputPanelView.activateInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(bounds: CGRect) {
|
||||
if let inputMediaNode = self.inputMediaNode {
|
||||
inputMediaNode.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
to: CGPoint(x: 0.0, y: bounds.height - inputMediaNode.frame.minY),
|
||||
duration: 0.3,
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
removeOnCompletion: false,
|
||||
additive: true
|
||||
)
|
||||
inputMediaNode.layer.animateAlpha(from: inputMediaNode.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
|
||||
self.inputMediaNodeBackground.animatePosition(
|
||||
from: CGPoint(),
|
||||
to: CGPoint(x: 0.0, y: bounds.height - self.inputMediaNodeBackground.frame.minY),
|
||||
duration: 0.3,
|
||||
timingFunction: kCAMediaTimingFunctionSpring,
|
||||
removeOnCompletion: false,
|
||||
additive: true
|
||||
)
|
||||
self.inputMediaNodeBackground.animateAlpha(from: CGFloat(self.inputMediaNodeBackground.opacity), to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) {
|
||||
guard let component = view.component else {
|
||||
return
|
||||
}
|
||||
let focusedItem = component.slice.item
|
||||
guard let peerId = focusedItem.peerId else {
|
||||
return
|
||||
}
|
||||
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
|
||||
|
||||
component.context.engine.messages.enqueueOutgoingMessage(
|
||||
to: peerId,
|
||||
replyTo: nil,
|
||||
storyId: focusedStoryId,
|
||||
content: .file(fileReference)
|
||||
)
|
||||
|
||||
self.currentInputMode = .text
|
||||
view.endEditing(true)
|
||||
|
||||
Queue.mainQueue().after(0.66) {
|
||||
if let controller = component.controller() {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .succeed(text: "Message Sent"),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performSendGifAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) {
|
||||
guard let component = view.component else {
|
||||
return
|
||||
}
|
||||
let focusedItem = component.slice.item
|
||||
guard let peerId = focusedItem.peerId else {
|
||||
return
|
||||
}
|
||||
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
|
||||
|
||||
component.context.engine.messages.enqueueOutgoingMessage(
|
||||
to: peerId,
|
||||
replyTo: nil,
|
||||
storyId: focusedStoryId,
|
||||
content: .file(fileReference)
|
||||
)
|
||||
|
||||
self.currentInputMode = .text
|
||||
view.endEditing(true)
|
||||
|
||||
Queue.mainQueue().after(0.66) {
|
||||
if let controller = component.controller() {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .succeed(text: "Message Sent"),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performSendMessageAction(
|
||||
@ -105,21 +412,24 @@ final class StoryItemSetContainerSendMessage {
|
||||
component.context.engine.messages.enqueueOutgoingMessage(
|
||||
to: peerId,
|
||||
replyTo: nil,
|
||||
storyId: StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id),
|
||||
storyId: focusedStoryId,
|
||||
content: .text(text.string, entities)
|
||||
)
|
||||
inputPanelView.clearSendMessageInput()
|
||||
self.currentInputMode = .text
|
||||
view.endEditing(true)
|
||||
|
||||
if let controller = component.controller() {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .succeed(text: "Message Sent"),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
Queue.mainQueue().after(0.66) {
|
||||
if let controller = component.controller() {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .succeed(text: "Message Sent"),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -448,6 +758,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
let inputIsActive = !"".isEmpty
|
||||
|
||||
self.currentInputMode = .text
|
||||
view.endEditing(true)
|
||||
|
||||
var banSendText: (Int32, Bool)?
|
||||
|
Loading…
x
Reference in New Issue
Block a user