Merge commit '6700f1af2aa6accfda96a28ab3cc3f45030a6da1' into features/history-loading

This commit is contained in:
Ali 2022-11-09 17:29:02 +04:00
commit e64016553e
55 changed files with 2350 additions and 438 deletions

View File

@ -1129,12 +1129,21 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
} }
} }
private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? { enum AvatarClipStyle {
case round
case roundedRect
}
private func avatarRoundImage(size: CGSize, source: UIImage, style: AvatarClipStyle) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0) UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext() let context = UIGraphicsGetCurrentContext()
context?.beginPath() context?.beginPath()
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) switch style {
case .round:
context?.addEllipse(in: CGRect(origin: .zero, size: size))
case .roundedRect:
context?.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.25).cgPath)
}
context?.clip() context?.clip()
source.draw(in: CGRect(origin: CGPoint(), size: size)) source.draw(in: CGRect(origin: CGPoint(), size: size))
@ -1214,8 +1223,8 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
return image return image
} }
private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize) -> UIImage { private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize, style: AvatarClipStyle) -> UIImage {
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) { if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image, style: style) {
return roundImage return roundImage
} else { } else {
return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)! return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)!
@ -1297,6 +1306,11 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
autoreleasepool { autoreleasepool {
var profileImage: INImage? var profileImage: INImage?
var isForum = false
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) {
isForum = true
}
if peer.id == accountPeerId { if peer.id == accountPeerId {
let cachedPath = mediaBox.cachedRepresentationPathForId("savedMessagesAvatar50x50", representationId: "intents.png", keepDuration: .shortLived) let cachedPath = mediaBox.cachedRepresentationPathForId("savedMessagesAvatar50x50", representationId: "intents.png", keepDuration: .shortLived)
if let _ = fileSize(cachedPath) { if let _ = fileSize(cachedPath) {
@ -1325,7 +1339,7 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
} catch { } catch {
} }
} else { } else {
let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0)) let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0), style: isForum ? .roundedRect : .round)
if let data = image.pngData() { if let data = image.pngData() {
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic) let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
} }
@ -1345,7 +1359,7 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
} catch { } catch {
} }
} else { } else {
let image = avatarImage(path: nil, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0)) let image = avatarImage(path: nil, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0), style: isForum ? .roundedRect : .round)
if let data = image.pngData() { if let data = image.pngData() {
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic) let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
} }

View File

@ -4351,6 +4351,27 @@ Unused sets are archived when you add more.";
"SettingsSearch.Synonyms.Support" = "Support"; "SettingsSearch.Synonyms.Support" = "Support";
"SettingsSearch.Synonyms.FAQ" = " "; "SettingsSearch.Synonyms.FAQ" = " ";
"SettingsSearch.Synonyms.Devices.TerminateOtherSessions" = " ";
"SettingsSearch.Synonyms.Devices.LinkDesktopDevice" = " ";
"SettingsSearch.Synonyms.Language.ShowTranslateButton" = " ";
"SettingsSearch.Synonyms.Language.DoNotTranslate" = " ";
"SettingsSearch.Synonyms.Premium" = " ";
"SettingsSearch.Synonyms.Premium.DoubledLimits" = " ";
"SettingsSearch.Synonyms.Premium.UploadSize" = " ";
"SettingsSearch.Synonyms.Premium.FasterSpeed" = " ";
"SettingsSearch.Synonyms.Premium.VoiceToText" = "Transcribe";
"SettingsSearch.Synonyms.Premium.NoAds" = " ";
"SettingsSearch.Synonyms.Premium.EmojiStatus" = " ";
"SettingsSearch.Synonyms.Premium.Reactions" = " ";
"SettingsSearch.Synonyms.Premium.Stickers" = " ";
"SettingsSearch.Synonyms.Premium.AnimatedEmoji" = " ";
"SettingsSearch.Synonyms.Premium.ChatManagement" = " ";
"SettingsSearch.Synonyms.Premium.Badge" = "Star";
"SettingsSearch.Synonyms.Premium.Avatar" = "Video Avatar";
"SettingsSearch.Synonyms.Premium.AppIcon" = " ";
"ChatList.DeleteForCurrentUser" = "Delete just for me"; "ChatList.DeleteForCurrentUser" = "Delete just for me";
"ChatList.DeleteForEveryone" = "Delete for me and %@"; "ChatList.DeleteForEveryone" = "Delete for me and %@";
"ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!"; "ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!";
@ -7564,9 +7585,7 @@ Sorry for the inconvenience.";
"Premium.Stickers.Proceed" = "Unlock Premium Stickers"; "Premium.Stickers.Proceed" = "Unlock Premium Stickers";
"Premium.Reactions.Proceed" = "Unlock Premium Reactions"; "Premium.Reactions.Proceed" = "Unlock Premium Reactions";
"Premium.AppIcons.Proceed" = "Unlock Premium Icons"; "Premium.AppIcons.Proceed" = "Unlock Premium Icons";
"Premium.NoAds.Proceed" = "About Telegram Premium"; "Premium.NoAds.Proceed" = "About Telegram Premium";
"AccessDenied.LocationPreciseDenied" = "To share your specific location in this chat, please go to Settings > Privacy > Location Services > Telegram and set Precise Location to On."; "AccessDenied.LocationPreciseDenied" = "To share your specific location in this chat, please go to Settings > Privacy > Location Services > Telegram and set Precise Location to On.";
@ -7645,13 +7664,14 @@ Sorry for the inconvenience.";
"Premium.FasterSpeed" = "Faster Download Speed"; "Premium.FasterSpeed" = "Faster Download Speed";
"Premium.FasterSpeedInfo" = "No more limits on the speed with which media and documents are downloaded."; "Premium.FasterSpeedInfo" = "No more limits on the speed with which media and documents are downloaded.";
"Premium.FasterSpeedStandaloneInfo" = "Subscribe to **Telegram Premium** to download media and files at the fastest possible speed.";
"Premium.VoiceToText" = "Voice-to-Text Conversion"; "Premium.VoiceToText" = "Voice-to-Text Conversion";
"Premium.VoiceToTextInfo" = "Ability to read the transcript of any incoming voice message."; "Premium.VoiceToTextInfo" = "Ability to read the transcript of any incoming voice message.";
"Premium.VoiceToTextStandaloneInfo" = "Subscribe to **Telegram Premium** to be able to convert voice and video messages to text.";
"Premium.NoAds" = "No Ads"; "Premium.NoAds" = "No Ads";
"Premium.NoAdsInfo" = "No more ads in public channels where Telegram sometimes shows ads."; "Premium.NoAdsInfo" = "No more ads in public channels where Telegram sometimes shows ads.";
"Premium.NoAdsStandaloneInfo" = "Remove ads such as this one by subscribing to **Telegram Premium**."; "Premium.NoAdsStandaloneInfo" = "Remove ads such as this one by subscribing to **Telegram Premium**.";
"Premium.Reactions" = "Unique Reactions"; "Premium.Reactions" = "Unique Reactions";
@ -7665,6 +7685,7 @@ Sorry for the inconvenience.";
"Premium.ChatManagement" = "Advanced Chat Management"; "Premium.ChatManagement" = "Advanced Chat Management";
"Premium.ChatManagementInfo" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts."; "Premium.ChatManagementInfo" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts.";
"Premium.ChatManagementStandaloneInfo" = "Subscribers of **Telegram Premium** can set the default folder, auto-archive and hide new chats from non-contacts.";
"Premium.Badge" = "Profile Badge"; "Premium.Badge" = "Profile Badge";
"Premium.BadgeInfo" = "A badge next to your name showing that you are helping support Telegram."; "Premium.BadgeInfo" = "A badge next to your name showing that you are helping support Telegram.";
@ -8247,3 +8268,15 @@ Sorry for the inconvenience.";
"EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons"; "EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons";
"EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found"; "EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found";
"Username.UsernamePurchaseAvailable" = "**This username is already taken.** However, it is being resold via auction. [Learn more...]()";
"Channel.Username.UsernamePurchaseAvailable" = "**This username is already taken.** However, it is being resold via auction. [Learn more...]()";
"DownloadList.IncreaseSpeed" = "Increase Speed";
"Conversation.IncreaseSpeed" = "Increase Speed";
"Premium.ChatManagement.Proceed" = "About Telegram Premium";
"Premium.FasterSpeed.Proceed" = "About Telegram Premium";
"OwnershipTransfer.EnterPassword" = "Enter Password";
"OwnershipTransfer.EnterPasswordText" = "Please enter your 2-Step Verification password to confirm the action.";

View File

@ -23,12 +23,22 @@ private let gradientColors: [NSArray] = [
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor], [UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
] ]
private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? { enum AvatarClipStyle {
case round
case roundedRect
}
private func avatarRoundImage(size: CGSize, source: UIImage, style: AvatarClipStyle) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0) UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext() let context = UIGraphicsGetCurrentContext()
context?.beginPath() context?.beginPath()
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) switch style {
case .round:
context?.addEllipse(in: CGRect(origin: .zero, size: size))
case .roundedRect:
context?.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.25).cgPath)
}
context?.clip() context?.clip()
source.draw(in: CGRect(origin: CGPoint(), size: size)) source.draw(in: CGRect(origin: CGPoint(), size: size))
@ -159,10 +169,10 @@ private func savedMessagesImage(size: CGSize) -> UIImage? {
private let avatarSize = CGSize(width: 50.0, height: 50.0) private let avatarSize = CGSize(width: 50.0, height: 50.0)
func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize) -> UIImage { func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize, style: AvatarClipStyle) -> UIImage {
if let peer = peer, let accountPeerId = accountPeerId, peer.id == accountPeerId { if let peer = peer, let accountPeerId = accountPeerId, peer.id == accountPeerId {
return savedMessagesImage(size: size)! return savedMessagesImage(size: size)!
} else if let path = peer?.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) { } else if let path = peer?.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image, style: style) {
return roundImage return roundImage
} else { } else {
return avatarViewLettersImage(size: size, peerId: peer?.id ?? 1, accountPeerId: accountPeerId ?? 1, letters: peer?.letters ?? [" "])! return avatarViewLettersImage(size: size, peerId: peer?.id ?? 1, accountPeerId: accountPeerId ?? 1, letters: peer?.letters ?? [" "])!
@ -170,10 +180,10 @@ func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize) ->
} }
private final class AvatarView: UIImageView { private final class AvatarView: UIImageView {
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) { init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize, style: AvatarClipStyle) {
super.init(frame: CGRect()) super.init(frame: CGRect())
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) { if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image, style: style) {
self.image = roundImage self.image = roundImage
} else { } else {
self.image = avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters) self.image = avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)
@ -195,7 +205,7 @@ final class PeerView: UIView {
init(primaryColor: UIColor, accountPeerId: Int64, peer: WidgetDataPeer, tapped: @escaping () -> Void) { init(primaryColor: UIColor, accountPeerId: Int64, peer: WidgetDataPeer, tapped: @escaping () -> Void) {
self.peer = peer self.peer = peer
self.tapped = tapped self.tapped = tapped
self.avatarView = AvatarView(accountPeerId: accountPeerId, peer: peer, size: avatarSize) self.avatarView = AvatarView(accountPeerId: accountPeerId, peer: peer, size: avatarSize, style: peer.isForum ? .roundedRect : .round)
self.titleLabel = UILabel() self.titleLabel = UILabel()
var title = peer.name var title = peer.name

View File

@ -175,9 +175,14 @@ private func getCommonTimeline(friends: [Friend]?, in context: TimelineProviderC
} }
} }
var isForum = false
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) {
isForum = true
}
let widgetPeer = WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in let widgetPeer = WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource) return postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: mappedMessage) }, badge: badge, message: mappedMessage, isForum: isForum)
result.append(ParsedPeer(accountId: accountId, accountPeerId: state.peerId.toInt64(), peer: widgetPeer)) result.append(ParsedPeer(accountId: accountId, accountPeerId: state.peerId.toInt64(), peer: widgetPeer))
} }
@ -271,8 +276,8 @@ struct AvatarItemView: View {
var body: some View { var body: some View {
return ZStack { return ZStack {
if let peer = peer { if let peer = peer {
Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize))) Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize), style: peer.peer.isForum ? .roundedRect : .round))
.clipShape(Circle()) .mask(peer.peer.isForum ? AnyView(RoundedRectangle(cornerRadius: itemSize * 0.25)) : AnyView(Circle()))
} else { } else {
Circle() Circle()
.fill(placeholderColor) .fill(placeholderColor)

View File

@ -836,6 +836,7 @@ public enum PremiumIntroSource {
case profile(PeerId) case profile(PeerId)
case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?) case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?)
case voiceToText case voiceToText
case fasterDownload
} }
#if ENABLE_WALLET #if ENABLE_WALLET

View File

@ -761,12 +761,13 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
}))) })))
} }
// items.append(.separator) if canOpenClose {
// items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Select, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in items.append(.separator)
// f(.default) items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Select, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
// f(.default)
// chatListController?.selectPeerThread(peerId: peerId, threadId: threadId)
// }))) })))
}
return .single(items) return .single(items)
} }

View File

@ -527,21 +527,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationController.replaceController(strongSelf, with: chatController, animated: true) navigationController.replaceController(strongSelf, with: chatController, animated: true)
} }
} }
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel {
switch channel.participationStatus {
case .member:
strongSelf.setToolbar(nil, transition: .animated(duration: 0.4, curve: .spring))
default:
let actionTitle: String
if channel.flags.contains(.requestToJoin) {
actionTitle = strongSelf.presentationData.strings.Group_ApplyToJoin
} else {
actionTitle = strongSelf.presentationData.strings.Channel_JoinChannel
}
strongSelf.setToolbar(Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: actionTitle, isEnabled: true)), transition: .animated(duration: 0.4, curve: .spring))
}
}
}) })
} }
} }
@ -1781,11 +1766,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
} }
let peerView: Signal<PeerView?, NoError>
if case let .forum(peerId) = location {
peerView = context.account.viewTracker.peerView(peerId)
|> map(Optional.init)
} else {
peerView = .single(nil)
}
let previousToolbarValue = Atomic<Toolbar?>(value: nil) let previousToolbarValue = Atomic<Toolbar?>(value: nil)
self.stateDisposable.set(combineLatest(queue: .mainQueue(), self.stateDisposable.set(combineLatest(queue: .mainQueue(),
self.presentationDataValue.get(), self.presentationDataValue.get(),
peerIdsAndOptions peerIdsAndOptions,
).start(next: { [weak self] presentationData, peerIdsAndOptions in peerView
).start(next: { [weak self] presentationData, peerIdsAndOptions, peerView in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1838,6 +1832,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: middleAction) toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: middleAction)
} }
} else if let peerView = peerView, let channel = peerView.peers[peerView.peerId] as? TelegramChannel {
switch channel.participationStatus {
case .member:
strongSelf.setToolbar(nil, transition: .animated(duration: 0.4, curve: .spring))
default:
let actionTitle: String
if channel.flags.contains(.requestToJoin) {
actionTitle = strongSelf.presentationData.strings.Group_ApplyToJoin
} else {
actionTitle = strongSelf.presentationData.strings.Channel_JoinChannel
}
strongSelf.setToolbar(Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: actionTitle, isEnabled: true)), transition: .animated(duration: 0.4, curve: .spring))
}
} }
var transition: ContainedViewLayoutTransition = .immediate var transition: ContainedViewLayoutTransition = .immediate
let previousToolbar = previousToolbarValue.swap(toolbar) let previousToolbar = previousToolbarValue.swap(toolbar)
@ -3823,6 +3830,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.present(actionSheet, in: .window(.root)) self.present(actionSheet, in: .window(.root))
} }
func selectPeerThread(peerId: EnginePeer.Id, threadId: Int64) {
self.chatListDisplayNode.containerNode.updateState({ state in
var state = state
state.selectedThreadIds.insert(threadId)
return state
})
self.chatListDisplayNode.containerNode.didBeginSelectingChats?()
}
private func commitDeletePeerThread(peerId: EnginePeer.Id, threadId: Int64, completion: @escaping () -> Void) { private func commitDeletePeerThread(peerId: EnginePeer.Id, threadId: Int64, completion: @escaping () -> Void) {
self.forEachController({ controller in self.forEachController({ controller in
if let controller = controller as? UndoOverlayController { if let controller = controller as? UndoOverlayController {

View File

@ -603,7 +603,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
) )
|> map { hasOngoingCall, preloadItems -> Set<ChatHistoryPreloadItem> in |> map { hasOngoingCall, preloadItems -> Set<ChatHistoryPreloadItem> in
if hasOngoingCall { if hasOngoingCall {
return [] return Set()
} else { } else {
return Set(preloadItems) return Set(preloadItems)
} }

View File

@ -33,6 +33,7 @@ import Postbox
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import AnimationCache import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import PremiumUI
private enum ChatListTokenId: Int32 { private enum ChatListTokenId: Int32 {
case archive case archive
@ -284,7 +285,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
isForum = true isForum = true
} }
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: strongSelf.hasDownloads).map(\.filter) filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: !isForum && strongSelf.hasDownloads).map(\.filter)
} }
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition) strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
} }
@ -896,13 +897,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let items = combineLatest(queue: .mainQueue(), let items = combineLatest(queue: .mainQueue(),
context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], messages: [message.id: message], peers: [:]), context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], messages: [message.id: message], peers: [:]),
isCachedValue |> take(1) isCachedValue |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { [weak self] actions, isCachedValue -> [ContextMenuItem] in |> map { [weak self] actions, isCachedValue, accountPeer -> [ContextMenuItem] in
guard let strongSelf = self else { guard let strongSelf = self else {
return [] return []
} }
let isPremium = accountPeer?.isPremium ?? false
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
@ -920,6 +923,31 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}) })
}))) })))
} else { } else {
if !isPremium, let size = downloadResource?.size, size >= 300 * 1024 * 1024 {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
guard let strongSelf = self else {
f(.default)
return
}
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: {})
f(.default)
})))
items.append(.separator)
}
if let downloadResource = downloadResource, !downloadResource.isFirstInList { if let downloadResource = downloadResource, !downloadResource.isFirstInList {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Raise"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Raise"), color: theme.contextMenu.primaryColor)
@ -1032,7 +1060,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: { [weak self] in c.dismiss(completion: { [weak self] in
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false) self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
}) })
}))) })))
@ -1078,7 +1106,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: { c.dismiss(completion: {
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false) self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
}) })
}))) })))
@ -1399,29 +1427,41 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
} }
} else { } else {
let _ = (ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: nil, { currentState in let _ = (ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: threadId, { currentState in
return currentState.withUpdatedForwardMessageIds(Array(messageIds)) return currentState.withUpdatedForwardMessageIds(Array(messageIds))
}) })
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let controller = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) let proceed: (ChatController) -> Void = { chatController in
controller.purposefulAction = { [weak self] in chatController.purposefulAction = { [weak self] in
self?.cancel?() self?.cancel?()
} }
if let navigationController = strongSelf.navigationController {
if let navigationController = strongSelf.navigationController, let peerSelectionControllerIndex = navigationController.viewControllers.firstIndex(where: { $0 is PeerSelectionController }) {
var viewControllers = navigationController.viewControllers var viewControllers = navigationController.viewControllers
viewControllers.insert(controller, at: peerSelectionControllerIndex) if threadId != nil {
navigationController.setViewControllers(viewControllers, animated: false) viewControllers.insert(chatController, at: viewControllers.count - 2)
Queue.mainQueue().after(0.2) {
peerSelectionController?.dismiss()
}
} else { } else {
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: { viewControllers.insert(chatController, at: viewControllers.count - 1)
if let peerSelectionController = peerSelectionController {
peerSelectionController.dismiss()
} }
navigationController.setViewControllers(viewControllers, animated: false)
strongSelf.activeActionDisposable.set((chatController.ready.get()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak navigationController] _ in
viewControllers.removeAll(where: { $0 is PeerSelectionController })
navigationController?.setViewControllers(viewControllers, animated: true)
}))
}
}
if let threadId = threadId {
let _ = (strongSelf.context.sharedContext.chatControllerForForumThread(context: strongSelf.context, peerId: peerId, threadId: threadId)
|> deliverOnMainQueue).start(next: { chatController in
proceed(chatController)
}) })
} else {
proceed(strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)))
} }
strongSelf.updateState { state in strongSelf.updateState { state in

View File

@ -1975,9 +1975,9 @@ public final class ChatListNode: ListView {
} }
self.view.addGestureRecognizer(selectionRecognizer) self.view.addGestureRecognizer(selectionRecognizer)
if case .forum = location { // if case .forum = location {
self.isSelectionGestureEnabled = false // self.isSelectionGestureEnabled = false
} // }
} }
deinit { deinit {

View File

@ -908,6 +908,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
} }
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
}
let actionsVerticalTransitionDirection: CGFloat let actionsVerticalTransitionDirection: CGFloat
if let contentNode = contentNode { if let contentNode = contentNode {
if contentNode.frame.minY < self.actionsStackNode.frame.minY { if contentNode.frame.minY < self.actionsStackNode.frame.minY {
@ -1123,6 +1127,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if case .center = actionsHorizontalAlignment { if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
} }
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
}
let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsStackNode.layer.animate( self.actionsStackNode.layer.animate(
from: NSValue(cgPoint: CGPoint()), from: NSValue(cgPoint: CGPoint()),

View File

@ -196,10 +196,19 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
self.highlight?(touchLocation) self.highlight?(touchLocation)
} }
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton { if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event) {
var fail = false
if let _ = hitResult as? UIButton {
fail = true
} else if let node = hitResult.asyncdisplaykit_node, node is ASControlNode {
fail = true
}
if fail {
self.state = .failed self.state = .failed
return return
} }
}
self.tapCount += 1 self.tapCount += 1
if self.tapCount == 2 && self.touchCount == 1 { if self.tapCount == 2 && self.touchCount == 1 {

View File

@ -19,6 +19,7 @@ import Speak
import TranslateUI import TranslateUI
import ShareController import ShareController
import UndoUI import UndoUI
import ContextUI
enum ChatMediaGalleryThumbnail: Equatable { enum ChatMediaGalleryThumbnail: Equatable {
case image(ImageMediaReference) case image(ImageMediaReference)
@ -201,6 +202,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
private let moreBarButton: MoreHeaderButton
private var tilingNode: TilingNode? private var tilingNode: TilingNode?
fileprivate let _ready = Promise<Void>() fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>() fileprivate let _title = Promise<String>()
@ -238,6 +241,10 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0)) self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
self.statusNode.isHidden = true self.statusNode.isHidden = true
self.moreBarButton = MoreHeaderButton()
self.moreBarButton.isUserInteractionEnabled = true
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
super.init() super.init()
self.clipsToBounds = true self.clipsToBounds = true
@ -275,6 +282,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
} }
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
self?.openMoreMenu(sourceNode: sourceNode, gesture: gesture)
}
} }
override func isPagingEnabled() -> Signal<Bool, NoError> { override func isPagingEnabled() -> Signal<Bool, NoError> {
@ -326,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .medium, .full: case .medium, .full:
strongSelf.statusNodeContainer.isHidden = true strongSelf.statusNodeContainer.isHidden = true
Queue.concurrentDefaultQueue().async { // Queue.concurrentDefaultQueue().async {
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) { // if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id) // strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|> deliverOnMainQueue).start(next: { [weak self] results in // |> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self { // if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode() // strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty { // if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size // let size = strongSelf.imageNode.bounds.size
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in // let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self { // if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a) // strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
} // }
}, performAction: { [weak self] string, action in // }, performAction: { [weak self] string, action in
guard let strongSelf = self else { // guard let strongSelf = self else {
return // return
} // }
switch action { // switch action {
case .copy: // case .copy:
UIPasteboard.general.string = string // UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController { // if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 }) // let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }) // let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
controller.present(tooltipController, in: .window(.root)) // controller.present(tooltipController, in: .window(.root))
} // }
case .share: // case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController { // if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData)) // let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
controller.present(shareController, in: .window(.root)) // controller.present(shareController, in: .window(.root))
} // }
case .lookup: // case .lookup:
let controller = UIReferenceLibraryViewController(term: string) // let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window { // if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = window // controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) // controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true) // window.rootViewController?.present(controller, animated: true)
} // }
case .speak: // case .speak:
let _ = speakText(context: strongSelf.context, text: string) // let _ = speakText(context: strongSelf.context, text: string)
case .translate: // case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController { // if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil) // let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
controller.pushController = { [weak parentController] c in // controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true // (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c) // parentController?.push(c)
} // }
controller.presentController = { [weak parentController] c in // controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root)) // parentController?.present(c, in: .window(.root))
} // }
parentController.present(controller, in: .window(.root)) // parentController.present(controller, in: .window(.root))
} // }
} // }
}) // })
recognizedContentNode.barcodeAction = { [weak self] payload, rect in // recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else { // guard let strongSelf = self, let message = strongSelf.message else {
return // return
} // }
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message) // strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
} // }
recognizedContentNode.alpha = 0.0 // recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size) // recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate) // recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode) // strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode // strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn() // strongSelf.recognitionOverlayContentNode.transitionIn()
} // }
} // }
})) // }))
} // }
} // }
case .none, .blurred: case .none, .blurred:
strongSelf.statusNodeContainer.isHidden = false strongSelf.statusNodeContainer.isHidden = false
@ -411,12 +423,17 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} else { } else {
self._ready.set(.single(Void())) self._ready.set(.single(Void()))
} }
var barButtonItems: [UIBarButtonItem] = []
if imageReference.media.flags.contains(.hasStickers) { if imageReference.media.flags.contains(.hasStickers) {
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed)) let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
self._rightBarButtonItems.set(.single([rightBarButtonItem])) barButtonItems.append(rightBarButtonItem)
} else {
self._rightBarButtonItems.set(.single([]))
} }
if self.message != nil {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
barButtonItems.append(moreMenuItem)
}
self._rightBarButtonItems.set(.single(barButtonItems))
} }
self.contextAndMedia = (self.context, imageReference.abstract) self.contextAndMedia = (self.context, imageReference.abstract)
} }
@ -453,6 +470,125 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self._ready.set(.single(Void())) self._ready.set(.single(Void()))
} }
@objc private func moreButtonPressed() {
self.moreBarButton.play()
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
}
private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> {
var items: [ContextMenuItem] = []
if let message = self.message {
let context = self.context
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
if let navigationController = strongSelf.baseNavigationController() {
strongSelf.beginCustomDismiss(true)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
Queue.mainQueue().after(0.3) {
strongSelf.completeCustomDismiss()
}
}
f(.default)
})
})))
}
// if #available(iOS 11.0, *) {
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
// f(.default)
// guard let strongSelf = self else {
// return
// }
// strongSelf.beginAirPlaySetup()
// })))
// }
// if let (message, _, _) = strongSelf.contentInfo() {
// for media in message.media {
// if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
// let url = content.url
//
// let item = OpenInItem.url(url: url)
// let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
// items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self, let controller = strongSelf.galleryController() {
// var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// if !presentationData.theme.overallDarkAppearance {
// presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
// }
// let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
// if let strongSelf = self {
// strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
// }
// })
// controller.present(actionSheet, in: .window(.root))
// }
// })))
// break
// }
// }
// }
// if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() {
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self {
// switch strongSelf.fetchStatus {
// case .Local:
// let _ = (SaveToCameraRoll.saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, mediaReference: .message(message: MessageReference(message), media: file))
// |> deliverOnMainQueue).start(completed: {
// guard let strongSelf = self else {
// return
// }
// guard let controller = strongSelf.galleryController() else {
// return
// }
// controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: strongSelf.presentationData.strings.Gallery_VideoSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
// })
// default:
// guard let controller = strongSelf.galleryController() else {
// return
// }
// controller.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Gallery_WaitForVideoDownoad, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
// })]), in: .window(.root))
// }
// }
// })))
// }
// if strongSelf.canDelete() {
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self {
// strongSelf.footerContentNode.deleteButtonPressed()
// }
// })))
// }
return .single(items)
}
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@objc func openStickersButtonPressed() { @objc func openStickersButtonPressed() {
guard let (context, media) = self.contextAndMedia else { guard let (context, media) = self.contextAndMedia else {
return return

View File

@ -290,7 +290,7 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14) })?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
} }
private func optionsCircleImage(dark: Bool) -> UIImage? { func optionsCircleImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
@ -339,7 +339,7 @@ private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .whi
}) })
} }
private final class MoreHeaderButton: HighlightableButtonNode { final class MoreHeaderButton: HighlightableButtonNode {
enum Content { enum Content {
case image(UIImage?) case image(UIImage?)
case more(UIImage?) case more(UIImage?)
@ -2476,6 +2476,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if let (message, _, _) = strongSelf.contentInfo() {
let context = strongSelf.context
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
if let navigationController = strongSelf.baseNavigationController() {
strongSelf.beginCustomDismiss(true)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
Queue.mainQueue().after(0.3) {
strongSelf.completeCustomDismiss()
}
}
f(.default)
})
})))
}
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
var speedIconText: String = "1x" var speedIconText: String = "1x"
for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) { for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
@ -2804,7 +2827,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextReferenceContentNode private let sourceNode: ContextReferenceContentNode

View File

@ -840,7 +840,7 @@ func importStickerPackShortNameController(context: AccountContext, title: String
case .taken: case .taken:
contentNode?.infoText = .taken contentNode?.infoText = .taken
contentNode?.actionNodes.last?.actionEnabled = false contentNode?.actionNodes.last?.actionEnabled = false
case .invalid: case .invalid, .purchaseAvailable:
contentNode?.infoText = .info contentNode?.infoText = .info
contentNode?.actionNodes.last?.actionEnabled = false contentNode?.actionNodes.last?.actionEnabled = false
} }

View File

@ -5,19 +5,32 @@ import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData import TelegramPresentationData
import ActivityIndicator import ActivityIndicator
import TextFormat
import Markdown
public class ItemListActivityTextItem: ListViewItem, ItemListItem { public class ItemListActivityTextItem: ListViewItem, ItemListItem {
public enum TextColor {
case generic
case constructive
case destructive
case warning
}
let displayActivity: Bool let displayActivity: Bool
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let text: NSAttributedString let text: String
let color: TextColor
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
public let isAlwaysPlain: Bool = true public let isAlwaysPlain: Bool = true
public init(displayActivity: Bool, presentationData: ItemListPresentationData, text: NSAttributedString, sectionId: ItemListSectionId) { public init(displayActivity: Bool, presentationData: ItemListPresentationData, text: String, color: TextColor, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, sectionId: ItemListSectionId) {
self.displayActivity = displayActivity self.displayActivity = displayActivity
self.presentationData = presentationData self.presentationData = presentationData
self.text = text self.text = text
self.color = color
self.linkAction = linkAction
self.sectionId = sectionId self.sectionId = sectionId
} }
@ -78,11 +91,21 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
self.addSubnode(self.activityIndicator) self.addSubnode(self.activityIndicator)
} }
override public func didLoad() {
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { _ in
return .waitForSingleTap
}
self.view.addGestureRecognizer(recognizer)
}
public func asyncLayout() -> (_ item: ItemListActivityTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { public func asyncLayout() -> (_ item: ItemListActivityTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
return { item, params, neighbors in return { item, params, neighbors in
let leftInset: CGFloat = 12.0 + params.leftInset let leftInset: CGFloat = 15.0 + params.leftInset
let verticalInset: CGFloat = 7.0 let verticalInset: CGFloat = 7.0
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize) let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
@ -92,14 +115,23 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
activityWidth = 25.0 activityWidth = 25.0
} }
let titleString = NSMutableAttributedString(attributedString: item.text) let textColor: UIColor
let hasFont = titleString.attribute(.font, at: 0, effectiveRange: nil) != nil switch item.color {
if !hasFont { case .generic:
titleString.removeAttribute(NSAttributedString.Key.font, range: NSMakeRange(0, titleString.length)) textColor = item.presentationData.theme.list.freeTextColor
titleString.addAttributes([NSAttributedString.Key.font: titleFont], range: NSMakeRange(0, titleString.length)) case .constructive:
textColor = item.presentationData.theme.list.freeTextSuccessColor
case .destructive:
textColor = item.presentationData.theme.list.freeTextErrorColor
case .warning:
textColor = UIColor(rgb: 0xef8c00)
} }
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 22.0)), insets: UIEdgeInsets())) let attributedString = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.freeTextErrorColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(topLeft: CGSize(width: activityWidth, height: 22.0)), insets: UIEdgeInsets()))
let contentSize: CGSize let contentSize: CGSize
let insets: UIEdgeInsets let insets: UIEdgeInsets
@ -137,4 +169,27 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
} }
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
let titleFrame = self.titleNode.frame
if let item = self.item, titleFrame.contains(location) {
if let (_, attributes) = self.titleNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
item.linkAction?(.tap(url))
}
}
}
default:
break
}
}
default:
break
}
}
} }

View File

@ -426,7 +426,7 @@ public class InfoItemNode: ListViewItemNode {
if let current = self.linkHighlightingNode { if let current = self.linkHighlightingNode {
linkHighlightingNode = current linkHighlightingNode = current
} else { } else {
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)) linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2))
self.linkHighlightingNode = linkHighlightingNode self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
} }

View File

@ -20,6 +20,8 @@ import ContextUI
import FileMediaResourceStatus import FileMediaResourceStatus
import ManagedAnimationNode import ManagedAnimationNode
import ShimmerEffect import ShimmerEffect
import ComponentFlow
import EmojiStatusComponent
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:]) private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
@ -159,6 +161,167 @@ final class CachedChatListSearchResult {
} }
public final class ListMessageFileItemNode: ListMessageNode { public final class ListMessageFileItemNode: ListMessageNode {
public final class DescriptionNode: ASDisplayNode {
let descriptionNode: TextNode
var titleTopicArrowNode: ASImageNode?
var topicTitleNode: TextNode?
var titleTopicIconView: ComponentHostView<Empty>?
var titleTopicIconComponent: EmojiStatusComponent?
var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent {
let _ = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibilityStatus)),
environment: {},
containerSize: titleTopicIconView.bounds.size
)
}
}
}
}
override init() {
self.descriptionNode = TextNode()
self.descriptionNode.displaysAsynchronously = true
super.init()
self.addSubnode(self.descriptionNode)
}
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topic: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) {
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
let makeTopicTitleLayout = TextNode.asyncLayout(self.topicTitleNode)
return { [weak self] context, constrainedWidth, theme, authorTitle, topic in
var maxTitleWidth = constrainedWidth
if let _ = topic {
maxTitleWidth = floor(constrainedWidth * 0.7)
}
let descriptionLayout = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: authorTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
var remainingWidth = constrainedWidth - descriptionLayout.0.size.width
var topicTitleArguments: TextNodeLayoutArguments?
var arrowIconImage: UIImage?
if let topic = topic {
remainingWidth -= 22.0 + 2.0
if authorTitle != nil {
arrowIconImage = PresentationResourcesChatList.topicArrowIcon(theme)
if let arrowIconImage = arrowIconImage {
remainingWidth -= arrowIconImage.size.width + 6.0 * 2.0
}
}
topicTitleArguments = TextNodeLayoutArguments(attributedString: topic.title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))
}
let topicTitleLayout = topicTitleArguments.flatMap(makeTopicTitleLayout)
var size = descriptionLayout.0.size
if let topicTitleLayout = topicTitleLayout {
size.height = max(size.height, topicTitleLayout.0.size.height)
size.width += 10.0 + topicTitleLayout.0.size.width
}
return (size, {
guard let self else {
return
}
let _ = descriptionLayout.1()
let authorFrame = CGRect(origin: CGPoint(), size: descriptionLayout.0.size)
self.descriptionNode.frame = authorFrame
var nextX = authorFrame.maxX - 1.0
if authorTitle == nil {
nextX = 0.0
}
if let arrowIconImage = arrowIconImage {
let titleTopicArrowNode: ASImageNode
if let current = self.titleTopicArrowNode {
titleTopicArrowNode = current
} else {
titleTopicArrowNode = ASImageNode()
self.titleTopicArrowNode = titleTopicArrowNode
self.addSubnode(titleTopicArrowNode)
}
titleTopicArrowNode.image = arrowIconImage
nextX += 6.0
titleTopicArrowNode.frame = CGRect(origin: CGPoint(x: nextX, y: 5.0), size: arrowIconImage.size)
nextX += arrowIconImage.size.width + 6.0
} else {
if let titleTopicArrowNode = self.titleTopicArrowNode {
self.titleTopicArrowNode = nil
titleTopicArrowNode.removeFromSupernode()
}
}
if let topic {
let titleTopicIconView: ComponentHostView<Empty>
if let current = self.titleTopicIconView {
titleTopicIconView = current
} else {
titleTopicIconView = ComponentHostView<Empty>()
self.titleTopicIconView = titleTopicIconView
self.view.addSubview(titleTopicIconView)
}
let titleTopicIconContent: EmojiStatusComponent.Content
if let fileId = topic.iconId, fileId != 0 {
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2))
} else {
titleTopicIconContent = .topic(title: String(topic.title.string.prefix(1)), color: topic.iconColor, size: CGSize(width: 22.0, height: 22.0))
}
let titleTopicIconComponent = EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: titleTopicIconContent,
isVisibleForAnimations: self.visibilityStatus,
action: nil
)
self.titleTopicIconComponent = titleTopicIconComponent
let iconSize = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleTopicIconView.frame = CGRect(origin: CGPoint(x: nextX, y: UIScreenPixel), size: iconSize)
nextX += iconSize.width + 2.0
} else {
if let titleTopicIconView = self.titleTopicIconView {
self.titleTopicIconView = nil
titleTopicIconView.removeFromSuperview()
}
}
if let topicTitleLayout = topicTitleLayout {
let topicTitleNode = topicTitleLayout.1()
if topicTitleNode.supernode == nil {
self.addSubnode(topicTitleNode)
self.topicTitleNode = topicTitleNode
}
topicTitleNode.frame = CGRect(origin: CGPoint(x: nextX - 1.0, y: 0.0), size: topicTitleLayout.0.size)
} else if let topicTitleNode = self.topicTitleNode {
self.topicTitleNode = nil
topicTitleNode.removeFromSupernode()
}
})
}
}
}
private let contextSourceNode: ContextExtractedContentContainingNode private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode private let extractedBackgroundImageNode: ASImageNode
@ -377,6 +540,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode) let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
// let newDescriptionNodeMakeLayout = self.descriptionNode.asyncLayout()
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText) let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
let iconImageLayout = self.iconImageNode.asyncLayout() let iconImageLayout = self.iconImageNode.asyncLayout()
@ -754,6 +918,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
// let forumThreadTitle: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)? = nil
// let (newDescriptionNodeLayout, newDescriptionNodeApply) = newDescriptionNodeMakeLayout(item.context, params.width - leftInset - rightInset - 30.0, item.presentationData.theme.theme, descriptionText, forumThreadTitle)
var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
if extensionTextLayout.truncated, let text = extensionText?.string { if extensionTextLayout.truncated, let text = extensionText?.string {
extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center) extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center)

View File

@ -174,6 +174,7 @@ private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UI
public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode { public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
private let strings: PresentationStrings private let strings: PresentationStrings
private let bot: Bool
private let titleNode: ASTextNode private let titleNode: ASTextNode
private let textNode: ASTextNode private let textNode: ASTextNode
@ -205,9 +206,10 @@ public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
return self.isUserInteractionEnabled return self.isUserInteractionEnabled
} }
public init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction]) { public init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, bot: Bool = false, actions: [TextAlertAction]) {
self.strings = strings self.strings = strings
self.theme = ptheme self.theme = ptheme
self.bot = bot
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2 self.titleNode.maximumNumberOfLines = 2
@ -277,8 +279,18 @@ public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
} }
public override func updateTheme(_ theme: AlertControllerTheme) { public override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPassword, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) let title: String
self.textNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPasswordText, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) let text: String
if self.bot {
title = self.strings.OwnershipTransfer_EnterPassword
text = self.strings.OwnershipTransfer_EnterPasswordText
} else {
title = self.strings.Channel_OwnershipTransfer_EnterPassword
text = self.strings.Channel_OwnershipTransfer_EnterPasswordText
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.actionNodesSeparator.backgroundColor = theme.separatorColor self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes { for actionNode in self.actionNodes {

View File

@ -23,6 +23,7 @@ import ContextUI
import UndoUI import UndoUI
import QrCodeUI import QrCodeUI
import PremiumUI import PremiumUI
import TextFormat
private final class ChannelVisibilityControllerArguments { private final class ChannelVisibilityControllerArguments {
let context: AccountContext let context: AccountContext
@ -41,8 +42,9 @@ private final class ChannelVisibilityControllerArguments {
let toggleApproveMembers: (Bool) -> Void let toggleApproveMembers: (Bool) -> Void
let activateLink: (String) -> Void let activateLink: (String) -> Void
let deactivateLink: (String) -> Void let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) { init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.context = context self.context = context
self.updateCurrentType = updateCurrentType self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
@ -59,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
self.toggleApproveMembers = toggleApproveMembers self.toggleApproveMembers = toggleApproveMembers
self.activateLink = activateLink self.activateLink = activateLink
self.deactivateLink = deactivateLink self.deactivateLink = deactivateLink
self.openAuction = openAuction
} }
} }
@ -108,7 +111,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case privateLinkManageInfo(PresentationTheme, String) case privateLinkManageInfo(PresentationTheme, String)
case publicLinkInfo(PresentationTheme, String) case publicLinkInfo(PresentationTheme, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus) case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case existingLinksInfo(PresentationTheme, String) case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
@ -316,8 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus): case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus { if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
return true return true
} else { } else {
return false return false
@ -629,10 +632,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .publicLinkHeader(_, title): case let .publicLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .publicLinkAvailability(theme, text, value): case let .publicLinkAvailability(_, text, value):
let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: text, color: value ? .generic : .destructive, sectionId: self.section)
attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length))
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section)
case let .linksLimitInfo(theme, text, count, limit, premiumLimit, isPremiumDisabled): case let .linksLimitInfo(theme, text, count, limit, premiumLimit, isPremiumDisabled):
return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section) return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section)
case let .privateLinkHeader(_, title): case let .privateLinkHeader(_, title):
@ -672,26 +673,30 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkInfo(_, text): case let .publicLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkStatus(theme, text, status): case let .publicLinkStatus(_, text, status, username):
var displayActivity = false var displayActivity = false
let color: UIColor let textColor: ItemListActivityTextItem.TextColor
switch status { switch status {
case .invalidFormat: case .invalidFormat:
color = theme.list.freeTextErrorColor textColor = .destructive
case let .availability(availability): case let .availability(availability):
switch availability { switch availability {
case .available: case .available:
color = theme.list.freeTextSuccessColor textColor = .constructive
case .invalid: case .invalid:
color = theme.list.freeTextErrorColor textColor = .destructive
case .taken: case .taken:
color = theme.list.freeTextErrorColor textColor = .destructive
case .purchaseAvailable:
textColor = .generic
} }
case .checking: case .checking:
color = theme.list.freeTextColor textColor = .generic
displayActivity = true displayActivity = true
} }
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: NSAttributedString(string: text, textColor: color), sectionId: self.section) return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
arguments.openAuction(username)
}, sectionId: self.section)
case let .existingLinksInfo(_, text): case let .existingLinksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
@ -1060,12 +1065,20 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
text = presentationData.strings.Channel_Username_InvalidCharacters text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken: case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken text = presentationData.strings.Channel_Username_InvalidTaken
case .purchaseAvailable:
var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
if let entity = entities.first {
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
}
text = markdownString
} }
case .checking: case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername text = presentationData.strings.Channel_Username_CheckingUsername
} }
entries.append(.publicLinkStatus(presentationData.theme, text, status)) entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
} }
if isGroup { if isGroup {
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil { if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
@ -1253,12 +1266,20 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
text = presentationData.strings.Channel_Username_InvalidCharacters text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken: case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken text = presentationData.strings.Channel_Username_InvalidTaken
case .purchaseAvailable:
var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
if let entity = entities.first {
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
}
text = markdownString
} }
case .checking: case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername text = presentationData.strings.Channel_Username_CheckingUsername
} }
entries.append(.publicLinkStatus(presentationData.theme, text, status)) entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
} }
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp)) entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
@ -1725,6 +1746,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start() let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
})]), nil) })]), nil)
}) })
}, openAuction: { username in
dismissInputImpl?()
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}) })
let peerView = context.account.viewTracker.peerView(peerId) let peerView = context.account.viewTracker.peerView(peerId)

View File

@ -252,7 +252,7 @@ final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0))
) )
context.add(text context.add(text
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 80.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 60.0 + text.size.height / 2.0))
) )
context.add(content context.add(content
.position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0)) .position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0))
@ -718,7 +718,7 @@ private final class DemoSheetContent: CombinedComponent {
decoration: .fasterStars decoration: .fasterStars
)), )),
title: strings.Premium_FasterSpeed, title: strings.Premium_FasterSpeed,
text: strings.Premium_FasterSpeedInfo, text: isStandalone ? strings.Premium_FasterSpeedStandaloneInfo : strings.Premium_FasterSpeedInfo,
textColor: textColor textColor: textColor
) )
) )
@ -736,7 +736,7 @@ private final class DemoSheetContent: CombinedComponent {
decoration: .badgeStars decoration: .badgeStars
)), )),
title: strings.Premium_VoiceToText, title: strings.Premium_VoiceToText,
text: strings.Premium_VoiceToTextInfo, text: isStandalone ? strings.Premium_VoiceToTextStandaloneInfo : strings.Premium_VoiceToTextInfo,
textColor: textColor textColor: textColor
) )
) )
@ -826,7 +826,7 @@ private final class DemoSheetContent: CombinedComponent {
decoration: .swirlStars decoration: .swirlStars
)), )),
title: strings.Premium_ChatManagement, title: strings.Premium_ChatManagement,
text: strings.Premium_ChatManagementInfo, text: isStandalone ? strings.Premium_ChatManagementStandaloneInfo : strings.Premium_ChatManagementInfo,
textColor: textColor textColor: textColor
) )
) )
@ -972,6 +972,10 @@ private final class DemoSheetContent: CombinedComponent {
buttonText = strings.Premium_Gift_GiftSubscription(price ?? "").string buttonText = strings.Premium_Gift_GiftSubscription(price ?? "").string
case .other: case .other:
switch component.subject { switch component.subject {
case .fasterDownload:
buttonText = strings.Premium_FasterSpeed_Proceed
case .advancedChatManagement:
buttonText = strings.Premium_ChatManagement_Proceed
case .uniqueReactions: case .uniqueReactions:
buttonText = strings.Premium_Reactions_Proceed buttonText = strings.Premium_Reactions_Proceed
buttonAnimationName = "premium_unlock" buttonAnimationName = "premium_unlock"
@ -1030,6 +1034,10 @@ private final class DemoSheetContent: CombinedComponent {
var contentHeight: CGFloat = context.availableSize.width + 146.0 var contentHeight: CGFloat = context.availableSize.width + 146.0
if case .other = component.source { if case .other = component.source {
contentHeight -= 40.0 contentHeight -= 40.0
if [.advancedChatManagement, .fasterDownload].contains(component.subject) {
contentHeight += 20.0
}
} }
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 20.0), size: button.size) let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 20.0), size: button.size)

View File

@ -159,6 +159,12 @@ public enum PremiumSource: Equatable {
} else { } else {
return false return false
} }
case .fasterDownload:
if case .fasterDownload = rhs {
return true
} else {
return false
}
} }
} }
@ -184,6 +190,7 @@ public enum PremiumSource: Equatable {
case gift(from: PeerId, to: PeerId, duration: Int32) case gift(from: PeerId, to: PeerId, duration: Int32)
case giftTerms case giftTerms
case voiceToText case voiceToText
case fasterDownload
var identifier: String? { var identifier: String? {
switch self { switch self {
@ -225,6 +232,8 @@ public enum PremiumSource: Equatable {
return "emoji_status" return "emoji_status"
case .voiceToText: case .voiceToText:
return "voice_to_text" return "voice_to_text"
case .fasterDownload:
return "faster_download"
case .gift, .giftTerms: case .gift, .giftTerms:
return nil return nil
case let .deeplink(reference): case let .deeplink(reference):

View File

@ -51,6 +51,10 @@ extension SettingsSearchableItemIcon {
return PresentationResourcesSettings.chatFolders return PresentationResourcesSettings.chatFolders
case .deleteAccount: case .deleteAccount:
return PresentationResourcesSettings.deleteAccount return PresentationResourcesSettings.deleteAccount
case .devices:
return PresentationResourcesSettings.devices
case .premium:
return PresentationResourcesSettings.premium
} }
} }
} }

View File

@ -18,6 +18,8 @@ import PhoneNumberFormat
import AccountUtils import AccountUtils
import InstantPageCache import InstantPageCache
import NotificationPeerExceptionController import NotificationPeerExceptionController
import QrCodeUI
import PremiumUI
enum SettingsSearchableItemIcon { enum SettingsSearchableItemIcon {
case profile case profile
@ -37,6 +39,8 @@ enum SettingsSearchableItemIcon {
case faq case faq
case chatFolders case chatFolders
case deleteAccount case deleteAccount
case devices
case premium
} }
public enum SettingsSearchableItemId: Hashable { public enum SettingsSearchableItemId: Hashable {
@ -57,6 +61,8 @@ public enum SettingsSearchableItemId: Hashable {
case faq(Int32) case faq(Int32)
case chatFolders(Int32) case chatFolders(Int32)
case deleteAccount(Int32) case deleteAccount(Int32)
case devices(Int32)
case premium(Int32)
private var namespace: Int32 { private var namespace: Int32 {
switch self { switch self {
@ -94,6 +100,10 @@ public enum SettingsSearchableItemId: Hashable {
return 16 return 16
case .deleteAccount: case .deleteAccount:
return 17 return 17
case .devices:
return 18
case .premium:
return 19
} }
} }
@ -115,7 +125,9 @@ public enum SettingsSearchableItemId: Hashable {
let .support(id), let .support(id),
let .faq(id), let .faq(id),
let .chatFolders(id), let .chatFolders(id),
let .deleteAccount(id): let .deleteAccount(id),
let .devices(id),
let .premium(id):
return id return id
} }
} }
@ -162,6 +174,10 @@ public enum SettingsSearchableItemId: Hashable {
self = .chatFolders(id) self = .chatFolders(id)
case 17: case 17:
self = .deleteAccount(id) self = .deleteAccount(id)
case 18:
self = .devices(id)
case 19:
self = .premium(id)
default: default:
return nil return nil
} }
@ -233,6 +249,103 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool
return items return items
} }
private func devicesSearchableItems(context: AccountContext, activeSessionsContext: ActiveSessionsContext?, webSessionsContext: WebSessionsContext?) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .devices
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var result: [SettingsSearchableItem] = []
if let activeSessionsContext = activeSessionsContext {
result.append(SettingsSearchableItem(id: .devices(0), title: strings.Settings_Devices, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions) + [strings.PrivacySettings_AuthSessions], icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false))
}))
result.append(SettingsSearchableItem(id: .devices(1), title: strings.AuthSessions_TerminateOtherSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Devices_TerminateOtherSessions), icon: icon, breadcrumbs: [strings.Settings_Devices], present: { context, _, present in
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false))
}))
result.append(SettingsSearchableItem(id: .devices(2), title: strings.AuthSessions_LinkDesktopDevice, alternate: synonyms(strings.SettingsSearch_Synonyms_Devices_LinkDesktopDevice), icon: icon, breadcrumbs: [strings.Settings_Devices], present: { context, _, present in
present(.push, QrCodeScanScreen(context: context, subject: .authTransfer(activeSessionsContext: activeSessionsContext)))
}))
}
return result
}
private func premiumSearchableItems(context: AccountContext) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .premium
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var result: [SettingsSearchableItem] = []
result.append(SettingsSearchableItem(id: .premium(0), title: strings.Settings_Premium, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, PremiumIntroScreen(context: context, modal: false, source: .settings))
}))
let presentDemo: (PremiumDemoScreen.Subject, (SettingsSearchableItemPresentation, ViewController?) -> Void) -> Void = { subject, present in
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: subject, action: {
let controller = PremiumIntroScreen(context: context, modal: false, source: .settings)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
present(.push, controller)
}
result.append(SettingsSearchableItem(id: .premium(1), title: strings.Premium_DoubledLimits, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_DoubledLimits), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { _, _, present in
presentDemo(.doubleLimits, present)
}))
result.append(SettingsSearchableItem(id: .premium(2), title: strings.Premium_UploadSize, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_UploadSize), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.moreUpload, present)
}))
result.append(SettingsSearchableItem(id: .premium(3), title: strings.Premium_FasterSpeed, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_FasterSpeed), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.fasterDownload, present)
}))
result.append(SettingsSearchableItem(id: .premium(4), title: strings.Premium_VoiceToText, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_VoiceToText), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.voiceToText, present)
}))
result.append(SettingsSearchableItem(id: .premium(5), title: strings.Premium_NoAds, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_NoAds), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.noAds, present)
}))
result.append(SettingsSearchableItem(id: .premium(6), title: strings.Premium_EmojiStatus, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_EmojiStatus), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.emojiStatus, present)
}))
result.append(SettingsSearchableItem(id: .premium(7), title: strings.Premium_Reactions, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Reactions), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.uniqueReactions, present)
}))
result.append(SettingsSearchableItem(id: .premium(8), title: strings.Premium_Stickers, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Stickers), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.premiumStickers, present)
}))
result.append(SettingsSearchableItem(id: .premium(9), title: strings.Premium_AnimatedEmoji, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_AnimatedEmoji), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.animatedEmoji, present)
}))
result.append(SettingsSearchableItem(id: .premium(10), title: strings.Premium_ChatManagement, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_ChatManagement), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.advancedChatManagement, present)
}))
result.append(SettingsSearchableItem(id: .premium(11), title: strings.Premium_Badge, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Badge), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.profileBadge, present)
}))
result.append(SettingsSearchableItem(id: .premium(12), title: strings.Premium_Avatar, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_Avatar), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.animatedUserpics, present)
}))
result.append(SettingsSearchableItem(id: .premium(13), title: strings.Premium_AppIcon, alternate: synonyms(strings.SettingsSearch_Synonyms_Premium_AppIcon), icon: icon, breadcrumbs: [strings.Settings_Premium], present: { context, _, present in
presentDemo(.appIcons, present)
}))
return result
}
private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .calls let icon: SettingsSearchableItemIcon = .calls
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
@ -532,9 +645,6 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
SettingsSearchableItem(id: .privacy(8), title: strings.PrivacySettings_TwoStepAuth, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_TwoStepAuth), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in SettingsSearchableItem(id: .privacy(8), title: strings.PrivacySettings_TwoStepAuth, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_TwoStepAuth), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.push, twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: true, data: nil))) present(.push, twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: true, data: nil)))
}), }),
activeSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(9), title: strings.Settings_Devices, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions) + [strings.PrivacySettings_AuthSessions], icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext!, webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: false))
}),
webSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_WebSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in webSessionsContext == nil ? nil : SettingsSearchableItem(id: .privacy(10), title: strings.PrivacySettings_WebSessions, alternate: synonyms(strings.SettingsSearch_Synonyms_Privacy_AuthSessions), icon: icon, breadcrumbs: [strings.Settings_PrivacySettings], present: { context, _, present in
present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext ?? context.engine.privacy.activeSessions(), webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: true)) present(.push, recentSessionsController(context: context, activeSessionsContext: activeSessionsContext ?? context.engine.privacy.activeSessions(), webSessionsContext: webSessionsContext ?? context.engine.privacy.webSessions(), websitesOnly: true))
}), }),
@ -724,6 +834,14 @@ private func languageSearchableItems(context: AccountContext, localizations: [Lo
})) }))
index += 1 index += 1
} }
items.append(SettingsSearchableItem(id: .language(1000), title: strings.Localization_ShowTranslate, alternate: synonyms(strings.SettingsSearch_Synonyms_Language_ShowTranslateButton), icon: icon, breadcrumbs: [strings.Settings_AppLanguage], present: { context, _, present in
present(.push, LocalizationListController(context: context))
}))
items.append(SettingsSearchableItem(id: .language(1001), title: strings.Localization_DoNotTranslate, alternate: synonyms(strings.SettingsSearch_Synonyms_Language_DoNotTranslate), icon: icon, breadcrumbs: [strings.Settings_AppLanguage], present: { context, _, present in
present(.push, LocalizationListController(context: context))
}))
return items return items
} }
@ -838,6 +956,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
}) })
allItems.append(savedMessages) allItems.append(savedMessages)
let devicesItems = devicesSearchableItems(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: activeWebSessionsContext)
allItems.append(contentsOf: devicesItems)
let callItems = callSearchableItems(context: context) let callItems = callSearchableItems(context: context)
allItems.append(contentsOf: callItems) allItems.append(contentsOf: callItems)
@ -867,6 +988,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
let languageItems = languageSearchableItems(context: context, localizations: localizations) let languageItems = languageSearchableItems(context: context, localizations: localizations)
allItems.append(contentsOf: languageItems) allItems.append(contentsOf: languageItems)
let premiumItems = premiumSearchableItems(context: context)
allItems.append(contentsOf: premiumItems)
if watchAppInstalled { if watchAppInstalled {
let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: synonyms(strings.SettingsSearch_Synonyms_Watch), icon: .watch, breadcrumbs: [], present: { context, _, present in let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: synonyms(strings.SettingsSearch_Synonyms_Watch), icon: .watch, breadcrumbs: [], present: { context, _, present in
present(.push, watchSettingsController(context: context)) present(.push, watchSettingsController(context: context))

View File

@ -11,22 +11,23 @@ import AccountContext
import ShareController import ShareController
import UndoUI import UndoUI
import InviteLinksUI import InviteLinksUI
import TextFormat
private final class UsernameSetupControllerArguments { private final class UsernameSetupControllerArguments {
let account: Account let account: Account
let updatePublicLinkText: (String?, String) -> Void let updatePublicLinkText: (String?, String) -> Void
let shareLink: () -> Void let shareLink: () -> Void
let activateLink: (String) -> Void let activateLink: (String) -> Void
let deactivateLink: (String) -> Void let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) { init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.account = account self.account = account
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
self.shareLink = shareLink self.shareLink = shareLink
self.activateLink = activateLink self.activateLink = activateLink
self.deactivateLink = deactivateLink self.deactivateLink = deactivateLink
self.openAuction = openAuction
} }
} }
@ -55,7 +56,7 @@ private enum UsernameSetupEntryId: Hashable {
private enum UsernameSetupEntry: ItemListNodeEntry { private enum UsernameSetupEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String) case publicLinkHeader(PresentationTheme, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String) case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String) case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String, String)
case publicLinkInfo(PresentationTheme, String) case publicLinkInfo(PresentationTheme, String)
case additionalLinkHeader(PresentationTheme, String) case additionalLinkHeader(PresentationTheme, String)
@ -110,8 +111,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText): case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText { if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
return true return true
} else { } else {
return false return false
@ -207,24 +208,28 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
arguments.shareLink() arguments.shareLink()
} }
}) })
case let .publicLinkStatus(theme, _, status, text): case let .publicLinkStatus(_, _, status, text, username):
var displayActivity = false var displayActivity = false
let string: NSAttributedString let textColor: ItemListActivityTextItem.TextColor
switch status { switch status {
case .invalidFormat: case .invalidFormat:
string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor) textColor = .destructive
case let .availability(availability): case let .availability(availability):
switch availability { switch availability {
case .available: case .available:
string = NSAttributedString(string: text, textColor: theme.list.freeTextSuccessColor) textColor = .constructive
case .purchaseAvailable:
textColor = .generic
case .invalid, .taken: case .invalid, .taken:
string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor) textColor = .destructive
} }
case .checking: case .checking:
string = NSAttributedString(string: text, textColor: theme.list.freeTextColor) textColor = .generic
displayActivity = true displayActivity = true
} }
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: string, sectionId: self.section) return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
arguments.openAuction(username)
}, sectionId: self.section)
case let .additionalLinkHeader(_, text): case let .additionalLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .additionalLink(_, link, _): case let .additionalLink(_, link, _):
@ -328,11 +333,19 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
statusText = presentationData.strings.Username_InvalidCharacters statusText = presentationData.strings.Username_InvalidCharacters
case .taken: case .taken:
statusText = presentationData.strings.Username_InvalidTaken statusText = presentationData.strings.Username_InvalidTaken
case .purchaseAvailable:
var markdownString = presentationData.strings.Username_UsernamePurchaseAvailable
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
if let entity = entities.first {
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
}
statusText = markdownString
} }
case .checking: case .checking:
statusText = presentationData.strings.Username_CheckingUsername statusText = presentationData.strings.Username_CheckingUsername
} }
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText)) entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText, currentUsername))
} }
var infoText = presentationData.strings.Username_Help var infoText = presentationData.strings.Username_Help
@ -460,6 +473,10 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: { presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start() let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
})]), nil) })]), nil)
}, openAuction: { username in
dismissInputImpl?()
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}) })
let temporaryOrder = Promise<[String]?>(nil) let temporaryOrder = Promise<[String]?>(nil)
@ -669,6 +686,5 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
return controller return controller
} }

View File

@ -683,12 +683,16 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
} }
public var isMusic: Bool { public var isMusic: Bool {
var hasNonVoiceAudio = false
var hasVideo = false
for attribute in self.attributes { for attribute in self.attributes {
if case .Audio(false, _, _, _, _) = attribute { if case .Audio(false, _, _, _, _) = attribute {
return true hasNonVoiceAudio = true
} else if case .Video = attribute {
hasVideo = true
} }
} }
return false return hasNonVoiceAudio && !hasVideo
} }
public var isVoice: Bool { public var isVoice: Bool {

View File

@ -17,6 +17,7 @@ public enum AddressNameAvailability: Equatable {
case available case available
case invalid case invalid
case taken case taken
case purchaseAvailable
} }
public enum AddressNameDomain { public enum AddressNameDomain {
@ -33,7 +34,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -
if index == 0 { if index == 0 {
return .startsWithUnderscore return .startsWithUnderscore
} else if index == length - 1 { } else if index == length - 1 {
return length < 5 ? .tooShort : .endsWithUnderscore return length < 4 ? .tooShort : .endsWithUnderscore
} }
} }
if index == 0 && char >= "0" && char <= "9" { if index == 0 && char >= "0" && char <= "9" {
@ -45,7 +46,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -
index += 1 index += 1
} }
if length < 5 && (!canEmpty || length != 0) { if length < 4 && (!canEmpty || length != 0) {
return .tooShort return .tooShort
} }
return nil return nil
@ -65,8 +66,12 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
} }
} }
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in |> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid) return .single(.invalid)
} }
}
case let .peer(peerId): case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name)) return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: name))
@ -79,8 +84,12 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
} }
} }
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in |> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid) return .single(.invalid)
} }
}
} else if peerId.namespace == Namespaces.Peer.CloudGroup { } else if peerId.namespace == Namespaces.Peer.CloudGroup {
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name)) return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|> map { result -> AddressNameAvailability in |> map { result -> AddressNameAvailability in
@ -92,8 +101,12 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
} }
} }
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in |> `catch` { error -> Signal<AddressNameAvailability, NoError> in
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
return .single(.purchaseAvailable)
} else {
return .single(.invalid) return .single(.invalid)
} }
}
} else { } else {
return .single(.invalid) return .single(.invalid)
} }

View File

@ -112,6 +112,10 @@ public enum PresentationResourceKey: Int32 {
case chatBubbleVerticalLineIncomingImage case chatBubbleVerticalLineIncomingImage
case chatBubbleVerticalLineOutgoingImage case chatBubbleVerticalLineOutgoingImage
case chatBubbleArrowFreeImage
case chatBubbleArrowIncomingImage
case chatBubbleArrowOutgoingImage
case chatBubbleCheckBubbleFullImage case chatBubbleCheckBubbleFullImage
case chatBubbleBubblePartialImage case chatBubbleBubblePartialImage
case checkBubbleMediaFullImage case checkBubbleMediaFullImage

View File

@ -122,6 +122,28 @@ public struct PresentationResourcesChat {
}) })
} }
public static func chatBubbleArrowImage(color: UIColor) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: color)
}
public static func chatBubbleArrowFreeImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleArrowFreeImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: UIColor(white: 1.0, alpha: 0.3))
})
}
public static func chatBubbleArrowIncomingImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleArrowIncomingImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.3))
})
}
public static func chatBubbleArrowOutgoingImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleArrowOutgoingImage.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: theme.chat.message.outgoing.accentTextColor.withAlphaComponent(0.3))
})
}
public static func chatBubbleConsumableContentIncomingIcon(_ theme: PresentationTheme) -> UIImage? { public static func chatBubbleConsumableContentIncomingIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in
return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor) return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor)

View File

@ -224,20 +224,7 @@ public final class EmojiStatusComponent: Component {
iconImage = nil iconImage = nil
} }
case let .topic(title, color, realSize): case let .topic(title, color, realSize):
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) { let colors = topicIconColors(for: color)
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let colors = topicColors[color] ?? generateTopicColors(color)
if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) { if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) {
iconImage = image iconImage = image
} else { } else {
@ -575,3 +562,16 @@ public final class EmojiStatusComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
} }
} }
public func topicIconColors(for color: Int32) -> ([UInt32], [UInt32]) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}

View File

@ -2944,7 +2944,7 @@ public final class EmojiPagerContentComponent: Component {
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
} }
case let .topic(title, color): case let .topic(title, color):
let colors = self.getTopicColors(color) let colors = topicIconColors(for: color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
@ -2993,19 +2993,6 @@ public final class EmojiPagerContentComponent: Component {
return nullAction return nullAction
} }
private func getTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
func update(content: ItemContent) { func update(content: ItemContent) {
if self.content != content { if self.content != content {
if case let .icon(icon) = content, case let .topic(title, color) = icon { if case let .icon(icon) = content, case let .topic(title, color) = icon {
@ -3014,7 +3001,7 @@ public final class EmojiPagerContentComponent: Component {
UIGraphicsPushContext(context) UIGraphicsPushContext(context)
let colors = self.getTopicColors(color) let colors = topicIconColors(for: color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size let imageSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))

View File

@ -430,8 +430,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
self.title = "" self.title = ""
self.fileId = 0 self.fileId = 0
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F] self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
self.iconColor = colors.randomElement() ?? 0x0
case let .edit(info): case let .edit(info):
self.title = info.title self.title = info.title
self.fileId = info.icon ?? 0 self.fileId = info.icon ?? 0

View File

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

View File

@ -0,0 +1,150 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.005127 2.995117 cm
0.000000 0.000000 0.000000 scn
1.330000 9.000000 m
1.330000 13.236024 4.763976 16.670000 9.000000 16.670000 c
13.236024 16.670000 16.670000 13.236024 16.670000 9.000000 c
16.670000 4.763976 13.236024 1.330000 9.000000 1.330000 c
4.763976 1.330000 1.330000 4.763976 1.330000 9.000000 c
h
9.000000 18.000000 m
4.029438 18.000000 0.000000 13.970562 0.000000 9.000000 c
0.000000 4.029437 4.029438 -0.000002 9.000000 -0.000002 c
13.970563 -0.000002 18.000002 4.029437 18.000002 9.000000 c
18.000002 13.970562 13.970563 18.000000 9.000000 18.000000 c
h
6.012677 4.763371 m
5.846505 4.591045 5.641757 4.504883 5.398434 4.504883 c
5.161045 4.504883 4.956297 4.591045 4.784191 4.763371 c
4.612084 4.941640 4.526031 5.149619 4.526031 5.387310 c
4.526031 5.625002 4.612084 5.830010 4.784191 6.002336 c
4.956297 6.174662 5.161045 6.260825 5.398434 6.260825 c
5.641757 6.260825 5.846505 6.174662 6.012677 6.002336 c
6.184784 5.830010 6.270837 5.625002 6.270837 5.387310 c
6.270837 5.149619 6.184784 4.941640 6.012677 4.763371 c
h
4.490422 8.355477 m
4.318315 8.183151 4.110600 8.096988 3.867277 8.096988 c
3.629888 8.096988 3.425140 8.183151 3.253033 8.355477 c
3.080926 8.527802 2.994873 8.732811 2.994873 8.970503 c
2.994873 9.214136 3.080926 9.419145 3.253033 9.585527 c
3.425140 9.757854 3.629888 9.844017 3.867277 9.844017 c
4.110600 9.844017 4.318315 9.757854 4.490422 9.585527 c
4.662529 9.419145 4.748582 9.214136 4.748582 8.970503 c
4.748582 8.732811 4.662529 8.527802 4.490422 8.355477 c
h
5.994873 11.938669 m
5.828701 11.766342 5.623953 11.680180 5.380630 11.680180 c
5.143241 11.680180 4.938493 11.766342 4.766387 11.938669 c
4.594280 12.110994 4.508226 12.316004 4.508226 12.553694 c
4.508226 12.797327 4.594280 13.002337 4.766387 13.168720 c
4.938493 13.341045 5.143241 13.427209 5.380630 13.427209 c
5.623953 13.427209 5.828701 13.341045 5.994873 13.168720 c
6.166980 13.002337 6.253033 12.797327 6.253033 12.553694 c
6.253033 12.316004 6.166980 12.110994 5.994873 11.938669 c
h
9.609117 13.507429 m
9.437010 13.335104 9.232262 13.248940 8.994873 13.248940 c
8.751550 13.248940 8.543835 13.335104 8.371728 13.507429 c
8.205556 13.679756 8.122470 13.884764 8.122470 14.122455 c
8.122470 14.366088 8.205556 14.574068 8.371728 14.746394 c
8.543835 14.918720 8.751550 15.004883 8.994873 15.004883 c
9.232262 15.004883 9.437010 14.918720 9.609117 14.746394 c
9.781223 14.574068 9.867277 14.366088 9.867277 14.122455 c
9.867277 13.884764 9.781223 13.679756 9.609117 13.507429 c
h
14.736713 8.355477 m
14.564607 8.183151 14.359859 8.096988 14.122470 8.096988 c
13.879147 8.096988 13.671432 8.183151 13.499325 8.355477 c
13.333152 8.527802 13.250066 8.732811 13.250066 8.970503 c
13.250066 9.214136 13.333152 9.419145 13.499325 9.585527 c
13.671432 9.757854 13.879147 9.844017 14.122470 9.844017 c
14.359859 9.844017 14.564607 9.757854 14.736713 9.585527 c
14.908820 9.419145 14.994873 9.214136 14.994873 8.970503 c
14.994873 8.732811 14.908820 8.527802 14.736713 8.355477 c
h
13.205556 4.763371 m
13.033449 4.591045 12.828701 4.504883 12.591312 4.504883 c
12.353924 4.504883 12.149176 4.591045 11.977069 4.763371 c
11.804962 4.941640 11.718909 5.149619 11.718909 5.387310 c
11.718909 5.625002 11.804962 5.830010 11.977069 6.002336 c
12.149176 6.174662 12.353924 6.260825 12.591312 6.260825 c
12.828701 6.260825 13.033449 6.174662 13.205556 6.002336 c
13.377663 5.830010 13.463717 5.625002 13.463717 5.387310 c
13.463717 5.149619 13.377663 4.941640 13.205556 4.763371 c
h
8.362825 7.169993 m
8.083894 7.217531 7.822766 7.360146 7.579443 7.597836 c
7.342054 7.841470 7.196653 8.102930 7.143241 8.382217 c
7.095763 8.667446 7.134339 8.943762 7.258968 9.211164 c
7.383597 9.484509 7.585378 9.719229 7.864309 9.915324 c
12.769354 13.355902 l
12.917722 13.456921 13.057188 13.483660 13.187752 13.436122 c
13.318316 13.394526 13.407336 13.308363 13.454814 13.177633 c
13.502292 13.046904 13.472618 12.907259 13.365793 12.758703 c
9.885081 7.891979 l
9.683301 7.612693 9.448879 7.410655 9.181817 7.285868 c
8.914755 7.161079 8.641757 7.122455 8.362825 7.169993 c
h
f*
n
Q
endstream
endobj
3 0 obj
4148
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000004238 00000 n
0000004261 00000 n
0000004434 00000 n
0000004508 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4567
%%EOF

View File

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

View File

@ -0,0 +1,92 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.000000 -0.459961 cm
0.000000 0.000000 0.000000 scn
0.470226 9.930187 m
0.210527 10.189886 -0.210527 10.189886 -0.470226 9.930187 c
-0.729925 9.670488 -0.729925 9.249434 -0.470226 8.989735 c
0.470226 9.930187 l
h
4.000000 5.459961 m
4.470226 4.989735 l
4.729925 5.249434 4.729925 5.670488 4.470226 5.930187 c
4.000000 5.459961 l
h
-0.470226 1.930187 m
-0.729925 1.670488 -0.729925 1.249434 -0.470226 0.989735 c
-0.210527 0.730036 0.210527 0.730036 0.470226 0.989735 c
-0.470226 1.930187 l
h
-0.470226 8.989735 m
3.529774 4.989735 l
4.470226 5.930187 l
0.470226 9.930187 l
-0.470226 8.989735 l
h
3.529774 5.930187 m
-0.470226 1.930187 l
0.470226 0.989735 l
4.470226 4.989735 l
3.529774 5.930187 l
h
f
n
Q
endstream
endobj
3 0 obj
772
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 8.000000 10.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000862 00000 n
0000000884 00000 n
0000001056 00000 n
0000001130 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1189
%%EOF

View File

@ -1057,6 +1057,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var tip: ContextController.Tip? var tip: ContextController.Tip?
if tip == nil { if tip == nil {
let isAd = message.adAttribute != nil
var isAction = false var isAction = false
for media in message.media { for media in message.media {
if media is TelegramMediaAction { if media is TelegramMediaAction {
@ -1064,7 +1066,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break break
} }
} }
if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction { if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction && !isAd {
if case .scheduledMessages = strongSelf.subject { if case .scheduledMessages = strongSelf.subject {
} else { } else {
var isChannel = false var isChannel = false
@ -1075,7 +1077,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} else { } else {
let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count
let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 && !isAd
if displayTextSelectionTip { if displayTextSelectionTip {
let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start() let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start()
tip = .textSelection tip = .textSelection
@ -1088,11 +1090,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
actions.context = strongSelf.context actions.context = strongSelf.context
actions.animationCache = strongSelf.controllerInteraction?.presentationContext.animationCache actions.animationCache = strongSelf.controllerInteraction?.presentationContext.animationCache
//let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty { if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty {
actions.reactionItems = topReactions.map(ReactionContextItem.reaction) actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
actions.selectedReactionItems = selectedReactions.reactions actions.selectedReactionItems = selectedReactions.reactions
@ -1821,6 +1820,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId) self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
}, navigateToMessageStandalone: { [weak self] id in }, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false) self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
}
}, tapMessage: nil, clickThroughMessage: { [weak self] in }, tapMessage: nil, clickThroughMessage: { [weak self] in
self?.chatDisplayNode.dismissInput() self?.chatDisplayNode.dismissInput()
}, toggleMessagesSelection: { [weak self] ids, value in }, toggleMessagesSelection: { [weak self] ids, value in

View File

@ -70,6 +70,7 @@ public final class ChatControllerInteraction {
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void let navigateToMessage: (MessageId, MessageId) -> Void
let navigateToMessageStandalone: (MessageId) -> Void let navigateToMessageStandalone: (MessageId) -> Void
let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void
let tapMessage: ((Message) -> Void)? let tapMessage: ((Message) -> Void)?
let clickThroughMessage: () -> Void let clickThroughMessage: () -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void let toggleMessagesSelection: ([MessageId], Bool) -> Void
@ -178,6 +179,7 @@ public final class ChatControllerInteraction {
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
navigateToMessage: @escaping (MessageId, MessageId) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void,
navigateToMessageStandalone: @escaping (MessageId) -> Void, navigateToMessageStandalone: @escaping (MessageId) -> Void,
navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void,
tapMessage: ((Message) -> Void)?, tapMessage: ((Message) -> Void)?,
clickThroughMessage: @escaping () -> Void, clickThroughMessage: @escaping () -> Void,
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
@ -269,6 +271,7 @@ public final class ChatControllerInteraction {
self.openMessageContextActions = openMessageContextActions self.openMessageContextActions = openMessageContextActions
self.navigateToMessage = navigateToMessage self.navigateToMessage = navigateToMessage
self.navigateToMessageStandalone = navigateToMessageStandalone self.navigateToMessageStandalone = navigateToMessageStandalone
self.navigateToThreadMessage = navigateToThreadMessage
self.tapMessage = tapMessage self.tapMessage = tapMessage
self.clickThroughMessage = clickThroughMessage self.clickThroughMessage = clickThroughMessage
self.toggleMessagesSelection = toggleMessagesSelection self.toggleMessagesSelection = toggleMessagesSelection

View File

@ -1446,7 +1446,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let restrictedNode = self.restrictedNode { if let restrictedNode = self.restrictedNode {
transition.updateFrame(node: restrictedNode, frame: contentBounds) transition.updateFrame(node: restrictedNode, frame: contentBounds)
restrictedNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) restrictedNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
restrictedNode.updateLayout(backgroundNode: self.backgroundNode, size: contentBounds.size, transition: transition) restrictedNode.updateLayout(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: self.chatPresentationInterfaceState.theme, wallpaper: self.chatPresentationInterfaceState.chatWallpaper), fontSize: self.chatPresentationInterfaceState.fontSize, strings: self.chatPresentationInterfaceState.strings, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), backgroundNode: self.backgroundNode, size: contentBounds.size, transition: transition)
} }
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
@ -2343,7 +2343,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNodeContainer.isHidden = true self.historyNodeContainer.isHidden = true
self.navigateButtons.isHidden = true self.navigateButtons.isHidden = true
self.loadingNode.isHidden = true self.loadingNode.isHidden = true
self.loadingPlaceholderNode?.isHidden = true
self.emptyNode?.isHidden = true self.emptyNode?.isHidden = true
self.updateIsLoading(isLoading: false, earlier: false, animated: false)
} else if let restrictedNode = self.restrictedNode { } else if let restrictedNode = self.restrictedNode {
self.restrictedNode = nil self.restrictedNode = nil
restrictedNode.removeFromSupernode() restrictedNode.removeFromSupernode()

View File

@ -963,8 +963,6 @@ final class ChatEmptyNode: ASDisplayNode {
self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), enableBlur: dateFillNeedsBlur(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), transition: .immediate) self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), enableBlur: dateFillNeedsBlur(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), transition: .immediate)
} }
var isScheduledMessages = false var isScheduledMessages = false
if case .scheduledMessages = interfaceState.subject { if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true isScheduledMessages = true

View File

@ -614,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId)) let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?), NoError> = combineLatest( let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
loadLimits, loadLimits,
loadStickerSaveStatusSignal, loadStickerSaveStatusSignal,
loadResourceStatusSignal, loadResourceStatusSignal,
@ -626,9 +626,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
context.engine.stickers.availableReactions(), context.engine.stickers.availableReactions(),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]),
context.engine.peers.notificationSoundList() |> take(1) context.engine.peers.notificationSoundList() |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
) )
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?) in |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
let (limitsConfiguration, appConfig) = limitsAndAppConfig let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false var canEdit = false
if !isAction { if !isAction {
@ -652,12 +653,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
loggingSettings = LoggingSettings.defaultSettings loggingSettings = LoggingSettings.defaultSettings
} }
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList) return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer)
} }
return dataSignal return dataSignal
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList -> ContextController.Items in |> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer -> ContextController.Items in
let isPremium = accountPeer?.isPremium ?? false
var actions: [ContextMenuItem] = [] var actions: [ContextMenuItem] = []
var isPinnedMessages = false var isPinnedMessages = false
@ -937,6 +940,53 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} }
} }
var isDownloading = false
let resourceAvailable: Bool
if let resourceStatus = data.resourceStatus {
if case .Local = resourceStatus {
resourceAvailable = true
} else {
resourceAvailable = false
}
if case .Fetching = resourceStatus {
isDownloading = true
}
} else {
resourceAvailable = false
}
if !isPremium && isDownloading {
var isLargeFile = false
for media in message.media {
if let file = media as? TelegramMediaFile {
if let size = file.size, size >= 300 * 1024 * 1024 {
isLargeFile = true
}
break
}
}
if isLargeFile {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_IncreaseSpeed, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
let context = context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
controllerInteraction.navigationController()?.pushViewController(controller)
f(.dismissWithoutContent)
})))
actions.append(.separator)
}
}
var isReplyThreadHead = false var isReplyThreadHead = false
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
@ -970,12 +1020,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}))) })))
} }
let resourceAvailable: Bool
if let resourceStatus = data.resourceStatus, case .Local = resourceStatus {
resourceAvailable = true
} else {
resourceAvailable = false
}
var messageText: String = "" var messageText: String = ""
for message in messages { for message in messages {

View File

@ -58,8 +58,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
class ChatMessageShareButton: HighlightableButtonNode { class ChatMessageShareButton: HighlightableButtonNode {
private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
private var iconOffset = CGPoint() private var iconOffset = CGPoint()
@ -242,7 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode? private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode? private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -472,6 +474,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.updateVisibility() self.updateVisibility()
self.haptic?.enabled = self.visibilityStatus == true self.haptic?.enabled = self.visibilityStatus == true
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.visibility = self.visibilityStatus == true self.replyInfoNode?.visibility = self.visibilityStatus == true
} }
} }
@ -789,6 +792,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate) backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
} }
if let threadInfoNode = self.threadInfoNode {
var threadInfoNodeFrame = threadInfoNode.frame
threadInfoNodeFrame.origin.x += rect.minX
threadInfoNodeFrame.origin.y += rect.minY
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
}
if let shareButtonNode = self.shareButtonNode {
var shareButtonNodeFrame = shareButtonNode.frame
shareButtonNodeFrame.origin.x += rect.minX
shareButtonNodeFrame.origin.y += rect.minY
shareButtonNode.updateAbsoluteRect(shareButtonNodeFrame, within: containerSize)
}
if let actionButtonsNode = self.actionButtonsNode {
var actionButtonsNodeFrame = actionButtonsNode.frame
actionButtonsNodeFrame.origin.x += rect.minX
actionButtonsNodeFrame.origin.y += rect.minY
actionButtonsNode.updateAbsoluteRect(actionButtonsNodeFrame, within: containerSize)
}
if let reactionButtonsNode = self.reactionButtonsNode { if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX reactionButtonsNodeFrame.origin.x += rect.minX
@ -796,6 +823,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
} }
if let replyBackgroundContent = self.replyBackgroundContent {
var replyBackgroundContentFrame = replyBackgroundContent.frame
replyBackgroundContentFrame.origin.x += rect.minX
replyBackgroundContentFrame.origin.y += rect.minY
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
}
} }
} }
@ -855,6 +890,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo let currentForwardInfo = self.appliedForwardInfo
@ -1124,11 +1160,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)? var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)? var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var needsReplyBackground = false var needsReplyBackground = false
var replyMarkup: ReplyMarkupMessageAttribute? var replyMarkup: ReplyMarkupMessageAttribute?
let availableContentWidth = min(120.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)) let availableContentWidth = min(120.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left))
var ignoreForward = false var ignoreForward = false
@ -1166,8 +1202,32 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
var hasReply = true
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else { hasReply = false
}
if case .peer = item.chatLocation, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId {
hasReply = false
}
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .standalone,
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
}
if hasReply {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,
strings: item.presentationData.strings, strings: item.presentationData.strings,
@ -1425,9 +1485,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
} }
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
strongSelf.replyBackgroundContent = backgroundContent
strongSelf.insertSubnode(backgroundContent, at: 0)
}
} else {
strongSelf.replyBackgroundContent?.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode { } else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
strongSelf.replyBackgroundNode = nil strongSelf.replyBackgroundNode = nil
replyBackgroundNode.removeFromSupernode() replyBackgroundNode.removeFromSupernode()
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
replyBackgroundContent.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
}
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
let threadInfoNode = threadInfoApply(synchronousLoads)
if strongSelf.threadInfoNode == nil {
strongSelf.threadInfoNode = threadInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
}
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
threadInfoNode.frame = threadInfoFrame
headersOffset += threadInfoSize.height + 10.0
} else if let replyInfoNode = strongSelf.replyInfoNode {
replyInfoNode.removeFromSupernode()
strongSelf.replyInfoNode = nil
} }
var messageInfoSize = CGSize() var messageInfoSize = CGSize()
@ -1447,7 +1539,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
} }
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size) let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
viaBotNode.frame = viaBotFrame viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
@ -1466,7 +1558,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
} }
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize) let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
forwardInfoNode.frame = forwardInfoFrame forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -1490,7 +1582,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
} }
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize) let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyInfoNode.frame = replyInfoFrame replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
@ -1500,13 +1592,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let replyBackgroundNode = strongSelf.replyBackgroundNode { if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0)) replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0 let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
if let backgroundContent = strongSelf.replyBackgroundContent {
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.isHidden = true
backgroundContent.cornerRadius = cornerRadius
backgroundContent.frame = replyBackgroundNode.frame
if let (rect, containerSize) = strongSelf.absoluteRect {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
} else {
replyBackgroundNode.isHidden = false
}
} }
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha

View File

@ -510,6 +510,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var forwardInfoReferenceNode: ASDisplayNode? { var forwardInfoReferenceNode: ASDisplayNode? {
return self.forwardInfoNode return self.forwardInfoNode
} }
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var contentContainersWrapperNode: ASDisplayNode private var contentContainersWrapperNode: ASDisplayNode
@ -547,6 +549,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
contentNode.visibility = mapVisibility(self.visibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) contentNode.visibility = mapVisibility(self.visibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode)
} }
if let threadInfoNode = self.threadInfoNode {
threadInfoNode.visibility = self.visibility != .none
}
if let replyInfoNode = self.replyInfoNode { if let replyInfoNode = self.replyInfoNode {
replyInfoNode.visibility = self.visibility != .none replyInfoNode.visibility = self.visibility != .none
} }
@ -932,6 +938,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
} }
if let threadInfoNode = strongSelf.threadInfoNode, threadInfoNode.frame.contains(point) {
if let _ = threadInfoNode.hitTest(strongSelf.view.convert(point, to: threadInfoNode.view), with: nil) {
return .fail
}
}
if let replyInfoNode = strongSelf.replyInfoNode, replyInfoNode.frame.contains(point) { if let replyInfoNode = strongSelf.replyInfoNode, replyInfoNode.frame.contains(point) {
return .waitForSingleTap return .waitForSingleTap
} }
@ -1054,6 +1065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let authorNameLayout = TextNode.asyncLayout(self.nameNode) let authorNameLayout = TextNode.asyncLayout(self.nameNode)
let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode) let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode)
let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode) let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
@ -1076,6 +1088,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts, currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
authorNameLayout: authorNameLayout, authorNameLayout: authorNameLayout,
adminBadgeLayout: adminBadgeLayout, adminBadgeLayout: adminBadgeLayout,
threadInfoLayout: threadInfoLayout,
forwardInfoLayout: forwardInfoLayout, forwardInfoLayout: forwardInfoLayout,
replyInfoLayout: replyInfoLayout, replyInfoLayout: replyInfoLayout,
actionButtonsLayout: actionButtonsLayout, actionButtonsLayout: actionButtonsLayout,
@ -1093,6 +1106,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))], currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode),
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
@ -1835,6 +1849,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var threadInfoOriginY: CGFloat = 0.0
var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
var replyInfoOriginY: CGFloat = 0.0 var replyInfoOriginY: CGFloat = 0.0
var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil }) var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil })
@ -1945,7 +1962,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
headerSize.height += forwardInfoSizeApply.0.height headerSize.height += forwardInfoSizeApply.0.height
} }
if !isInstantVideo, let replyMessage = replyMessage { var hasReply = replyMessage != nil
if !isInstantVideo, let replyMessage = replyMessage, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId {
hasReply = false
}
if headerSize.height.isZero {
headerSize.height += 14.0
} else {
headerSize.height += 5.0
}
let sizeAndApply = threadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .bubble(incoming: incoming),
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
threadInfoSizeApply = (sizeAndApply.0, { synchronousLoads in sizeAndApply.1(synchronousLoads) })
threadInfoOriginY = headerSize.height
headerSize.width = max(headerSize.width, threadInfoSizeApply.0.width + layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right)
headerSize.height += threadInfoSizeApply.0.height + 5.0
}
if !isInstantVideo, let replyMessage = replyMessage, hasReply {
if headerSize.height.isZero { if headerSize.height.isZero {
headerSize.height += 6.0 headerSize.height += 6.0
} else { } else {
@ -2401,6 +2448,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: currentCredibilityIcon, currentCredibilityIcon: currentCredibilityIcon,
adminNodeSizeApply: adminNodeSizeApply, adminNodeSizeApply: adminNodeSizeApply,
contentUpperRightCorner: contentUpperRightCorner, contentUpperRightCorner: contentUpperRightCorner,
threadInfoSizeApply: threadInfoSizeApply,
threadInfoOriginY: threadInfoOriginY,
forwardInfoSizeApply: forwardInfoSizeApply, forwardInfoSizeApply: forwardInfoSizeApply,
forwardInfoOriginY: forwardInfoOriginY, forwardInfoOriginY: forwardInfoOriginY,
replyInfoSizeApply: replyInfoSizeApply, replyInfoSizeApply: replyInfoSizeApply,
@ -2449,6 +2498,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: EmojiStatusComponent.Content?, currentCredibilityIcon: EmojiStatusComponent.Content?,
adminNodeSizeApply: (CGSize, () -> TextNode?), adminNodeSizeApply: (CGSize, () -> TextNode?),
contentUpperRightCorner: CGPoint, contentUpperRightCorner: CGPoint,
threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?),
threadInfoOriginY: CGFloat,
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?), forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
forwardInfoOriginY: CGFloat, forwardInfoOriginY: CGFloat,
replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?), replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?),
@ -2721,6 +2772,40 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
if let threadInfoNode = threadInfoSizeApply.1(synchronousLoads) {
strongSelf.threadInfoNode = threadInfoNode
var animateFrame = true
if threadInfoNode.supernode == nil {
strongSelf.clippingNode.addSubnode(threadInfoNode)
animateFrame = false
threadInfoNode.visibility = strongSelf.visibility != .none
if animation.isAnimated {
threadInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
let previousThreadInfoNodeFrame = threadInfoNode.frame
threadInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + threadInfoOriginY), size: threadInfoSizeApply.0)
if case let .System(duration, _) = animation {
if animateFrame {
threadInfoNode.layer.animateFrame(from: previousThreadInfoNodeFrame, to: threadInfoNode.frame, duration: duration, timingFunction: timingFunction)
}
}
} else {
if animation.isAnimated {
if let threadInfoNode = strongSelf.threadInfoNode {
strongSelf.threadInfoNode = nil
threadInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak threadInfoNode] _ in
threadInfoNode?.removeFromSupernode()
})
}
} else {
strongSelf.threadInfoNode?.removeFromSupernode()
strongSelf.threadInfoNode = nil
}
}
if let replyInfoNode = replyInfoSizeApply.1(synchronousLoads) { if let replyInfoNode = replyInfoSizeApply.1(synchronousLoads) {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
var animateFrame = true var animateFrame = true
@ -3498,6 +3583,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
} }
} else if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute, let threadId = attribute.threadMessageId {
return .optionalAction({
item.controllerInteraction.navigateToThreadMessage(item.message.id.peerId, Int64(clamping: threadId.id), item.message.id)
})
}
}
}
} }
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) { if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
if let item = self.item, let forwardInfo = item.message.forwardInfo { if let item = self.item, let forwardInfo = item.message.forwardInfo {
@ -3652,6 +3747,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let item = self.item, self.backgroundNode.frame.contains(location) { if let item = self.item, self.backgroundNode.frame.contains(location) {
let message = item.message let message = item.message
if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
return .action({})
}
var tapMessage: Message? = item.content.firstMessage var tapMessage: Message? = item.content.firstMessage
var selectAll = true var selectAll = true
var hasFiles = false var hasFiles = false
@ -3773,6 +3872,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return nil return nil
} }
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
return result
}
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view return shareButtonNode.view
} }

View File

@ -112,12 +112,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
} }
} }
var (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId) let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
if let threadId = arguments.parentMessage.threadId, Int64(arguments.message.id.id) == threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
titleString = "\(threadInfo.title)"
textString = NSAttributedString()
}
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let titleColor: UIColor let titleColor: UIColor

View File

@ -38,7 +38,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode? private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode? private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -71,6 +73,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var visibilityStatus: Bool? { private var visibilityStatus: Bool? {
didSet { didSet {
if self.visibilityStatus != oldValue { if self.visibilityStatus != oldValue {
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.visibility = self.visibilityStatus == true self.replyInfoNode?.visibility = self.visibilityStatus == true
} }
} }
@ -277,6 +280,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate) backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
} }
if let threadInfoNode = self.threadInfoNode {
var threadInfoNodeFrame = threadInfoNode.frame
threadInfoNodeFrame.origin.x += rect.minX
threadInfoNodeFrame.origin.y += rect.minY
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
}
if let shareButtonNode = self.shareButtonNode { if let shareButtonNode = self.shareButtonNode {
var shareButtonNodeFrame = shareButtonNode.frame var shareButtonNodeFrame = shareButtonNode.frame
shareButtonNodeFrame.origin.x += rect.minX shareButtonNodeFrame.origin.x += rect.minX
@ -300,6 +311,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
} }
if let replyBackgroundContent = self.replyBackgroundContent {
var replyBackgroundContentFrame = replyBackgroundContent.frame
replyBackgroundContentFrame.origin.x += rect.minX
replyBackgroundContentFrame.origin.y += rect.minY
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
}
} }
} }
@ -357,6 +376,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo let currentForwardInfo = self.appliedForwardInfo
@ -567,6 +587,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)? var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)? var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var replyMarkup: ReplyMarkupMessageAttribute? var replyMarkup: ReplyMarkupMessageAttribute?
@ -610,8 +631,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
var hasReply = true
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId { if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else { hasReply = false
}
if case .peer = item.chatLocation, replyMessage.threadId != nil, case let .peer(peerId) = item.chatLocation, peerId == replyMessage.id.peerId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId {
hasReply = false
}
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .standalone,
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
}
if hasReply {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,
strings: item.presentationData.strings, strings: item.presentationData.strings,
@ -764,9 +808,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
} }
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, _) = threadInfoApply {
headersOffset += threadInfoSize.height + 10.0
}
var viaBotFrame: CGRect? var viaBotFrame: CGRect?
if let (viaBotLayout, _) = viaBotApply { if let (viaBotLayout, _) = viaBotApply {
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size) viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
} }
var replyInfoFrame: CGRect? var replyInfoFrame: CGRect?
@ -775,7 +824,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let viaBotFrame = viaBotFrame { if let viaBotFrame = viaBotFrame {
viaBotSize = viaBotFrame.size viaBotSize = viaBotFrame.size
} }
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize) let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: headersOffset + 8.0 + viaBotSize.height), size: replyInfoSize)
replyInfoFrame = replyInfoFrameValue replyInfoFrame = replyInfoFrameValue
if let viaBotFrameValue = viaBotFrame { if let viaBotFrameValue = viaBotFrame {
if replyInfoFrameValue.minX < replyInfoFrameValue.minX { if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
@ -791,7 +840,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
viaBotSize = viaBotFrame.size viaBotSize = viaBotFrame.size
} }
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: headersOffset + replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
} }
if let replyBackgroundFrameValue = replyBackgroundFrame { if let replyBackgroundFrameValue = replyBackgroundFrame {
@ -875,9 +924,41 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
} }
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
strongSelf.replyBackgroundContent = backgroundContent
strongSelf.insertSubnode(backgroundContent, at: 0)
}
} else {
strongSelf.replyBackgroundContent?.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode { } else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.removeFromSupernode() replyBackgroundNode.removeFromSupernode()
strongSelf.replyBackgroundNode = nil strongSelf.replyBackgroundNode = nil
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
replyBackgroundContent.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
}
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
let threadInfoNode = threadInfoApply(synchronousLoads)
if strongSelf.threadInfoNode == nil {
strongSelf.threadInfoNode = threadInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
}
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
threadInfoNode.frame = threadInfoFrame
headersOffset += threadInfoSize.height + 10.0
} else if let replyInfoNode = strongSelf.replyInfoNode {
replyInfoNode.removeFromSupernode()
strongSelf.replyInfoNode = nil
} }
var messageInfoSize = CGSize() var messageInfoSize = CGSize()
@ -897,7 +978,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
} }
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size) let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
viaBotNode.frame = viaBotFrame viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
@ -916,7 +997,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
} }
} }
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize) let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
forwardInfoNode.frame = forwardInfoFrame forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -940,7 +1021,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
} }
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize) let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyInfoNode.frame = replyInfoFrame replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
@ -949,15 +1030,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = nil strongSelf.replyInfoNode = nil
} }
if let replyBackgroundNode = strongSelf.replyBackgroundNode { if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0)) replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0 let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
if let backgroundContent = strongSelf.replyBackgroundContent {
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.isHidden = true
backgroundContent.cornerRadius = cornerRadius
backgroundContent.frame = replyBackgroundNode.frame
if let (rect, containerSize) = strongSelf.absoluteRect {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
} else {
replyBackgroundNode.isHidden = false
}
} }
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha

View File

@ -0,0 +1,509 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import Display
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import LocalizedPeerData
import PhotoResources
import TelegramStringFormatting
import TextFormat
import InvisibleInkDustNode
import TextNodeWithEntities
import AnimationCache
import MultiAnimationRenderer
import ComponentFlow
import EmojiStatusComponent
import WallpaperBackgroundNode
private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) {
enum CornerType {
case topLeft
case topRight
case bottomLeft
case bottomRight
}
func drawFullCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) {
if radius.isZero {
return
}
context.setFillColor(color.cgColor)
switch type {
case .topLeft:
context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius)))
context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .topRight:
context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .bottomLeft:
context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .bottomRight:
context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
}
}
func drawConnectingCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) {
context.setFillColor(color.cgColor)
switch type {
case .topLeft:
context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius)))
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .topRight:
context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius)))
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .bottomLeft:
context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius)))
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
case .bottomRight:
context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius)))
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
}
}
if rects.isEmpty {
return (CGPoint(), nil)
}
var topLeft = rects[0].origin
var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY)
for i in 1 ..< rects.count {
topLeft.x = min(topLeft.x, rects[i].origin.x)
topLeft.y = min(topLeft.y, rects[i].origin.y)
bottomRight.x = max(bottomRight.x, rects[i].maxX)
bottomRight.y = max(bottomRight.y, rects[i].maxY)
}
topLeft.x -= inset
topLeft.y -= inset
bottomRight.x += inset * 2.0
bottomRight.y += inset * 2.0
return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.setBlendMode(.copy)
for i in 0 ..< rects.count {
let rect = rects[i].insetBy(dx: -inset, dy: -inset)
context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y))
}
for i in 0 ..< rects.count {
let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
var previous: CGRect?
if i != 0 {
previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
}
var next: CGRect?
if i != rects.count - 1 {
next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
}
if let previous = previous {
if previous.contains(rect.topLeft) {
if abs(rect.topLeft.x - previous.minX) >= innerRadius {
var radius = innerRadius
if let next = next {
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
}
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius)
}
if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) {
if abs(rect.topRight.x - previous.maxX) >= innerRadius {
var radius = innerRadius
if let next = next {
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
}
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius)
drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius)
}
if let next = next {
if next.contains(rect.bottomLeft) {
if abs(rect.bottomRight.x - next.maxX) >= innerRadius {
var radius = innerRadius
if let previous = previous {
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
}
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius)
}
if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) {
if abs(rect.bottomRight.x - next.maxX) >= innerRadius {
var radius = innerRadius
if let previous = previous {
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
}
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius)
}
} else {
drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius)
drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius)
}
}
}))
}
enum ChatMessageThreadInfoType {
case bubble(incoming: Bool)
case standalone
}
class ChatMessageThreadInfoNode: ASDisplayNode {
class Arguments {
let presentationData: ChatPresentationData
let strings: PresentationStrings
let context: AccountContext
let controllerInteraction: ChatControllerInteraction
let type: ChatMessageThreadInfoType
let message: Message
let parentMessage: Message
let constrainedSize: CGSize
let animationCache: AnimationCache?
let animationRenderer: MultiAnimationRenderer?
init(
presentationData: ChatPresentationData,
strings: PresentationStrings,
context: AccountContext,
controllerInteraction: ChatControllerInteraction,
type: ChatMessageThreadInfoType,
message: Message,
parentMessage: Message,
constrainedSize: CGSize,
animationCache: AnimationCache?,
animationRenderer: MultiAnimationRenderer?
) {
self.presentationData = presentationData
self.strings = strings
self.context = context
self.controllerInteraction = controllerInteraction
self.type = type
self.message = message
self.parentMessage = parentMessage
self.constrainedSize = constrainedSize
self.animationCache = animationCache
self.animationRenderer = animationRenderer
}
}
var visibility: Bool = false {
didSet {
if self.visibility != oldValue {
self.textNode?.visibilityRect = self.visibility ? CGRect.infinite : nil
if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent {
let _ = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibility)),
environment: {},
containerSize: titleTopicIconView.bounds.size
)
}
}
}
}
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var backgroundNode: NavigationBackgroundNode?
private let contentNode: HighlightTrackingButtonNode
private let contentBackgroundNode: ASImageNode
private var textNode: TextNodeWithEntities?
private let arrowNode: ASImageNode
private var titleTopicIconView: ComponentHostView<Empty>?
private var titleTopicIconComponent: EmojiStatusComponent?
private var lineRects: [CGRect] = []
private var pressed = { }
private var absolutePosition: (CGRect, CGSize)?
override init() {
self.contentNode = HighlightTrackingButtonNode()
self.contentBackgroundNode = ASImageNode()
self.contentBackgroundNode.alpha = 0.1
self.contentBackgroundNode.displaysAsynchronously = false
self.contentBackgroundNode.displayWithoutProcessing = true
self.contentBackgroundNode.isLayerBacked = true
self.contentBackgroundNode.isUserInteractionEnabled = false
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.arrowNode.isLayerBacked = true
self.arrowNode.isUserInteractionEnabled = false
super.init()
self.contentNode.isUserInteractionEnabled = true
self.addSubnode(self.contentNode)
self.contentNode.addSubnode(self.contentBackgroundNode)
self.contentNode.addSubnode(self.arrowNode)
self.contentNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted, !strongSelf.frame.width.isZero {
let scale = (strongSelf.frame.width - 10.0) / strongSelf.frame.width
strongSelf.contentNode.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false)
strongSelf.contentBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.contentBackgroundNode.alpha = 0.2
} else if let presentationLayer = strongSelf.contentNode.layer.presentation() {
strongSelf.contentNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
strongSelf.contentBackgroundNode.alpha = 0.1
strongSelf.contentBackgroundNode.layer.animateAlpha(from: 0.2, to: 0.1, duration: 0.2)
}
}
}
self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
@objc private func buttonPressed() {
self.pressed()
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.absolutePosition = (rect, containerSize)
if let backgroundContent = self.backgroundContent {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
}
class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) {
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
return { arguments in
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
let textFont = Font.medium(fontSize)
var topicTitle = ""
var topicIconId: Int64?
var topicIconColor: Int32 = 0
if let _ = arguments.parentMessage.threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
topicTitle = threadInfo.title
topicIconId = threadInfo.icon
topicIconColor = threadInfo.iconColor
}
let backgroundColor: UIColor
let textColor: UIColor
let arrowIcon: UIImage?
switch arguments.type {
case let .bubble(incoming):
if topicIconId == nil, topicIconColor != 0, incoming {
let colors = topicIconColors(for: topicIconColor)
backgroundColor = UIColor(rgb: colors.0.last ?? 0x000000)
textColor = UIColor(rgb: colors.1.first ?? 0x000000)
arrowIcon = PresentationResourcesChat.chatBubbleArrowImage(color: textColor)
} else {
backgroundColor = (incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor)
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
arrowIcon = incoming ? PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme) : PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme)
}
case .standalone:
textColor = .white
backgroundColor = .white
arrowIcon = PresentationResourcesChat.chatBubbleArrowFreeImage(arguments.presentationData.theme.theme)
}
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let text = NSAttributedString(string: topicTitle, font: textFont, textColor: textColor)
let lineInset: CGFloat = 7.0
let iconSize = CGSize(width: 22.0, height: 22.0)
let insets = UIEdgeInsets(top: 2.0, left: 4.0, bottom: 2.0, right: 4.0)
let spacing: CGFloat = 4.0
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: arguments.constrainedSize.width - insets.left - insets.right - iconSize.width - spacing, height: arguments.constrainedSize.height), alignment: .natural, cutout: nil, insets: .zero))
var lineRects = textLayout.linesRects().map { rect in
return CGRect(origin: rect.origin.offsetBy(dx: insets.left, dy: 0.0), size: CGSize(width: rect.width + iconSize.width + spacing + 3.0, height: rect.size.height))
}
if lineRects.count > 0 {
let lastRect = lineRects[lineRects.count - 1]
lineRects[lineRects.count - 1] = CGRect(origin: lastRect.origin, size: CGSize(width: lastRect.width + 11.0, height: lastRect.height))
}
let size = CGSize(width: insets.left + iconSize.width + spacing + textLayout.size.width + insets.right + lineInset * 2.0, height: insets.top + textLayout.size.height + insets.bottom)
return (size, { attemptSynchronous in
let node: ChatMessageThreadInfoNode
if let maybeNode = maybeNode {
node = maybeNode
} else {
node = ChatMessageThreadInfoNode()
}
node.pressed = {
if let threadId = arguments.message.threadId {
arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, threadId, arguments.parentMessage.id)
}
}
if node.lineRects != lineRects {
let (offset, image) = generateRectsImage(color: backgroundColor, rects: lineRects, inset: 5.0, outerRadius: 13.0, innerRadius: 8.0)
if let image = image {
if case .standalone = arguments.type {
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -3.0), size: CGSize(width: size.width + 5.0, height: size.height + 10.0))
if arguments.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if node.backgroundContent == nil, let backgroundContent = arguments.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
backgroundContent.isUserInteractionEnabled = false
node.backgroundContent = backgroundContent
node.contentNode.insertSubnode(backgroundContent, at: 0)
let backgroundMask = UIImageView(image: image)
backgroundContent.view.mask = backgroundMask
}
if let backgroundContent = node.backgroundContent {
backgroundContent.view.mask?.bounds = CGRect(origin: .zero, size: image.size)
(backgroundContent.view.mask as? UIImageView)?.image = image
backgroundContent.frame = backgroundFrame
if let (rect, containerSize) = node.absolutePosition {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
}
} else {
node.backgroundContent?.removeFromSupernode()
node.backgroundContent = nil
let backgroundNode: NavigationBackgroundNode
if let current = node.backgroundNode {
backgroundNode = current
} else {
backgroundNode = NavigationBackgroundNode(color: .clear)
backgroundNode.isUserInteractionEnabled = false
node.backgroundNode = backgroundNode
node.contentNode.insertSubnode(backgroundNode, at: 0)
let backgroundMask = UIImageView(image: image)
backgroundNode.view.mask = backgroundMask
}
backgroundNode.view.mask?.bounds = CGRect(origin: .zero, size: image.size)
(backgroundNode.view.mask as? UIImageView)?.image = image
backgroundNode.frame = backgroundFrame
backgroundNode.update(size: backgroundNode.bounds.size, cornerRadius: 0.0, transition: .immediate)
backgroundNode.updateColor(color: selectDateFillStaticColor(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper), transition: .immediate)
}
} else {
node.contentBackgroundNode.frame = CGRect(origin: offset.offsetBy(dx: 0.0, dy: -11.0), size: image.size)
node.contentBackgroundNode.image = image
}
}
}
node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview
var textArguments: TextNodeWithEntities.Arguments?
if let cache = arguments.animationCache, let renderer = arguments.animationRenderer {
textArguments = TextNodeWithEntities.Arguments(context: arguments.context, cache: cache, renderer: renderer, placeholderColor: placeholderColor, attemptSynchronous: attemptSynchronous)
}
let textNode = textApply(textArguments)
textNode.visibilityRect = node.visibility ? CGRect.infinite : nil
if node.textNode == nil {
textNode.textNode.isUserInteractionEnabled = false
node.textNode = textNode
node.contentNode.addSubnode(textNode.textNode)
}
let titleTopicIconView: ComponentHostView<Empty>
if let current = node.titleTopicIconView {
titleTopicIconView = current
} else {
titleTopicIconView = ComponentHostView<Empty>()
node.titleTopicIconView = titleTopicIconView
node.contentNode.view.addSubview(titleTopicIconView)
}
let titleTopicIconContent: EmojiStatusComponent.Content
if let fileId = topicIconId, fileId != 0 {
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: arguments.presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: arguments.presentationData.theme.theme.list.itemAccentColor, loopMode: .count(1))
} else {
titleTopicIconContent = .topic(title: String(topicTitle.prefix(1)), color: topicIconColor, size: CGSize(width: 22.0, height: 22.0))
}
if let animationCache = arguments.animationCache, let animationRenderer = arguments.animationRenderer {
let titleTopicIconComponent = EmojiStatusComponent(
context: arguments.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
content: titleTopicIconContent,
isVisibleForAnimations: node.visibility,
action: nil
)
node.titleTopicIconComponent = titleTopicIconComponent
let iconSize = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleTopicIconView.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: iconSize)
}
let textFrame = CGRect(origin: CGPoint(x: iconSize.width + 2.0 + insets.left, y: insets.top), size: textLayout.size)
textNode.textNode.frame = textFrame
if let arrowIcon = arrowIcon, let lastRect = lineRects.last {
node.arrowNode.image = arrowIcon
node.arrowNode.frame = CGRect(origin: CGPoint(x: lastRect.maxX - arrowIcon.size.width - 1.0, y: floorToScreenPixels(lastRect.midY - arrowIcon.size.height / 2.0) - 11.0 + UIScreenPixel), size: arrowIcon.size)
}
node.contentNode.frame = CGRect(origin: CGPoint(), size: size)
return node
})
}
}
}

View File

@ -50,6 +50,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable() private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
private let temporaryHiddenGalleryMediaDisposable = MetaDisposable() private let temporaryHiddenGalleryMediaDisposable = MetaDisposable()
private var chatPresentationData: ChatPresentationData
private var chatPresentationDataPromise: Promise<ChatPresentationData> private var chatPresentationDataPromise: Promise<ChatPresentationData>
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
@ -120,6 +121,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper, chatBubbleCorners: self.presentationData.chatBubbleCorners) self.emptyNode = ChatRecentActionsEmptyNode(theme: self.presentationData.theme, chatWallpaper: self.presentationData.chatWallpaper, chatBubbleCorners: self.presentationData.chatBubbleCorners)
self.emptyNode.alpha = 0.0 self.emptyNode.alpha = 0.0
self.chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners)
self.chatPresentationDataPromise = Promise() self.chatPresentationDataPromise = Promise()
self.eventLogContext = self.context.engine.peers.channelAdminEventLog(peerId: self.peer.id) self.eventLogContext = self.context.engine.peers.channelAdminEventLog(peerId: self.peer.id)
@ -261,6 +263,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, activateMessagePinch: { _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
@ -628,7 +631,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData self.presentationData = presentationData
self.chatPresentationDataPromise.set(.single(ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners))) self.chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper), fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: presentationData.largeEmoji, chatBubbleCorners: presentationData.chatBubbleCorners)
self.chatPresentationDataPromise.set(.single(self.chatPresentationData))
self.backgroundNode.update(wallpaper: presentationData.chatWallpaper) self.backgroundNode.update(wallpaper: presentationData.chatWallpaper)
self.backgroundNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners) self.backgroundNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners)
@ -667,7 +671,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
let emptyFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - panelHeight)) let emptyFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - panelHeight))
transition.updateFrame(node: self.emptyNode, frame: emptyFrame) transition.updateFrame(node: self.emptyNode, frame: emptyFrame)
self.emptyNode.update(rect: emptyFrame, within: layout.size) self.emptyNode.update(rect: emptyFrame, within: layout.size)
self.emptyNode.updateLayout(backgroundNode: self.backgroundNode, size: emptyFrame.size, transition: transition) self.emptyNode.updateLayout(presentationData: self.chatPresentationData, backgroundNode: self.backgroundNode, size: emptyFrame.size, transition: transition)
let contentBottomInset: CGFloat = panelHeight + 4.0 let contentBottomInset: CGFloat = panelHeight + 4.0
let listInsets = UIEdgeInsets(top: contentBottomInset, left: layout.safeInsets.right, bottom: insets.top, right: layout.safeInsets.left) let listInsets = UIEdgeInsets(top: contentBottomInset, left: layout.safeInsets.right, bottom: insets.top, right: layout.safeInsets.left)

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import WallpaperBackgroundNode import WallpaperBackgroundNode
import ChatPresentationInterfaceState
private let titleFont = Font.medium(16.0) private let titleFont = Font.medium(16.0)
private let textFont = Font.regular(15.0) private let textFont = Font.regular(15.0)
@ -13,7 +14,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
private var theme: PresentationTheme private var theme: PresentationTheme
private var chatWallpaper: TelegramWallpaper private var chatWallpaper: TelegramWallpaper
private let backgroundNode: ASImageNode private let backgroundNode: NavigationBackgroundNode
private let titleNode: TextNode private let titleNode: TextNode
private let textNode: TextNode private let textNode: TextNode
@ -22,7 +23,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
private var absolutePosition: (CGRect, CGSize)? private var absolutePosition: (CGRect, CGSize)?
private var layoutParams: CGSize? private var layoutParams: (CGSize, ChatPresentationData)?
private var title: String = "" private var title: String = ""
private var text: String = "" private var text: String = ""
@ -31,8 +32,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
self.theme = theme self.theme = theme
self.chatWallpaper = chatWallpaper self.chatWallpaper = chatWallpaper
self.backgroundNode = ASImageNode() self.backgroundNode = NavigationBackgroundNode(color: .clear)
self.backgroundNode.isLayerBacked = true
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false
@ -44,9 +44,6 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
self.allowsGroupOpacity = true self.allowsGroupOpacity = true
let graphics = PresentationResourcesChat.additionalGraphics(theme, wallpaper: chatWallpaper, bubbleCorners: chatBubbleCorners)
self.backgroundNode.image = graphics.chatEmptyItemBackgroundImage
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
@ -62,9 +59,14 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
} }
} }
func updateLayout(backgroundNode: WallpaperBackgroundNode, size: CGSize, transition: ContainedViewLayoutTransition) { func updateLayout(presentationData: ChatPresentationData, backgroundNode: WallpaperBackgroundNode, size: CGSize, transition: ContainedViewLayoutTransition) {
self.wallpaperBackgroundNode = backgroundNode self.wallpaperBackgroundNode = backgroundNode
self.layoutParams = size self.layoutParams = (size, presentationData)
self.theme = presentationData.theme.theme
self.chatWallpaper = presentationData.theme.wallpaper
self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate)
let insets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) let insets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
@ -82,6 +84,8 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
let contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: insets.top + insets.bottom + titleLayout.size.height + spacing + textLayout.size.height) let contentSize = CGSize(width: max(titleLayout.size.width, textLayout.size.width) + insets.left + insets.right, height: insets.top + insets.bottom + titleLayout.size.height + spacing + textLayout.size.height)
let backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize) let backgroundFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize)
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(14.0, self.backgroundNode.bounds.height / 2.0), transition: transition)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - titleLayout.size.width) / 2.0), y: backgroundFrame.minY + insets.top), size: titleLayout.size)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - titleLayout.size.width) / 2.0), y: backgroundFrame.minY + insets.top), size: titleLayout.size))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - textLayout.size.width) / 2.0), y: backgroundFrame.minY + insets.top + titleLayout.size.height + spacing), size: textLayout.size)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((contentSize.width - textLayout.size.width) / 2.0), y: backgroundFrame.minY + insets.top + titleLayout.size.height + spacing), size: textLayout.size))
@ -119,8 +123,8 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
if self.title != title || self.text != text { if self.title != title || self.text != text {
self.title = title self.title = title
self.text = text self.text = text
if let size = self.layoutParams, let wallpaperBackgroundNode = self.wallpaperBackgroundNode { if let (size, presentationData) = self.layoutParams, let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
self.updateLayout(backgroundNode: wallpaperBackgroundNode, size: size, transition: .immediate) self.updateLayout(presentationData: presentationData, backgroundNode: wallpaperBackgroundNode, size: size, transition: .immediate)
} }
} }
} }

View File

@ -112,6 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in }, presentControllerInCurrent: { _, _ in

View File

@ -76,6 +76,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessage: { _, _ in
}, navigateToMessageStandalone: { _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, tapMessage: nil, clickThroughMessage: {
}, toggleMessagesSelection: { _, _ in }, toggleMessagesSelection: { _, _ in
}, sendCurrentMessage: { _ in }, sendCurrentMessage: { _ in

View File

@ -23,7 +23,7 @@ private func commitOwnershipTransferController(context: AccountContext, updatedP
let disposable = MetaDisposable() let disposable = MetaDisposable()
let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { let contentNode = ChannelOwnershipTransferAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, bot: true, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?() dismissImpl?()
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: {
proceedImpl?() proceedImpl?()

View File

@ -2147,8 +2147,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: { c.dismiss(completion: {
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
if let channel = currentPeer as? TelegramChannel, channel.flags.contains(.isForum), let threadId = message.threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, keepStack: .default).start()
} else {
let targetLocation: NavigateToChatControllerParams.Location
if case let .replyThread(message) = strongSelf.chatLocation {
targetLocation = .replyThread(message)
} else {
targetLocation = .peer(EnginePeer(currentPeer))
}
let currentPeerId = strongSelf.peerId let currentPeerId = strongSelf.peerId
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(currentPeer)), subject: .message(id: .id(message.id), highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: true, timecode: nil), keepStack: .always, useExisting: false, purposefulAction: {
var viewControllers = navigationController.viewControllers var viewControllers = navigationController.viewControllers
var indexesToRemove = Set<Int>() var indexesToRemove = Set<Int>()
var keptCurrentChatController = false var keptCurrentChatController = false
@ -2171,6 +2181,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
navigationController.setViewControllers(viewControllers, animated: false) navigationController.setViewControllers(viewControllers, animated: false)
})) }))
} }
}
}) })
}))) })))
@ -2298,6 +2309,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: { c.dismiss(completion: {
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
if let channel = currentPeer as? TelegramChannel, channel.flags.contains(.isForum), let threadId = message.threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, keepStack: .default).start()
} else {
let targetLocation: NavigateToChatControllerParams.Location let targetLocation: NavigateToChatControllerParams.Location
if case let .replyThread(message) = strongSelf.chatLocation { if case let .replyThread(message) = strongSelf.chatLocation {
targetLocation = .replyThread(message) targetLocation = .replyThread(message)
@ -2329,6 +2343,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
navigationController.setViewControllers(viewControllers, animated: false) navigationController.setViewControllers(viewControllers, animated: false)
})) }))
} }
}
}) })
}))) })))
@ -2431,6 +2446,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}) })
}, navigateToMessage: { fromId, id in }, navigateToMessage: { fromId, id in
}, navigateToMessageStandalone: { _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, tapMessage: nil, clickThroughMessage: {
}, toggleMessagesSelection: { [weak self] ids, value in }, toggleMessagesSelection: { [weak self] ids, value in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -1293,6 +1293,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: { message in }, tapMessage: { message in
tapMessage?(message) tapMessage?(message)
}, clickThroughMessage: { }, clickThroughMessage: {
@ -1523,6 +1524,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .emojiStatus(peerId, fileId, file, packTitle) mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
case .voiceToText: case .voiceToText:
mappedSource = .voiceToText mappedSource = .voiceToText
case .fasterDownload:
mappedSource = .fasterDownload
} }
return PremiumIntroScreen(context: context, source: mappedSource) return PremiumIntroScreen(context: context, source: mappedSource)
} }

View File

@ -228,7 +228,12 @@ final class WidgetDataContext {
name = peer.debugDisplayTitle name = peer.debugDisplayTitle
} }
result.append(WidgetDataPeer(id: peerId.toInt64(), name: name, lastName: lastName, letters: [], avatarPath: nil, badge: nil, message: WidgetDataPeer.Message(accountPeerId: account.peerId, message: message))) var isForum = false
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) {
isForum = true
}
result.append(WidgetDataPeer(id: peerId.toInt64(), name: name, lastName: lastName, letters: [], avatarPath: nil, badge: nil, message: WidgetDataPeer.Message(accountPeerId: account.peerId, message: message), isForum: isForum))
} }
result.sort(by: { lhs, rhs in result.sort(by: { lhs, rhs in
return lhs.id < rhs.id return lhs.id < rhs.id

View File

@ -249,8 +249,9 @@ public struct WidgetDataPeer: Codable, Equatable {
public var avatarPath: String? public var avatarPath: String?
public var badge: Badge? public var badge: Badge?
public var message: Message? public var message: Message?
public var isForum: Bool
public init(id: Int64, name: String, lastName: String?, letters: [String], avatarPath: String?, badge: Badge?, message: Message?) { public init(id: Int64, name: String, lastName: String?, letters: [String], avatarPath: String?, badge: Badge?, message: Message?, isForum: Bool) {
self.id = id self.id = id
self.name = name self.name = name
self.lastName = lastName self.lastName = lastName
@ -258,6 +259,7 @@ public struct WidgetDataPeer: Codable, Equatable {
self.avatarPath = avatarPath self.avatarPath = avatarPath
self.badge = badge self.badge = badge
self.message = message self.message = message
self.isForum = isForum
} }
} }