mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'beta'
This commit is contained in:
commit
f27952b7c2
@ -4418,6 +4418,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Call.Mute" = "mute";
|
||||
"Call.Camera" = "camera";
|
||||
"Call.Video" = "video";
|
||||
"Call.Flip" = "flip";
|
||||
"Call.End" = "end";
|
||||
"Call.Speaker" = "speaker";
|
||||
@ -5646,6 +5647,7 @@ Sorry for the inconvenience.";
|
||||
"Call.CameraConfirmationText" = "Switch to video call?";
|
||||
"Call.CameraConfirmationConfirm" = "Switch";
|
||||
|
||||
"Call.YourCameraOff" = "Your camera is off";
|
||||
"Call.YourMicrophoneOff" = "Your microphone is off";
|
||||
"Call.MicrophoneOff" = "%@'s microphone is off";
|
||||
"Call.CameraOff" = "%@'s camera is off";
|
||||
@ -10867,3 +10869,12 @@ Sorry for the inconvenience.";
|
||||
"Chat.PlayOnceMesasgeClose" = "Close";
|
||||
"Chat.PlayOnceMesasgeCloseAndDelete" = "Close and Delete";
|
||||
"Chat.PlayOnceMesasge.DisableScreenCapture" = "Sorry, you can't play this message while screen recording is active.";
|
||||
|
||||
"Call.EncryptedAlertTitle" = "This call is end-to-end encrypted";
|
||||
"Call.EncryptedAlertText" = "If the emoji on %@'s screen are the same, this call is 100% secure.";
|
||||
"Call.EncryptionKeyTooltip" = "Encryption key of this call";
|
||||
"Call.StatusBusy" = "Line Busy";
|
||||
"Call.StatusDeclined" = "Call Declined";
|
||||
"Call.StatusFailed" = "Call Failed";
|
||||
"Call.StatusEnded" = "Call Ended";
|
||||
"Call.StatusMissed" = "Call Missed";
|
||||
|
@ -43,6 +43,7 @@ swift_library(
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/MetalEngine",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUI/Components/Calls/CallScreen",
|
||||
],
|
||||
)
|
||||
@ -188,5 +189,4 @@ xcodeproj(
|
||||
},
|
||||
},
|
||||
default_xcode_configuration = "Debug"
|
||||
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import MetalEngine
|
||||
import Display
|
||||
import CallScreen
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
|
||||
private extension UIScreen {
|
||||
private static let cornerRadiusKey: String = {
|
||||
@ -24,6 +25,7 @@ private extension UIScreen {
|
||||
public final class ViewController: UIViewController {
|
||||
private var callScreenView: PrivateCallScreen?
|
||||
private var callState: PrivateCallScreen.State = PrivateCallScreen.State(
|
||||
strings: defaultPresentationStrings,
|
||||
lifecycleState: .connecting,
|
||||
name: "Emma Walters",
|
||||
shortName: "Emma",
|
||||
|
@ -356,27 +356,34 @@ public enum ChatSearchDomain: Equatable {
|
||||
case everything
|
||||
case members
|
||||
case member(Peer)
|
||||
case tag(String)
|
||||
|
||||
public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool {
|
||||
switch lhs {
|
||||
case .everything:
|
||||
if case .everything = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .members:
|
||||
if case .members = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .member(lhsPeer):
|
||||
if case let .member(rhsPeer) = rhs, lhsPeer.isEqual(rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .everything:
|
||||
if case .everything = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .members:
|
||||
if case .members = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .member(lhsPeer):
|
||||
if case let .member(rhsPeer) = rhs, lhsPeer.isEqual(rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .tag(tag):
|
||||
if case .tag(tag) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName:
|
||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||
private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white)
|
||||
private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white)
|
||||
private let myNotesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/MyNotesIcon"), color: .white)
|
||||
|
||||
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||
return Font.with(size: size, design: .round, weight: .bold)
|
||||
@ -95,6 +96,8 @@ private func calculateColors(context: AccountContext?, explicitColorIndex: Int?,
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case .anonymousSavedMessagesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case .myNotesIcon = icon {
|
||||
colors = AvatarNode.savedMessagesColors
|
||||
} else if case .editAvatarIcon = icon, let theme {
|
||||
colors = [theme.list.itemAccentColor.withAlphaComponent(0.1), theme.list.itemAccentColor.withAlphaComponent(0.1)]
|
||||
} else if case let .archivedChatsIcon(hiddenByDefault) = icon, let theme = theme {
|
||||
@ -176,6 +179,7 @@ private enum AvatarNodeIcon: Equatable {
|
||||
case savedMessagesIcon
|
||||
case repliesIcon
|
||||
case anonymousSavedMessagesIcon
|
||||
case myNotesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon
|
||||
case deletedIcon
|
||||
@ -189,6 +193,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
||||
case savedMessagesIcon
|
||||
case repliesIcon
|
||||
case anonymousSavedMessagesIcon
|
||||
case myNotesIcon
|
||||
case archivedChatsIcon(hiddenByDefault: Bool)
|
||||
case editAvatarIcon(forceNone: Bool)
|
||||
case deletedIcon
|
||||
@ -492,6 +497,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .anonymousSavedMessagesIcon:
|
||||
representation = nil
|
||||
icon = .anonymousSavedMessagesIcon
|
||||
case .myNotesIcon:
|
||||
representation = nil
|
||||
icon = .myNotesIcon
|
||||
case let .archivedChatsIcon(hiddenByDefault):
|
||||
representation = nil
|
||||
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
|
||||
@ -662,6 +670,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .anonymousSavedMessagesIcon:
|
||||
representation = nil
|
||||
icon = .anonymousSavedMessagesIcon
|
||||
case .myNotesIcon:
|
||||
representation = nil
|
||||
icon = .myNotesIcon
|
||||
case let .archivedChatsIcon(hiddenByDefault):
|
||||
representation = nil
|
||||
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
|
||||
@ -901,6 +912,15 @@ public final class AvatarNode: ASDisplayNode {
|
||||
if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon {
|
||||
context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size))
|
||||
}
|
||||
} else if case .myNotesIcon = parameters.icon {
|
||||
let factor = bounds.size.width / 60.0
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
context.scaleBy(x: factor, y: -factor)
|
||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
|
||||
if let myNotesIcon = myNotesIcon {
|
||||
context.draw(myNotesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - myNotesIcon.size.width) / 2.0), y: floor((bounds.size.height - myNotesIcon.size.height) / 2.0)), size: myNotesIcon.size))
|
||||
}
|
||||
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage {
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
|
@ -217,7 +217,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
sortOrder: nameSortOrder,
|
||||
displayOrder: nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: .peer(peer: primaryPeer, chatPeer: chatPeer),
|
||||
status: status,
|
||||
badge: badge,
|
||||
@ -508,7 +508,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
toggleExpandGlobalResults()
|
||||
})
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .generalSearch, peer: .thread(peer: peer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor), status: .none, badge: nil, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .thread(peer: peer, title: threadInfo.info.title, icon: threadInfo.info.icon, color: threadInfo.info.iconColor), status: .none, badge: nil, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
interaction.peerSelected(peer, nil, threadInfo.id, nil)
|
||||
}, contextAction: nil, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer)
|
||||
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, storyStats):
|
||||
@ -572,7 +572,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
|
||||
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
|
||||
interaction.peerSelected(chatPeer, peer, nil, nil)
|
||||
} else {
|
||||
@ -671,8 +671,12 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
toggleExpandLocalResults()
|
||||
})
|
||||
}
|
||||
var isSavedMessages = false
|
||||
if case .savedMessagesChats = location {
|
||||
isSavedMessages = true
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
|
||||
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
|
||||
interaction.peerSelected(chatPeer, peer, nil, nil)
|
||||
} else {
|
||||
@ -747,8 +751,13 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
toggleExpandGlobalResults()
|
||||
})
|
||||
}
|
||||
|
||||
var isSavedMessages = false
|
||||
if case .savedMessagesChats = location {
|
||||
isSavedMessages = true
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil)
|
||||
}, contextAction: peerContextAction.flatMap { peerContextAction in
|
||||
return { node, gesture, location in
|
||||
@ -1414,8 +1423,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
if let query {
|
||||
foundLocalPeers = context.engine.messages.searchLocalSavedMessagesPeers(query: query.lowercased(), indexNameMapping: [
|
||||
context.account.peerId: [
|
||||
PeerIndexNameRepresentation.title(title: "saved messages", addressNames: []),
|
||||
PeerIndexNameRepresentation.title(title: presentationData.strings.DialogList_SavedMessages.lowercased(), addressNames: [])
|
||||
PeerIndexNameRepresentation.title(title: "my notes", addressNames: []),
|
||||
//TODO:localize
|
||||
PeerIndexNameRepresentation.title(title: "my notes".lowercased(), addressNames: [])
|
||||
],
|
||||
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2666000)): [
|
||||
PeerIndexNameRepresentation.title(title: presentationData.strings.ChatList_AuthorHidden.lowercased(), addressNames: [])
|
||||
|
@ -1406,7 +1406,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if peer.id.isAnonymousSavedMessages {
|
||||
overrideImage = .anonymousSavedMessagesIcon
|
||||
} else if peer.id == item.context.account.peerId && !displayAsMessage {
|
||||
overrideImage = .savedMessagesIcon
|
||||
if case .savedMessagesChats = item.chatListLocation {
|
||||
overrideImage = .myNotesIcon
|
||||
} else {
|
||||
overrideImage = .savedMessagesIcon
|
||||
}
|
||||
} else if peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
@ -2302,7 +2306,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if isPeerGroup {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor)
|
||||
} else if itemPeer.chatMainPeer?.id == item.context.account.peerId {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor)
|
||||
if case .savedMessagesChats = item.chatListLocation {
|
||||
//TODO:localize
|
||||
titleAttributedString = NSAttributedString(string: "My Notes", font: titleFont, textColor: theme.titleColor)
|
||||
} else {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor)
|
||||
}
|
||||
} else if let id = itemPeer.chatMainPeer?.id, id.isReplies {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleFont, textColor: theme.titleColor)
|
||||
} else if let id = itemPeer.chatMainPeer?.id, id.isAnonymousSavedMessages {
|
||||
@ -2608,7 +2617,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var isFirstForumThreadSelectable = false
|
||||
var forumThreads: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)] = []
|
||||
if forumThread != nil || !topForumTopicItems.isEmpty {
|
||||
if case .savedMessagesChats = item.chatListLocation {
|
||||
} else if forumThread != nil || !topForumTopicItems.isEmpty {
|
||||
if let forumThread = forumThread {
|
||||
isFirstForumThreadSelectable = forumThread.isUnread
|
||||
forumThreads.append((id: forumThread.id, title: NSAttributedString(string: forumThread.title, font: textFont, textColor: forumThread.isUnread || isSearching ? theme.authorNameColor : theme.messageTextColor), iconId: forumThread.iconId, iconColor: forumThread.iconColor))
|
||||
|
@ -580,7 +580,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: enabled,
|
||||
@ -619,7 +619,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: true,
|
||||
@ -681,7 +681,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: true,
|
||||
@ -899,7 +899,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: enabled,
|
||||
@ -938,7 +938,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: true,
|
||||
@ -1000,7 +1000,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
sortOrder: presentationData.nameSortOrder,
|
||||
displayOrder: presentationData.nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peer: peerContent,
|
||||
status: status,
|
||||
enabled: true,
|
||||
|
@ -24,6 +24,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -14,6 +14,11 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
import AppBundle
|
||||
|
||||
private let tagImage: UIImage? = {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ReactionTagBackground"), color: .white)?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 15)
|
||||
}()
|
||||
|
||||
public final class ReactionIconView: PortalSourceView {
|
||||
private var animationLayer: InlineStickerItemLayer?
|
||||
@ -273,6 +278,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
var colors: Colors
|
||||
var size: CGSize
|
||||
var counter: Counter?
|
||||
var isTag: Bool
|
||||
}
|
||||
|
||||
private struct AnimationState {
|
||||
@ -366,98 +372,116 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
let animationState = self.animationState
|
||||
|
||||
DispatchQueue.global().async { [weak self] in
|
||||
var image: UIImage?
|
||||
|
||||
if true {
|
||||
image = generateImage(layout.size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
let image = generateImage(layout.size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
func drawContents(colors: Colors) {
|
||||
let backgroundColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
if isExtracted {
|
||||
backgroundColor = UIColor(argb: colors.extractedBackground)
|
||||
foregroundColor = UIColor(argb: colors.extractedForeground)
|
||||
} else {
|
||||
backgroundColor = UIColor(argb: colors.background)
|
||||
foregroundColor = UIColor(argb: colors.foreground)
|
||||
}
|
||||
|
||||
func drawContents(colors: Colors) {
|
||||
let backgroundColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
if isExtracted {
|
||||
backgroundColor = UIColor(argb: colors.extractedBackground)
|
||||
foregroundColor = UIColor(argb: colors.extractedForeground)
|
||||
} else {
|
||||
backgroundColor = UIColor(argb: colors.background)
|
||||
foregroundColor = UIColor(argb: colors.foreground)
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
if layout.isTag {
|
||||
if let tagImage {
|
||||
let rect = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
|
||||
context.setFillColor(UIColor(rgb: layout.colors.background).cgColor)
|
||||
context.fill(rect)
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
tagImage.draw(in: rect, blendMode: .destinationIn, alpha: 1.0)
|
||||
UIGraphicsPopContext()
|
||||
|
||||
context.setBlendMode(.destinationIn)
|
||||
context.setFillColor(UIColor(white: 1.0, alpha: 0.5).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: rect.width - 6.0 - 6.0, y: floor((rect.height - 6.0) * 0.5)), size: CGSize(width: 6.0, height: 6.0)))
|
||||
context.setBlendMode(.copy)
|
||||
}
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
} else {
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height)))
|
||||
|
||||
if let counter = layout.counter {
|
||||
let isForegroundTransparent = foregroundColor.alpha < 1.0
|
||||
context.setBlendMode(isForegroundTransparent ? .copy : .normal)
|
||||
|
||||
let textOrigin: CGFloat = 36.0
|
||||
|
||||
var rightTextOrigin = textOrigin + totalComponentWidth
|
||||
|
||||
let animationFraction: CGFloat
|
||||
if let animationState = animationState, animationState.fromCounter != nil {
|
||||
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
|
||||
} else {
|
||||
animationFraction = 1.0
|
||||
}
|
||||
|
||||
for i in (0 ..< counter.components.count).reversed() {
|
||||
let component = counter.components[i]
|
||||
var componentAlpha: CGFloat = 1.0
|
||||
var componentVerticalOffset: CGFloat = 0.0
|
||||
|
||||
if let animationState = animationState, let fromCounter = animationState.fromCounter {
|
||||
let reverseIndex = counter.components.count - 1 - i
|
||||
if reverseIndex < fromCounter.components.count {
|
||||
let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex]
|
||||
|
||||
if previousComponent != component {
|
||||
componentAlpha = animationFraction
|
||||
componentVerticalOffset = -(1.0 - animationFraction) * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
componentVerticalOffset = -componentVerticalOffset
|
||||
}
|
||||
|
||||
let previousComponentAlpha = 1.0 - componentAlpha
|
||||
var previousComponentVerticalOffset = animationFraction * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
previousComponentVerticalOffset = -previousComponentVerticalOffset
|
||||
}
|
||||
|
||||
var componentOrigin = rightTextOrigin - previousComponent.bounds.width
|
||||
componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel)
|
||||
let previousColor: UIColor
|
||||
if isForegroundTransparent {
|
||||
previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)
|
||||
} else {
|
||||
previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha)
|
||||
}
|
||||
let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor)
|
||||
string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let componentOrigin = rightTextOrigin - component.bounds.width
|
||||
let currentColor: UIColor
|
||||
if isForegroundTransparent {
|
||||
currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha)
|
||||
} else {
|
||||
currentColor = foregroundColor.withMultipliedAlpha(componentAlpha)
|
||||
}
|
||||
let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor)
|
||||
string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset))
|
||||
|
||||
rightTextOrigin -= component.bounds.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let counter = layout.counter {
|
||||
let isForegroundTransparent = foregroundColor.alpha < 1.0
|
||||
context.setBlendMode(isForegroundTransparent ? .copy : .normal)
|
||||
|
||||
let textOrigin: CGFloat = 36.0
|
||||
|
||||
var rightTextOrigin = textOrigin + totalComponentWidth
|
||||
|
||||
let animationFraction: CGFloat
|
||||
if let animationState = animationState, animationState.fromCounter != nil {
|
||||
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
|
||||
} else {
|
||||
animationFraction = 1.0
|
||||
}
|
||||
|
||||
for i in (0 ..< counter.components.count).reversed() {
|
||||
let component = counter.components[i]
|
||||
var componentAlpha: CGFloat = 1.0
|
||||
var componentVerticalOffset: CGFloat = 0.0
|
||||
|
||||
if let animationState = animationState, let fromCounter = animationState.fromCounter {
|
||||
let reverseIndex = counter.components.count - 1 - i
|
||||
if reverseIndex < fromCounter.components.count {
|
||||
let previousComponent = fromCounter.components[fromCounter.components.count - 1 - reverseIndex]
|
||||
|
||||
if previousComponent != component {
|
||||
componentAlpha = animationFraction
|
||||
componentVerticalOffset = -(1.0 - animationFraction) * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
componentVerticalOffset = -componentVerticalOffset
|
||||
}
|
||||
|
||||
let previousComponentAlpha = 1.0 - componentAlpha
|
||||
var previousComponentVerticalOffset = animationFraction * 8.0
|
||||
if previousComponent.string < component.string {
|
||||
previousComponentVerticalOffset = -previousComponentVerticalOffset
|
||||
}
|
||||
|
||||
var componentOrigin = rightTextOrigin - previousComponent.bounds.width
|
||||
componentOrigin = max(componentOrigin, layout.size.height / 2.0 + UIScreenPixel)
|
||||
let previousColor: UIColor
|
||||
if isForegroundTransparent {
|
||||
previousColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)
|
||||
} else {
|
||||
previousColor = foregroundColor.withMultipliedAlpha(previousComponentAlpha)
|
||||
}
|
||||
let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: previousColor)
|
||||
string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let componentOrigin = rightTextOrigin - component.bounds.width
|
||||
let currentColor: UIColor
|
||||
if isForegroundTransparent {
|
||||
currentColor = foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha)
|
||||
} else {
|
||||
currentColor = foregroundColor.withMultipliedAlpha(componentAlpha)
|
||||
}
|
||||
let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: currentColor)
|
||||
string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset))
|
||||
|
||||
rightTextOrigin -= component.bounds.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if layout.isTag {
|
||||
drawContents(colors: layout.colors)
|
||||
} else {
|
||||
if let animationState = animationState, animationState.fromColors.isSelected != layout.colors.isSelected {
|
||||
var animationFraction: CGFloat = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
|
||||
if !layout.colors.isSelected {
|
||||
@ -473,7 +497,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
drawContents(colors: layout.colors.isSelected ? layout.colors : animationState.fromColors)
|
||||
|
||||
context.resetClip()
|
||||
|
||||
|
||||
context.beginPath()
|
||||
context.addRect(CGRect(origin: CGPoint(), size: size))
|
||||
context.addEllipse(in: CGRect(origin: CGPoint(x: center.x - diameter / 2.0, y: center.y - diameter / 2.0), size: CGSize(width: diameter, height: diameter)))
|
||||
@ -482,10 +506,10 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
} else {
|
||||
drawContents(colors: layout.colors)
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let strongSelf = self, let image = image {
|
||||
@ -641,7 +665,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
|
||||
let backgroundColor = spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: sideInsets + floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
||||
let imageFrame: CGRect
|
||||
if spec.component.isTag {
|
||||
imageFrame = CGRect(origin: CGPoint(x: 6.0 + floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
||||
} else {
|
||||
imageFrame = CGRect(origin: CGPoint(x: sideInsets + floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
||||
}
|
||||
|
||||
|
||||
var counterLayout: CounterLayout?
|
||||
|
||||
@ -653,6 +683,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
} else {
|
||||
size.width -= 2.0
|
||||
}
|
||||
} else if spec.component.isTag {
|
||||
size.width += 2.0
|
||||
} else {
|
||||
let counterSpec = CounterLayout.Spec(
|
||||
stringComponents: counterComponents
|
||||
@ -686,7 +718,8 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
||||
let backgroundLayout = ContainerButtonNode.Layout(
|
||||
colors: backgroundColors,
|
||||
size: size,
|
||||
counter: backgroundCounter
|
||||
counter: backgroundCounter,
|
||||
isTag: spec.component.isTag
|
||||
)
|
||||
|
||||
return Layout(
|
||||
@ -1023,6 +1056,7 @@ public final class ReactionButtonComponent: Equatable {
|
||||
public let colors: Colors
|
||||
public let reaction: Reaction
|
||||
public let avatarPeers: [EnginePeer]
|
||||
public let isTag: Bool
|
||||
public let count: Int
|
||||
public let chosenOrder: Int?
|
||||
public let action: (MessageReaction.Reaction) -> Void
|
||||
@ -1032,6 +1066,7 @@ public final class ReactionButtonComponent: Equatable {
|
||||
colors: Colors,
|
||||
reaction: Reaction,
|
||||
avatarPeers: [EnginePeer],
|
||||
isTag: Bool,
|
||||
count: Int,
|
||||
chosenOrder: Int?,
|
||||
action: @escaping (MessageReaction.Reaction) -> Void
|
||||
@ -1040,6 +1075,7 @@ public final class ReactionButtonComponent: Equatable {
|
||||
self.colors = colors
|
||||
self.reaction = reaction
|
||||
self.avatarPeers = avatarPeers
|
||||
self.isTag = isTag
|
||||
self.count = count
|
||||
self.chosenOrder = chosenOrder
|
||||
self.action = action
|
||||
@ -1058,6 +1094,9 @@ public final class ReactionButtonComponent: Equatable {
|
||||
if lhs.avatarPeers != rhs.avatarPeers {
|
||||
return false
|
||||
}
|
||||
if lhs.isTag != rhs.isTag {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
@ -1180,6 +1219,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
|
||||
action: @escaping (MessageReaction.Reaction) -> Void,
|
||||
reactions: [ReactionButtonsAsyncLayoutContainer.Reaction],
|
||||
colors: ReactionButtonComponent.Colors,
|
||||
isTag: Bool,
|
||||
constrainedWidth: CGFloat
|
||||
) -> Result {
|
||||
var items: [Result.Item] = []
|
||||
@ -1228,8 +1268,9 @@ public final class ReactionButtonsAsyncLayoutContainer {
|
||||
context: context,
|
||||
colors: colors,
|
||||
reaction: reaction.reaction,
|
||||
avatarPeers: avatarPeers,
|
||||
count: reaction.count,
|
||||
avatarPeers: isTag ? [] : avatarPeers,
|
||||
isTag: isTag,
|
||||
count: isTag ? 0 : reaction.count,
|
||||
chosenOrder: reaction.chosenOrder,
|
||||
action: action
|
||||
))
|
||||
|
@ -213,7 +213,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||
interaction.openPeer(peer, .generic)
|
||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, storyStats: storyStats, openStories: { peer, sourceNode in
|
||||
if case let .peer(peerValue, _) = peer, let peerValue {
|
||||
|
@ -63,8 +63,8 @@ public struct ContactsPeerItemEditing: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemPeerMode {
|
||||
case generalSearch
|
||||
public enum ContactsPeerItemPeerMode: Equatable {
|
||||
case generalSearch(isSavedMessages: Bool)
|
||||
case peer
|
||||
}
|
||||
|
||||
@ -779,8 +779,13 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
}
|
||||
if case let .user(user) = peer {
|
||||
if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor)
|
||||
if peer.id == item.context.account.peerId, case let .generalSearch(isSavedMessages) = item.peerMode {
|
||||
if isSavedMessages {
|
||||
//TODO:localize
|
||||
titleAttributedString = NSAttributedString(string: "My Notes", font: titleBoldFont, textColor: textColor)
|
||||
} else {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor)
|
||||
}
|
||||
} else if peer.id.isReplies {
|
||||
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleBoldFont, textColor: textColor)
|
||||
} else if peer.id.isAnonymousSavedMessages {
|
||||
@ -1028,8 +1033,12 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
case let .peer(peer, _):
|
||||
if let peer = peer {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode {
|
||||
overrideImage = .savedMessagesIcon
|
||||
if peer.id == item.context.account.peerId, case let .generalSearch(isSavedMessages) = item.peerMode {
|
||||
if isSavedMessages {
|
||||
overrideImage = .myNotesIcon
|
||||
} else {
|
||||
overrideImage = .savedMessagesIcon
|
||||
}
|
||||
} else if peer.id.isReplies, case .generalSearch = item.peerMode {
|
||||
overrideImage = .repliesIcon
|
||||
} else if peer.id.isAnonymousSavedMessages, case .generalSearch = item.peerMode {
|
||||
|
@ -2249,6 +2249,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
public var context: AccountContext?
|
||||
public var reactionItems: [ReactionContextItem]
|
||||
public var selectedReactionItems: Set<MessageReaction.Reaction>
|
||||
public var reactionsTitle: String?
|
||||
public var animationCache: AnimationCache?
|
||||
public var alwaysAllowPremiumReactions: Bool
|
||||
public var getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?
|
||||
@ -2263,6 +2264,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
context: AccountContext? = nil,
|
||||
reactionItems: [ReactionContextItem] = [],
|
||||
selectedReactionItems: Set<MessageReaction.Reaction> = Set(),
|
||||
reactionsTitle: String? = nil,
|
||||
animationCache: AnimationCache? = nil,
|
||||
alwaysAllowPremiumReactions: Bool = false,
|
||||
getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)? = nil,
|
||||
@ -2277,6 +2279,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.animationCache = animationCache
|
||||
self.reactionItems = reactionItems
|
||||
self.selectedReactionItems = selectedReactionItems
|
||||
self.reactionsTitle = reactionsTitle
|
||||
self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions
|
||||
self.getEmojiContent = getEmojiContent
|
||||
self.disablePositionLock = disablePositionLock
|
||||
@ -2291,6 +2294,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.context = nil
|
||||
self.reactionItems = []
|
||||
self.selectedReactionItems = Set()
|
||||
self.reactionsTitle = nil
|
||||
self.alwaysAllowPremiumReactions = false
|
||||
self.getEmojiContent = nil
|
||||
self.disablePositionLock = false
|
||||
|
@ -46,7 +46,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
|
||||
var id: AnyHashable? { get }
|
||||
var tip: ContextController.Tip? { get }
|
||||
var tipSignal: Signal<ContextController.Tip?, NoError>? { get }
|
||||
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? { get }
|
||||
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? { get }
|
||||
var dismissed: (() -> Void)? { get }
|
||||
}
|
||||
|
||||
@ -911,7 +911,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
|
||||
|
||||
let id: AnyHashable?
|
||||
let items: [ContextMenuItem]
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let tip: ContextController.Tip?
|
||||
let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
let dismissed: (() -> Void)?
|
||||
@ -919,7 +919,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
|
||||
init(
|
||||
id: AnyHashable?,
|
||||
items: [ContextMenuItem],
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
@ -1009,7 +1009,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
|
||||
let id: AnyHashable?
|
||||
private let content: ContextControllerItemsContent
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let tip: ContextController.Tip?
|
||||
let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
let dismissed: (() -> Void)?
|
||||
@ -1017,7 +1017,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
init(
|
||||
id: AnyHashable?,
|
||||
content: ContextControllerItemsContent,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
@ -1046,9 +1046,9 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
||||
}
|
||||
|
||||
func makeContextControllerActionsStackItem(items: ContextController.Items) -> [ContextControllerActionsStackItem] {
|
||||
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
if let context = items.context, let animationCache = items.animationCache, !items.reactionItems.isEmpty {
|
||||
reactionItems = (context, items.reactionItems, items.selectedReactionItems, animationCache, alwaysAllowPremiumReactions: items.alwaysAllowPremiumReactions, items.getEmojiContent)
|
||||
reactionItems = (context, items.reactionItems, items.selectedReactionItems, reactionsTitle: items.reactionsTitle, animationCache, alwaysAllowPremiumReactions: items.alwaysAllowPremiumReactions, items.getEmojiContent)
|
||||
}
|
||||
switch items.content {
|
||||
case let .list(listItems):
|
||||
@ -1172,7 +1172,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
var tip: ContextController.Tip?
|
||||
let tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
var tipNode: InnerTextSelectionTipContainerNode?
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let itemDismissed: (() -> Void)?
|
||||
var storedScrollingState: CGFloat?
|
||||
let positionLock: CGFloat?
|
||||
@ -1187,7 +1187,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
item: ContextControllerActionsStackItem,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
itemDismissed: (() -> Void)?,
|
||||
positionLock: CGFloat?
|
||||
) {
|
||||
@ -1338,7 +1338,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
|
||||
private var selectionPanGesture: UIPanGestureRecognizer?
|
||||
|
||||
var topReactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? {
|
||||
var topReactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? {
|
||||
return self.itemContainers.last?.reactionItems
|
||||
}
|
||||
|
||||
|
@ -381,7 +381,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
//TODO:
|
||||
}
|
||||
|
||||
return self.scrollNode.hitTest(self.view.convert(point, to: self.scrollNode.view), with: event)
|
||||
if let result = self.scrollNode.hitTest(self.view.convert(point, to: self.scrollNode.view), with: event) {
|
||||
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isExpanded {
|
||||
if result === self.actionsContainerNode.view {
|
||||
return self.dismissTapNode.view
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -639,6 +648,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
presentationData: presentationData,
|
||||
items: reactionItems.reactionItems,
|
||||
selectedItems: reactionItems.selectedReactionItems,
|
||||
title: reactionItems.reactionsTitle,
|
||||
alwaysAllowPremiumReactions: reactionItems.alwaysAllowPremiumReactions,
|
||||
getEmojiContent: reactionItems.getEmojiContent,
|
||||
isExpandedUpdated: { [weak self] transition in
|
||||
|
@ -46,15 +46,17 @@ public struct SearchBarToken {
|
||||
public let icon: UIImage?
|
||||
public let iconOffset: CGFloat?
|
||||
public let peer: (EnginePeer, AccountContext, PresentationTheme)?
|
||||
public let isTag: Bool
|
||||
public let title: String
|
||||
public let style: Style?
|
||||
public let permanent: Bool
|
||||
|
||||
public init(id: AnyHashable, icon: UIImage?, iconOffset: CGFloat? = 0.0, peer: (EnginePeer, AccountContext, PresentationTheme)? = nil, title: String, style: Style? = nil, permanent: Bool) {
|
||||
public init(id: AnyHashable, icon: UIImage?, iconOffset: CGFloat? = 0.0, peer: (EnginePeer, AccountContext, PresentationTheme)? = nil, isTag: Bool = false, title: String, style: Style? = nil, permanent: Bool) {
|
||||
self.id = id
|
||||
self.icon = icon
|
||||
self.iconOffset = iconOffset
|
||||
self.peer = peer
|
||||
self.isTag = isTag
|
||||
self.title = title
|
||||
self.style = style
|
||||
self.permanent = permanent
|
||||
@ -108,15 +110,20 @@ private final class TokenNode: ASDisplayNode {
|
||||
} else {
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
|
||||
let backgroundColor = token.style?.backgroundColor ?? theme.inputIcon
|
||||
let backgroundColor = token.isTag ? theme.inputIcon.withMultipliedAlpha(0.2) : (token.style?.backgroundColor ?? theme.inputIcon)
|
||||
let strokeColor = token.style?.strokeColor ?? backgroundColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
|
||||
let foregroundColor = token.style?.foregroundColor ?? .white
|
||||
if token.isTag {
|
||||
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/SearchTagTokenBackground"), color: backgroundColor)?.stretchableImage(withLeftCapWidth: 7, topCapHeight: 0)
|
||||
} else {
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
}
|
||||
|
||||
let foregroundColor = token.isTag ? theme.primaryText : (token.style?.foregroundColor ?? .white)
|
||||
self.iconNode.image = generateTintedImage(image: token.icon, color: foregroundColor)
|
||||
self.containerNode.addSubnode(self.iconNode)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(token.isTag ? 14.0 : 17.0), textColor: foregroundColor)
|
||||
self.containerNode.addSubnode(self.titleNode)
|
||||
}
|
||||
}
|
||||
@ -132,19 +139,24 @@ private final class TokenNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
let targetFrame = self.containerNode.frame
|
||||
self.containerNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.backgroundNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
if let avatarNode = self.avatarNode {
|
||||
avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
if self.token.isTag {
|
||||
self.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
} else {
|
||||
let targetFrame = self.containerNode.frame
|
||||
self.containerNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.backgroundNode.layer.animateFrame(from: CGRect(origin: targetFrame.origin, size: CGSize(width: 1.0, height: targetFrame.height)), to: targetFrame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
if let avatarNode = self.avatarNode {
|
||||
avatarNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
self.titleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
self.titleNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
@ -160,11 +172,21 @@ private final class TokenNode: ASDisplayNode {
|
||||
self.isCollapsed = isCollapsed
|
||||
|
||||
if theme !== self.theme || isSelected != wasSelected {
|
||||
let backgroundColor = isSelected ? self.theme.accent : (token.style?.backgroundColor ?? self.theme.inputIcon)
|
||||
let strokeColor = isSelected ? backgroundColor : (token.style?.strokeColor ?? backgroundColor)
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
let backgroundColor: UIColor
|
||||
if isSelected {
|
||||
backgroundColor = self.theme.accent
|
||||
} else {
|
||||
backgroundColor = token.isTag ? theme.inputIcon.withMultipliedAlpha(0.2) : (token.style?.backgroundColor ?? self.theme.inputIcon)
|
||||
}
|
||||
|
||||
var foregroundColor = isSelected ? .white : (token.style?.foregroundColor ?? .white)
|
||||
let strokeColor = isSelected ? backgroundColor : (token.style?.strokeColor ?? backgroundColor)
|
||||
if token.isTag {
|
||||
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/SearchTagTokenBackground"), color: backgroundColor)?.stretchableImage(withLeftCapWidth: 7, topCapHeight: 0)
|
||||
} else {
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 8.0, color: backgroundColor, strokeColor: strokeColor, strokeWidth: UIScreenPixel, backgroundColor: nil)
|
||||
}
|
||||
|
||||
var foregroundColor = isSelected ? .white : (token.isTag ? self.theme.primaryText : (token.style?.foregroundColor ?? .white))
|
||||
if foregroundColor.distance(to: backgroundColor) < 1 {
|
||||
foregroundColor = .black
|
||||
}
|
||||
@ -172,12 +194,15 @@ private final class TokenNode: ASDisplayNode {
|
||||
if let image = token.icon {
|
||||
self.iconNode.image = generateTintedImage(image: image, color: foregroundColor)
|
||||
}
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(17.0), textColor: foregroundColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title, font: Font.regular(token.isTag ? 14.0 : 17.0), textColor: foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let height: CGFloat = 24.0
|
||||
var height: CGFloat = 24.0
|
||||
if self.token.isTag {
|
||||
height += 2.0
|
||||
}
|
||||
|
||||
var leftInset: CGFloat = 3.0
|
||||
if let icon = self.iconNode.image {
|
||||
@ -189,6 +214,9 @@ private final class TokenNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||
leftInset += icon.size.width + 3.0
|
||||
}
|
||||
if self.token.isTag {
|
||||
leftInset += 2.0
|
||||
}
|
||||
|
||||
let iconSize = self.token.icon?.size ?? CGSize()
|
||||
let titleSize = self.titleNode.measure(CGSize(width: constrainedSize.width - 6.0, height: constrainedSize.height))
|
||||
@ -196,6 +224,9 @@ private final class TokenNode: ASDisplayNode {
|
||||
if !iconSize.width.isZero {
|
||||
width += iconSize.width + 7.0
|
||||
}
|
||||
if self.token.isTag {
|
||||
width += 19.0
|
||||
}
|
||||
|
||||
let size: CGSize
|
||||
if let avatarNode = self.avatarNode {
|
||||
@ -358,6 +389,10 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
|
||||
}
|
||||
longTitlesWidth += resolvedSideInset
|
||||
|
||||
if !tokenSizes.isEmpty {
|
||||
leftOffset -= 8.0
|
||||
}
|
||||
|
||||
let verticalOffset: CGFloat = 0.0
|
||||
var horizontalOffset: CGFloat = 0.0
|
||||
for i in 0 ..< tokenSizes.count {
|
||||
|
@ -157,6 +157,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
}
|
||||
|
||||
self.callScreenState = PrivateCallScreen.State(
|
||||
strings: presentationData.strings,
|
||||
lifecycleState: .connecting,
|
||||
name: " ",
|
||||
shortName: " ",
|
||||
|
@ -420,6 +420,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/WallpaperGridScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -68,6 +68,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramPresentationData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Snow.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
@ -3,34 +3,39 @@ import UIKit
|
||||
import Display
|
||||
|
||||
final class BackButtonView: HighlightableButton {
|
||||
private struct Params: Equatable {
|
||||
var text: String
|
||||
|
||||
init(text: String) {
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
private struct Layout: Equatable {
|
||||
var params: Params
|
||||
var size: CGSize
|
||||
|
||||
init(params: Params, size: CGSize) {
|
||||
self.params = params
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
private let iconView: UIImageView
|
||||
private let textView: TextView
|
||||
|
||||
let size: CGSize
|
||||
private var currentLayout: Layout?
|
||||
|
||||
var pressAction: (() -> Void)?
|
||||
|
||||
init(text: String) {
|
||||
override init(frame: CGRect) {
|
||||
self.iconView = UIImageView(image: NavigationBar.backArrowImage(color: .white))
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
|
||||
self.textView = TextView()
|
||||
self.textView.isUserInteractionEnabled = false
|
||||
|
||||
let spacing: CGFloat = 8.0
|
||||
|
||||
var iconSize: CGSize = self.iconView.image?.size ?? CGSize(width: 2.0, height: 2.0)
|
||||
let iconScaleFactor: CGFloat = 0.9
|
||||
iconSize.width = floor(iconSize.width * iconScaleFactor)
|
||||
iconSize.height = floor(iconSize.height * iconScaleFactor)
|
||||
|
||||
let textSize = self.textView.update(string: text, fontSize: 17.0, fontWeight: UIFont.Weight.regular.rawValue, color: .white, constrainedWidth: 100.0, transition: .immediate)
|
||||
self.size = CGSize(width: iconSize.width + spacing + textSize.width, height: textSize.height)
|
||||
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.size.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
self.textView.frame = CGRect(origin: CGPoint(x: iconSize.width + spacing, y: floorToScreenPixels((self.size.height - textSize.height) * 0.5)), size: textSize)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.iconView)
|
||||
self.addSubview(self.textView)
|
||||
@ -53,4 +58,31 @@ final class BackButtonView: HighlightableButton {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func update(text: String) -> CGSize {
|
||||
let params = Params(text: text)
|
||||
if let currentLayout = self.currentLayout, currentLayout.params == params {
|
||||
return currentLayout.size
|
||||
}
|
||||
let size = self.update(params: params)
|
||||
self.currentLayout = Layout(params: params, size: size)
|
||||
return size
|
||||
}
|
||||
|
||||
private func update(params: Params) -> CGSize {
|
||||
let spacing: CGFloat = 8.0
|
||||
|
||||
var iconSize: CGSize = self.iconView.image?.size ?? CGSize(width: 2.0, height: 2.0)
|
||||
let iconScaleFactor: CGFloat = 0.9
|
||||
iconSize.width = floor(iconSize.width * iconScaleFactor)
|
||||
iconSize.height = floor(iconSize.height * iconScaleFactor)
|
||||
|
||||
let textSize = self.textView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.regular.rawValue, color: .white, constrainedWidth: 100.0, transition: .immediate)
|
||||
let size = CGSize(width: iconSize.width + spacing + textSize.width, height: textSize.height)
|
||||
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
self.textView.frame = CGRect(origin: CGPoint(x: iconSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) * 0.5)), size: textSize)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import AppBundle
|
||||
import TelegramPresentationData
|
||||
|
||||
final class ButtonGroupView: OverlayMaskContainerView {
|
||||
final class Button {
|
||||
@ -85,7 +86,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
return result
|
||||
}
|
||||
|
||||
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
|
||||
func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, strings: PresentationStrings, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat {
|
||||
self.buttons = buttons
|
||||
|
||||
let buttonSize: CGFloat = 56.0
|
||||
@ -190,7 +191,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
}
|
||||
}
|
||||
let closeButtonSize = CGSize(width: minWidth, height: buttonSize)
|
||||
closeButtonView.update(text: "Close", size: closeButtonSize, transition: closeButtonTransition)
|
||||
closeButtonView.update(text: strings.Common_Close, size: closeButtonSize, transition: closeButtonTransition)
|
||||
closeButtonTransition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: floor((size.width - closeButtonSize.width) * 0.5), y: buttonY), size: closeButtonSize))
|
||||
|
||||
if animateIn && !transition.animation.isImmediate {
|
||||
@ -218,9 +219,9 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
case let .speaker(audioOutput):
|
||||
switch audioOutput {
|
||||
case .internalSpeaker, .speaker:
|
||||
title = "speaker"
|
||||
title = strings.Call_Speaker
|
||||
default:
|
||||
title = "audio"
|
||||
title = strings.Call_Audio
|
||||
}
|
||||
|
||||
switch audioOutput {
|
||||
@ -247,19 +248,19 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
isActive = true
|
||||
}
|
||||
case .flipCamera:
|
||||
title = "flip"
|
||||
title = strings.Call_Flip
|
||||
image = UIImage(bundleImageName: "Call/Flip")
|
||||
isActive = false
|
||||
case let .video(isActiveValue):
|
||||
title = "video"
|
||||
title = strings.Call_Video
|
||||
image = UIImage(bundleImageName: "Call/Video")
|
||||
isActive = isActiveValue
|
||||
case let .microphone(isActiveValue):
|
||||
title = "mute"
|
||||
title = strings.Call_Mute
|
||||
image = UIImage(bundleImageName: "Call/Mute")
|
||||
isActive = isActiveValue
|
||||
case .end:
|
||||
title = "end"
|
||||
title = strings.Call_End
|
||||
image = UIImage(bundleImageName: "Call/End")
|
||||
isActive = false
|
||||
isDestructive = true
|
||||
|
@ -7,6 +7,7 @@ import MetalEngine
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import UIKitRuntimeUtils
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictureControllerDelegate {
|
||||
public struct State: Equatable {
|
||||
@ -67,6 +68,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
case bluetooth
|
||||
}
|
||||
|
||||
public var strings: PresentationStrings
|
||||
public var lifecycleState: LifecycleState
|
||||
public var name: String
|
||||
public var shortName: String
|
||||
@ -77,8 +79,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
public var localVideo: VideoSource?
|
||||
public var remoteVideo: VideoSource?
|
||||
public var isRemoteBatteryLow: Bool
|
||||
public var displaySnowEffect: Bool
|
||||
|
||||
public init(
|
||||
strings: PresentationStrings,
|
||||
lifecycleState: LifecycleState,
|
||||
name: String,
|
||||
shortName: String,
|
||||
@ -88,8 +92,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
isRemoteAudioMuted: Bool,
|
||||
localVideo: VideoSource?,
|
||||
remoteVideo: VideoSource?,
|
||||
isRemoteBatteryLow: Bool
|
||||
isRemoteBatteryLow: Bool,
|
||||
displaySnowEffect: Bool = false
|
||||
) {
|
||||
self.strings = strings
|
||||
self.lifecycleState = lifecycleState
|
||||
self.name = name
|
||||
self.shortName = shortName
|
||||
@ -100,9 +106,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
self.localVideo = localVideo
|
||||
self.remoteVideo = remoteVideo
|
||||
self.isRemoteBatteryLow = isRemoteBatteryLow
|
||||
self.displaySnowEffect = displaySnowEffect
|
||||
}
|
||||
|
||||
public static func ==(lhs: State, rhs: State) -> Bool {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.lifecycleState != rhs.lifecycleState {
|
||||
return false
|
||||
}
|
||||
@ -133,6 +143,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
if lhs.isRemoteBatteryLow != rhs.isRemoteBatteryLow {
|
||||
return false
|
||||
}
|
||||
if lhs.displaySnowEffect != rhs.displaySnowEffect {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -218,6 +231,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
private var pipVideoCallViewController: UIViewController?
|
||||
private var pipController: AVPictureInPictureController?
|
||||
|
||||
private var snowEffectView: SnowEffectView?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.overlayContentsView = UIView()
|
||||
self.overlayContentsView.isUserInteractionEnabled = false
|
||||
@ -240,7 +255,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
self.titleView = TextView()
|
||||
self.statusView = StatusView()
|
||||
|
||||
self.backButtonView = BackButtonView(text: "Back")
|
||||
self.backButtonView = BackButtonView(frame: CGRect())
|
||||
|
||||
self.pipView = PrivateCallPictureInPictureView(frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||
|
||||
@ -740,16 +755,16 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
var notices: [ButtonGroupView.Notice] = []
|
||||
if !isTerminated {
|
||||
if params.state.isLocalAudioMuted {
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), icon: "Call/CallToastMicrophone", text: "Your microphone is turned off"))
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), icon: "Call/CallToastMicrophone", text: params.state.strings.Call_YourMicrophoneOff))
|
||||
}
|
||||
if params.state.isRemoteAudioMuted {
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), icon: "Call/CallToastMicrophone", text: "\(params.state.shortName)'s microphone is turned off"))
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), icon: "Call/CallToastMicrophone", text: params.state.strings.Call_MicrophoneOff(params.state.shortName).string))
|
||||
}
|
||||
if params.state.remoteVideo != nil && params.state.localVideo == nil {
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), icon: "Call/CallToastCamera", text: "Your camera is turned off"))
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), icon: "Call/CallToastCamera", text: params.state.strings.Call_YourCameraOff))
|
||||
}
|
||||
if params.state.isRemoteBatteryLow {
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), icon: "Call/CallToastBattery", text: "\(params.state.shortName)'s battery is low"))
|
||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), icon: "Call/CallToastBattery", text: params.state.strings.Call_BatteryLow(params.state.shortName).string))
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,7 +774,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}*/
|
||||
let displayClose = false
|
||||
|
||||
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition)
|
||||
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, strings: params.state.strings, buttons: buttons, notices: notices, transition: transition)
|
||||
|
||||
var expandedEmojiKeyRect: CGRect?
|
||||
if self.isEmojiKeyExpanded {
|
||||
@ -777,7 +792,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
alphaTransition = genericAlphaTransition
|
||||
}
|
||||
|
||||
emojiExpandedInfoView = EmojiExpandedInfoView(title: "This call is end-to-end encrypted", text: "If the emoji on \(params.state.shortName)'s screen are the same, this call is 100% secure.")
|
||||
emojiExpandedInfoView = EmojiExpandedInfoView(title: params.state.strings.Call_EncryptedAlertTitle, text: params.state.strings.Call_EncryptedAlertText(params.state.shortName).string)
|
||||
self.emojiExpandedInfoView = emojiExpandedInfoView
|
||||
emojiExpandedInfoView.alpha = 0.0
|
||||
Transition.immediate.setScale(view: emojiExpandedInfoView, scale: 0.5)
|
||||
@ -824,13 +839,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}
|
||||
}
|
||||
|
||||
let backButtonSize = self.backButtonView.update(text: params.state.strings.Common_Back)
|
||||
|
||||
let backButtonY: CGFloat
|
||||
if currentAreControlsHidden {
|
||||
backButtonY = -self.backButtonView.size.height - 12.0
|
||||
backButtonY = -backButtonSize.height - 12.0
|
||||
} else {
|
||||
backButtonY = params.insets.top + 12.0
|
||||
}
|
||||
let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: self.backButtonView.size)
|
||||
let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: backButtonSize)
|
||||
transition.setFrame(view: self.backButtonView, frame: backButtonFrame)
|
||||
transition.setAlpha(view: self.backButtonView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
|
||||
|
||||
@ -914,7 +931,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
emojiTooltipView = current
|
||||
} else {
|
||||
emojiTooltipTransition = emojiTooltipTransition.withAnimation(.none)
|
||||
emojiTooltipView = EmojiTooltipView(text: "Encryption key of this call")
|
||||
emojiTooltipView = EmojiTooltipView(text: params.state.strings.Call_EncryptionKeyTooltip)
|
||||
animateIn = true
|
||||
self.emojiTooltipView = emojiTooltipView
|
||||
self.addSubview(emojiTooltipView)
|
||||
@ -1192,15 +1209,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
|
||||
switch terminatedState.reason {
|
||||
case .busy:
|
||||
titleString = "Line Busy"
|
||||
titleString = params.state.strings.Call_StatusBusy
|
||||
case .declined:
|
||||
titleString = "Call Declined"
|
||||
titleString = params.state.strings.Call_StatusDeclined
|
||||
case .failed:
|
||||
titleString = "Call Failed"
|
||||
titleString = params.state.strings.Call_StatusFailed
|
||||
case .hangUp:
|
||||
titleString = "Call Ended"
|
||||
titleString = params.state.strings.Call_StatusEnded
|
||||
case .missed:
|
||||
titleString = "Call Missed"
|
||||
titleString = params.state.strings.Call_StatusMissed
|
||||
}
|
||||
default:
|
||||
displayAudioLevelBlob = !params.state.isRemoteAudioMuted
|
||||
@ -1358,5 +1375,75 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*if params.state.displaySnowEffect {
|
||||
let snowEffectView: SnowEffectView
|
||||
if let current = self.snowEffectView {
|
||||
snowEffectView = current
|
||||
} else {
|
||||
snowEffectView = SnowEffectView(frame: CGRect())
|
||||
self.snowEffectView = snowEffectView
|
||||
self.maskContents.addSubview(snowEffectView)
|
||||
}
|
||||
transition.setFrame(view: snowEffectView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||
snowEffectView.update(size: params.size)
|
||||
} else {
|
||||
if let snowEffectView = self.snowEffectView {
|
||||
self.snowEffectView = nil
|
||||
snowEffectView.removeFromSuperview()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
final class SnowEffectView: UIView {
|
||||
private let particlesLayer: CAEmitterLayer
|
||||
|
||||
override init(frame: CGRect) {
|
||||
let particlesLayer = CAEmitterLayer()
|
||||
self.particlesLayer = particlesLayer
|
||||
self.particlesLayer.backgroundColor = nil
|
||||
self.particlesLayer.isOpaque = false
|
||||
|
||||
particlesLayer.emitterShape = .circle
|
||||
particlesLayer.emitterMode = .surface
|
||||
particlesLayer.renderMode = .oldestLast
|
||||
|
||||
let image1 = UIImage(named: "Call/Snow")?.cgImage
|
||||
|
||||
let cell1 = CAEmitterCell()
|
||||
cell1.contents = image1
|
||||
cell1.name = "Snow"
|
||||
cell1.birthRate = 92.0
|
||||
cell1.lifetime = 20.0
|
||||
cell1.velocity = 59.0
|
||||
cell1.velocityRange = -15.0
|
||||
cell1.xAcceleration = 5.0
|
||||
cell1.yAcceleration = 40.0
|
||||
cell1.emissionRange = 90.0 * (.pi / 180.0)
|
||||
cell1.spin = -28.6 * (.pi / 180.0)
|
||||
cell1.spinRange = 57.2 * (.pi / 180.0)
|
||||
cell1.scale = 0.06
|
||||
cell1.scaleRange = 0.3
|
||||
cell1.color = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor
|
||||
|
||||
particlesLayer.emitterCells = [cell1]
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(particlesLayer)
|
||||
self.clipsToBounds = true
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
self.particlesLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
|
||||
self.particlesLayer.emitterSize = CGSize(width: size.width * 3.0, height: size.height * 2.0)
|
||||
self.particlesLayer.emitterPosition = CGPoint(x: size.width * 0.5, y: -325.0)
|
||||
}
|
||||
}
|
||||
|
@ -1045,6 +1045,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -657,6 +657,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: chatLocation == .peer(id: context.account.peerId),
|
||||
replyCount: dateReplies,
|
||||
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
|
@ -2113,6 +2113,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
|
@ -238,6 +238,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -228,6 +228,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var reactions: [MessageReaction]
|
||||
var reactionPeers: [(MessageReaction.Reaction, EnginePeer)]
|
||||
var displayAllReactionPeers: Bool
|
||||
var isSavedMessages: Bool
|
||||
var replyCount: Int
|
||||
var isPinned: Bool
|
||||
var hasAutoremove: Bool
|
||||
@ -248,6 +249,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
reactions: [MessageReaction],
|
||||
reactionPeers: [(MessageReaction.Reaction, EnginePeer)],
|
||||
displayAllReactionPeers: Bool,
|
||||
isSavedMessages: Bool,
|
||||
replyCount: Int,
|
||||
isPinned: Bool,
|
||||
hasAutoremove: Bool,
|
||||
@ -267,6 +269,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
self.reactions = reactions
|
||||
self.reactionPeers = reactionPeers
|
||||
self.displayAllReactionPeers = displayAllReactionPeers
|
||||
self.isSavedMessages = isSavedMessages
|
||||
self.replyCount = replyCount
|
||||
self.isPinned = isPinned
|
||||
self.hasAutoremove = hasAutoremove
|
||||
@ -744,6 +747,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
},
|
||||
reactions: [],
|
||||
colors: reactionColors,
|
||||
isTag: arguments.isSavedMessages,
|
||||
constrainedWidth: arguments.constrainedSize.width
|
||||
)
|
||||
case let .trailingContent(contentWidth, reactionSettings):
|
||||
@ -805,6 +809,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
)
|
||||
},
|
||||
colors: reactionColors,
|
||||
isTag: arguments.isSavedMessages,
|
||||
constrainedWidth: arguments.constrainedSize.width
|
||||
)
|
||||
} else {
|
||||
@ -818,6 +823,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
},
|
||||
reactions: [],
|
||||
colors: reactionColors,
|
||||
isTag: arguments.isSavedMessages,
|
||||
constrainedWidth: arguments.constrainedSize.width
|
||||
)
|
||||
}
|
||||
|
@ -519,6 +519,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -937,6 +937,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: arguments.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: arguments.chatLocation == .peer(id: arguments.context.account.peerId),
|
||||
replyCount: dateReplies,
|
||||
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
|
||||
hasAutoremove: arguments.message.isSelfExpiring,
|
||||
|
@ -570,6 +570,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -872,6 +872,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
reactions: dateAndStatus.dateReactions,
|
||||
reactionPeers: dateAndStatus.dateReactionPeers,
|
||||
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: message.id.peerId == context.account.peerId,
|
||||
replyCount: dateAndStatus.dateReplies,
|
||||
isPinned: dateAndStatus.isPinned,
|
||||
hasAutoremove: message.isSelfExpiring,
|
||||
|
@ -276,6 +276,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -1024,6 +1024,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -178,6 +178,7 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
)
|
||||
},
|
||||
colors: reactionColors,
|
||||
isTag: message.id.peerId == context.account.peerId,
|
||||
constrainedWidth: constrainedWidth
|
||||
)
|
||||
|
||||
|
@ -132,6 +132,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -628,6 +628,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -573,6 +573,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
reactions: dateReactionsAndPeers.reactions,
|
||||
reactionPeers: dateReactionsAndPeers.peers,
|
||||
displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser,
|
||||
isSavedMessages: item.chatLocation.peerId == item.context.account.peerId,
|
||||
replyCount: dateReplies,
|
||||
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
|
@ -31,21 +31,23 @@ public enum ChatTitleContent: Equatable {
|
||||
public var peerId: PeerId
|
||||
public var peer: Peer?
|
||||
public var isContact: Bool
|
||||
public var isSavedMessages: Bool
|
||||
public var notificationSettings: TelegramPeerNotificationSettings?
|
||||
public var peerPresences: [PeerId: PeerPresence]
|
||||
public var cachedData: CachedPeerData?
|
||||
|
||||
public init(peerId: PeerId, peer: Peer?, isContact: Bool, notificationSettings: TelegramPeerNotificationSettings?, peerPresences: [PeerId: PeerPresence], cachedData: CachedPeerData?) {
|
||||
public init(peerId: PeerId, peer: Peer?, isContact: Bool, isSavedMessages: Bool, notificationSettings: TelegramPeerNotificationSettings?, peerPresences: [PeerId: PeerPresence], cachedData: CachedPeerData?) {
|
||||
self.peerId = peerId
|
||||
self.peer = peer
|
||||
self.isContact = isContact
|
||||
self.isSavedMessages = isSavedMessages
|
||||
self.notificationSettings = notificationSettings
|
||||
self.peerPresences = peerPresences
|
||||
self.cachedData = cachedData
|
||||
}
|
||||
|
||||
public init(peerView: PeerView) {
|
||||
self.init(peerId: peerView.peerId, peer: peerViewMainPeer(peerView), isContact: peerView.peerIsContact, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, peerPresences: peerView.peerPresences, cachedData: peerView.cachedData)
|
||||
self.init(peerId: peerView.peerId, peer: peerViewMainPeer(peerView), isContact: peerView.peerIsContact, isSavedMessages: false, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, peerPresences: peerView.peerPresences, cachedData: peerView.cachedData)
|
||||
}
|
||||
|
||||
public static func ==(lhs: PeerData, rhs: PeerData) -> Bool {
|
||||
@ -59,6 +61,9 @@ public enum ChatTitleContent: Equatable {
|
||||
if lhs.isContact != rhs.isContact {
|
||||
return false
|
||||
}
|
||||
if lhs.isSavedMessages != rhs.isSavedMessages {
|
||||
return false
|
||||
}
|
||||
if lhs.notificationSettings != rhs.notificationSettings {
|
||||
return false
|
||||
}
|
||||
@ -246,7 +251,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
if let customTitle = customTitle {
|
||||
segments = [.text(0, NSAttributedString(string: customTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
} else if peerView.peerId == self.context.account.peerId {
|
||||
segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
if peerView.isSavedMessages {
|
||||
//TODO:localize
|
||||
segments = [.text(0, NSAttributedString(string: "My Notes", font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
} else {
|
||||
segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
}
|
||||
} else if peerView.peerId.isAnonymousSavedMessages {
|
||||
segments = [.text(0, NSAttributedString(string: self.strings.ChatList_AuthorHidden, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))]
|
||||
} else {
|
||||
|
@ -13,6 +13,7 @@ public final class PlainButtonComponent: Component {
|
||||
public let content: AnyComponent<Empty>
|
||||
public let effectAlignment: EffectAlignment
|
||||
public let minSize: CGSize?
|
||||
public let contentInsets: UIEdgeInsets
|
||||
public let action: () -> Void
|
||||
public let isEnabled: Bool
|
||||
|
||||
@ -20,12 +21,14 @@ public final class PlainButtonComponent: Component {
|
||||
content: AnyComponent<Empty>,
|
||||
effectAlignment: EffectAlignment,
|
||||
minSize: CGSize? = nil,
|
||||
contentInsets: UIEdgeInsets = UIEdgeInsets(),
|
||||
action: @escaping () -> Void,
|
||||
isEnabled: Bool = true
|
||||
) {
|
||||
self.content = content
|
||||
self.effectAlignment = effectAlignment
|
||||
self.minSize = minSize
|
||||
self.contentInsets = contentInsets
|
||||
self.action = action
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
@ -40,6 +43,9 @@ public final class PlainButtonComponent: Component {
|
||||
if lhs.minSize != rhs.minSize {
|
||||
return false
|
||||
}
|
||||
if lhs.contentInsets != rhs.contentInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
@ -143,6 +149,8 @@ public final class PlainButtonComponent: Component {
|
||||
size.width = max(size.width, minSize.width)
|
||||
size.height = max(size.height, minSize.height)
|
||||
}
|
||||
size.width += component.contentInsets.left + component.contentInsets.right
|
||||
size.height += component.contentInsets.top + component.contentInsets.bottom
|
||||
|
||||
if let contentView = self.content.view {
|
||||
var contentTransition = transition
|
||||
@ -151,7 +159,7 @@ public final class PlainButtonComponent: Component {
|
||||
contentView.isUserInteractionEnabled = false
|
||||
self.contentContainer.addSubview(contentView)
|
||||
}
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) * 0.5), y: floor((size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: component.contentInsets.left + floor((size.width - component.contentInsets.left - component.contentInsets.right - contentSize.width) * 0.5), y: component.contentInsets.top + floor((size.height - component.contentInsets.top - component.contentInsets.bottom - contentSize.height) * 0.5)), size: contentSize)
|
||||
|
||||
contentTransition.setFrame(view: contentView, frame: contentFrame)
|
||||
contentTransition.setAlpha(view: contentView, alpha: contentAlpha)
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Avatar/MyNotesIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Avatar/MyNotesIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mynotes.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
131
submodules/TelegramUI/Images.xcassets/Avatar/MyNotesIcon.imageset/mynotes.pdf
vendored
Normal file
131
submodules/TelegramUI/Images.xcassets/Avatar/MyNotesIcon.imageset/mynotes.pdf
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
%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 14.110840 14.817200 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.348888 21.606785 m
|
||||
0.014841 23.983654 -0.152183 25.172089 0.182797 26.144941 c
|
||||
0.477454 27.000687 1.035458 27.741184 1.776835 28.260302 c
|
||||
2.619668 28.850460 3.808101 29.017483 6.184968 29.351530 c
|
||||
15.946176 30.723377 l
|
||||
18.323046 31.057425 19.511480 31.224449 20.484333 30.889469 c
|
||||
21.340078 30.594812 22.080576 30.036808 22.599693 29.295431 c
|
||||
23.167820 28.484060 23.343815 27.352407 23.654181 25.148548 c
|
||||
14.146844 25.148548 l
|
||||
14.146827 25.148548 l
|
||||
12.996770 25.148567 12.047814 25.148581 11.274830 25.085426 c
|
||||
10.472054 25.019836 9.734429 24.879089 9.041680 24.526117 c
|
||||
7.967140 23.978611 7.093512 23.104984 6.546007 22.030443 c
|
||||
6.193034 21.337694 6.052288 20.600069 5.986698 19.797295 c
|
||||
5.923542 19.024311 5.923557 18.075354 5.923575 16.925297 c
|
||||
5.923575 16.925280 l
|
||||
5.923575 6.950388 l
|
||||
5.923575 6.950371 l
|
||||
5.923568 6.490812 5.923562 6.063360 5.927587 5.666420 c
|
||||
5.553736 5.683525 5.230090 5.739155 4.927324 5.843405 c
|
||||
4.071579 6.138062 3.331082 6.696066 2.811965 7.437443 c
|
||||
2.221807 8.280275 2.054783 9.468710 1.720736 11.845575 c
|
||||
0.348888 21.606785 l
|
||||
h
|
||||
7.348551 16.866423 m
|
||||
7.348551 19.266651 7.348551 20.466766 7.815666 21.383530 c
|
||||
8.226552 22.189939 8.882182 22.845570 9.688591 23.256456 c
|
||||
10.605356 23.723572 11.805469 23.723572 14.205694 23.723572 c
|
||||
24.062830 23.723572 l
|
||||
26.463058 23.723572 27.663174 23.723572 28.579939 23.256456 c
|
||||
29.386347 22.845570 30.041979 22.189939 30.452864 21.383530 c
|
||||
30.919979 20.466766 30.919979 19.266653 30.919979 16.866428 c
|
||||
30.919979 7.009293 l
|
||||
30.919979 4.609062 30.919979 3.408947 30.452864 2.492184 c
|
||||
30.041979 1.685776 29.386347 1.030144 28.579939 0.619259 c
|
||||
27.663174 0.152142 26.463060 0.152142 24.062836 0.152142 c
|
||||
14.205700 0.152142 l
|
||||
11.805470 0.152142 10.605356 0.152142 9.688591 0.619259 c
|
||||
8.882182 1.030144 8.226552 1.685776 7.815666 2.492184 c
|
||||
7.348551 3.408947 7.348551 4.609062 7.348551 7.009285 c
|
||||
7.348551 16.866423 l
|
||||
h
|
||||
12.706041 17.294989 m
|
||||
12.706041 17.886723 13.185737 18.366417 13.777471 18.366417 c
|
||||
24.491756 18.366417 l
|
||||
25.083490 18.366417 25.563185 17.886723 25.563185 17.294989 c
|
||||
25.563185 16.703255 25.083488 16.223560 24.491756 16.223560 c
|
||||
13.777470 16.223560 l
|
||||
13.185736 16.223560 12.706041 16.703255 12.706041 17.294989 c
|
||||
h
|
||||
12.706041 11.937845 m
|
||||
12.706041 12.529579 13.185737 13.009274 13.777471 13.009274 c
|
||||
24.491756 13.009274 l
|
||||
25.083490 13.009274 25.563185 12.529579 25.563185 11.937845 c
|
||||
25.563185 11.346111 25.083488 10.866417 24.491756 10.866417 c
|
||||
13.777470 10.866417 l
|
||||
13.185736 10.866417 12.706041 11.346111 12.706041 11.937845 c
|
||||
h
|
||||
13.777471 7.652130 m
|
||||
13.185737 7.652130 12.706041 7.172436 12.706041 6.580704 c
|
||||
12.706041 5.988968 13.185737 5.509274 13.777471 5.509274 c
|
||||
21.277472 5.509274 l
|
||||
21.869204 5.509274 22.348900 5.988968 22.348900 6.580704 c
|
||||
22.348900 7.172436 21.869204 7.652130 21.277472 7.652130 c
|
||||
13.777471 7.652130 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2941
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 60.000000 60.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
|
||||
0000003031 00000 n
|
||||
0000003054 00000 n
|
||||
0000003227 00000 n
|
||||
0000003301 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3360
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/ReactionTagBackground.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/ReactionTagBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "MessageTagBackground.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="23" height="30" viewBox="0 0 23 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H8.155C10.5853 0 12.8838 1.10472 14.402 3.00244L22.001 12.5012C23.1697 13.9621 23.1697 16.0379 22.001 17.4988L14.402 26.9976C12.8838 28.8953 10.5853 30 8.155 30H6C2.68629 30 0 27.3137 0 24V6Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 348 B |
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "SearchTagTokenBackground.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
<svg width="30" height="26" viewBox="0 0 30 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 0C2.68628 0 0 2.68628 0 6V20.0024C0 23.316 2.68628 26.0024 6 26.0024H15.155C17.5853 26.0024 19.8838 24.8977 21.402 23L29.001 15.5012C30.1697 14.0403 30.1697 11.9645 29.001 10.5035L21.402 3.00244C19.8838 1.10474 17.5853 0 15.155 0H6ZM18 16C19.6569 16 21 14.6569 21 13C21 11.3431 19.6569 10 18 10C16.3431 10 15 11.3431 15 13C15 14.6569 16.3431 16 18 16Z" fill="white"/>
|
||||
<circle cx="18" cy="13" r="3" fill="white" fill-opacity="0.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 587 B |
@ -110,6 +110,10 @@ extension ChatControllerImpl {
|
||||
|
||||
if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty {
|
||||
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
|
||||
if case .peer(self.context.account.peerId) = self.presentationInterfaceState.chatLocation {
|
||||
//TODO:localize
|
||||
actions.reactionsTitle = "Tag the message with an emoji for quick access later"
|
||||
}
|
||||
actions.selectedReactionItems = selectedReactions.reactions
|
||||
|
||||
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
|
@ -5920,6 +5920,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
peerId: savedMessagesPeerId,
|
||||
peer: savedMessagesPeer?.peer?._asPeer(),
|
||||
isContact: true,
|
||||
isSavedMessages: true,
|
||||
notificationSettings: nil,
|
||||
peerPresences: [:],
|
||||
cachedData: nil
|
||||
@ -5930,7 +5931,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let imageOverride: AvatarNodeImageOverride?
|
||||
if strongSelf.context.account.peerId == savedMessagesPeerId {
|
||||
imageOverride = .savedMessagesIcon
|
||||
imageOverride = .myNotesIcon
|
||||
} else if savedMessagesPeerId.isReplies {
|
||||
imageOverride = .repliesIcon
|
||||
} else if savedMessagesPeerId.isAnonymousSavedMessages {
|
||||
@ -9274,12 +9275,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil))
|
||||
} else if let search = state.search {
|
||||
switch search.domain {
|
||||
case .everything:
|
||||
return state
|
||||
case .members:
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil))
|
||||
case .member:
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil))
|
||||
case .everything:
|
||||
return state
|
||||
case .tag:
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil))
|
||||
case .members:
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil))
|
||||
case .member:
|
||||
return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil))
|
||||
}
|
||||
} else {
|
||||
return state
|
||||
@ -15792,12 +15795,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
break
|
||||
}
|
||||
switch search.domain {
|
||||
case .everything:
|
||||
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
|
||||
case .members:
|
||||
derivedSearchState = nil
|
||||
case let .member(peer):
|
||||
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
|
||||
case .everything:
|
||||
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
|
||||
case .members:
|
||||
derivedSearchState = nil
|
||||
case let .member(peer):
|
||||
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: peer.id, tags: nil, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
|
||||
case let .tag(tag):
|
||||
derivedSearchState = ChatSearchState(query: "@#\(tag) " + search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2655,7 +2655,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var activate = false
|
||||
if self.searchNavigationNode == nil {
|
||||
activate = true
|
||||
self.searchNavigationNode = ChatSearchNavigationContentNode(theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction)
|
||||
self.searchNavigationNode = ChatSearchNavigationContentNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction)
|
||||
}
|
||||
self.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated)
|
||||
self.searchNavigationNode?.update(presentationInterfaceState: self.chatPresentationInterfaceState)
|
||||
|
@ -12,8 +12,18 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
if chatPresentationInterfaceState.renderedPeer?.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||
return nil
|
||||
}
|
||||
if chatPresentationInterfaceState.search != nil {
|
||||
return nil
|
||||
if let search = chatPresentationInterfaceState.search {
|
||||
if chatPresentationInterfaceState.chatLocation.peerId == context.account.peerId, case .everything = search.domain {
|
||||
if let currentPanel = currentPanel as? ChatSearchTitleAccessoryPanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatSearchTitleAccessoryPanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var inhibitTitlePanelDisplay = false
|
||||
|
@ -14,6 +14,7 @@ import ChatPresentationInterfaceState
|
||||
private let searchBarFont = Font.regular(17.0)
|
||||
|
||||
final class ChatSearchNavigationContentNode: NavigationBarContentNode {
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
private let chatLocation: ChatLocation
|
||||
@ -23,7 +24,8 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
|
||||
|
||||
private var searchingActivityDisposable: Disposable?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, chatLocation: ChatLocation, interaction: ChatPanelInterfaceInteraction) {
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, chatLocation: ChatLocation, interaction: ChatPanelInterfaceInteraction) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.chatLocation = chatLocation
|
||||
@ -33,7 +35,12 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
|
||||
let placeholderText: String
|
||||
switch chatLocation {
|
||||
case .peer, .replyThread, .feed:
|
||||
placeholderText = strings.Conversation_SearchPlaceholder
|
||||
if chatLocation.peerId == context.account.peerId {
|
||||
//TODO:localize
|
||||
placeholderText = "Search messages or tags"
|
||||
} else {
|
||||
placeholderText = strings.Conversation_SearchPlaceholder
|
||||
}
|
||||
}
|
||||
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
|
||||
@ -99,23 +106,34 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
|
||||
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings)
|
||||
|
||||
switch search.domain {
|
||||
case .everything:
|
||||
self.searchBar.tokens = []
|
||||
self.searchBar.prefixString = nil
|
||||
let placeholderText: String
|
||||
switch self.chatLocation {
|
||||
case .peer, .replyThread, .feed:
|
||||
case .everything:
|
||||
self.searchBar.tokens = []
|
||||
self.searchBar.prefixString = nil
|
||||
let placeholderText: String
|
||||
switch self.chatLocation {
|
||||
case .peer, .replyThread, .feed:
|
||||
if self.chatLocation.peerId == self.context.account.peerId {
|
||||
//TODO:localize
|
||||
placeholderText = "Search messages or tags"
|
||||
} else {
|
||||
placeholderText = self.strings.Conversation_SearchPlaceholder
|
||||
}
|
||||
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
case .members:
|
||||
self.searchBar.tokens = []
|
||||
self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor)
|
||||
self.searchBar.placeholderString = nil
|
||||
case let .member(peer):
|
||||
self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: EnginePeer(peer).compactDisplayTitle, permanent: false)]
|
||||
self.searchBar.prefixString = nil
|
||||
self.searchBar.placeholderString = nil
|
||||
}
|
||||
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
case let .tag(tag):
|
||||
//TODO:localize
|
||||
let placeholderText = "Search"
|
||||
self.searchBar.placeholderString = NSAttributedString(string: placeholderText, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
//TODO:localize
|
||||
self.searchBar.tokens = [SearchBarToken(id: AnyHashable(tag), icon: nil, isTag: true, title: "\(tag) Tag", permanent: false)]
|
||||
case .members:
|
||||
self.searchBar.tokens = []
|
||||
self.searchBar.prefixString = NSAttributedString(string: strings.Conversation_SearchByName_Prefix, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputTextColor)
|
||||
self.searchBar.placeholderString = nil
|
||||
case let .member(peer):
|
||||
self.searchBar.tokens = [SearchBarToken(id: peer.id, icon: UIImage(bundleImageName: "Chat List/Search/User"), title: EnginePeer(peer).compactDisplayTitle, permanent: false)]
|
||||
self.searchBar.prefixString = nil
|
||||
self.searchBar.placeholderString = nil
|
||||
}
|
||||
|
||||
if self.searchBar.text != search.query {
|
||||
|
@ -76,13 +76,13 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
return false
|
||||
}
|
||||
|
||||
public func item(context: AccountContext, interaction: ChatListNodeInteraction) -> ListViewItem {
|
||||
public func item(context: AccountContext, interaction: ChatListNodeInteraction, location: ChatListControllerLocation) -> ListViewItem {
|
||||
switch self {
|
||||
case let .message(message, peer, readState, presentationData):
|
||||
return ChatListItem(
|
||||
presentationData: presentationData,
|
||||
context: context,
|
||||
chatListLocation: .chatList(groupId: .root),
|
||||
chatListLocation: location,
|
||||
filterData: nil,
|
||||
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)),
|
||||
content: .peer(ChatListItemContent.PeerData(
|
||||
@ -129,12 +129,12 @@ public struct ChatListSearchContainerTransition {
|
||||
}
|
||||
}
|
||||
|
||||
private func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], context: AccountContext, interaction: ChatListNodeInteraction) -> ChatListSearchContainerTransition {
|
||||
private func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], context: AccountContext, interaction: ChatListNodeInteraction, location: ChatListControllerLocation) -> ChatListSearchContainerTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, location: location), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, location: location), directionHint: nil) }
|
||||
|
||||
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -148,6 +148,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
private let searchQuery: String
|
||||
private var searchResult: SearchMessagesResult
|
||||
private var searchState: SearchMessagesState
|
||||
private let mappedLocation: ChatListControllerLocation
|
||||
|
||||
private var interaction: ChatListNodeInteraction?
|
||||
|
||||
@ -173,6 +174,12 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
self.searchQuery = searchQuery
|
||||
self.searchResult = searchResult
|
||||
self.searchState = searchState
|
||||
|
||||
if case let .peer(peerId, _, _, _, _, _) = location, peerId == context.account.peerId {
|
||||
self.mappedLocation = .savedMessagesChats
|
||||
} else {
|
||||
self.mappedLocation = .chatList(groupId: .root)
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
@ -283,7 +290,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
let previousEntries = strongSelf.previousEntries.swap(entries)
|
||||
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction)
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction, location: strongSelf.mappedLocation)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
@ -352,7 +359,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
let previousEntries = strongSelf.previousEntries.swap(entries)
|
||||
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction)
|
||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, context: context, interaction: interaction, location: strongSelf.mappedLocation)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
|
@ -0,0 +1,182 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import ChatPresentationInterfaceState
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import PlainButtonComponent
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UIScrollViewDelegate {
|
||||
private final class Item {
|
||||
let tag: String
|
||||
|
||||
init(tag: String) {
|
||||
self.tag = tag
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemView: UIView {
|
||||
private let context: AccountContext
|
||||
private let item: Item
|
||||
private let action: (String) -> Void
|
||||
|
||||
private let view = ComponentView<Empty>()
|
||||
|
||||
init(context: AccountContext, item: Item, action: @escaping ((String) -> Void)) {
|
||||
self.context = context
|
||||
self.item = item
|
||||
self.action = action
|
||||
|
||||
super.init(frame: CGRect())
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme, height: CGFloat, transition: Transition) -> CGSize {
|
||||
let viewSize = self.view.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: self.item.tag, font: Font.regular(15.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
|
||||
insets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0)
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: 0.0, height: height),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.action(self.item.tag)
|
||||
},
|
||||
isEnabled: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let componentView = self.view.view {
|
||||
if componentView.superview == nil {
|
||||
self.addSubview(componentView)
|
||||
}
|
||||
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(), size: viewSize))
|
||||
}
|
||||
return viewSize
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme?
|
||||
private var strings: PresentationStrings?
|
||||
|
||||
private let scrollView: ScrollView
|
||||
private let itemViews: [ItemView]
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
self.scrollView = ScrollView(frame: CGRect())
|
||||
|
||||
let tags: [String] = [
|
||||
"⭐️", "❤️", "✅", "⏰", "💭", "❗️", "👍", "👎", "🤩", "⚡️", "🤡", "👌", "👏"
|
||||
]
|
||||
let items = tags.map {
|
||||
Item(tag: $0)
|
||||
}
|
||||
var itemAction: ((String) -> Void)?
|
||||
self.itemViews = items.map { item in
|
||||
return ItemView(context: context, item: item, action: { tag in
|
||||
itemAction?(tag)
|
||||
})
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.alwaysBounceVertical = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
|
||||
self.view.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
for itemView in self.itemViews {
|
||||
self.scrollView.addSubview(itemView)
|
||||
}
|
||||
|
||||
itemAction = { [weak self] tag in
|
||||
guard let self, let interfaceInteraction = self.interfaceInteraction else {
|
||||
return
|
||||
}
|
||||
interfaceInteraction.beginMessageSearch(.tag(tag), "")
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
||||
if interfaceState.strings !== self.strings {
|
||||
self.strings = interfaceState.strings
|
||||
}
|
||||
|
||||
if interfaceState.theme !== self.theme {
|
||||
self.theme = interfaceState.theme
|
||||
}
|
||||
|
||||
let panelHeight: CGFloat = 33.0
|
||||
|
||||
let containerInsets = UIEdgeInsets(top: 0.0, left: leftInset + 2.0, bottom: 0.0, right: rightInset + 2.0)
|
||||
let itemSpacing: CGFloat = 2.0
|
||||
|
||||
var contentSize = CGSize(width: 0.0, height: panelHeight)
|
||||
contentSize.width += containerInsets.left
|
||||
|
||||
var isFirst = true
|
||||
for itemView in self.itemViews {
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
} else {
|
||||
contentSize.width += itemSpacing
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(theme: interfaceState.theme, height: panelHeight, transition: .immediate)
|
||||
itemView.frame = CGRect(origin: CGPoint(x: contentSize.width, y: 0.0), size: itemSize)
|
||||
contentSize.width += itemSize.width
|
||||
}
|
||||
|
||||
contentSize.width += containerInsets.right
|
||||
|
||||
let scrollSize = CGSize(width: width, height: contentSize.height)
|
||||
if self.scrollView.bounds.size != scrollSize {
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: -5.0), size: scrollSize)
|
||||
}
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
|
||||
return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user