mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '6700f1af2aa6accfda96a28ab3cc3f45030a6da1' into features/history-loading
This commit is contained in:
commit
e64016553e
@ -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)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
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()
|
||||
|
||||
source.draw(in: CGRect(origin: CGPoint(), size: size))
|
||||
@ -1214,8 +1223,8 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
||||
return image
|
||||
}
|
||||
|
||||
private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize) -> UIImage {
|
||||
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
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, style: style) {
|
||||
return roundImage
|
||||
} else {
|
||||
return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)!
|
||||
@ -1297,6 +1306,11 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
|
||||
autoreleasepool {
|
||||
var profileImage: INImage?
|
||||
|
||||
var isForum = false
|
||||
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) {
|
||||
isForum = true
|
||||
}
|
||||
|
||||
if peer.id == accountPeerId {
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId("savedMessagesAvatar50x50", representationId: "intents.png", keepDuration: .shortLived)
|
||||
if let _ = fileSize(cachedPath) {
|
||||
@ -1325,7 +1339,7 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
|
||||
} catch {
|
||||
}
|
||||
} 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() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
@ -1345,7 +1359,7 @@ private func mapPeersToFriends(accountId: AccountRecordId, accountPeerId: PeerId
|
||||
} catch {
|
||||
}
|
||||
} 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() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
|
@ -4351,6 +4351,27 @@ Unused sets are archived when you add more.";
|
||||
"SettingsSearch.Synonyms.Support" = "Support";
|
||||
"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.DeleteForEveryone" = "Delete for me and %@";
|
||||
"ChatList.DeleteForEveryoneConfirmationTitle" = "Warning!";
|
||||
@ -7564,9 +7585,7 @@ Sorry for the inconvenience.";
|
||||
"Premium.Stickers.Proceed" = "Unlock Premium Stickers";
|
||||
|
||||
"Premium.Reactions.Proceed" = "Unlock Premium Reactions";
|
||||
|
||||
"Premium.AppIcons.Proceed" = "Unlock Premium Icons";
|
||||
|
||||
"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.";
|
||||
@ -7645,13 +7664,14 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Premium.FasterSpeed" = "Faster Download Speed";
|
||||
"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.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.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.Reactions" = "Unique Reactions";
|
||||
@ -7665,6 +7685,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Premium.ChatManagement" = "Advanced Chat Management";
|
||||
"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.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.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.";
|
||||
|
@ -23,12 +23,22 @@ private let gradientColors: [NSArray] = [
|
||||
[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)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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 {
|
||||
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
|
||||
} else {
|
||||
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 {
|
||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize, style: AvatarClipStyle) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
self.peer = peer
|
||||
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()
|
||||
var title = peer.name
|
||||
|
@ -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
|
||||
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))
|
||||
}
|
||||
@ -271,8 +276,8 @@ struct AvatarItemView: View {
|
||||
var body: some View {
|
||||
return ZStack {
|
||||
if let peer = peer {
|
||||
Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize)))
|
||||
.clipShape(Circle())
|
||||
Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize), style: peer.peer.isForum ? .roundedRect : .round))
|
||||
.mask(peer.peer.isForum ? AnyView(RoundedRectangle(cornerRadius: itemSize * 0.25)) : AnyView(Circle()))
|
||||
} else {
|
||||
Circle()
|
||||
.fill(placeholderColor)
|
||||
|
@ -836,6 +836,7 @@ public enum PremiumIntroSource {
|
||||
case profile(PeerId)
|
||||
case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?)
|
||||
case voiceToText
|
||||
case fasterDownload
|
||||
}
|
||||
|
||||
#if ENABLE_WALLET
|
||||
|
@ -761,12 +761,13 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
|
||||
})))
|
||||
}
|
||||
|
||||
// items.append(.separator)
|
||||
// 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)
|
||||
//
|
||||
//
|
||||
// })))
|
||||
if canOpenClose {
|
||||
items.append(.separator)
|
||||
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)
|
||||
}
|
||||
|
@ -527,21 +527,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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)
|
||||
self.stateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
self.presentationDataValue.get(),
|
||||
peerIdsAndOptions
|
||||
).start(next: { [weak self] presentationData, peerIdsAndOptions in
|
||||
peerIdsAndOptions,
|
||||
peerView
|
||||
).start(next: { [weak self] presentationData, peerIdsAndOptions, peerView in
|
||||
guard let strongSelf = self else {
|
||||
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)
|
||||
}
|
||||
} 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
|
||||
let previousToolbar = previousToolbarValue.swap(toolbar)
|
||||
@ -3823,6 +3830,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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) {
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
|
@ -603,7 +603,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
)
|
||||
|> map { hasOngoingCall, preloadItems -> Set<ChatHistoryPreloadItem> in
|
||||
if hasOngoingCall {
|
||||
return []
|
||||
return Set()
|
||||
} else {
|
||||
return Set(preloadItems)
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import Postbox
|
||||
import TelegramAnimatedStickerNode
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import PremiumUI
|
||||
|
||||
private enum ChatListTokenId: Int32 {
|
||||
case archive
|
||||
@ -284,7 +285,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
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)
|
||||
}
|
||||
@ -896,13 +897,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
let items = combineLatest(queue: .mainQueue(),
|
||||
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
|
||||
|> map { [weak self] actions, isCachedValue -> [ContextMenuItem] in
|
||||
|> map { [weak self] actions, isCachedValue, accountPeer -> [ContextMenuItem] in
|
||||
guard let strongSelf = self else {
|
||||
return []
|
||||
}
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
@ -920,6 +923,31 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
})
|
||||
})))
|
||||
} 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 {
|
||||
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)
|
||||
@ -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
|
||||
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
|
||||
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)
|
||||
}
|
||||
} 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))
|
||||
})
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
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))
|
||||
controller.purposefulAction = { [weak self] in
|
||||
self?.cancel?()
|
||||
}
|
||||
|
||||
if let navigationController = strongSelf.navigationController, let peerSelectionControllerIndex = navigationController.viewControllers.firstIndex(where: { $0 is PeerSelectionController }) {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
viewControllers.insert(controller, at: peerSelectionControllerIndex)
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
Queue.mainQueue().after(0.2) {
|
||||
peerSelectionController?.dismiss()
|
||||
let proceed: (ChatController) -> Void = { chatController in
|
||||
chatController.purposefulAction = { [weak self] in
|
||||
self?.cancel?()
|
||||
}
|
||||
} else {
|
||||
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: {
|
||||
if let peerSelectionController = peerSelectionController {
|
||||
peerSelectionController.dismiss()
|
||||
if let navigationController = strongSelf.navigationController {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if threadId != nil {
|
||||
viewControllers.insert(chatController, at: viewControllers.count - 2)
|
||||
} else {
|
||||
viewControllers.insert(chatController, at: viewControllers.count - 1)
|
||||
}
|
||||
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
|
||||
|
@ -1975,9 +1975,9 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
self.view.addGestureRecognizer(selectionRecognizer)
|
||||
|
||||
if case .forum = location {
|
||||
self.isSelectionGestureEnabled = false
|
||||
}
|
||||
// if case .forum = location {
|
||||
// self.isSelectionGestureEnabled = false
|
||||
// }
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -908,6 +908,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
if case .reference = self.source {
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
let actionsVerticalTransitionDirection: CGFloat
|
||||
if let contentNode = contentNode {
|
||||
if contentNode.frame.minY < self.actionsStackNode.frame.minY {
|
||||
@ -1123,6 +1127,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
if case .center = actionsHorizontalAlignment {
|
||||
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
|
||||
self.actionsStackNode.layer.animate(
|
||||
from: NSValue(cgPoint: CGPoint()),
|
||||
|
@ -196,9 +196,18 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
|
||||
self.highlight?(touchLocation)
|
||||
}
|
||||
|
||||
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton {
|
||||
self.state = .failed
|
||||
return
|
||||
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
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.tapCount += 1
|
||||
|
@ -19,6 +19,7 @@ import Speak
|
||||
import TranslateUI
|
||||
import ShareController
|
||||
import UndoUI
|
||||
import ContextUI
|
||||
|
||||
enum ChatMediaGalleryThumbnail: Equatable {
|
||||
case image(ImageMediaReference)
|
||||
@ -201,6 +202,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
|
||||
|
||||
private let moreBarButton: MoreHeaderButton
|
||||
|
||||
private var tilingNode: TilingNode?
|
||||
fileprivate let _ready = Promise<Void>()
|
||||
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.isHidden = true
|
||||
|
||||
self.moreBarButton = MoreHeaderButton()
|
||||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
super.init()
|
||||
|
||||
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> {
|
||||
@ -326,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
case .medium, .full:
|
||||
strongSelf.statusNodeContainer.isHidden = true
|
||||
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
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)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
if let strongSelf = self {
|
||||
strongSelf.recognizedContentNode?.removeFromSupernode()
|
||||
if !results.isEmpty {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
}, performAction: { [weak self] string, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .copy:
|
||||
UIPasteboard.general.string = string
|
||||
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
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 })
|
||||
controller.present(tooltipController, in: .window(.root))
|
||||
}
|
||||
case .share:
|
||||
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))
|
||||
controller.present(shareController, in: .window(.root))
|
||||
}
|
||||
case .lookup:
|
||||
let controller = UIReferenceLibraryViewController(term: string)
|
||||
if let window = strongSelf.baseNavigationController()?.view.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))
|
||||
window.rootViewController?.present(controller, animated: true)
|
||||
}
|
||||
case .speak:
|
||||
let _ = speakText(context: strongSelf.context, text: string)
|
||||
case .translate:
|
||||
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
|
||||
controller.pushController = { [weak parentController] c in
|
||||
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
parentController?.push(c)
|
||||
}
|
||||
controller.presentController = { [weak parentController] c in
|
||||
parentController?.present(c, in: .window(.root))
|
||||
}
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
||||
guard let strongSelf = self, let message = strongSelf.message else {
|
||||
return
|
||||
}
|
||||
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
|
||||
}
|
||||
recognizedContentNode.alpha = 0.0
|
||||
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
|
||||
strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
strongSelf.recognizedContentNode = recognizedContentNode
|
||||
strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
// Queue.concurrentDefaultQueue().async {
|
||||
// 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)
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.recognizedContentNode?.removeFromSupernode()
|
||||
// if !results.isEmpty {
|
||||
// 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
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
|
||||
// }
|
||||
// }, performAction: { [weak self] string, action in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// switch action {
|
||||
// case .copy:
|
||||
// UIPasteboard.general.string = string
|
||||
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
// 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 })
|
||||
// controller.present(tooltipController, in: .window(.root))
|
||||
// }
|
||||
// case .share:
|
||||
// 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))
|
||||
// controller.present(shareController, in: .window(.root))
|
||||
// }
|
||||
// case .lookup:
|
||||
// let controller = UIReferenceLibraryViewController(term: string)
|
||||
// if let window = strongSelf.baseNavigationController()?.view.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))
|
||||
// window.rootViewController?.present(controller, animated: true)
|
||||
// }
|
||||
// case .speak:
|
||||
// let _ = speakText(context: strongSelf.context, text: string)
|
||||
// case .translate:
|
||||
// if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
// let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
|
||||
// controller.pushController = { [weak parentController] c in
|
||||
// (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
// parentController?.push(c)
|
||||
// }
|
||||
// controller.presentController = { [weak parentController] c in
|
||||
// parentController?.present(c, in: .window(.root))
|
||||
// }
|
||||
// parentController.present(controller, in: .window(.root))
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
||||
// guard let strongSelf = self, let message = strongSelf.message else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
|
||||
// }
|
||||
// recognizedContentNode.alpha = 0.0
|
||||
// recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
// recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
|
||||
// strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
// strongSelf.recognizedContentNode = recognizedContentNode
|
||||
// strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
// }
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
|
||||
case .none, .blurred:
|
||||
strongSelf.statusNodeContainer.isHidden = false
|
||||
@ -411,12 +423,17 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
} else {
|
||||
self._ready.set(.single(Void()))
|
||||
}
|
||||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
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))
|
||||
self._rightBarButtonItems.set(.single([rightBarButtonItem]))
|
||||
} else {
|
||||
self._rightBarButtonItems.set(.single([]))
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
}
|
||||
if self.message != nil {
|
||||
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
barButtonItems.append(moreMenuItem)
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
}
|
||||
self.contextAndMedia = (self.context, imageReference.abstract)
|
||||
}
|
||||
@ -453,6 +470,125 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
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() {
|
||||
guard let (context, media) = self.contextAndMedia else {
|
||||
return
|
||||
|
@ -290,7 +290,7 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||
})?.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
|
||||
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 {
|
||||
case image(UIImage?)
|
||||
case more(UIImage?)
|
||||
@ -2475,6 +2475,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
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 speedIconText: String = "1x"
|
||||
@ -2804,7 +2827,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
|
@ -840,7 +840,7 @@ func importStickerPackShortNameController(context: AccountContext, title: String
|
||||
case .taken:
|
||||
contentNode?.infoText = .taken
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
case .invalid:
|
||||
case .invalid, .purchaseAvailable:
|
||||
contentNode?.infoText = .info
|
||||
contentNode?.actionNodes.last?.actionEnabled = false
|
||||
}
|
||||
|
@ -5,19 +5,32 @@ import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ActivityIndicator
|
||||
import TextFormat
|
||||
import Markdown
|
||||
|
||||
public class ItemListActivityTextItem: ListViewItem, ItemListItem {
|
||||
public enum TextColor {
|
||||
case generic
|
||||
case constructive
|
||||
case destructive
|
||||
case warning
|
||||
}
|
||||
|
||||
let displayActivity: Bool
|
||||
let presentationData: ItemListPresentationData
|
||||
let text: NSAttributedString
|
||||
let text: String
|
||||
let color: TextColor
|
||||
let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
||||
public let sectionId: ItemListSectionId
|
||||
|
||||
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.presentationData = presentationData
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.linkAction = linkAction
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
@ -78,11 +91,21 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
|
||||
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) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
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 titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||
@ -92,14 +115,23 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
|
||||
activityWidth = 25.0
|
||||
}
|
||||
|
||||
let titleString = NSMutableAttributedString(attributedString: item.text)
|
||||
let hasFont = titleString.attribute(.font, at: 0, effectiveRange: nil) != nil
|
||||
if !hasFont {
|
||||
titleString.removeAttribute(NSAttributedString.Key.font, range: NSMakeRange(0, titleString.length))
|
||||
titleString.addAttributes([NSAttributedString.Key.font: titleFont], range: NSMakeRange(0, titleString.length))
|
||||
let textColor: UIColor
|
||||
switch item.color {
|
||||
case .generic:
|
||||
textColor = item.presentationData.theme.list.freeTextColor
|
||||
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 insets: UIEdgeInsets
|
||||
@ -137,4 +169,27 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
|
||||
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ public class InfoItemNode: ListViewItemNode {
|
||||
if let current = self.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
} 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.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import ContextUI
|
||||
import FileMediaResourceStatus
|
||||
import ManagedAnimationNode
|
||||
import ShimmerEffect
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
|
||||
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
|
||||
|
||||
@ -159,6 +161,167 @@ final class CachedChatListSearchResult {
|
||||
}
|
||||
|
||||
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 containerNode: ContextControllerSourceNode
|
||||
private let extractedBackgroundImageNode: ASImageNode
|
||||
@ -377,6 +540,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
|
||||
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
// let newDescriptionNodeMakeLayout = self.descriptionNode.asyncLayout()
|
||||
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
|
||||
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
|
||||
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 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()))
|
||||
if extensionTextLayout.truncated, let text = extensionText?.string {
|
||||
extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center)
|
||||
|
@ -174,6 +174,7 @@ private final class ChannelOwnershipTransferPasswordFieldNode: ASDisplayNode, UI
|
||||
|
||||
public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let bot: Bool
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
@ -205,9 +206,10 @@ public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
|
||||
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.theme = ptheme
|
||||
self.bot = bot
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
@ -277,8 +279,18 @@ public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode {
|
||||
}
|
||||
|
||||
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)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.strings.Channel_OwnershipTransfer_EnterPasswordText, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
let title: String
|
||||
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
|
||||
for actionNode in self.actionNodes {
|
||||
|
@ -23,6 +23,7 @@ import ContextUI
|
||||
import UndoUI
|
||||
import QrCodeUI
|
||||
import PremiumUI
|
||||
import TextFormat
|
||||
|
||||
private final class ChannelVisibilityControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -41,8 +42,9 @@ private final class ChannelVisibilityControllerArguments {
|
||||
let toggleApproveMembers: (Bool) -> Void
|
||||
let activateLink: (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.updateCurrentType = updateCurrentType
|
||||
self.updatePublicLinkText = updatePublicLinkText
|
||||
@ -59,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
|
||||
self.toggleApproveMembers = toggleApproveMembers
|
||||
self.activateLink = activateLink
|
||||
self.deactivateLink = deactivateLink
|
||||
self.openAuction = openAuction
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +111,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
case privateLinkManageInfo(PresentationTheme, String)
|
||||
|
||||
case publicLinkInfo(PresentationTheme, String)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
|
||||
|
||||
case existingLinksInfo(PresentationTheme, String)
|
||||
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
|
||||
@ -316,8 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus {
|
||||
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -629,10 +632,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .publicLinkHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .publicLinkAvailability(theme, text, value):
|
||||
let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor)
|
||||
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 .publicLinkAvailability(_, text, value):
|
||||
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: text, color: value ? .generic : .destructive, sectionId: self.section)
|
||||
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)
|
||||
case let .privateLinkHeader(_, title):
|
||||
@ -672,26 +673,30 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .publicLinkInfo(_, text):
|
||||
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
|
||||
let color: UIColor
|
||||
let textColor: ItemListActivityTextItem.TextColor
|
||||
switch status {
|
||||
case .invalidFormat:
|
||||
color = theme.list.freeTextErrorColor
|
||||
case let .availability(availability):
|
||||
switch availability {
|
||||
case .available:
|
||||
color = theme.list.freeTextSuccessColor
|
||||
case .invalid:
|
||||
color = theme.list.freeTextErrorColor
|
||||
case .taken:
|
||||
color = theme.list.freeTextErrorColor
|
||||
}
|
||||
case .checking:
|
||||
color = theme.list.freeTextColor
|
||||
displayActivity = true
|
||||
case .invalidFormat:
|
||||
textColor = .destructive
|
||||
case let .availability(availability):
|
||||
switch availability {
|
||||
case .available:
|
||||
textColor = .constructive
|
||||
case .invalid:
|
||||
textColor = .destructive
|
||||
case .taken:
|
||||
textColor = .destructive
|
||||
case .purchaseAvailable:
|
||||
textColor = .generic
|
||||
}
|
||||
case .checking:
|
||||
textColor = .generic
|
||||
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):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
|
||||
@ -1060,12 +1065,20 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
text = presentationData.strings.Channel_Username_InvalidCharacters
|
||||
case .taken:
|
||||
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:
|
||||
text = presentationData.strings.Channel_Username_CheckingUsername
|
||||
}
|
||||
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
|
||||
}
|
||||
if isGroup {
|
||||
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
|
||||
case .taken:
|
||||
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:
|
||||
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))
|
||||
@ -1725,6 +1746,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
|
||||
})]), 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)
|
||||
|
@ -252,7 +252,7 @@ final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0))
|
||||
)
|
||||
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
|
||||
.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
|
||||
)),
|
||||
title: strings.Premium_FasterSpeed,
|
||||
text: strings.Premium_FasterSpeedInfo,
|
||||
text: isStandalone ? strings.Premium_FasterSpeedStandaloneInfo : strings.Premium_FasterSpeedInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
@ -736,7 +736,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
decoration: .badgeStars
|
||||
)),
|
||||
title: strings.Premium_VoiceToText,
|
||||
text: strings.Premium_VoiceToTextInfo,
|
||||
text: isStandalone ? strings.Premium_VoiceToTextStandaloneInfo : strings.Premium_VoiceToTextInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
@ -826,7 +826,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
decoration: .swirlStars
|
||||
)),
|
||||
title: strings.Premium_ChatManagement,
|
||||
text: strings.Premium_ChatManagementInfo,
|
||||
text: isStandalone ? strings.Premium_ChatManagementStandaloneInfo : strings.Premium_ChatManagementInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
@ -972,6 +972,10 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
buttonText = strings.Premium_Gift_GiftSubscription(price ?? "–").string
|
||||
case .other:
|
||||
switch component.subject {
|
||||
case .fasterDownload:
|
||||
buttonText = strings.Premium_FasterSpeed_Proceed
|
||||
case .advancedChatManagement:
|
||||
buttonText = strings.Premium_ChatManagement_Proceed
|
||||
case .uniqueReactions:
|
||||
buttonText = strings.Premium_Reactions_Proceed
|
||||
buttonAnimationName = "premium_unlock"
|
||||
@ -1030,6 +1034,10 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
var contentHeight: CGFloat = context.availableSize.width + 146.0
|
||||
if case .other = component.source {
|
||||
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)
|
||||
|
@ -159,6 +159,12 @@ public enum PremiumSource: Equatable {
|
||||
} else {
|
||||
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 giftTerms
|
||||
case voiceToText
|
||||
case fasterDownload
|
||||
|
||||
var identifier: String? {
|
||||
switch self {
|
||||
@ -225,6 +232,8 @@ public enum PremiumSource: Equatable {
|
||||
return "emoji_status"
|
||||
case .voiceToText:
|
||||
return "voice_to_text"
|
||||
case .fasterDownload:
|
||||
return "faster_download"
|
||||
case .gift, .giftTerms:
|
||||
return nil
|
||||
case let .deeplink(reference):
|
||||
|
@ -51,6 +51,10 @@ extension SettingsSearchableItemIcon {
|
||||
return PresentationResourcesSettings.chatFolders
|
||||
case .deleteAccount:
|
||||
return PresentationResourcesSettings.deleteAccount
|
||||
case .devices:
|
||||
return PresentationResourcesSettings.devices
|
||||
case .premium:
|
||||
return PresentationResourcesSettings.premium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ import PhoneNumberFormat
|
||||
import AccountUtils
|
||||
import InstantPageCache
|
||||
import NotificationPeerExceptionController
|
||||
import QrCodeUI
|
||||
import PremiumUI
|
||||
|
||||
enum SettingsSearchableItemIcon {
|
||||
case profile
|
||||
@ -37,6 +39,8 @@ enum SettingsSearchableItemIcon {
|
||||
case faq
|
||||
case chatFolders
|
||||
case deleteAccount
|
||||
case devices
|
||||
case premium
|
||||
}
|
||||
|
||||
public enum SettingsSearchableItemId: Hashable {
|
||||
@ -57,43 +61,49 @@ public enum SettingsSearchableItemId: Hashable {
|
||||
case faq(Int32)
|
||||
case chatFolders(Int32)
|
||||
case deleteAccount(Int32)
|
||||
case devices(Int32)
|
||||
case premium(Int32)
|
||||
|
||||
private var namespace: Int32 {
|
||||
switch self {
|
||||
case .profile:
|
||||
return 1
|
||||
case .proxy:
|
||||
return 2
|
||||
case .savedMessages:
|
||||
return 3
|
||||
case .calls:
|
||||
return 4
|
||||
case .stickers:
|
||||
return 5
|
||||
case .notifications:
|
||||
return 6
|
||||
case .privacy:
|
||||
return 7
|
||||
case .data:
|
||||
return 8
|
||||
case .appearance:
|
||||
return 9
|
||||
case .language:
|
||||
return 10
|
||||
case .watch:
|
||||
return 11
|
||||
case .passport:
|
||||
return 12
|
||||
case .wallet:
|
||||
return 13
|
||||
case .support:
|
||||
return 14
|
||||
case .faq:
|
||||
return 15
|
||||
case .chatFolders:
|
||||
return 16
|
||||
case .deleteAccount:
|
||||
return 17
|
||||
case .profile:
|
||||
return 1
|
||||
case .proxy:
|
||||
return 2
|
||||
case .savedMessages:
|
||||
return 3
|
||||
case .calls:
|
||||
return 4
|
||||
case .stickers:
|
||||
return 5
|
||||
case .notifications:
|
||||
return 6
|
||||
case .privacy:
|
||||
return 7
|
||||
case .data:
|
||||
return 8
|
||||
case .appearance:
|
||||
return 9
|
||||
case .language:
|
||||
return 10
|
||||
case .watch:
|
||||
return 11
|
||||
case .passport:
|
||||
return 12
|
||||
case .wallet:
|
||||
return 13
|
||||
case .support:
|
||||
return 14
|
||||
case .faq:
|
||||
return 15
|
||||
case .chatFolders:
|
||||
return 16
|
||||
case .deleteAccount:
|
||||
return 17
|
||||
case .devices:
|
||||
return 18
|
||||
case .premium:
|
||||
return 19
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +125,9 @@ public enum SettingsSearchableItemId: Hashable {
|
||||
let .support(id),
|
||||
let .faq(id),
|
||||
let .chatFolders(id),
|
||||
let .deleteAccount(id):
|
||||
let .deleteAccount(id),
|
||||
let .devices(id),
|
||||
let .premium(id):
|
||||
return id
|
||||
}
|
||||
}
|
||||
@ -128,42 +140,46 @@ public enum SettingsSearchableItemId: Hashable {
|
||||
let namespace = Int32((index >> 32) & 0x7fffffff)
|
||||
let id = Int32(bitPattern: UInt32(index & 0xffffffff))
|
||||
switch namespace {
|
||||
case 1:
|
||||
self = .profile(id)
|
||||
case 2:
|
||||
self = .proxy(id)
|
||||
case 3:
|
||||
self = .savedMessages(id)
|
||||
case 4:
|
||||
self = .calls(id)
|
||||
case 5:
|
||||
self = .stickers(id)
|
||||
case 6:
|
||||
self = .notifications(id)
|
||||
case 7:
|
||||
self = .privacy(id)
|
||||
case 8:
|
||||
self = .data(id)
|
||||
case 9:
|
||||
self = .appearance(id)
|
||||
case 10:
|
||||
self = .language(id)
|
||||
case 11:
|
||||
self = .watch(id)
|
||||
case 12:
|
||||
self = .passport(id)
|
||||
case 13:
|
||||
self = .wallet(id)
|
||||
case 14:
|
||||
self = .support(id)
|
||||
case 15:
|
||||
self = .faq(id)
|
||||
case 16:
|
||||
self = .chatFolders(id)
|
||||
case 17:
|
||||
self = .deleteAccount(id)
|
||||
default:
|
||||
return nil
|
||||
case 1:
|
||||
self = .profile(id)
|
||||
case 2:
|
||||
self = .proxy(id)
|
||||
case 3:
|
||||
self = .savedMessages(id)
|
||||
case 4:
|
||||
self = .calls(id)
|
||||
case 5:
|
||||
self = .stickers(id)
|
||||
case 6:
|
||||
self = .notifications(id)
|
||||
case 7:
|
||||
self = .privacy(id)
|
||||
case 8:
|
||||
self = .data(id)
|
||||
case 9:
|
||||
self = .appearance(id)
|
||||
case 10:
|
||||
self = .language(id)
|
||||
case 11:
|
||||
self = .watch(id)
|
||||
case 12:
|
||||
self = .passport(id)
|
||||
case 13:
|
||||
self = .wallet(id)
|
||||
case 14:
|
||||
self = .support(id)
|
||||
case 15:
|
||||
self = .faq(id)
|
||||
case 16:
|
||||
self = .chatFolders(id)
|
||||
case 17:
|
||||
self = .deleteAccount(id)
|
||||
case 18:
|
||||
self = .devices(id)
|
||||
case 19:
|
||||
self = .premium(id)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,6 +249,103 @@ private func profileSearchableItems(context: AccountContext, canAddAccount: Bool
|
||||
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] {
|
||||
let icon: SettingsSearchableItemIcon = .calls
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -838,6 +956,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
|
||||
})
|
||||
allItems.append(savedMessages)
|
||||
|
||||
let devicesItems = devicesSearchableItems(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: activeWebSessionsContext)
|
||||
allItems.append(contentsOf: devicesItems)
|
||||
|
||||
let callItems = callSearchableItems(context: context)
|
||||
allItems.append(contentsOf: callItems)
|
||||
|
||||
@ -867,6 +988,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
|
||||
let languageItems = languageSearchableItems(context: context, localizations: localizations)
|
||||
allItems.append(contentsOf: languageItems)
|
||||
|
||||
let premiumItems = premiumSearchableItems(context: context)
|
||||
allItems.append(contentsOf: premiumItems)
|
||||
|
||||
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
|
||||
present(.push, watchSettingsController(context: context))
|
||||
@ -880,7 +1004,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
|
||||
})
|
||||
allItems.append(passport)
|
||||
}
|
||||
|
||||
|
||||
let support = SettingsSearchableItem(id: .support(0), title: strings.Settings_Support, alternate: synonyms(strings.SettingsSearch_Synonyms_Support), icon: .support, breadcrumbs: [], present: { context, _, present in
|
||||
let _ = (context.engine.peers.supportPeerId()
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
|
@ -11,22 +11,23 @@ import AccountContext
|
||||
import ShareController
|
||||
import UndoUI
|
||||
import InviteLinksUI
|
||||
import TextFormat
|
||||
|
||||
private final class UsernameSetupControllerArguments {
|
||||
let account: Account
|
||||
|
||||
let updatePublicLinkText: (String?, String) -> Void
|
||||
let shareLink: () -> Void
|
||||
|
||||
let activateLink: (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.updatePublicLinkText = updatePublicLinkText
|
||||
self.shareLink = shareLink
|
||||
self.activateLink = activateLink
|
||||
self.deactivateLink = deactivateLink
|
||||
self.openAuction = openAuction
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ private enum UsernameSetupEntryId: Hashable {
|
||||
private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
case publicLinkHeader(PresentationTheme, 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 additionalLinkHeader(PresentationTheme, String)
|
||||
@ -110,8 +111,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText {
|
||||
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -207,24 +208,28 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
arguments.shareLink()
|
||||
}
|
||||
})
|
||||
case let .publicLinkStatus(theme, _, status, text):
|
||||
case let .publicLinkStatus(_, _, status, text, username):
|
||||
var displayActivity = false
|
||||
let string: NSAttributedString
|
||||
let textColor: ItemListActivityTextItem.TextColor
|
||||
switch status {
|
||||
case .invalidFormat:
|
||||
string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor)
|
||||
case let .availability(availability):
|
||||
switch availability {
|
||||
case .available:
|
||||
string = NSAttributedString(string: text, textColor: theme.list.freeTextSuccessColor)
|
||||
case .invalid, .taken:
|
||||
string = NSAttributedString(string: text, textColor: theme.list.freeTextErrorColor)
|
||||
}
|
||||
case .checking:
|
||||
string = NSAttributedString(string: text, textColor: theme.list.freeTextColor)
|
||||
displayActivity = true
|
||||
case .invalidFormat:
|
||||
textColor = .destructive
|
||||
case let .availability(availability):
|
||||
switch availability {
|
||||
case .available:
|
||||
textColor = .constructive
|
||||
case .purchaseAvailable:
|
||||
textColor = .generic
|
||||
case .invalid, .taken:
|
||||
textColor = .destructive
|
||||
}
|
||||
case .checking:
|
||||
textColor = .generic
|
||||
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):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .additionalLink(_, link, _):
|
||||
@ -328,11 +333,19 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
|
||||
statusText = presentationData.strings.Username_InvalidCharacters
|
||||
case .taken:
|
||||
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:
|
||||
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
|
||||
@ -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: {
|
||||
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
|
||||
})]), 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)
|
||||
@ -669,6 +686,5 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -683,12 +683,16 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
|
||||
}
|
||||
|
||||
public var isMusic: Bool {
|
||||
var hasNonVoiceAudio = false
|
||||
var hasVideo = false
|
||||
for attribute in self.attributes {
|
||||
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 {
|
||||
|
@ -17,6 +17,7 @@ public enum AddressNameAvailability: Equatable {
|
||||
case available
|
||||
case invalid
|
||||
case taken
|
||||
case purchaseAvailable
|
||||
}
|
||||
|
||||
public enum AddressNameDomain {
|
||||
@ -33,7 +34,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -
|
||||
if index == 0 {
|
||||
return .startsWithUnderscore
|
||||
} else if index == length - 1 {
|
||||
return length < 5 ? .tooShort : .endsWithUnderscore
|
||||
return length < 4 ? .tooShort : .endsWithUnderscore
|
||||
}
|
||||
}
|
||||
if index == 0 && char >= "0" && char <= "9" {
|
||||
@ -45,7 +46,7 @@ func _internal_checkAddressNameFormat(_ value: String, canEmpty: Bool = false) -
|
||||
index += 1
|
||||
}
|
||||
|
||||
if length < 5 && (!canEmpty || length != 0) {
|
||||
if length < 4 && (!canEmpty || length != 0) {
|
||||
return .tooShort
|
||||
}
|
||||
return nil
|
||||
@ -65,7 +66,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
|
||||
return .single(.purchaseAvailable)
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
}
|
||||
}
|
||||
case let .peer(peerId):
|
||||
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
|
||||
@ -79,7 +84,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
|
||||
return .single(.purchaseAvailable)
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
}
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
return account.network.request(Api.functions.channels.checkUsername(channel: .inputChannelEmpty, username: name))
|
||||
@ -92,7 +101,11 @@ func _internal_addressNameAvailability(account: Account, domain: AddressNameDoma
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<AddressNameAvailability, NoError> in
|
||||
return .single(.invalid)
|
||||
if error.errorDescription == "USERNAME_PURCHASE_AVAILABLE" {
|
||||
return .single(.purchaseAvailable)
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.invalid)
|
||||
|
@ -112,6 +112,10 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatBubbleVerticalLineIncomingImage
|
||||
case chatBubbleVerticalLineOutgoingImage
|
||||
|
||||
case chatBubbleArrowFreeImage
|
||||
case chatBubbleArrowIncomingImage
|
||||
case chatBubbleArrowOutgoingImage
|
||||
|
||||
case chatBubbleCheckBubbleFullImage
|
||||
case chatBubbleBubblePartialImage
|
||||
case checkBubbleMediaFullImage
|
||||
|
@ -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? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in
|
||||
return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor)
|
||||
|
@ -224,20 +224,7 @@ public final class EmojiStatusComponent: Component {
|
||||
iconImage = nil
|
||||
}
|
||||
case let .topic(title, color, realSize):
|
||||
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
|
||||
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)
|
||||
|
||||
let colors = topicIconColors(for: color)
|
||||
if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) {
|
||||
iconImage = image
|
||||
} else {
|
||||
@ -575,3 +562,16 @@ public final class EmojiStatusComponent: Component {
|
||||
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])
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
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) {
|
||||
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))
|
||||
@ -2993,19 +2993,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
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) {
|
||||
if self.content != content {
|
||||
if case let .icon(icon) = content, case let .topic(title, color) = icon {
|
||||
@ -3014,7 +3001,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
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) {
|
||||
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))
|
||||
|
@ -430,8 +430,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
self.title = ""
|
||||
self.fileId = 0
|
||||
|
||||
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F]
|
||||
self.iconColor = colors.randomElement() ?? 0x0
|
||||
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
|
||||
case let .edit(info):
|
||||
self.title = info.title
|
||||
self.fileId = info.icon ?? 0
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "iOS Speed.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
150
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/iOS Speed.pdf
vendored
Normal file
150
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/iOS Speed.pdf
vendored
Normal 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
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
92
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/arrow.pdf
vendored
Normal file
92
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/arrow.pdf
vendored
Normal 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
|
@ -1057,6 +1057,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var tip: ContextController.Tip?
|
||||
|
||||
if tip == nil {
|
||||
let isAd = message.adAttribute != nil
|
||||
|
||||
var isAction = false
|
||||
for media in message.media {
|
||||
if media is TelegramMediaAction {
|
||||
@ -1064,7 +1066,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
break
|
||||
}
|
||||
}
|
||||
if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction {
|
||||
if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction && !isAd {
|
||||
if case .scheduledMessages = strongSelf.subject {
|
||||
} else {
|
||||
var isChannel = false
|
||||
@ -1075,7 +1077,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start()
|
||||
tip = .textSelection
|
||||
@ -1088,11 +1090,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
actions.context = strongSelf.context
|
||||
|
||||
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 {
|
||||
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
|
||||
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)
|
||||
}, navigateToMessageStandalone: { [weak self] id in
|
||||
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
|
||||
self?.chatDisplayNode.dismissInput()
|
||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||
|
@ -70,6 +70,7 @@ public final class ChatControllerInteraction {
|
||||
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
let navigateToMessage: (MessageId, MessageId) -> Void
|
||||
let navigateToMessageStandalone: (MessageId) -> Void
|
||||
let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void
|
||||
let tapMessage: ((Message) -> Void)?
|
||||
let clickThroughMessage: () -> Void
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
@ -178,6 +179,7 @@ public final class ChatControllerInteraction {
|
||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
||||
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
||||
navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void,
|
||||
tapMessage: ((Message) -> Void)?,
|
||||
clickThroughMessage: @escaping () -> Void,
|
||||
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
|
||||
@ -269,6 +271,7 @@ public final class ChatControllerInteraction {
|
||||
self.openMessageContextActions = openMessageContextActions
|
||||
self.navigateToMessage = navigateToMessage
|
||||
self.navigateToMessageStandalone = navigateToMessageStandalone
|
||||
self.navigateToThreadMessage = navigateToThreadMessage
|
||||
self.tapMessage = tapMessage
|
||||
self.clickThroughMessage = clickThroughMessage
|
||||
self.toggleMessagesSelection = toggleMessagesSelection
|
||||
|
@ -1446,7 +1446,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let restrictedNode = self.restrictedNode {
|
||||
transition.updateFrame(node: restrictedNode, frame: contentBounds)
|
||||
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)
|
||||
@ -2343,7 +2343,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.historyNodeContainer.isHidden = true
|
||||
self.navigateButtons.isHidden = true
|
||||
self.loadingNode.isHidden = true
|
||||
self.loadingPlaceholderNode?.isHidden = true
|
||||
self.emptyNode?.isHidden = true
|
||||
self.updateIsLoading(isLoading: false, earlier: false, animated: false)
|
||||
} else if let restrictedNode = self.restrictedNode {
|
||||
self.restrictedNode = nil
|
||||
restrictedNode.removeFromSupernode()
|
||||
|
@ -962,9 +962,7 @@ 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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
|
@ -614,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
|
||||
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,
|
||||
loadStickerSaveStatusSignal,
|
||||
loadResourceStatusSignal,
|
||||
@ -626,9 +626,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
|
||||
context.engine.stickers.availableReactions(),
|
||||
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
|
||||
var canEdit = false
|
||||
if !isAction {
|
||||
@ -652,12 +653,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
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
|
||||
|> 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 isPinnedMessages = false
|
||||
@ -794,7 +797,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
||||
} else {
|
||||
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
controllerInteraction.displayUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_SaveSuccess_Text, action: {
|
||||
controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil))
|
||||
}))
|
||||
@ -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
|
||||
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||
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 = ""
|
||||
for message in messages {
|
||||
|
@ -58,8 +58,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
|
||||
|
||||
class ChatMessageShareButton: HighlightableButtonNode {
|
||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private var iconOffset = CGPoint()
|
||||
|
||||
@ -242,7 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
|
||||
@ -472,6 +474,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.updateVisibility()
|
||||
self.haptic?.enabled = self.visibilityStatus == true
|
||||
|
||||
self.threadInfoNode?.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)
|
||||
}
|
||||
|
||||
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 {
|
||||
var reactionButtonsNodeFrame = reactionButtonsNode.frame
|
||||
reactionButtonsNodeFrame.origin.x += rect.minX
|
||||
@ -796,6 +823,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
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 viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
|
||||
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
@ -1124,11 +1160,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
|
||||
var needsReplyBackground = false
|
||||
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))
|
||||
|
||||
var ignoreForward = false
|
||||
@ -1166,8 +1202,32 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
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 {
|
||||
} 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(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
@ -1425,9 +1485,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyBackgroundNode = 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 {
|
||||
strongSelf.replyBackgroundNode = nil
|
||||
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()
|
||||
@ -1447,7 +1539,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.viaBotNode = 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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
|
||||
@ -1490,7 +1582,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = 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
|
||||
|
||||
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 {
|
||||
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
|
||||
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
|
||||
strongSelf.threadInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.replyInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.viaBotNode?.alpha = panelsAlpha
|
||||
strongSelf.forwardInfoNode?.alpha = panelsAlpha
|
||||
|
@ -510,6 +510,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var forwardInfoReferenceNode: ASDisplayNode? {
|
||||
return self.forwardInfoNode
|
||||
}
|
||||
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode {
|
||||
threadInfoNode.visibility = self.visibility != .none
|
||||
}
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
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) {
|
||||
return .waitForSingleTap
|
||||
}
|
||||
@ -1054,6 +1065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
let authorNameLayout = TextNode.asyncLayout(self.nameNode)
|
||||
let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode)
|
||||
let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
|
||||
let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
|
||||
@ -1076,6 +1088,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
|
||||
authorNameLayout: authorNameLayout,
|
||||
adminBadgeLayout: adminBadgeLayout,
|
||||
threadInfoLayout: threadInfoLayout,
|
||||
forwardInfoLayout: forwardInfoLayout,
|
||||
replyInfoLayout: replyInfoLayout,
|
||||
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))))],
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
@ -1834,6 +1848,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var nameNodeOriginY: CGFloat = 0.0
|
||||
var nameNodeSizeApply: (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 replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil })
|
||||
@ -1945,7 +1962,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
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 {
|
||||
headerSize.height += 6.0
|
||||
} else {
|
||||
@ -2401,6 +2448,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentCredibilityIcon: currentCredibilityIcon,
|
||||
adminNodeSizeApply: adminNodeSizeApply,
|
||||
contentUpperRightCorner: contentUpperRightCorner,
|
||||
threadInfoSizeApply: threadInfoSizeApply,
|
||||
threadInfoOriginY: threadInfoOriginY,
|
||||
forwardInfoSizeApply: forwardInfoSizeApply,
|
||||
forwardInfoOriginY: forwardInfoOriginY,
|
||||
replyInfoSizeApply: replyInfoSizeApply,
|
||||
@ -2449,6 +2498,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentCredibilityIcon: EmojiStatusComponent.Content?,
|
||||
adminNodeSizeApply: (CGSize, () -> TextNode?),
|
||||
contentUpperRightCorner: CGPoint,
|
||||
threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?),
|
||||
threadInfoOriginY: CGFloat,
|
||||
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
|
||||
forwardInfoOriginY: CGFloat,
|
||||
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) {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
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 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) {
|
||||
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 selectAll = true
|
||||
var hasFiles = false
|
||||
@ -3773,6 +3872,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
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) {
|
||||
return shareButtonNode.view
|
||||
}
|
||||
@ -3803,7 +3906,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
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 (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 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
|
||||
|
@ -38,7 +38,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
|
||||
@ -71,6 +73,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
private var visibilityStatus: Bool? {
|
||||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
self.threadInfoNode?.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)
|
||||
}
|
||||
|
||||
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
|
||||
@ -300,6 +311,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
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 viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
|
||||
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
@ -567,6 +587,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
@ -610,8 +631,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
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 {
|
||||
} 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(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
@ -764,9 +808,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
if let (threadInfoSize, _) = threadInfoApply {
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
}
|
||||
|
||||
var viaBotFrame: CGRect?
|
||||
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?
|
||||
@ -775,7 +824,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
if let viaBotFrame = viaBotFrame {
|
||||
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
|
||||
if let viaBotFrameValue = viaBotFrame {
|
||||
if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
|
||||
@ -791,7 +840,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
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 {
|
||||
@ -875,9 +924,41 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyBackgroundNode = 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 {
|
||||
replyBackgroundNode.removeFromSupernode()
|
||||
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()
|
||||
@ -897,7 +978,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.viaBotNode = 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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
|
||||
@ -940,7 +1021,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = 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
|
||||
|
||||
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
|
||||
@ -949,15 +1030,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = nil
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
strongSelf.threadInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.replyInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.viaBotNode?.alpha = panelsAlpha
|
||||
strongSelf.forwardInfoNode?.alpha = panelsAlpha
|
||||
|
509
submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift
Normal file
509
submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
|
||||
private let temporaryHiddenGalleryMediaDisposable = MetaDisposable()
|
||||
|
||||
private var chatPresentationData: ChatPresentationData
|
||||
private var chatPresentationDataPromise: Promise<ChatPresentationData>
|
||||
|
||||
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.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.eventLogContext = self.context.engine.peers.channelAdminEventLog(peerId: self.peer.id)
|
||||
@ -261,6 +263,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ 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
|
||||
self?.openUrl(url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
@ -628,7 +631,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
func updatePresentationData(_ 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.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))
|
||||
transition.updateFrame(node: self.emptyNode, frame: emptyFrame)
|
||||
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 listInsets = UIEdgeInsets(top: contentBottomInset, left: layout.safeInsets.right, bottom: insets.top, right: layout.safeInsets.left)
|
||||
|
@ -5,6 +5,7 @@ import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import WallpaperBackgroundNode
|
||||
import ChatPresentationInterfaceState
|
||||
|
||||
private let titleFont = Font.medium(16.0)
|
||||
private let textFont = Font.regular(15.0)
|
||||
@ -12,8 +13,8 @@ private let textFont = Font.regular(15.0)
|
||||
final class ChatRecentActionsEmptyNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
private var chatWallpaper: TelegramWallpaper
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
|
||||
@ -22,7 +23,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
|
||||
|
||||
private var absolutePosition: (CGRect, CGSize)?
|
||||
|
||||
private var layoutParams: CGSize?
|
||||
private var layoutParams: (CGSize, ChatPresentationData)?
|
||||
|
||||
private var title: String = ""
|
||||
private var text: String = ""
|
||||
@ -31,8 +32,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
|
||||
self.theme = theme
|
||||
self.chatWallpaper = chatWallpaper
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode = NavigationBackgroundNode(color: .clear)
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -43,10 +43,7 @@ final class ChatRecentActionsEmptyNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.allowsGroupOpacity = true
|
||||
|
||||
let graphics = PresentationResourcesChat.additionalGraphics(theme, wallpaper: chatWallpaper, bubbleCorners: chatBubbleCorners)
|
||||
self.backgroundNode.image = graphics.chatEmptyItemBackgroundImage
|
||||
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
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.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)
|
||||
|
||||
@ -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 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)
|
||||
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.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 {
|
||||
self.title = title
|
||||
self.text = text
|
||||
if let size = self.layoutParams, let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
|
||||
self.updateLayout(backgroundNode: wallpaperBackgroundNode, size: size, transition: .immediate)
|
||||
if let (size, presentationData) = self.layoutParams, let wallpaperBackgroundNode = self.wallpaperBackgroundNode {
|
||||
self.updateLayout(presentationData: presentationData, backgroundNode: wallpaperBackgroundNode, size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ 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
|
||||
}, presentController: { _, _ in
|
||||
}, presentControllerInCurrent: { _, _ in
|
||||
|
@ -76,6 +76,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
}, toggleMessagesSelection: { _, _ in
|
||||
}, sendCurrentMessage: { _ in
|
||||
|
@ -23,7 +23,7 @@ private func commitOwnershipTransferController(context: AccountContext, updatedP
|
||||
|
||||
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?()
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.OwnershipTransfer_Transfer, action: {
|
||||
proceedImpl?()
|
||||
|
@ -2147,29 +2147,40 @@ 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
|
||||
c.dismiss(completion: {
|
||||
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
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: {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
var indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
var index: Int = viewControllers.count - 1
|
||||
for controller in viewControllers.reversed() {
|
||||
if let controller = controller as? ChatController, case let .peer(peerId) = controller.chatLocation {
|
||||
if peerId == currentPeerId && !keptCurrentChatController {
|
||||
keptCurrentChatController = true
|
||||
} else {
|
||||
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
|
||||
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 indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
var index: Int = viewControllers.count - 1
|
||||
for controller in viewControllers.reversed() {
|
||||
if let controller = controller as? ChatController, case let .peer(peerId) = controller.chatLocation {
|
||||
if peerId == currentPeerId && !keptCurrentChatController {
|
||||
keptCurrentChatController = true
|
||||
} else {
|
||||
indexesToRemove.insert(index)
|
||||
}
|
||||
} else if controller is PeerInfoScreen {
|
||||
indexesToRemove.insert(index)
|
||||
}
|
||||
} else if controller is PeerInfoScreen {
|
||||
indexesToRemove.insert(index)
|
||||
index -= 1
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
})))
|
||||
@ -2298,36 +2309,40 @@ 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
|
||||
c.dismiss(completion: {
|
||||
if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
let targetLocation: NavigateToChatControllerParams.Location
|
||||
if case let .replyThread(message) = strongSelf.chatLocation {
|
||||
targetLocation = .replyThread(message)
|
||||
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 {
|
||||
targetLocation = .peer(EnginePeer(currentPeer))
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
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 indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
var index: Int = viewControllers.count - 1
|
||||
for controller in viewControllers.reversed() {
|
||||
if let controller = controller as? ChatController, case let .peer(peerId) = controller.chatLocation {
|
||||
if peerId == currentPeerId && !keptCurrentChatController {
|
||||
keptCurrentChatController = true
|
||||
} else {
|
||||
let targetLocation: NavigateToChatControllerParams.Location
|
||||
if case let .replyThread(message) = strongSelf.chatLocation {
|
||||
targetLocation = .replyThread(message)
|
||||
} else {
|
||||
targetLocation = .peer(EnginePeer(currentPeer))
|
||||
}
|
||||
|
||||
let currentPeerId = strongSelf.peerId
|
||||
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 indexesToRemove = Set<Int>()
|
||||
var keptCurrentChatController = false
|
||||
var index: Int = viewControllers.count - 1
|
||||
for controller in viewControllers.reversed() {
|
||||
if let controller = controller as? ChatController, case let .peer(peerId) = controller.chatLocation {
|
||||
if peerId == currentPeerId && !keptCurrentChatController {
|
||||
keptCurrentChatController = true
|
||||
} else {
|
||||
indexesToRemove.insert(index)
|
||||
}
|
||||
} else if controller is PeerInfoScreen {
|
||||
indexesToRemove.insert(index)
|
||||
}
|
||||
} else if controller is PeerInfoScreen {
|
||||
indexesToRemove.insert(index)
|
||||
index -= 1
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
for i in indexesToRemove.sorted().reversed() {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
})))
|
||||
@ -2431,6 +2446,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
})
|
||||
}, navigateToMessage: { fromId, id in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -1293,6 +1293,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: { message in
|
||||
tapMessage?(message)
|
||||
}, clickThroughMessage: {
|
||||
@ -1523,6 +1524,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
|
||||
case .voiceToText:
|
||||
mappedSource = .voiceToText
|
||||
case .fasterDownload:
|
||||
mappedSource = .fasterDownload
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
|
@ -228,7 +228,12 @@ final class WidgetDataContext {
|
||||
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
|
||||
return lhs.id < rhs.id
|
||||
|
@ -249,8 +249,9 @@ public struct WidgetDataPeer: Codable, Equatable {
|
||||
public var avatarPath: String?
|
||||
public var badge: Badge?
|
||||
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.name = name
|
||||
self.lastName = lastName
|
||||
@ -258,6 +259,7 @@ public struct WidgetDataPeer: Codable, Equatable {
|
||||
self.avatarPath = avatarPath
|
||||
self.badge = badge
|
||||
self.message = message
|
||||
self.isForum = isForum
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user