Merge commit '1750bc5a737db31d458de66d8c6462fb26b338f7'

This commit is contained in:
Ali 2022-11-19 23:06:54 +04:00
commit dc085a2fe9
28 changed files with 885 additions and 160 deletions

View File

@ -8299,3 +8299,6 @@ Sorry for the inconvenience.";
"Notification.ForumTopicUnhiddenAuthor" = "%1$@ unhidden topic";
"Notification.OverviewTopicHidden" = "%1$@ hidden %2$@ %3$@";
"Notification.OverviewTopicUnhidden" = "%1$@ unhidden %2$@ %3$@";
"CreateTopic.ShowGeneral" = "Show in Topics";
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";

View File

@ -793,6 +793,7 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
@ -832,6 +833,22 @@ public enum PremiumIntroSource {
case fasterDownload
}
public enum PremiumDemoSubject {
case doubleLimits
case moreUpload
case fasterDownload
case voiceToText
case noAds
case uniqueReactions
case premiumStickers
case advancedChatManagement
case profileBadge
case animatedUserpics
case appIcons
case animatedEmoji
case emojiStatus
}
public protocol ComposeController: ViewController {
}

View File

@ -705,6 +705,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)

View File

@ -1185,7 +1185,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
controller.navigationPresentation = .modal
controller.completion = { [weak controller] title, fileId in
controller.completion = { [weak controller] title, fileId, _ in
controller?.isInProgress = true
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
@ -2431,7 +2431,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
controller.navigationPresentation = .modal
controller.completion = { [weak controller] title, fileId in
controller.completion = { [weak controller] title, fileId, _ in
controller?.isInProgress = true
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)

View File

@ -148,6 +148,7 @@ public final class ChatPanelInterfaceInteraction {
public let insertText: (NSAttributedString) -> Void
public let backwardsDeleteText: () -> Void
public let restartTopic: () -> Void
public let requestLayout: (ContainedViewLayoutTransition) -> Void
public let chatController: () -> ViewController?
public let statuses: ChatPanelInterfaceInteractionStatuses?
@ -245,6 +246,7 @@ public final class ChatPanelInterfaceInteraction {
insertText: @escaping (NSAttributedString) -> Void,
backwardsDeleteText: @escaping () -> Void,
restartTopic: @escaping () -> Void,
requestLayout: @escaping (ContainedViewLayoutTransition) -> Void,
chatController: @escaping () -> ViewController?,
statuses: ChatPanelInterfaceInteractionStatuses?
) {
@ -341,6 +343,7 @@ public final class ChatPanelInterfaceInteraction {
self.insertText = insertText
self.backwardsDeleteText = backwardsDeleteText
self.restartTopic = restartTopic
self.requestLayout = requestLayout
self.chatController = chatController
self.statuses = statuses
@ -445,6 +448,7 @@ public final class ChatPanelInterfaceInteraction {
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)

View File

@ -941,7 +941,11 @@ private final class LimitSheetContent: CombinedComponent {
contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom)
} else {
contentSize = CGSize(width: context.availableSize.width, height: 351.0 + environment.safeInsets.bottom)
var height: CGFloat = 351.0
if isPremiumDisabled {
height -= 78.0
}
contentSize = CGSize(width: context.availableSize.width, height: height + environment.safeInsets.bottom)
}
return contentSize

View File

@ -1262,6 +1262,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { [weak self] groupId, isPremiumLocked in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return

View File

@ -439,7 +439,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
}
var existingIds = Set<String>()
var showTranslate = true
var showTranslate = false
var ignoredLanguages: [String] = []
if let translationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
showTranslate = translationSettings.showTranslate

View File

@ -456,7 +456,8 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
dismissInputImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_ActivateAlertTitle, text: presentationData.strings.Username_ActivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_ActivateAlertShow, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true).start(error: { error in
let _ = (context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true)
|> deliverOnMainQueue).start(error: { error in
let errorText: String
switch error {
case .activeLimitReached:

View File

@ -341,6 +341,12 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
var flags: Int32 = 0
flags |= (1 << 2)
var hidden: Api.Bool? = nil
if threadId == 1, !isClosed {
flags |= (1 << 3)
hidden = .boolFalse
}
return account.network.request(Api.functions.channels.editForumTopic(
flags: flags,
channel: inputChannel,
@ -348,7 +354,7 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
title: nil,
iconEmojiId: nil,
closed: isClosed ? .boolTrue : .boolFalse,
hidden: nil
hidden: hidden
))
|> mapError { _ -> EditForumChannelTopicError in
return .generic
@ -361,6 +367,9 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
var data = initialData
data.isClosed = isClosed
if let _ = hidden {
data.isHidden = false
}
if data != initialData {
if let entry = StoredMessageHistoryThreadInfo(data) {

View File

@ -350,7 +350,7 @@ public final class EmojiStatusSelectionController: ViewController {
return
}
strongSelf.controller?._ready.set(.single(true))
var emojiContent = emojiContent
if let emojiSearchResult = emojiSearchResult {
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
@ -387,6 +387,8 @@ public final class EmojiStatusSelectionController: ViewController {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { groupId, isPremiumLocked in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return

View File

@ -1517,6 +1517,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
var useOpaqueTheme: Bool
var isActive: Bool
var size: CGSize
var canFocus: Bool
static func ==(lhs: Params, rhs: Params) -> Bool {
if lhs.theme !== rhs.theme {
@ -1537,6 +1538,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
if lhs.size != rhs.size {
return false
}
if lhs.canFocus != rhs.canFocus {
return false
}
return true
}
}
@ -1673,7 +1677,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if self.textField == nil, let textComponentView = self.textView.view {
if self.textField == nil, let textComponentView = self.textView.view, self.params?.canFocus == true {
let backgroundFrame = self.backgroundLayer.frame
let textFieldFrame = CGRect(origin: CGPoint(x: textComponentView.frame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textComponentView.frame.minX, height: backgroundFrame.height))
@ -1697,13 +1701,15 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
self.clearIconTintView.isHidden = true
self.clearIconButton.isHidden = true
self.deactivated()
if let textField = self.textField {
self.textField = nil
textField.resignFirstResponder()
textField.removeFromSuperview()
}
self.deactivated()
}
@objc private func clearPressed() {
@ -1746,17 +1752,18 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
return
}
self.params = nil
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, transition: transition)
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, transition: transition)
}
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, transition: Transition) {
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, transition: Transition) {
let params = Params(
theme: theme,
strings: strings,
text: text,
useOpaqueTheme: useOpaqueTheme,
isActive: isActive,
size: size
size: size,
canFocus: canFocus
)
if self.params == params {
@ -1931,7 +1938,7 @@ private final class EmptySearchResultsView: UIView {
fatalError("init(coder:) has not been implemented")
}
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, transition: Transition) {
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) {
let titleColor: UIColor
if useOpaqueTheme {
titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
@ -1973,7 +1980,7 @@ private final class EmptySearchResultsView: UIView {
let spacing: CGFloat = 4.0
let contentHeight = iconSize.height + spacing + titleSize.height
let contentOriginY = floor((size.height - contentHeight) / 2.0)
let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize)
@ -2069,6 +2076,7 @@ public final class EmojiPagerContentComponent: Component {
public let deleteBackwards: () -> Void
public let openStickerSettings: () -> Void
public let openFeatured: () -> Void
public let openSearch: () -> Void
public let addGroupAction: (AnyHashable, Bool) -> Void
public let clearGroup: (AnyHashable) -> Void
public let pushController: (ViewController) -> Void
@ -2089,6 +2097,7 @@ public final class EmojiPagerContentComponent: Component {
deleteBackwards: @escaping () -> Void,
openStickerSettings: @escaping () -> Void,
openFeatured: @escaping () -> Void,
openSearch: @escaping () -> Void,
addGroupAction: @escaping (AnyHashable, Bool) -> Void,
clearGroup: @escaping (AnyHashable) -> Void,
pushController: @escaping (ViewController) -> Void,
@ -2108,6 +2117,7 @@ public final class EmojiPagerContentComponent: Component {
self.deleteBackwards = deleteBackwards
self.openStickerSettings = openStickerSettings
self.openFeatured = openFeatured
self.openSearch = openSearch
self.addGroupAction = addGroupAction
self.clearGroup = clearGroup
self.pushController = pushController
@ -2349,6 +2359,8 @@ public final class EmojiPagerContentComponent: Component {
public let itemContentUniqueId: AnyHashable?
public let warpContentsOnEdges: Bool
public let displaySearchWithPlaceholder: String?
public let searchInitiallyHidden: Bool
public let searchIsPlaceholderOnly: Bool
public let emptySearchResults: EmptySearchResults?
public let enableLongPress: Bool
public let selectedItems: Set<MediaId>
@ -2365,6 +2377,8 @@ public final class EmojiPagerContentComponent: Component {
itemContentUniqueId: AnyHashable?,
warpContentsOnEdges: Bool,
displaySearchWithPlaceholder: String?,
searchInitiallyHidden: Bool,
searchIsPlaceholderOnly: Bool,
emptySearchResults: EmptySearchResults?,
enableLongPress: Bool,
selectedItems: Set<MediaId>
@ -2380,6 +2394,8 @@ public final class EmojiPagerContentComponent: Component {
self.itemContentUniqueId = itemContentUniqueId
self.warpContentsOnEdges = warpContentsOnEdges
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
self.searchInitiallyHidden = searchInitiallyHidden
self.searchIsPlaceholderOnly = searchIsPlaceholderOnly
self.emptySearchResults = emptySearchResults
self.enableLongPress = enableLongPress
self.selectedItems = selectedItems
@ -2398,6 +2414,8 @@ public final class EmojiPagerContentComponent: Component {
itemContentUniqueId: itemContentUniqueId,
warpContentsOnEdges: self.warpContentsOnEdges,
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
searchInitiallyHidden: self.searchInitiallyHidden,
searchIsPlaceholderOnly: self.searchIsPlaceholderOnly,
emptySearchResults: emptySearchResults,
enableLongPress: self.enableLongPress,
selectedItems: self.selectedItems
@ -2438,6 +2456,12 @@ public final class EmojiPagerContentComponent: Component {
if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder {
return false
}
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
return false
}
if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly {
return false
}
if lhs.emptySearchResults != rhs.emptySearchResults {
return false
}
@ -3233,6 +3257,8 @@ public final class EmojiPagerContentComponent: Component {
private let mirrorContentScrollView: UIView
private var warpView: WarpView?
private var mirrorContentWarpView: WarpView?
private let scrollViewClippingView: UIView
private let scrollView: ContentScrollView
private var scrollGradientLayer: SimpleGradientLayer?
private let boundsChangeTrackerLayer = SimpleLayer()
@ -3276,6 +3302,9 @@ public final class EmojiPagerContentComponent: Component {
self.standaloneShimmerEffect = nil
}
self.scrollViewClippingView = UIView()
self.scrollViewClippingView.clipsToBounds = true
self.mirrorContentScrollView = UIView()
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
self.mirrorContentScrollView.clipsToBounds = true
@ -3311,7 +3340,8 @@ public final class EmojiPagerContentComponent: Component {
self.scrollView.delegate = self
self.scrollView.clipsToBounds = false
self.scrollView.scrollsToTop = false
self.addSubview(self.scrollView)
self.addSubview(self.scrollViewClippingView)
self.scrollViewClippingView.addSubview(self.scrollView)
self.scrollView.addSubview(self.placeholdersContainerView)
@ -4750,6 +4780,10 @@ public final class EmojiPagerContentComponent: Component {
self.updateVisibleItems(transition: .immediate, attemptSynchronousLoads: false, previousItemPositions: nil, updatedItemPositions: nil)
self.updateScrollingOffset(isReset: false, transition: .immediate)
if self.isSearchActivated {
self.visibleSearchHeader?.endEditing(true)
}
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
@ -5899,9 +5933,15 @@ public final class EmojiPagerContentComponent: Component {
self.ignoreScrolling = true
let scrollOriginY: CGFloat = 0.0
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
transition.setFrame(view: self.scrollViewClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
transition.setBounds(view: self.scrollViewClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? itemLayout.searchHeight : 0.0), size: availableSize))
let previousSize = self.scrollView.bounds.size
var resetScrolling = false
if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil {
@ -5944,8 +5984,13 @@ public final class EmojiPagerContentComponent: Component {
})
}
if self.scrollView.contentSize != itemLayout.contentSize {
self.scrollView.contentSize = itemLayout.contentSize
var effectiveContentSize = itemLayout.contentSize
if self.isSearchActivated {
effectiveContentSize.height = max(itemLayout.contentSize.height, availableSize.height + 1.0)
}
if self.scrollView.contentSize != effectiveContentSize {
self.scrollView.contentSize = effectiveContentSize
}
var scrollIndicatorInsets = pagerEnvironment.containerInsets
if self.warpView != nil {
@ -6003,7 +6048,7 @@ public final class EmojiPagerContentComponent: Component {
}
if resetScrolling {
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated {
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated && component.searchInitiallyHidden {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: scrollSize)
} else {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
@ -6063,7 +6108,16 @@ public final class EmojiPagerContentComponent: Component {
if self.isSearchActivated {
if visibleSearchHeader.superview != self {
self.addSubview(visibleSearchHeader)
self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView)
if self.mirrorContentClippingView != nil {
self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView)
} else {
self.mirrorContentScrollView.superview?.addSubview(visibleSearchHeader.tintContainerView)
}
}
} else {
if visibleSearchHeader.superview != self.scrollView {
self.scrollView.addSubview(visibleSearchHeader)
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
}
} else {
@ -6071,9 +6125,14 @@ public final class EmojiPagerContentComponent: Component {
guard let strongSelf = self else {
return
}
strongSelf.isSearchActivated = true
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
if let component = strongSelf.component, component.searchIsPlaceholderOnly {
component.inputInteractionHolder.inputInteraction?.openSearch()
} else {
strongSelf.isSearchActivated = true
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(true)
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
}
}, deactivated: { [weak self] in
guard let strongSelf = self else {
return
@ -6083,7 +6142,11 @@ public final class EmojiPagerContentComponent: Component {
strongSelf.isSearchActivated = false
strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false)
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
if strongSelf.component?.searchInitiallyHidden == false {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.5))
} else {
strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate)
}
}, updateQuery: { [weak self] query, languageCode in
guard let strongSelf = self else {
return
@ -6101,7 +6164,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(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, transition: transition)
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
@ -6120,6 +6183,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
if let emptySearchResults = component.emptySearchResults {
let visibleEmptySearchResultsView: EmptySearchResultsView
var emptySearchResultsTransition = transition
@ -6140,6 +6204,7 @@ public final class EmojiPagerContentComponent: Component {
text: emptySearchResults.text,
file: emptySearchResults.iconFile,
size: emptySearchResultsSize,
searchInitiallyHidden: component.searchInitiallyHidden,
transition: emptySearchResultsTransition
)
emptySearchResultsTransition.setFrame(view: visibleEmptySearchResultsView, frame: CGRect(origin: CGPoint(x: 0.0, y: itemLayout.searchInsets.top + itemLayout.searchHeight), size: emptySearchResultsSize))
@ -6899,12 +6964,16 @@ public final class EmojiPagerContentComponent: Component {
}
var displaySearchWithPlaceholder: String?
var searchInitiallyHidden = true
if isReactionSelection {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder
} else if isStatusSelection {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder
} else if isTopicIconSelection {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchTopicIconsPlaceholder
} else {
displaySearchWithPlaceholder = "Search Emoji"
searchInitiallyHidden = false
}
return EmojiPagerContentComponent(
@ -6952,6 +7021,8 @@ public final class EmojiPagerContentComponent: Component {
itemContentUniqueId: nil,
warpContentsOnEdges: isReactionSelection || isStatusSelection,
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
searchInitiallyHidden: searchInitiallyHidden,
searchIsPlaceholderOnly: false,
emptySearchResults: nil,
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
selectedItems: selectedItems

View File

@ -337,16 +337,16 @@ public final class EntityKeyboardComponent: Component {
}
))))
contentIcons.append(PagerComponentContentIcon(id: "gifs", imageName: "Chat/Input/Media/EntityInputGifsIcon"))
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputSearchIcon",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
maxSize: nil
)),
action: { [weak self] in
self?.openSearch()
}
).minSize(CGSize(width: 38.0, height: 38.0)))))
// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button(
// content: AnyComponent(BundleIconComponent(
// name: "Chat/Input/Media/EntityInputSearchIcon",
// tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
// maxSize: nil
// )),
// action: { [weak self] in
// self?.openSearch()
// }
// ).minSize(CGSize(width: 38.0, height: 38.0)))))
}
if let stickerContent = component.stickerContent {
@ -451,16 +451,16 @@ public final class EntityKeyboardComponent: Component {
}
))))
contentIcons.append(PagerComponentContentIcon(id: "stickers", imageName: "Chat/Input/Media/EntityInputStickersIcon"))
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputSearchIcon",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
maxSize: nil
)),
action: { [weak self] in
self?.openSearch()
}
).minSize(CGSize(width: 38.0, height: 38.0)))))
// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
// content: AnyComponent(BundleIconComponent(
// name: "Chat/Input/Media/EntityInputSearchIcon",
// tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
// maxSize: nil
// )),
// action: { [weak self] in
// self?.openSearch()
// }
// ).minSize(CGSize(width: 38.0, height: 38.0)))))
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent(
name: "Chat/Input/Media/EntityInputSettingsIcon",
@ -749,7 +749,7 @@ public final class EntityKeyboardComponent: Component {
component.hideTopPanelUpdated(self.isTopPanelHidden, transition)
}
private func openSearch() {
public func openSearch() {
guard let component = self.component else {
return
}

View File

@ -136,15 +136,18 @@ public final class GifPagerContentComponent: Component {
public let performItemAction: (Item, UIView, CGRect) -> Void
public let openGifContextMenu: (Item, UIView, CGRect, ContextGesture, Bool) -> Void
public let loadMore: (String) -> Void
public let openSearch: () -> Void
public init(
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
openGifContextMenu: @escaping (Item, UIView, CGRect, ContextGesture, Bool) -> Void,
loadMore: @escaping (String) -> Void
loadMore: @escaping (String) -> Void,
openSearch: @escaping () -> Void
) {
self.performItemAction = performItemAction
self.openGifContextMenu = openGifContextMenu
self.loadMore = loadMore
self.openSearch = openSearch
}
}
@ -178,6 +181,8 @@ public final class GifPagerContentComponent: Component {
public let items: [Item]
public let isLoading: Bool
public let loadMoreToken: String?
public let displaySearchWithPlaceholder: String?
public let searchInitiallyHidden: Bool
public init(
context: AccountContext,
@ -185,7 +190,9 @@ public final class GifPagerContentComponent: Component {
subject: Subject,
items: [Item],
isLoading: Bool,
loadMoreToken: String?
loadMoreToken: String?,
displaySearchWithPlaceholder: String?,
searchInitiallyHidden: Bool
) {
self.context = context
self.inputInteraction = inputInteraction
@ -193,6 +200,8 @@ public final class GifPagerContentComponent: Component {
self.items = items
self.isLoading = isLoading
self.loadMoreToken = loadMoreToken
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
self.searchInitiallyHidden = searchInitiallyHidden
}
public static func ==(lhs: GifPagerContentComponent, rhs: GifPagerContentComponent) -> Bool {
@ -214,7 +223,12 @@ public final class GifPagerContentComponent: Component {
if lhs.loadMoreToken != rhs.loadMoreToken {
return false
}
if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder {
return false
}
if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden {
return false
}
return true
}
@ -240,6 +254,9 @@ public final class GifPagerContentComponent: Component {
let itemsPerRow: Int
let contentSize: CGSize
var searchInsets: UIEdgeInsets
var searchHeight: CGFloat
init(width: CGFloat, containerInsets: UIEdgeInsets, itemCount: Int) {
self.width = width
self.containerInsets = containerInsets
@ -247,6 +264,9 @@ public final class GifPagerContentComponent: Component {
self.horizontalSpacing = 1.0
self.verticalSpacing = 1.0
self.searchHeight = 54.0
self.searchInsets = UIEdgeInsets(top: max(0.0, containerInsets.top + 1.0), left: containerInsets.left, bottom: 0.0, right: containerInsets.right)
let defaultItemSize: CGFloat = 120.0
let itemHorizontalSpace = width - self.containerInsets.left - self.containerInsets.right
@ -408,17 +428,103 @@ public final class GifPagerContentComponent: Component {
}
}
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
public final class ContentScrollLayer: CALayer {
public var mirrorLayer: CALayer?
override public init() {
super.init()
}
override public init(layer: Any) {
super.init(layer: layer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public var position: CGPoint {
get {
return super.position
} set(value) {
if let mirrorLayer = self.mirrorLayer {
mirrorLayer.position = value
}
super.position = value
}
}
override public var bounds: CGRect {
get {
return super.bounds
} set(value) {
if let mirrorLayer = self.mirrorLayer {
mirrorLayer.bounds = value
}
super.bounds = value
}
}
override public func add(_ animation: CAAnimation, forKey key: String?) {
if let mirrorLayer = self.mirrorLayer {
mirrorLayer.add(animation, forKey: key)
}
super.add(animation, forKey: key)
}
override public func removeAllAnimations() {
if let mirrorLayer = self.mirrorLayer {
mirrorLayer.removeAllAnimations()
}
super.removeAllAnimations()
}
override public func removeAnimation(forKey: String) {
if let mirrorLayer = self.mirrorLayer {
mirrorLayer.removeAnimation(forKey: forKey)
}
super.removeAnimation(forKey: forKey)
}
}
private let backgroundView: BlurredBackgroundView
private final class ContentScrollView: UIScrollView, PagerExpandableScrollView {
override static var layerClass: AnyClass {
return ContentScrollLayer.self
}
private let mirrorView: UIView
init(mirrorView: UIView) {
self.mirrorView = mirrorView
super.init(frame: CGRect())
(self.layer as? ContentScrollLayer)?.mirrorLayer = mirrorView.layer
self.canCancelContentTouches = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
private let shimmerHostView: PortalSourceView
private let standaloneShimmerEffect: StandaloneShimmerEffect
private let backgroundView: BlurredBackgroundView
private var vibrancyEffectView: UIVisualEffectView?
private let mirrorContentScrollView: UIView
private let scrollView: ContentScrollView
private let placeholdersContainerView: UIView
private var visibleSearchHeader: EmojiSearchHeaderView?
private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:]
private var visibleItemLayers: [ItemKey: ItemLayer] = [:]
private var ignoreScrolling: Bool = false
@ -438,7 +544,11 @@ public final class GifPagerContentComponent: Component {
self.placeholdersContainerView = UIView()
self.scrollView = ContentScrollView()
self.mirrorContentScrollView = UIView()
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
self.mirrorContentScrollView.clipsToBounds = true
self.scrollView = ContentScrollView(mirrorView: self.mirrorContentScrollView)
self.scrollView.layer.anchorPoint = CGPoint()
super.init(frame: frame)
@ -636,6 +746,11 @@ public final class GifPagerContentComponent: Component {
var validIds = Set<ItemKey>()
var searchInset: CGFloat = 0.0
if let _ = component.displaySearchWithPlaceholder {
searchInset += itemLayout.searchHeight
}
if let itemRange = itemLayout.visibleItems(for: self.scrollView.bounds) {
for index in itemRange.lowerBound ..< itemRange.upperBound {
var item: Item?
@ -657,7 +772,7 @@ public final class GifPagerContentComponent: Component {
validIds.insert(itemId)
let itemFrame = itemLayout.frame(at: index)
let itemFrame = itemLayout.frame(at: index).offsetBy(dx: 0.0, dy: searchInset)
let itemTransition: Transition = .immediate
var updateItemLayerPlaceholder = false
@ -768,9 +883,23 @@ public final class GifPagerContentComponent: Component {
guard let theme = self.theme else {
return
}
if self.vibrancyEffectView == nil {
let style: UIBlurEffect.Style
style = .extraLight
let blurEffect = UIBlurEffect(style: style)
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
self.vibrancyEffectView = vibrancyEffectView
self.backgroundView.addSubview(vibrancyEffectView)
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
}
self.backgroundView.updateColor(color: theme.chat.inputMediaPanel.backgroundColor, enableBlur: true, forceKeepBlur: false, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition)
if let vibrancyEffectView = self.vibrancyEffectView {
transition.setFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 0.0, y: -backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height + backgroundFrame.minY)))
}
}
func update(component: GifPagerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
@ -802,7 +931,12 @@ public final class GifPagerContentComponent: Component {
self.itemLayout = itemLayout
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
let scrollOriginY: CGFloat = 0.0
let scrollSize = CGSize(width: availableSize.width, height: availableSize.height)
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize)
if self.scrollView.contentSize != itemLayout.contentSize {
self.scrollView.contentSize = itemLayout.contentSize
}
@ -817,6 +951,44 @@ public final class GifPagerContentComponent: Component {
self.previousScrollingOffset = self.scrollView.contentOffset.y
self.ignoreScrolling = false
if let displaySearchWithPlaceholder = component.displaySearchWithPlaceholder {
let visibleSearchHeader: EmojiSearchHeaderView
if let current = self.visibleSearchHeader {
visibleSearchHeader = current
} else {
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.component?.inputInteraction.openSearch()
}, deactivated: {
}, updateQuery: {_, _ in
})
self.visibleSearchHeader = visibleSearchHeader
self.scrollView.addSubview(visibleSearchHeader)
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
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(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: false, isActive: false, size: searchHeaderFrame.size, canFocus: false, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
if visibleSearchHeader.superview != strongSelf.scrollView {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
})
} else {
if let visibleSearchHeader = self.visibleSearchHeader {
self.visibleSearchHeader = nil
visibleSearchHeader.removeFromSuperview()
visibleSearchHeader.tintContainerView.removeFromSuperview()
}
}
self.updateVisibleItems(attemptSynchronousLoads: true)
return availableSize

View File

@ -17,6 +17,72 @@ import Postbox
import PremiumUI
import ProgressNavigationButtonNode
private final class SwitchComponent: Component {
typealias EnvironmentType = Empty
let value: Bool
let valueUpdated: (Bool) -> Void
init(
value: Bool,
valueUpdated: @escaping (Bool) -> Void
) {
self.value = value
self.valueUpdated = valueUpdated
}
static func ==(lhs: SwitchComponent, rhs: SwitchComponent) -> Bool {
if lhs.value != rhs.value {
return false
}
return true
}
final class View: UIView {
private let switchView: UISwitch
private var component: SwitchComponent?
override init(frame: CGRect) {
self.switchView = UISwitch()
super.init(frame: frame)
self.addSubview(self.switchView)
self.switchView.addTarget(self, action: #selector(self.valueChanged(_:)), for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func valueChanged(_ sender: Any) {
self.component?.valueUpdated(self.switchView.isOn)
}
func update(component: SwitchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.component = component
self.switchView.setOn(component.value, animated: !transition.animation.isImmediate)
self.switchView.sizeToFit()
self.switchView.frame = CGRect(origin: .zero, size: self.switchView.frame.size)
return self.switchView.frame.size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class TitleFieldComponent: Component {
typealias EnvironmentType = Empty
@ -384,14 +450,16 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
let mode: ForumCreateTopicScreen.Mode
let titleUpdated: (String) -> Void
let iconUpdated: (Int64?) -> Void
let isHiddenUpdated: (Bool) -> Void
let openPremium: () -> Void
init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, openPremium: @escaping () -> Void) {
init(context: AccountContext, peerId: EnginePeer.Id, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, isHiddenUpdated: @escaping (Bool) -> Void, openPremium: @escaping () -> Void) {
self.context = context
self.peerId = peerId
self.mode = mode
self.titleUpdated = titleUpdated
self.iconUpdated = iconUpdated
self.isHiddenUpdated = isHiddenUpdated
self.openPremium = openPremium
}
@ -412,6 +480,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
private let context: AccountContext
private let titleUpdated: (String) -> Void
private let iconUpdated: (Int64?) -> Void
private let isHiddenUpdated: (Bool) -> Void
private let openPremium: () -> Void
var emojiContent: EmojiPagerContentComponent?
@ -426,13 +495,15 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
var title: String
var fileId: Int64
var iconColor: Int32
var isHidden: Bool
private var hasPremium: Bool = false
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, openPremium: @escaping () -> Void) {
init(context: AccountContext, mode: ForumCreateTopicScreen.Mode, titleUpdated: @escaping (String) -> Void, iconUpdated: @escaping (Int64?) -> Void, isHiddenUpdated: @escaping (Bool) -> Void, openPremium: @escaping () -> Void) {
self.context = context
self.titleUpdated = titleUpdated
self.iconUpdated = iconUpdated
self.isHiddenUpdated = isHiddenUpdated
self.openPremium = openPremium
switch mode {
@ -440,13 +511,14 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
self.isGeneral = false
self.title = ""
self.fileId = 0
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
case let .edit(threadId, info):
self.isHidden = false
case let .edit(threadId, info, isHidden):
self.isGeneral = threadId == 1
self.title = info.title
self.fileId = info.icon ?? 0
self.iconColor = info.iconColor
self.isHidden = isHidden
}
super.init()
@ -511,6 +583,12 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
self.updateEmojiContent()
}
func updateIsHidden(_ isHidden: Bool) {
self.isHidden = isHidden
self.updated(transition: .immediate)
self.isHiddenUpdated(isHidden)
}
func updateEmojiContent() {
self.emojiContentDisposable.set((
EmojiPagerContentComponent.emojiInputData(
@ -575,6 +653,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
mode: self.mode,
titleUpdated: self.titleUpdated,
iconUpdated: self.iconUpdated,
isHiddenUpdated: self.isHiddenUpdated,
openPremium: self.openPremium
)
}
@ -585,6 +664,11 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
let titleBackground = Child(RoundedRectangle.self)
let titleField = Child(TitleFieldComponent.self)
let hideBackground = Child(RoundedRectangle.self)
let hideTitle = Child(MultilineTextComponent.self)
let hideSwitch = Child(SwitchComponent.self)
let hideInfo = Child(MultilineTextComponent.self)
let iconHeader = Child(MultilineTextComponent.self)
let iconBackground = Child(RoundedRectangle.self)
let iconSelector = Child(TopicIconSelectionComponent.self)
@ -624,7 +708,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
horizontalAlignment: .natural,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(titleHeader
@ -637,7 +721,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 44.0),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
transition: context.transition
)
context.add(titleBackground
@ -662,7 +746,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
state?.switchIcon()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 44.0),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
transition: context.transition
)
context.add(titleField
@ -671,8 +755,79 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
contentHeight += titleBackground.size.height + sectionSpacing
if case let .edit(threadId, _) = context.component.mode, threadId == 1 {
if case let .edit(threadId, _, _) = context.component.mode, threadId == 1 {
let hideBackground = hideBackground.update(
component: RoundedRectangle(
color: environment.theme.list.itemBlocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 44.0),
transition: context.transition
)
context.add(hideBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
)
let hideTitle = hideTitle.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreateTopic_ShowGeneral,
font: Font.regular(17.0),
textColor: environment.theme.list.itemPrimaryTextColor,
paragraphAlignment: .natural)
),
horizontalAlignment: .natural,
maximumNumberOfLines: 0
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(hideTitle
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + hideTitle.size.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
)
let hideSwitch = hideSwitch.update(
component: SwitchComponent(
value: !state.isHidden,
valueUpdated: { [weak state] newValue in
state?.updateIsHidden(!newValue)
}
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(hideSwitch
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - sideInset - 16.0 - hideSwitch.size.width / 2.0, y: contentHeight + hideBackground.size.height / 2.0))
)
contentHeight += hideBackground.size.height
let hideInfo = hideInfo.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.CreateTopic_ShowGeneralInfo,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor,
paragraphAlignment: .natural)
),
horizontalAlignment: .natural,
maximumNumberOfLines: 0
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(hideInfo
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + hideInfo.size.width / 2.0, y: contentHeight + 7.0 + hideInfo.size.height / 2.0))
)
} else {
let iconHeader = iconHeader.update(
component: MultilineTextComponent(
@ -686,13 +841,13 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
maximumNumberOfLines: 1
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
height: CGFloat.greatestFiniteMagnitude
),
transition: .immediate
)
context.add(iconHeader
.position(CGPoint(x: sideInset * 2.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
.position(CGPoint(x: environment.safeInsets.left + sideInset + 16.0 + iconHeader.size.width / 2.0, y: contentHeight + iconHeader.size.height / 2.0))
)
contentHeight += iconHeader.size.height + headerSpacing
@ -704,7 +859,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
cornerRadius: 10.0
),
availableSize: CGSize(
width: context.availableSize.width - sideInset * 2.0,
width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right,
height: context.availableSize.height - contentHeight - bottomInset
),
transition: context.transition
@ -726,7 +881,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
separatorColor: environment.theme.list.blocksBackgroundColor
),
environment: {},
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: availableHeight),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: availableHeight),
transition: context.transition
)
context.add(iconSelector
@ -746,6 +901,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { groupId, isPremiumLocked in
guard let collectionId = groupId.base as? ItemCollectionId else {
return
@ -805,7 +962,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
case create
case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info)
case edit(threadId: Int64, threadInfo: EngineMessageHistoryThread.Info, isHidden: Bool)
}
private let context: AccountContext
@ -813,8 +970,8 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
private var doneBarItem: UIBarButtonItem?
private var state: (String, Int64?) = ("", nil)
public var completion: (String, Int64?) -> Void = { _, _ in }
private var state: (String, Int64?, Bool?) = ("", nil, nil)
public var completion: (String, Int64?, Bool?) -> Void = { _, _, _ in }
public var isInProgress: Bool = false {
didSet {
@ -835,12 +992,15 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
var titleUpdatedImpl: ((String) -> Void)?
var iconUpdatedImpl: ((Int64?) -> Void)?
var isHiddenUpdatedImpl: ((Bool) -> Void)?
var openPremiumImpl: (() -> Void)?
super.init(context: context, component: ForumCreateTopicScreenComponent(context: context, peerId: peerId, mode: mode, titleUpdated: { title in
titleUpdatedImpl?(title)
}, iconUpdated: { fileId in
iconUpdatedImpl?(fileId)
}, isHiddenUpdated: { isHidden in
isHiddenUpdatedImpl?(isHidden)
}, openPremium: {
openPremiumImpl?()
}), navigationBarAppearance: .transparent)
@ -852,11 +1012,11 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
case .create:
title = presentationData.strings.CreateTopic_CreateTitle
doneTitle = presentationData.strings.CreateTopic_Create
case let .edit(_, topic):
case let .edit(threadId, topic, isHidden):
title = presentationData.strings.CreateTopic_EditTitle
doneTitle = presentationData.strings.Common_Done
self.state = (topic.title, topic.icon)
self.state = (topic.title, topic.icon, threadId == 1 ? isHidden : nil)
}
self.title = title
@ -872,12 +1032,12 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
}
titleUpdatedImpl = { [weak self] title in
guard let self else {
guard let strongSelf = self else {
return
}
self.doneBarItem?.isEnabled = !title.isEmpty
strongSelf.doneBarItem?.isEnabled = !title.isEmpty
self.state = (title, self.state.1)
strongSelf.state = (title, strongSelf.state.1, strongSelf.state.2)
}
iconUpdatedImpl = { [weak self] fileId in
@ -885,7 +1045,15 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
return
}
strongSelf.state = (strongSelf.state.0, fileId)
strongSelf.state = (strongSelf.state.0, fileId, strongSelf.state.2)
}
isHiddenUpdatedImpl = { [weak self] isHidden in
guard let strongSelf = self else {
return
}
strongSelf.state = (strongSelf.state.0, strongSelf.state.1, isHidden)
}
openPremiumImpl = { [weak self] in
@ -916,6 +1084,6 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer {
}
@objc private func createPressed() {
self.completion(self.state.0, self.state.1)
self.completion(self.state.0, self.state.1, self.state.2)
}
}

View File

@ -9588,6 +9588,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let _ = strongSelf.context.engine.peers.setForumChannelTopicClosed(id: peerId, threadId: threadId, isClosed: false).start()
}, requestLayout: { [weak self] transition in
if let strongSelf = self, let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: transition)
}
}, chatController: { [weak self] in
return self
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get()))

View File

@ -1349,7 +1349,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let heightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: inputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus, isExpanded: self.inputPanelContainerNode.stableIsExpanded)
let boundedHeight = min(heightAndOverflow.0, layout.standardInputHeight)
let boundedHeight = inputNode.followsDefaultHeight ? min(heightAndOverflow.0, layout.standardInputHeight) : heightAndOverflow.0
inputNodeHeightAndOverflow = (
boundedHeight,

View File

@ -537,7 +537,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemLayoutType: .detailed,
itemContentUniqueId: nil,
warpContentsOnEdges: false,
displaySearchWithPlaceholder: nil,
displaySearchWithPlaceholder: "Search Stickers",
searchInitiallyHidden: false,
searchIsPlaceholderOnly: true,
emptySearchResults: nil,
enableLongPress: false,
selectedItems: Set()
@ -585,6 +587,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
openGifContextMenu: { _, _, _, _, _ in
},
loadMore: { _ in
},
openSearch: {
}
)
@ -597,7 +601,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
subject: .recent,
items: [],
isLoading: false,
loadMoreToken: nil
loadMoreToken: nil,
displaySearchWithPlaceholder: nil,
searchInitiallyHidden: false
)
))
@ -645,7 +651,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
availableGifSearchEmojies.append(EntityKeyboardComponent.GifSearchEmoji(emoji: reaction, file: file, title: title))
}
}
return InputData(
emoji: emoji,
stickers: stickers,
@ -664,6 +670,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private var inputDataDisposable: Disposable?
private var hasRecentGifsDisposable: Disposable?
private let emojiSearchDisposable = MetaDisposable()
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
private let controllerInteraction: ChatControllerInteraction?
private var inputNodeInteraction: ChatMediaInputNodeInteraction?
@ -672,6 +681,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private var isMarkInputCollapsed: Bool = false
private var isEmojiSearchActive: Bool = false {
didSet {
self.followsDefaultHeight = !self.isEmojiSearchActive
}
}
var externalTopPanelContainerImpl: PagerExternalTopPanelContainer?
override var externalTopPanelContainer: UIView? {
return self.externalTopPanelContainerImpl
@ -753,7 +768,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
subject: subject,
items: items,
isLoading: false,
loadMoreToken: nil
loadMoreToken: nil,
displaySearchWithPlaceholder: "Search GIFs",
searchInitiallyHidden: false
)
)
}
@ -782,7 +799,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
subject: subject,
items: items,
isLoading: isLoading,
loadMoreToken: nil
loadMoreToken: nil,
displaySearchWithPlaceholder: nil,
searchInitiallyHidden: false
)
)
}
@ -813,7 +832,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
subject: subject,
items: items,
isLoading: isLoading,
loadMoreToken: loadMoreToken
loadMoreToken: loadMoreToken,
displaySearchWithPlaceholder: nil,
searchInitiallyHidden: false
)
)
}
@ -892,7 +913,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
subject: subject,
items: items,
isLoading: isLoading,
loadMoreToken: loadMoreToken
loadMoreToken: loadMoreToken,
displaySearchWithPlaceholder: nil,
searchInitiallyHidden: false
)
)
}
@ -918,6 +941,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private weak var currentUndoOverlayController: UndoOverlayController?
init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) {
self.context = context
self.currentInputData = currentInputData
@ -1049,6 +1073,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { [weak self, weak controllerInteraction] groupId, isPremiumLocked in
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
return
@ -1092,6 +1118,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
return
}
if groupId == AnyHashable("recent") {
controllerInteraction.dismissTextInput()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = []
@ -1128,9 +1155,136 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
navigationController: { [weak controllerInteraction] in
return controllerInteraction?.navigationController()
},
requestUpdate: { _ in
requestUpdate: { [weak self] transition in
guard let _ = self else {
return
}
// if !transition.animation.isImmediate {
// strongSelf.interfaceInteraction?.requestLayout(transition.containedViewLayoutTransition)
// }
},
updateSearchQuery: { _, _ in
updateSearchQuery: { [weak self] rawQuery, languageCode in
guard let strongSelf = self else {
return
}
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isEmpty {
strongSelf.emojiSearchDisposable.set(nil)
strongSelf.emojiSearchResult.set(.single(nil))
} else {
let context = strongSelf.context
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
guard case let .user(user) = peer else {
return false
}
return user.isPremium
}
|> distinctUntilChanged
let resultSignal = signal
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
context.engine.stickers.availableReactions(),
hasPremium
)
|> take(1)
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
for keyword in keywords {
for emoticon in keyword.emoticons {
allEmoticons[emoticon] = keyword.keyword
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
for attribute in item.file.attributes {
switch attribute {
case let .CustomEmoji(_, alt, _):
if !item.file.isPremiumEmoji || hasPremium {
if !alt.isEmpty, let keyword = allEmoticons[alt] {
result.append((alt, item.file, keyword))
} else if alt == query {
result.append((alt, item.file, alt))
}
}
default:
break
}
}
}
var items: [EmojiPagerContentComponent.Item] = []
var existingIds = Set<MediaId>()
for item in result {
if let itemFile = item.1 {
if existingIds.contains(itemFile.fileId) {
continue
}
existingIds.insert(itemFile.fileId)
let animationData = EntityKeyboardAnimationData(file: itemFile)
let item = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: itemFile, subgroupId: nil,
icon: .none,
accentTint: false
)
items.append(item)
}
}
return [EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "search",
title: nil,
subtitle: nil,
actionButtonTitle: nil,
isFeatured: false,
isPremiumLocked: false,
isEmbedded: false,
hasClear: false,
collapsedLineCount: nil,
displayPremiumBadges: false,
headerItem: nil,
items: items
)]
}
}
strongSelf.emojiSearchDisposable.set((resultSignal
|> delay(0.15, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
}))
}
},
chatPeerId: chatPeerId,
peekBehavior: nil,
@ -1231,6 +1385,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}
))
},
openSearch: { [weak self] in
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View {
pagerView.openSearch()
}
},
addGroupAction: { groupId, isPremiumLocked in
guard let controllerInteraction = controllerInteraction, let collectionId = groupId.base as? ItemCollectionId else {
return
@ -1281,6 +1440,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
return
}
if groupId == AnyHashable("recent") {
controllerInteraction.dismissTextInput()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = []
@ -1346,15 +1506,27 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
self.inputDataDisposable = (combineLatest(queue: .mainQueue(),
updatedInputData,
self.gifComponent.get()
self.gifComponent.get(),
self.emojiSearchResult.get()
)
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs in
|> deliverOnMainQueue).start(next: { [weak self] inputData, gifs, emojiSearchResult in
guard let strongSelf = self else {
return
}
var inputData = inputData
inputData.gifs = gifs
if let emojiSearchResult = emojiSearchResult {
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
text: "No emoji found", //strongSelf.presentationData.strings.EmojiSearch_SearchStatusesEmptyResult,
iconFile: nil
)
}
inputData.emoji = inputData.emoji.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
}
var transition: Transition = .immediate
var useAnimation = false
@ -1436,6 +1608,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
return
}
gifContext.loadMore(token: token)
},
openSearch: { [weak self] in
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View {
pagerView.openSearch()
}
}
)
@ -1474,6 +1651,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
deinit {
self.inputDataDisposable?.dispose()
self.hasRecentGifsDisposable?.dispose()
self.emojiSearchDisposable.dispose()
}
private func reloadGifContext() {
@ -1515,7 +1693,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
let wasMarkedInputCollapsed = self.isMarkInputCollapsed
self.isMarkInputCollapsed = false
let expandedHeight = standardInputHeight
var expandedHeight = standardInputHeight
if self.isEmojiSearchActive && !isExpanded {
expandedHeight += 118.0
}
var hiddenInputHeight: CGFloat = 0.0
if self.hideInput && !self.adjustLayoutForHiddenInput {
@ -1563,7 +1744,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
theme: interfaceState.theme,
strings: interfaceState.strings,
isContentInFocus: isVisible,
containerInsets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -34.0 : 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
topPanelInsets: UIEdgeInsets(),
emojiContent: self.currentInputData.emoji,
stickerContent: stickerContent,
@ -1591,7 +1772,12 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.hideInputUpdated?(transition.containedViewLayoutTransition)
}
},
hideTopPanelUpdated: { _, _ in
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
guard let strongSelf = self else {
return
}
strongSelf.isEmojiSearchActive = hideTopPanel
strongSelf.performLayout(transition: transition)
},
switchToTextInput: { [weak self] in
self?.switchToTextInput?()
@ -1637,7 +1823,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
deviceMetrics: deviceMetrics,
hiddenInputHeight: hiddenInputHeight,
displayBottomPanel: true,
isExpanded: isExpanded
isExpanded: isExpanded && !self.isEmojiSearchActive
)),
environment: {},
containerSize: CGSize(width: width, height: expandedHeight)
@ -1722,7 +1908,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private func processInputData(inputData: InputData) -> InputData {
return InputData(
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil),
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: inputData.emoji.itemContentUniqueId, emptySearchResults: inputData.emoji.emptySearchResults),
stickers: inputData.stickers.flatMap { stickers in
return stickers.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil)
},
@ -2027,6 +2213,8 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { _, _ in
},
clearGroup: { [weak self] groupId in
@ -2034,6 +2222,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
return
}
if groupId == AnyHashable("recent") {
strongSelf.window?.endEditing(true)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = []

View File

@ -2822,7 +2822,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
let previousReplyInfoNodeFrame = replyInfoNode.frame
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: replyInfoSizeApply.0)
replyInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: CGSize(width: backgroundFrame.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: replyInfoSizeApply.0.height))
if case let .System(duration, _) = animation {
if animateFrame {
replyInfoNode.layer.animateFrame(from: previousReplyInfoNodeFrame, to: replyInfoNode.frame, duration: duration, timingFunction: timingFunction)

View File

@ -186,7 +186,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
let (_, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
let (initialWidth, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
context: item.context,
presentationData: item.presentationData,
message: item.message,
@ -239,19 +239,19 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode {
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none, shareButtonOffset: isExpanded ? nil : CGPoint(x: displaySize.width + 4.0, y: -25.0), hidesHeaders: !isExpanded, avatarOffset: !isExpanded && isPlaying ? -100.0 : 0.0)
let width = videoFrame.width + 2.0
let videoFrameWidth = videoFrame.width + 2.0
return (contentProperties, nil, width, { constrainedSize, position in
var refinedWidth = videoFrame.width + 2.0
return (contentProperties, nil, initialWidth, { constrainedSize, position in
var refinedWidth = videoFrameWidth
var finishLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))?
if isExpanded || !didSetupFileNode {
(refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
(refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right - 44.0, height: constrainedSize.height))
refinedWidth += layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right
}
if !isExpanded {
refinedWidth = videoFrame.width + 2.0
refinedWidth = videoFrameWidth
}
return (refinedWidth, { boundingWidth in

View File

@ -224,6 +224,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
private var isWaitingForCollapse: Bool = false
private var hapticFeedback: HapticFeedback?
override init() {
self.titleNode = TextNode()
self.titleNode.displaysAsynchronously = false
@ -355,11 +357,23 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
guard arguments.associatedData.isPremium else {
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.impact(.medium)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
if case .undo = action {
let introController = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
arguments.controllerInteraction.navigationController()?.pushViewController(introController, animated: true)
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
arguments.controllerInteraction.navigationController()?.pushViewController(controller, animated: true)
let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: context.sharedContext.accountManager).start()
}

View File

@ -141,6 +141,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
private var isWaitingForCollapse: Bool = false
private var hapticFeedback: HapticFeedback?
var requestUpdateLayout: (Bool) -> Void = { _ in }
override init() {
@ -1486,11 +1488,24 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
guard item.associatedData.isPremium else {
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.impact(.medium)
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
if case .undo = action {
let introController = item.context.sharedContext.makePremiumIntroController(context: item.context, source: .settings)
item.controllerInteraction.navigationController()?.pushViewController(introController, animated: true)
let context = item.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
item.controllerInteraction.navigationController()?.pushViewController(controller, animated: true)
let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: item.context.sharedContext.accountManager).start()
}

View File

@ -156,6 +156,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)

View File

@ -374,6 +374,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)
@ -2906,15 +2907,22 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case .edit:
if case let .replyThread(message) = strongSelf.chatLocation {
let threadId = Int64(message.messageId.id)
if let threadInfo = strongSelf.data?.threadData?.info {
let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadInfo))
if let threadData = strongSelf.data?.threadData {
let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(threadId: threadId, threadInfo: threadData.info, isHidden: threadData.isHidden))
controller.navigationPresentation = .modal
let context = strongSelf.context
controller.completion = { [weak controller] title, fileId in
controller.completion = { [weak controller] title, fileId, isHidden in
let _ = (context.engine.peers.editForumChannelTopic(id: peerId, threadId: threadId, title: title, iconFileId: fileId)
|> deliverOnMainQueue).start(completed: {
controller?.dismiss()
})
if let isHidden = isHidden {
let _ = (context.engine.peers.setForumChannelTopicHidden(id: peerId, threadId: threadId, isHidden: isHidden)
|> deliverOnMainQueue).start(completed: {
controller?.dismiss()
})
}
}
strongSelf.controller?.push(controller)
}

View File

@ -370,6 +370,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)

View File

@ -1424,50 +1424,83 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
let mappedSource: PremiumSource
switch source {
case .settings:
mappedSource = .settings
case .stickers:
mappedSource = .stickers
case .reactions:
mappedSource = .reactions
case .ads:
mappedSource = .ads
case .upload:
mappedSource = .upload
case .groupsAndChannels:
mappedSource = .groupsAndChannels
case .pinnedChats:
mappedSource = .pinnedChats
case .publicLinks:
mappedSource = .publicLinks
case .savedGifs:
mappedSource = .savedGifs
case .savedStickers:
mappedSource = .savedStickers
case .folders:
mappedSource = .folders
case .chatsPerFolder:
mappedSource = .chatsPerFolder
case .appIcons:
mappedSource = .appIcons
case .accounts:
mappedSource = .accounts
case .about:
mappedSource = .about
case let .deeplink(reference):
mappedSource = .deeplink(reference)
case let .profile(peerId):
mappedSource = .profile(peerId)
case let .emojiStatus(peerId, fileId, file, packTitle):
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
case .voiceToText:
mappedSource = .voiceToText
case .fasterDownload:
mappedSource = .fasterDownload
case .settings:
mappedSource = .settings
case .stickers:
mappedSource = .stickers
case .reactions:
mappedSource = .reactions
case .ads:
mappedSource = .ads
case .upload:
mappedSource = .upload
case .groupsAndChannels:
mappedSource = .groupsAndChannels
case .pinnedChats:
mappedSource = .pinnedChats
case .publicLinks:
mappedSource = .publicLinks
case .savedGifs:
mappedSource = .savedGifs
case .savedStickers:
mappedSource = .savedStickers
case .folders:
mappedSource = .folders
case .chatsPerFolder:
mappedSource = .chatsPerFolder
case .appIcons:
mappedSource = .appIcons
case .accounts:
mappedSource = .accounts
case .about:
mappedSource = .about
case let .deeplink(reference):
mappedSource = .deeplink(reference)
case let .profile(peerId):
mappedSource = .profile(peerId)
case let .emojiStatus(peerId, fileId, file, packTitle):
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
case .voiceToText:
mappedSource = .voiceToText
case .fasterDownload:
mappedSource = .fasterDownload
}
return PremiumIntroScreen(context: context, source: mappedSource)
}
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController {
let mappedSubject: PremiumDemoScreen.Subject
switch subject {
case .doubleLimits:
mappedSubject = .doubleLimits
case .moreUpload:
mappedSubject = .moreUpload
case .fasterDownload:
mappedSubject = .fasterDownload
case .voiceToText:
mappedSubject = .voiceToText
case .noAds:
mappedSubject = .noAds
case .uniqueReactions:
mappedSubject = .uniqueReactions
case .premiumStickers:
mappedSubject = .premiumStickers
case .advancedChatManagement:
mappedSubject = .advancedChatManagement
case .profileBadge:
mappedSubject = .profileBadge
case .animatedUserpics:
mappedSubject = .animatedUserpics
case .appIcons:
mappedSubject = .appIcons
case .animatedEmoji:
mappedSubject = .animatedEmoji
case .emojiStatus:
mappedSubject = .emojiStatus
}
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
}
public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController {
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
}

View File

@ -390,7 +390,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
}
subscriber.putNext(result)
}, completed: {
subscriber.putCompletion()
// subscriber.putCompletion()
})
}

View File

@ -287,7 +287,6 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
}
let _ = download
let _ = failed
if let position = position {
if self.ignoreEarlierTimestamps {
@ -307,8 +306,14 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
}
if let updateStatus = self.updateStatus, let playback = playback, let duration = duration {
let playbackStatus: MediaPlayerPlaybackStatus
switch playback {
if let failed, failed {
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: 0.0, dimensions: self.status.dimensions, timestamp: 0.0, baseRate: 0.0, seekId: self.status.seekId, status: .playing, soundEnabled: false)
updateStatus(self.status)
self.onPlaybackStarted?()
} else {
let playbackStatus: MediaPlayerPlaybackStatus
switch playback {
case 0:
if newTimestamp > Double(duration) - 1.0 {
self.isPlaying = false
@ -325,17 +330,18 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
playbackStatus = .buffering(initial: !self.started, whilePlaying: self.isPlaying, progress: 0.0, display: false)
default:
playbackStatus = .buffering(initial: true, whilePlaying: true, progress: 0.0, display: false)
}
if case .playing = playbackStatus, !self.started {
self.started = true
print("YT started in \(CFAbsoluteTimeGetCurrent() - self.benchmarkStartTime)")
}
self.onPlaybackStarted?()
if case .playing = playbackStatus, !self.started {
self.started = true
print("YT started in \(CFAbsoluteTimeGetCurrent() - self.benchmarkStartTime)")
self.onPlaybackStarted?()
}
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: Double(duration), dimensions: self.status.dimensions, timestamp: newTimestamp, baseRate: self.status.baseRate, seekId: self.status.seekId, status: playbackStatus, soundEnabled: true)
updateStatus(self.status)
}
self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: Double(duration), dimensions: self.status.dimensions, timestamp: newTimestamp, baseRate: self.status.baseRate, seekId: self.status.seekId, status: playbackStatus, soundEnabled: true)
updateStatus(self.status)
}
}