mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-08-08 23:36:08 +02:00
Update Ghostgram features
This commit is contained in:
+6
-4
@@ -83,6 +83,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
public let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
public let imageNode: TransformImageNode
|
||||
public var sizeCoefficient: Float = 1.0
|
||||
private var enableSynchronousImageApply: Bool = false
|
||||
private var backgroundNode: WallpaperBubbleBackgroundNode?
|
||||
public private(set) var placeholderNode: StickerShimmerEffectNode
|
||||
@@ -117,7 +118,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private let disposables = DisposableSet()
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
public let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
@@ -1165,6 +1166,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.message.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
@@ -1410,11 +1412,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil)
|
||||
]),
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
|
||||
])
|
||||
],
|
||||
flags: [],
|
||||
|
||||
+1
@@ -749,6 +749,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
context: context,
|
||||
presentationData: presentationData,
|
||||
edited: edited && !isPreview,
|
||||
isDeleted: message.ghostgramIsDeleted,
|
||||
impressionCount: !isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgdeps = [
|
||||
"//Swiftgram/SGStrings:SGStrings",
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
||||
"//submodules/TranslateUI:TranslateUI"
|
||||
]
|
||||
|
||||
sgsrc = [
|
||||
"//Swiftgram/SGDoubleTapMessageAction:SGDoubleTapMessageAction"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageBubbleItemNode",
|
||||
module_name = "ChatMessageBubbleItemNode",
|
||||
srcs = glob([
|
||||
srcs = sgsrc + glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgdeps + [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
|
||||
+93
-21
@@ -1,3 +1,6 @@
|
||||
import SGStrings
|
||||
import SGSimpleSettings
|
||||
import TranslateUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@@ -132,7 +135,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
|
||||
outer: for (message, itemAttributes) in item.content {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }, chatId: message.author?.id.id._internalGetInt64Value()) != nil {
|
||||
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
break outer
|
||||
@@ -316,6 +319,35 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
isMediaInverted = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
var message = message
|
||||
if message.canRevealContent(contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||
let originalTextLength = message.text.count
|
||||
let noticeString = i18n("Message.HoldToShowOrReport", item.presentationData.strings.baseLanguageCode)
|
||||
|
||||
message = message.withUpdatedText(message.text + "\n" + noticeString)
|
||||
let noticeStringLength = noticeString.count
|
||||
let startIndex = originalTextLength + 1 // +1 for the newline character
|
||||
// Calculate the end index, which is the start index plus the length of noticeString
|
||||
let endIndex = startIndex + noticeStringLength
|
||||
|
||||
var newAttributes = message.attributes
|
||||
newAttributes.append(
|
||||
TextEntitiesMessageAttribute(
|
||||
entities: [
|
||||
MessageTextEntity(
|
||||
range: startIndex..<endIndex,
|
||||
// TODO(swiftgram): Add more instructions to collapsed block?
|
||||
type: .BlockQuote(isCollapsed: false) //.Custom(type: ApplicationSpecificEntityType.Button)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
message = message.withUpdatedAttributes(newAttributes)
|
||||
}
|
||||
|
||||
|
||||
if isMediaInverted {
|
||||
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo ? 1 : 0)
|
||||
} else {
|
||||
@@ -482,7 +514,7 @@ private func mapVisibility(_ visibility: ListViewItemNodeVisibility, boundsSize:
|
||||
}
|
||||
|
||||
private func isDeletedBubbleMessage(_ message: Message) -> Bool {
|
||||
return AntiDeleteManager.shared.isMessageDeleted(peerId: message.id.peerId.toInt64(), messageId: message.id.id) || AntiDeleteManager.shared.isMessageDeleted(text: message.text)
|
||||
return message.ghostgramIsDeleted
|
||||
}
|
||||
|
||||
public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode {
|
||||
@@ -1235,7 +1267,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
if let shareButtonNode = strongSelf.shareButtonNode, shareButtonNode.frame.contains(point) {
|
||||
return .fail
|
||||
}
|
||||
|
||||
if let actionButtonsNode = strongSelf.actionButtonsNode {
|
||||
if let _ = actionButtonsNode.hitTest(strongSelf.view.convert(point, to: actionButtonsNode.view), with: nil) {
|
||||
return .fail
|
||||
@@ -1734,6 +1765,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
|
||||
|
||||
var needsShareButton = false
|
||||
var mayHaveSeparateCommentsButton = false // MARK: Swiftgram
|
||||
var needsSummarizeButton = false
|
||||
|
||||
if incoming, case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind {
|
||||
@@ -1789,7 +1821,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
needsShareButton = true
|
||||
}
|
||||
}
|
||||
var mayHaveSeparateCommentsButton = false
|
||||
// var mayHaveSeparateCommentsButton = false // MARK: Swiftgram
|
||||
if !needsShareButton {
|
||||
loop: for media in item.message.media {
|
||||
if media is TelegramMediaGame || media is TelegramMediaInvoice {
|
||||
@@ -1831,7 +1863,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
needsShareButton = true
|
||||
}
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }, chatId: item.content.firstMessage.author?.id.id._internalGetInt64Value()) != nil {
|
||||
needsShareButton = false
|
||||
needsSummarizeButton = false
|
||||
}
|
||||
@@ -1862,11 +1894,21 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
tmpWidth -= deliveryFailedInset
|
||||
// MARK: Swifgram
|
||||
let renderWideChannelPosts: Bool
|
||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, SGSimpleSettings.shared.wideChannelPosts, !(mayHaveSeparateCommentsButton && hasCommentButton(item: item)) {
|
||||
renderWideChannelPosts = true
|
||||
|
||||
tmpWidth = baseWidth
|
||||
needsShareButton = false
|
||||
} else {
|
||||
renderWideChannelPosts = false
|
||||
}
|
||||
|
||||
let (contentNodeMessagesAndClasses, needSeparateContainers, needReactions) = contentNodeMessagesAndClassesForItem(item)
|
||||
|
||||
var maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset * 3.0 - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
|
||||
if (needsShareButton && !isSidePanelOpen) {
|
||||
if needsShareButton && !isSidePanelOpen {
|
||||
maximumContentWidth -= 10.0
|
||||
}
|
||||
|
||||
@@ -2386,13 +2428,29 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var mosaicStatusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)?
|
||||
|
||||
if let mosaicRange = mosaicRange {
|
||||
let maxSize = layoutConstants.image.maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
||||
let (innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts[mosaicRange].map { item in
|
||||
// MARK: Swiftgram
|
||||
var maxDimensions = layoutConstants.image.maxDimensions
|
||||
if renderWideChannelPosts {
|
||||
maxDimensions.width = maximumContentWidth
|
||||
}
|
||||
var maxSize = maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
||||
var (innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts[mosaicRange].map { item in
|
||||
guard let size = item.0, size.width > 0.0, size.height > 0 else {
|
||||
return CGSize(width: 256.0, height: 256.0)
|
||||
}
|
||||
return size
|
||||
})
|
||||
}/*, TODO(swiftgram): fillWidth: SGSimpleSettings.shared.wideChannelPosts */)
|
||||
// MARK: Swiftgram
|
||||
if innerSize.height > maxSize.height, maxDimensions.width != layoutConstants.image.maxDimensions.width {
|
||||
maxDimensions.width = max(round(maxDimensions.width * maxSize.height / innerSize.height), layoutConstants.image.maxDimensions.width)
|
||||
maxSize = maxDimensions.fittedToWidthOrSmaller(maximumContentWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
||||
(innerFramesAndPositions, innerSize) = chatMessageBubbleMosaicLayout(maxSize: maxSize, itemSizes: contentPropertiesAndLayouts[mosaicRange].map { item in
|
||||
guard let size = item.0, size.width > 0.0, size.height > 0 else {
|
||||
return CGSize(width: 256.0, height: 256.0)
|
||||
}
|
||||
return size
|
||||
}/*, TODO(swiftgram): fillWidth: SGSimpleSettings.shared.wideChannelPosts */)
|
||||
}
|
||||
|
||||
let framesAndPositions = innerFramesAndPositions.map { ($0.0.offsetBy(dx: layoutConstants.image.bubbleInsets.left, dy: layoutConstants.image.bubbleInsets.top), $0.1) }
|
||||
|
||||
@@ -2474,6 +2532,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited && !item.presentationData.isPreview,
|
||||
isDeleted: message.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
@@ -2852,7 +2911,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(
|
||||
buttons: [ReplyMarkupButton(title: item.presentationData.strings.Channel_AdminLog_ShowMoreMessages(Int32(messages.count - 1)), titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data())))]
|
||||
buttons: [ReplyMarkupButton(title: item.presentationData.strings.Channel_AdminLog_ShowMoreMessages(Int32(messages.count - 1)), titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data())), style: nil)]
|
||||
)
|
||||
],
|
||||
flags: [],
|
||||
@@ -2891,8 +2950,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil)
|
||||
])
|
||||
],
|
||||
flags: [],
|
||||
@@ -2940,11 +2999,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil)
|
||||
]),
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
|
||||
])
|
||||
],
|
||||
flags: [],
|
||||
@@ -2978,7 +3037,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_MessageContinueLastThread, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: button))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_MessageContinueLastThread, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: button), style: nil)
|
||||
])
|
||||
],
|
||||
flags: [],
|
||||
@@ -4371,7 +4430,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
strongSelf.replyInfoNode = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let deletedMessageAlpha = CGFloat(AntiDeleteManager.shared.deletedMessageDisplayAlpha)
|
||||
var deletedMessageStableIds = Set<UInt32>()
|
||||
for (message, _) in item.content {
|
||||
@@ -4524,7 +4583,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, selectionInsets: selectionInsets, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, presentationContext: item.controllerInteraction.presentationContext, mediaBox: item.context.account.postbox.mediaBox, messageSelection: itemSelection)
|
||||
|
||||
|
||||
if let contentContainer = contentContainer {
|
||||
let containerAlpha: CGFloat = deletedMessageStableIds.contains(stableId) ? deletedMessageAlpha : 1.0
|
||||
if case .System = animation {
|
||||
@@ -4536,7 +4595,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
|
||||
let mainContainerAlpha: CGFloat
|
||||
if contentContainerNodeFrames.isEmpty, !deletedMessageStableIds.isEmpty {
|
||||
mainContainerAlpha = deletedMessageAlpha
|
||||
@@ -5153,7 +5212,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
animation.animator.updateAlpha(layer: shareButtonNode.layer, alpha: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.0 : 1.0, completion: nil)
|
||||
animation.animator.updateScale(layer: shareButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||
legacyTransition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame)
|
||||
if let backgroundHighlightNode = strongSelf.backgroundHighlightNode {
|
||||
@@ -5291,18 +5350,32 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
case let .optionalAction(f):
|
||||
f()
|
||||
case let .openContextMenu(openContextMenu):
|
||||
switch (sgDoubleTapMessageAction(incoming: openContextMenu.tapMessage.effectivelyIncoming(item.context.account.peerId), message: openContextMenu.tapMessage)) {
|
||||
case SGSimpleSettings.MessageDoubleTapAction.none.rawValue:
|
||||
break
|
||||
case SGSimpleSettings.MessageDoubleTapAction.edit.rawValue:
|
||||
item.controllerInteraction.sgStartMessageEdit(openContextMenu.tapMessage)
|
||||
default:
|
||||
if canAddMessageReactions(message: openContextMenu.tapMessage) {
|
||||
item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default, false, nil)
|
||||
} else {
|
||||
item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if case .tap = gesture {
|
||||
item.controllerInteraction.clickThroughMessage(self.view, location)
|
||||
} else if case .doubleTap = gesture {
|
||||
switch (sgDoubleTapMessageAction(incoming: item.message.effectivelyIncoming(item.context.account.peerId), message: item.message)) {
|
||||
case SGSimpleSettings.MessageDoubleTapAction.none.rawValue:
|
||||
break
|
||||
case SGSimpleSettings.MessageDoubleTapAction.edit.rawValue:
|
||||
item.controllerInteraction.sgStartMessageEdit(item.message)
|
||||
default:
|
||||
if canAddMessageReactions(message: item.message) {
|
||||
item.controllerInteraction.updateMessageReaction(item.message, .default, false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -5971,7 +6044,6 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
||||
return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event)
|
||||
}
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
if let result = self.traceSelectionNodes(parent: self, point: point.offsetBy(dx: -42.0, dy: 0.0)) {
|
||||
return result.view
|
||||
|
||||
+1
@@ -298,6 +298,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgdeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageDateAndStatusNode",
|
||||
module_name = "ChatMessageDateAndStatusNode",
|
||||
@@ -9,7 +13,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgdeps + [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
|
||||
+57
-62
@@ -1,3 +1,4 @@
|
||||
import SGSimpleSettings
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@@ -30,6 +31,10 @@ private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
|
||||
layer.add(basicAnimation, forKey: "clockFrameAnimation")
|
||||
}
|
||||
|
||||
private func generateDeletedStatusIcon(color: UIColor) -> UIImage? {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: color)?.precomposed()
|
||||
}
|
||||
|
||||
public enum ChatMessageDateAndStatusOutgoingType: Equatable {
|
||||
case Sent(read: Bool)
|
||||
case Sending
|
||||
@@ -183,6 +188,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var context: AccountContext
|
||||
var presentationData: ChatPresentationData
|
||||
var edited: Bool
|
||||
var isDeleted: Bool
|
||||
var impressionCount: Int?
|
||||
var dateText: String
|
||||
var type: ChatMessageDateAndStatusType
|
||||
@@ -201,7 +207,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var tonAmount: Int64?
|
||||
var isPinned: Bool
|
||||
var hasAutoremove: Bool
|
||||
var isDeleted: Bool
|
||||
var canViewReactionList: Bool
|
||||
var animationCache: AnimationCache
|
||||
var animationRenderer: MultiAnimationRenderer
|
||||
@@ -210,6 +215,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
context: AccountContext,
|
||||
presentationData: ChatPresentationData,
|
||||
edited: Bool,
|
||||
isDeleted: Bool,
|
||||
impressionCount: Int?,
|
||||
dateText: String,
|
||||
type: ChatMessageDateAndStatusType,
|
||||
@@ -228,7 +234,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
tonAmount: Int64? = nil,
|
||||
isPinned: Bool,
|
||||
hasAutoremove: Bool,
|
||||
isDeleted: Bool = false,
|
||||
canViewReactionList: Bool,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer
|
||||
@@ -236,6 +241,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.edited = edited
|
||||
self.isDeleted = isDeleted
|
||||
self.impressionCount = impressionCount == 0 ? nil : impressionCount
|
||||
self.dateText = dateText
|
||||
self.type = type
|
||||
@@ -254,7 +260,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
self.tonAmount = tonAmount
|
||||
self.isPinned = isPinned
|
||||
self.hasAutoremove = hasAutoremove
|
||||
self.isDeleted = isDeleted
|
||||
self.canViewReactionList = canViewReactionList
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
@@ -268,6 +273,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
private var clockFrameNode: ASImageNode?
|
||||
private var clockMinNode: ASImageNode?
|
||||
private let dateNode: TextNode
|
||||
private var deletedIcon: ASImageNode?
|
||||
private var impressionIcon: ASImageNode?
|
||||
private var reactionNodes: [MessageReaction.Reaction: StatusReactionNode] = [:]
|
||||
private let reactionButtonsContainer = ReactionButtonsAsyncLayoutContainer()
|
||||
@@ -277,7 +283,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
private var replyCountNode: TextNode?
|
||||
private var starsIcon: ASImageNode?
|
||||
private var starsCountNode: TextNode?
|
||||
private var deletedIcon: ASImageNode?
|
||||
|
||||
private var type: ChatMessageDateAndStatusType?
|
||||
private var theme: ChatPresentationThemeData?
|
||||
@@ -330,6 +335,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var clockMinNode = self.clockMinNode
|
||||
|
||||
var currentBackgroundNode = self.backgroundNode
|
||||
var currentDeletedIcon = self.deletedIcon
|
||||
var currentImpressionIcon = self.impressionIcon
|
||||
var currentRepliesIcon = self.repliesIcon
|
||||
var currentStarsIcon = self.starsIcon
|
||||
@@ -353,6 +359,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let loadedCheckPartialImage: UIImage?
|
||||
let clockFrameImage: UIImage?
|
||||
let clockMinImage: UIImage?
|
||||
let deletedImage: UIImage?
|
||||
var impressionImage: UIImage?
|
||||
var repliesImage: UIImage?
|
||||
var starsImage: UIImage?
|
||||
@@ -541,6 +548,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
starsImage = graphics.freeTonIcon
|
||||
}
|
||||
}
|
||||
|
||||
deletedImage = arguments.isDeleted ? generateDeletedStatusIcon(color: dateColor) : nil
|
||||
|
||||
var updatedDateText = arguments.dateText
|
||||
if arguments.edited {
|
||||
@@ -561,6 +570,23 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var checkReadFrame: CGRect?
|
||||
|
||||
var clockPosition = CGPoint()
|
||||
|
||||
var deletedSize = CGSize()
|
||||
var deletedWidth: CGFloat = 0.0
|
||||
if deletedImage != nil {
|
||||
if currentDeletedIcon == nil {
|
||||
let iconNode = ASImageNode()
|
||||
iconNode.isLayerBacked = true
|
||||
iconNode.displayWithoutProcessing = true
|
||||
iconNode.displaysAsynchronously = false
|
||||
iconNode.contentMode = .scaleAspectFit
|
||||
currentDeletedIcon = iconNode
|
||||
}
|
||||
deletedSize = CGSize(width: 11.0, height: 11.0)
|
||||
deletedWidth = deletedSize.width + 4.0
|
||||
} else {
|
||||
currentDeletedIcon = nil
|
||||
}
|
||||
|
||||
var impressionSize = CGSize()
|
||||
var impressionWidth: CGFloat = 0.0
|
||||
@@ -606,36 +632,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
currentStarsIcon = nil
|
||||
}
|
||||
|
||||
// ANTIDELETE: Deleted message icon
|
||||
var currentDeletedIcon = self?.deletedIcon
|
||||
var deletedIconSize = CGSize()
|
||||
if arguments.isDeleted {
|
||||
if currentDeletedIcon == nil {
|
||||
let iconNode = ASImageNode()
|
||||
iconNode.isLayerBacked = true
|
||||
iconNode.displayWithoutProcessing = true
|
||||
iconNode.displaysAsynchronously = false
|
||||
currentDeletedIcon = iconNode
|
||||
}
|
||||
// Use trash icon from bundle or create simple one
|
||||
let deletedImage = UIImage(bundleImageName: "Chat/Message/DeletedIcon") ?? generateTintedImage(image: UIImage(systemName: "trash"), color: dateColor)
|
||||
deletedIconSize = deletedImage?.size ?? CGSize(width: 10, height: 10)
|
||||
// Scale down the image
|
||||
if let img = deletedImage {
|
||||
let scaledSize = CGSize(width: 10, height: 10)
|
||||
UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0.0)
|
||||
img.draw(in: CGRect(origin: .zero, size: scaledSize))
|
||||
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
currentDeletedIcon?.image = scaledImage
|
||||
deletedIconSize = scaledSize
|
||||
} else {
|
||||
currentDeletedIcon?.image = deletedImage
|
||||
}
|
||||
} else {
|
||||
currentDeletedIcon = nil
|
||||
}
|
||||
|
||||
if let outgoingStatus = outgoingStatus {
|
||||
switch outgoingStatus {
|
||||
case .Sending:
|
||||
@@ -670,7 +666,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
clockMinNode?.displayWithoutProcessing = true
|
||||
clockMinNode?.frame = CGRect(origin: CGPoint(), size: clockMinImage?.size ?? CGSize())
|
||||
}
|
||||
clockPosition = CGPoint(x: leftInset + date.size.width + 8.5, y: 7.5 + offset)
|
||||
clockPosition = CGPoint(x: leftInset + deletedWidth + impressionWidth + date.size.width + 8.5, y: 7.5 + offset)
|
||||
case let .Sent(read):
|
||||
let hideStatus: Bool
|
||||
switch arguments.type {
|
||||
@@ -710,9 +706,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let checkSize = loadedCheckFullImage!.size
|
||||
|
||||
if read {
|
||||
checkReadFrame = CGRect(origin: CGPoint(x: leftInset + impressionWidth + date.size.width + 5.0 + statusWidth - checkSize.width, y: 3.0 + offset), size: checkSize)
|
||||
checkReadFrame = CGRect(origin: CGPoint(x: leftInset + deletedWidth + impressionWidth + date.size.width + 5.0 + statusWidth - checkSize.width, y: 3.0 + offset), size: checkSize)
|
||||
}
|
||||
checkSentFrame = CGRect(origin: CGPoint(x: leftInset + impressionWidth + date.size.width + 5.0 + statusWidth - checkSize.width - checkOffset, y: 3.0 + offset), size: checkSize)
|
||||
checkSentFrame = CGRect(origin: CGPoint(x: leftInset + deletedWidth + impressionWidth + date.size.width + 5.0 + statusWidth - checkSize.width - checkOffset, y: 3.0 + offset), size: checkSize)
|
||||
}
|
||||
case .Failed:
|
||||
statusWidth = 0.0
|
||||
@@ -799,15 +795,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
reactionInset += 13.0
|
||||
}
|
||||
|
||||
// ANTIDELETE: Add deleted icon space
|
||||
var deletedIconWidth: CGFloat = 0.0
|
||||
if arguments.isDeleted {
|
||||
deletedIconWidth = deletedIconSize.width + 3.0
|
||||
}
|
||||
|
||||
leftInset += reactionInset
|
||||
|
||||
let layoutSize = CGSize(width: leftInset + deletedIconWidth + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom)
|
||||
let layoutSize = CGSize(width: leftInset + deletedWidth + impressionWidth + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom)
|
||||
|
||||
let verticalReactionsInset: CGFloat
|
||||
let verticalInset: CGFloat
|
||||
@@ -1132,8 +1122,26 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
|
||||
let _ = dateApply()
|
||||
|
||||
if let currentDeletedIcon = currentDeletedIcon {
|
||||
let deletedIconFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left, y: backgroundInsets.top + 1.0 + offset + verticalInset + floor((date.size.height - deletedSize.height) / 2.0)), size: deletedSize)
|
||||
currentDeletedIcon.displaysAsynchronously = false
|
||||
if currentDeletedIcon.image !== deletedImage {
|
||||
currentDeletedIcon.image = deletedImage
|
||||
}
|
||||
if currentDeletedIcon.supernode == nil {
|
||||
strongSelf.deletedIcon = currentDeletedIcon
|
||||
strongSelf.addSubnode(currentDeletedIcon)
|
||||
currentDeletedIcon.frame = deletedIconFrame
|
||||
} else {
|
||||
animation.animator.updateFrame(layer: currentDeletedIcon.layer, frame: deletedIconFrame, completion: nil)
|
||||
}
|
||||
} else if let deletedIcon = strongSelf.deletedIcon {
|
||||
deletedIcon.removeFromSupernode()
|
||||
strongSelf.deletedIcon = nil
|
||||
}
|
||||
|
||||
if let currentImpressionIcon = currentImpressionIcon {
|
||||
let impressionIconFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left, y: backgroundInsets.top + 1.0 + offset + verticalInset + floor((date.size.height - impressionSize.height) / 2.0)), size: impressionSize)
|
||||
let impressionIconFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + deletedWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset + floor((date.size.height - impressionSize.height) / 2.0)), size: impressionSize)
|
||||
currentImpressionIcon.displaysAsynchronously = false
|
||||
if currentImpressionIcon.image !== impressionImage {
|
||||
currentImpressionIcon.image = impressionImage
|
||||
@@ -1150,22 +1158,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
strongSelf.impressionIcon = nil
|
||||
}
|
||||
|
||||
// ANTIDELETE: Position deleted icon
|
||||
if let currentDeletedIcon = currentDeletedIcon {
|
||||
let deletedIconFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset + floor((date.size.height - deletedIconSize.height) / 2.0)), size: deletedIconSize)
|
||||
if currentDeletedIcon.supernode == nil {
|
||||
strongSelf.deletedIcon = currentDeletedIcon
|
||||
strongSelf.addSubnode(currentDeletedIcon)
|
||||
currentDeletedIcon.frame = deletedIconFrame
|
||||
} else {
|
||||
animation.animator.updateFrame(layer: currentDeletedIcon.layer, frame: deletedIconFrame, completion: nil)
|
||||
}
|
||||
} else if let deletedIcon = strongSelf.deletedIcon {
|
||||
deletedIcon.removeFromSupernode()
|
||||
strongSelf.deletedIcon = nil
|
||||
}
|
||||
|
||||
animation.animator.updateFrame(layer: strongSelf.dateNode.layer, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + impressionWidth + deletedIconWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size), completion: nil)
|
||||
animation.animator.updateFrame(layer: strongSelf.dateNode.layer, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + backgroundInsets.left + deletedWidth + impressionWidth, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: date.size), completion: nil)
|
||||
|
||||
if let clockFrameNode = clockFrameNode {
|
||||
let clockPosition = CGPoint(x: leftOffset + backgroundInsets.left + clockPosition.x + reactionInset, y: backgroundInsets.top + clockPosition.y + verticalInset)
|
||||
@@ -1527,5 +1520,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func shouldDisplayInlineDateReactions(message: Message, isPremium: Bool, forceInline: Bool) -> Bool {
|
||||
return false
|
||||
// MARK: Swiftgram
|
||||
// With 10.13 it now hides reactions in favor of message effect badge
|
||||
return SGSimpleSettings.shared.hideReactions
|
||||
}
|
||||
|
||||
+1
@@ -437,6 +437,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited && !item.presentationData.isPreview,
|
||||
isDeleted: message.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
+16
-3
@@ -282,7 +282,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
switch action.action {
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
default:
|
||||
break
|
||||
@@ -378,6 +378,17 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||
return { item, _, _, _, _, _ in
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
||||
return (contentProperties, nil, 220.0, { _, _ in
|
||||
return (220.0, { _ in
|
||||
return (CGSize(width: 220.0, height: 120.0), { [weak self] _, _, _ in
|
||||
self?.item = item
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
#if false
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeSubtitleLayout = TextNodeWithEntities.asyncLayout(self.subtitleNode)
|
||||
@@ -402,10 +413,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let cachedTonImage = self.cachedTonImage
|
||||
|
||||
return { item, layoutConstants, _, _, _, _ in
|
||||
let asyncLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize, CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) = { (item: ChatMessageBubbleContentItem, layoutConstants: ChatMessageItemLayoutConstants, preparePosition: ChatMessageBubblePreparePosition, messageSelection: Bool?, constrainedSize: CGSize, avatarInset: CGFloat) in
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
||||
|
||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { (constrainedSize: CGSize, position: ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)) in
|
||||
var giftSize = CGSize(width: 220.0, height: 240.0)
|
||||
|
||||
let incoming: Bool
|
||||
@@ -1471,6 +1482,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
})
|
||||
})
|
||||
}
|
||||
return asyncLayout
|
||||
#endif
|
||||
}
|
||||
|
||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
|
||||
+1
@@ -565,6 +565,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgdeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings"
|
||||
]
|
||||
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageInteractiveFileNode",
|
||||
module_name = "ChatMessageInteractiveFileNode",
|
||||
@@ -9,7 +14,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgdeps + [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
|
||||
+68
-10
@@ -1,3 +1,4 @@
|
||||
import SGSimpleSettings
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@@ -349,16 +350,53 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func transcribe() {
|
||||
guard let _ = self.arguments, let context = self.context, let message = self.message else {
|
||||
guard let arguments = self.arguments, let context = self.context, let message = self.message else {
|
||||
return
|
||||
}
|
||||
|
||||
if !context.isPremium, case .inProgress = self.audioTranscriptionState {
|
||||
if /*!context.isPremium,*/ case .inProgress = self.audioTranscriptionState {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
// GHOSTGRAM: Premium check removed - local transcription is free!
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let transcriptionText = self.forcedAudioTranscriptionText ?? transcribedText(message: message)
|
||||
// MARK: Swiftgram
|
||||
if transcriptionText == nil && false {
|
||||
if premiumConfiguration.audioTransciptionTrialCount > 0 {
|
||||
if !arguments.associatedData.isPremium {
|
||||
if self.presentAudioTranscriptionTooltip(finished: false) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
guard arguments.associatedData.isPremium else {
|
||||
if self.hapticFeedback == nil {
|
||||
self.hapticFeedback = HapticFeedback()
|
||||
}
|
||||
self.hapticFeedback?.impact(.medium)
|
||||
|
||||
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
|
||||
replaceImpl?(controller)
|
||||
}, dismissed: nil)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
arguments.controllerInteraction.navigationController()?.pushViewController(controller, animated: true)
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: context.sharedContext.accountManager).startStandalone()
|
||||
}
|
||||
return false })
|
||||
arguments.controllerInteraction.presentControllerInCurrent(tipController, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var shouldBeginTranscription = false
|
||||
var shouldExpandNow = false
|
||||
@@ -384,8 +422,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
self.audioTranscriptionState = .inProgress
|
||||
self.requestUpdateLayout(true)
|
||||
|
||||
// GHOSTGRAM: Always use local transcription (free, private, on-device!)
|
||||
if true {
|
||||
if context.sharedContext.immediateExperimentalUISettings.localTranscription || !arguments.associatedData.isPremium || SGSimpleSettings.shared.transcriptionBackend == SGSimpleSettings.TranscriptionBackend.apple.rawValue {
|
||||
let appLocale = presentationData.strings.baseLanguageCode
|
||||
|
||||
let signal: Signal<LocallyTranscribedAudio?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
|
||||
@@ -417,7 +454,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
guard let result = result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return transcribeAudio(path: result, appLocale: appLocale)
|
||||
|
||||
return transcribeAudio(path: result, appLocale: arguments.controllerInteraction.sgGetChatPredictedLang() ?? appLocale)
|
||||
}
|
||||
|
||||
self.transcribeDisposable = (signal
|
||||
@@ -605,6 +643,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
var audioWaveform: AudioWaveform?
|
||||
var isVoice = false
|
||||
var audioDuration: Int32 = 0
|
||||
var isConsumed: Bool?
|
||||
|
||||
var consumableContentIcon: UIImage?
|
||||
for attribute in arguments.message.attributes {
|
||||
if let attribute = attribute as? ConsumableContentMessageAttribute {
|
||||
@@ -615,6 +655,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
consumableContentIcon = PresentationResourcesChat.chatBubbleConsumableContentOutgoingIcon(arguments.presentationData.theme.theme)
|
||||
}
|
||||
}
|
||||
isConsumed = attribute.consumed
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -733,8 +774,25 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
if Namespaces.Message.allNonRegular.contains(arguments.message.id.namespace) {
|
||||
displayTranscribe = false
|
||||
} else if arguments.message.id.peerId.namespace != Namespaces.Peer.SecretChat && !isViewOnceMessage && !arguments.presentationData.isPreview {
|
||||
// GHOSTGRAM: Always show transcribe button for voice messages
|
||||
displayTranscribe = true
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 })
|
||||
// MARK: Swiftgram
|
||||
if arguments.associatedData.isPremium || true {
|
||||
displayTranscribe = true
|
||||
} else if premiumConfiguration.audioTransciptionTrialCount > 0 {
|
||||
if arguments.incoming {
|
||||
if audioDuration < premiumConfiguration.audioTransciptionTrialMaxDuration {
|
||||
displayTranscribe = true
|
||||
}
|
||||
}
|
||||
} else if arguments.associatedData.alwaysDisplayTranscribeButton.canBeDisplayed {
|
||||
if audioDuration >= 60 {
|
||||
displayTranscribe = true
|
||||
} else if arguments.incoming && isConsumed == false && arguments.associatedData.alwaysDisplayTranscribeButton.displayForNotConsumed {
|
||||
displayTranscribe = true
|
||||
}
|
||||
} else if arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost {
|
||||
displayTranscribe = true
|
||||
}
|
||||
}
|
||||
|
||||
let transcribedText = forcedAudioTranscriptionText ?? transcribedText(message: arguments.message)
|
||||
@@ -749,7 +807,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let currentTime = Int32(Date().timeIntervalSince1970)
|
||||
if transcribedText == nil, let cooldownUntilTime = arguments.associatedData.audioTranscriptionTrial.cooldownUntilTime, cooldownUntilTime > currentTime {
|
||||
if transcribedText == nil, let cooldownUntilTime = arguments.associatedData.audioTranscriptionTrial.cooldownUntilTime, cooldownUntilTime > currentTime, { return false }() /* MARK: Swiftgram */ {
|
||||
updatedAudioTranscriptionState = .locked
|
||||
}
|
||||
|
||||
@@ -899,6 +957,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
context: arguments.context,
|
||||
presentationData: arguments.presentationData,
|
||||
edited: edited && !arguments.presentationData.isPreview,
|
||||
isDeleted: arguments.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: !arguments.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
@@ -2173,4 +2232,3 @@ public final class FileMessageSelectionNode: ASDisplayNode {
|
||||
self.checkNode.frame = CGRect(origin: checkOrigin, size: checkSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -577,6 +577,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited && !sentViaBot && !item.presentationData.isPreview,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgdeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageInteractiveMediaNode",
|
||||
module_name = "ChatMessageInteractiveMediaNode",
|
||||
@@ -9,7 +13,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgdeps + [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
|
||||
+25
@@ -1,3 +1,4 @@
|
||||
import SGSimpleSettings
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@@ -947,6 +948,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
var isSticker = false
|
||||
var maxDimensions = layoutConstants.image.maxDimensions
|
||||
var maxHeight = layoutConstants.image.maxDimensions.height
|
||||
// MARK: Swiftgram
|
||||
var imageOriginalMaxDimensions: CGSize?
|
||||
var isStory = false
|
||||
var isGift = false
|
||||
|
||||
@@ -969,6 +972,19 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
} else if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
||||
unboundSize = CGSize(width: max(10.0, floor(dimensions.cgSize.width * 0.5)), height: max(10.0, floor(dimensions.cgSize.height * 0.5)))
|
||||
// MARK: Swiftgram
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, SGSimpleSettings.shared.wideChannelPosts {
|
||||
imageOriginalMaxDimensions = maxDimensions
|
||||
switch sizeCalculation {
|
||||
case let .constrained(constrainedSize):
|
||||
maxDimensions.width = constrainedSize.width
|
||||
case .unconstrained:
|
||||
maxDimensions.width = unboundSize.width
|
||||
}
|
||||
if message.text.isEmpty {
|
||||
maxDimensions.width = max(layoutConstants.image.maxDimensions.width, unboundSize.aspectFitted(CGSize(width: maxDimensions.width, height: layoutConstants.image.minDimensions.height)).width)
|
||||
}
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile, var dimensions = file.dimensions {
|
||||
if let thumbnail = file.previewRepresentations.first {
|
||||
let dimensionsVertical = dimensions.width < dimensions.height
|
||||
@@ -1112,6 +1128,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
context: context,
|
||||
presentationData: presentationData,
|
||||
edited: dateAndStatus.edited && !presentationData.isPreview,
|
||||
isDeleted: message.ghostgramIsDeleted,
|
||||
impressionCount: !presentationData.isPreview ? dateAndStatus.viewCount : nil,
|
||||
dateText: dateAndStatus.dateText,
|
||||
type: dateAndStatus.type,
|
||||
@@ -1196,6 +1213,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
|
||||
boundingSize = CGSize(width: boundingWidth, height: filledSize.height).cropped(CGSize(width: CGFloat.greatestFiniteMagnitude, height: maxHeight))
|
||||
if let imageOriginalMaxDimensions = imageOriginalMaxDimensions {
|
||||
boundingSize.height = min(boundingSize.height, nativeSize.aspectFitted(imageOriginalMaxDimensions).height)
|
||||
}
|
||||
boundingSize.height = max(boundingSize.height, layoutConstants.image.minDimensions.height)
|
||||
boundingSize.width = max(boundingSize.width, layoutConstants.image.minDimensions.width)
|
||||
switch contentMode {
|
||||
@@ -2992,6 +3012,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
icon = .eye
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if displaySpoiler, let context = self.context {
|
||||
let extendedMediaOverlayNode: ExtendedMediaOverlayNode
|
||||
@@ -3272,6 +3293,10 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
|
||||
public func scrubberTransition() -> GalleryItemScrubberTransition? {
|
||||
if "".isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
final class TimestampContainerTransitionView: UIView {
|
||||
let containerView: UIView
|
||||
let containerMaskView: UIImageView
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgdeps = [
|
||||
"//Swiftgram/SGSimpleSettings:SGSimpleSettings",
|
||||
"//submodules/TranslateUI:TranslateUI"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageItemImpl",
|
||||
module_name = "ChatMessageItemImpl",
|
||||
@@ -9,7 +14,7 @@ swift_library(
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
deps = sgdeps + [
|
||||
"//submodules/Postbox",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
|
||||
+17
-4
@@ -1,3 +1,5 @@
|
||||
import SGSimpleSettings
|
||||
import TranslateUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
@@ -327,14 +329,14 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
}
|
||||
displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
|
||||
|
||||
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum {
|
||||
if let chatPeer = content.firstMessage.peers[content.firstMessage.id.peerId], chatPeer.isForumOrMonoForum {
|
||||
if case .replyThread = chatLocation {
|
||||
if channel.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() {
|
||||
if chatPeer.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() {
|
||||
displayAuthorInfo = false
|
||||
}
|
||||
} else {
|
||||
if channel.isMonoForum {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
if chatPeer.isMonoForum {
|
||||
if let chatPeer = chatPeer as? TelegramChannel, let linkedMonoforumId = chatPeer.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
headerSeparableThreadId = content.firstMessage.threadId
|
||||
|
||||
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {
|
||||
@@ -533,6 +535,17 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
|
||||
let configure = {
|
||||
let node = (viewClassName as! ChatMessageItemView.Type).init(rotated: self.controllerInteraction.chatIsRotated)
|
||||
if let node = node as? ChatMessageStickerItemNode {
|
||||
node.sizeCoefficient = Float(SGSimpleSettings.shared.stickerSize) / 100.0
|
||||
if !SGSimpleSettings.shared.stickerTimestamp {
|
||||
node.dateAndStatusNode.isHidden = true
|
||||
}
|
||||
} else if let node = node as? ChatMessageAnimatedStickerItemNode {
|
||||
node.sizeCoefficient = Float(SGSimpleSettings.shared.stickerSize) / 100.0
|
||||
if !SGSimpleSettings.shared.stickerTimestamp {
|
||||
node.dateAndStatusNode.isHidden = true
|
||||
}
|
||||
}
|
||||
node.setupItem(self, synchronousLoad: synchronousLoads)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
|
||||
+17
@@ -1,3 +1,4 @@
|
||||
import SGSimpleSettings
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@@ -663,6 +664,9 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
public var playedEffectAnimation: Bool = false
|
||||
public var effectAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||
|
||||
private var wasFilteredKeywordTested: Bool = false
|
||||
private var matchedFilterKeyword: String? = nil
|
||||
|
||||
public required init(rotated: Bool) {
|
||||
super.init(layerBacked: false, rotated: rotated)
|
||||
if rotated {
|
||||
@@ -683,10 +687,23 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
|
||||
self.item = nil
|
||||
self.frame = CGRect()
|
||||
self.wasFilteredKeywordTested = false
|
||||
self.matchedFilterKeyword = nil
|
||||
}
|
||||
|
||||
open func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
|
||||
self.item = item
|
||||
|
||||
if !self.wasFilteredKeywordTested && !SGSimpleSettings.shared.messageFilterKeywords.isEmpty && SGSimpleSettings.shared.ephemeralStatus > 1 {
|
||||
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
if incomingMessage {
|
||||
if let matchedKeyword = SGSimpleSettings.shared.messageFilterKeywords.first(where: { item.message.text.contains($0) }) {
|
||||
self.matchedFilterKeyword = matchedKeyword
|
||||
self.alpha = item.presentationData.theme.theme.overallDarkAppearance ? 0.2 : 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
self.wasFilteredKeywordTested = true
|
||||
}
|
||||
|
||||
open func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
|
||||
|
||||
+1
@@ -274,6 +274,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
+1
@@ -1117,6 +1117,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
+1
@@ -130,6 +130,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: message.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
if let actions = self.actions, actions.isCopyProtected {
|
||||
self.interfaceInteraction?.displayCopyProtectionTip(self.forwardButton, false)
|
||||
} else if !self.forwardButton.isImplicitlyDisabled {
|
||||
self.interfaceInteraction?.forwardSelectedMessages()
|
||||
self.interfaceInteraction?.forwardSelectedMessages(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -38,6 +38,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
public let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
public let imageNode: TransformImageNode
|
||||
public var sizeCoefficient: Float = 1.0
|
||||
private var backgroundNode: WallpaperBubbleBackgroundNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
public var textNode: TextNode?
|
||||
@@ -55,7 +56,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode?
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
public let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
@@ -654,6 +655,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.message.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
@@ -897,11 +899,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionReject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline), style: nil),
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionApprove, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove), style: nil)
|
||||
]),
|
||||
ReplyMarkupRow(buttons: [
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges))
|
||||
ReplyMarkupButton(title: item.presentationData.strings.Chat_PostApproval_Message_ActionSuggestChanges, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges), style: nil)
|
||||
])
|
||||
],
|
||||
flags: [],
|
||||
|
||||
+2
-2
@@ -725,6 +725,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited && !item.presentationData.isPreview,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: !item.presentationData.isPreview ? viewCount : nil,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
@@ -742,7 +743,6 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
starsCount: starsCount,
|
||||
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
|
||||
hasAutoremove: item.message.isSelfExpiring,
|
||||
isDeleted: AntiDeleteManager.shared.isMessageDeleted(peerId: item.message.id.peerId.toInt64(), messageId: item.message.id.id) || AntiDeleteManager.shared.isMessageDeleted(text: item.message.text),
|
||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
@@ -1256,7 +1256,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
private func updateIsTranslating(_ isTranslating: Bool) {
|
||||
public func updateIsTranslating(_ isTranslating: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
+1
@@ -1173,6 +1173,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
edited: edited,
|
||||
isDeleted: item.topMessage.ghostgramIsDeleted,
|
||||
impressionCount: viewCount,
|
||||
dateText: dateText,
|
||||
type: statusType,
|
||||
|
||||
+2
-2
@@ -65,9 +65,9 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, blockMessageAuthor: { _, _ in
|
||||
}, deleteMessages: { _, _, f in
|
||||
f(.default)
|
||||
}, forwardSelectedMessages: {
|
||||
}, forwardSelectedMessages: { _ in
|
||||
}, forwardCurrentForwardMessages: {
|
||||
}, forwardMessages: { _ in
|
||||
}, forwardMessages: { _, _ in
|
||||
}, updateForwardOptionsState: { _ in
|
||||
}, presentForwardOptions: { _ in
|
||||
}, presentReplyOptions: { _ in
|
||||
|
||||
+12
@@ -1465,6 +1465,18 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
break
|
||||
case .sendGift:
|
||||
break
|
||||
case .unknownDeepLink:
|
||||
break
|
||||
case .oauth:
|
||||
break
|
||||
case .chats:
|
||||
break
|
||||
case .compose:
|
||||
break
|
||||
case .postStory:
|
||||
break
|
||||
case .contacts:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
+10
-2
@@ -19,6 +19,14 @@ import GlassBackgroundComponent
|
||||
import ComponentDisplayAdapters
|
||||
import StarsParticleEffect
|
||||
|
||||
private func neutralActionButtonGlassTint(theme: PresentationTheme) -> GlassBackgroundView.TintColor {
|
||||
if theme.overallDarkAppearance {
|
||||
return .init(kind: .custom, color: UIColor(white: 0.0, alpha: 0.38))
|
||||
} else {
|
||||
return .init(kind: .custom, color: UIColor(white: 1.0, alpha: 0.68))
|
||||
}
|
||||
}
|
||||
|
||||
private final class EffectBadgeView: UIView {
|
||||
private let context: AccountContext
|
||||
private var currentEffectId: Int64?
|
||||
@@ -345,7 +353,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
|
||||
}
|
||||
|
||||
transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))
|
||||
self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: neutralActionButtonGlassTint(theme: interfaceState.theme), isInteractive: true, transition: ComponentTransition(transition))
|
||||
|
||||
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.micButton.layoutItems()
|
||||
@@ -401,7 +409,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
|
||||
|
||||
transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))
|
||||
self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: neutralActionButtonGlassTint(theme: interfaceState.theme), isInteractive: true, transition: ComponentTransition(transition))
|
||||
if let image = self.expandMediaInputButtonIcon.image {
|
||||
let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.expandMediaInputButtonIcon.center = expandIconFrame.center
|
||||
|
||||
+4
-2
@@ -418,11 +418,11 @@ public final class ChatTextInputPanelComponent: Component {
|
||||
deleteMessages: { _, _, f in
|
||||
f(.default)
|
||||
},
|
||||
forwardSelectedMessages: {
|
||||
forwardSelectedMessages: { _ in
|
||||
},
|
||||
forwardCurrentForwardMessages: {
|
||||
},
|
||||
forwardMessages: { _ in
|
||||
forwardMessages: { _, _ in
|
||||
},
|
||||
updateForwardOptionsState: { _ in
|
||||
},
|
||||
@@ -472,6 +472,7 @@ public final class ChatTextInputPanelComponent: Component {
|
||||
var presentationInterfaceState = ChatPresentationInterfaceState(
|
||||
chatWallpaper: .color(0),
|
||||
theme: component.theme,
|
||||
preferredGlassType: .default,
|
||||
strings: component.strings,
|
||||
dateTimeFormat: PresentationDateTimeFormat(),
|
||||
nameDisplayOrder: .firstLast,
|
||||
@@ -773,6 +774,7 @@ public final class ChatTextInputPanelComponent: Component {
|
||||
var presentationInterfaceState = ChatPresentationInterfaceState(
|
||||
chatWallpaper: .color(0),
|
||||
theme: component.theme,
|
||||
preferredGlassType: .default,
|
||||
strings: component.strings,
|
||||
dateTimeFormat: PresentationDateTimeFormat(),
|
||||
nameDisplayOrder: .firstLast,
|
||||
|
||||
+209
-14
@@ -1,3 +1,9 @@
|
||||
// MARK: Swiftgram
|
||||
import TelegramUIPreferences
|
||||
import SGSimpleSettings
|
||||
import SwiftUI
|
||||
import SGInputToolbar
|
||||
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
import UIKit
|
||||
@@ -65,6 +71,14 @@ public let chatTextInputMinFontSize: CGFloat = 5.0
|
||||
|
||||
private let minInputFontSize = chatTextInputMinFontSize
|
||||
|
||||
private func neutralChatInputGlassTint(theme: PresentationTheme, innerColor: UIColor? = nil) -> GlassBackgroundView.TintColor {
|
||||
if theme.overallDarkAppearance {
|
||||
return .init(kind: .custom, color: UIColor(white: 0.0, alpha: 0.38), innerColor: innerColor)
|
||||
} else {
|
||||
return .init(kind: .custom, color: UIColor(white: 1.0, alpha: 0.68), innerColor: innerColor)
|
||||
}
|
||||
}
|
||||
|
||||
private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
var baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
||||
if "".isEmpty {
|
||||
@@ -333,6 +347,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
// MARK: Swiftgram
|
||||
private var sendWithReturnKey: Bool
|
||||
private var sendWithReturnKeyDisposable: Disposable?
|
||||
// private var toolbarHostingController: UIViewController? //Any? // UIHostingController<ChatToolbarView>?
|
||||
private var toolbarNode: ASDisplayNode?
|
||||
|
||||
public var inputTextState: ChatTextInputState {
|
||||
if let textInputNode = self.textInputNode {
|
||||
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
|
||||
@@ -619,6 +639,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
self.textInputViewInternalInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 4.0, right: 11.0)
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
self.sendWithReturnKey = SGSimpleSettings.shared.sendWithReturnKey // MARK: Swiftgram
|
||||
//
|
||||
|
||||
var hasSpoilers = true
|
||||
var hasQuotes = true
|
||||
if presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat {
|
||||
@@ -755,7 +780,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_input_bounce"] != nil {
|
||||
self.enableBounceAnimations = false
|
||||
}*/
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
self.initToolbarIfNeeded(context: context)
|
||||
//
|
||||
|
||||
self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@@ -818,6 +847,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
}
|
||||
self.attachmentButtonDisabledNode.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
|
||||
// MARK: Swiftgram
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.attachmentButtonLongPressed(_:)))
|
||||
longPressGesture.minimumPressDuration = 1.0
|
||||
self.attachmentButton.addGestureRecognizer(longPressGesture)
|
||||
|
||||
self.sendActionButtons.sendButtonLongPressed = { [weak self] node, gesture in
|
||||
self?.interfaceInteraction?.displaySendMessageOptions(node, gesture)
|
||||
@@ -1013,6 +1046,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.sendWithReturnKeyDisposable?.dispose()
|
||||
self.tooltipController?.dismiss()
|
||||
self.currentEmojiSuggestion?.disposable.dispose()
|
||||
}
|
||||
@@ -1060,6 +1094,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
self.textInputNodeClippingContainer.addSubnode(textInputNode)
|
||||
textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
textInputNode.isUserInteractionEnabled = !self.sendingTextDisabled
|
||||
textInputNode.textView.returnKeyType = self.sendWithReturnKey ? .send : .default
|
||||
self.textInputNode = textInputNode
|
||||
|
||||
if let textInputBackgroundTapRecognizer = self.textInputBackgroundTapRecognizer {
|
||||
@@ -1421,6 +1456,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
let previousAdditionalSideInsets = self.validLayout?.4
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
|
||||
let defaultGlassTintColor: GlassBackgroundView.TintColor
|
||||
let defaultGlassTintWithInnerColor: GlassBackgroundView.TintColor
|
||||
if case .clear = interfaceState.preferredGlassType {
|
||||
defaultGlassTintColor = .init(kind: .custom, color: UIColor.clear)
|
||||
defaultGlassTintWithInnerColor = .init(kind: .custom, color: UIColor.clear, innerColor: interfaceState.theme.list.itemCheckColors.fillColor)
|
||||
} else {
|
||||
defaultGlassTintColor = neutralChatInputGlassTint(theme: interfaceState.theme)
|
||||
defaultGlassTintWithInnerColor = neutralChatInputGlassTint(theme: interfaceState.theme, innerColor: interfaceState.theme.list.itemCheckColors.fillColor)
|
||||
}
|
||||
|
||||
var leftInset = leftInset
|
||||
var rightInset = rightInset
|
||||
|
||||
@@ -2205,6 +2250,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
if buttonTitleUpdated && !transition.isAnimated {
|
||||
transition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
}
|
||||
// MARK: Swiftgram
|
||||
let originalLeftInset = leftInset
|
||||
//
|
||||
|
||||
let textInputBackgroundWidthOffset: CGFloat = 0.0
|
||||
var attachmentButtonX: CGFloat = hideOffset.x + leftInset + leftMenuInset + 8.0
|
||||
@@ -2365,7 +2413,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
|
||||
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
|
||||
transition.updateFrame(view: self.menuButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
||||
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: interfaceState.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition))
|
||||
self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintWithInnerColor, transition: ComponentTransition(transition))
|
||||
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
|
||||
var menuButtonTitleTransition = transition
|
||||
if buttonTitleUpdated {
|
||||
@@ -2385,7 +2433,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
if additionalSideInsets.right > 0.0 {
|
||||
textFieldInsets.right += additionalSideInsets.right / 3.0
|
||||
}
|
||||
if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward {
|
||||
if SGSimpleSettings.shared.hideRecordingButton || inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward {
|
||||
} else {
|
||||
if let customRightAction = self.customRightAction, case .empty = customRightAction {
|
||||
textFieldInsets.right = 8.0
|
||||
@@ -2513,7 +2561,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
case let .audio(recorder, isLocked):
|
||||
let hadAudioRecorder = self.mediaActionButtons.micButton.audioRecorder != nil
|
||||
if !hadAudioRecorder, isLocked {
|
||||
self.mediaActionButtons.micButton.lock()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mediaActionButtons.micButton.lock()
|
||||
}
|
||||
}
|
||||
self.mediaActionButtons.micButton.audioRecorder = recorder
|
||||
audioRecordingTimeNode.audioRecorder = recorder
|
||||
@@ -2887,7 +2937,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
self.updateCounterTextNode(backgroundSize: textInputContainerBackgroundFrame.size, transition: transition)
|
||||
|
||||
let textInputContainerBackgroundTransition = ComponentTransition(transition)
|
||||
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: textInputContainerBackgroundTransition)
|
||||
self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintColor, isInteractive: true, transition: textInputContainerBackgroundTransition)
|
||||
|
||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputContainerBackgroundFrame)
|
||||
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
||||
@@ -3277,7 +3327,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
|
||||
let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0))
|
||||
attachmentButtonX += 40.0 + 6.0
|
||||
self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))
|
||||
self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintColor, isInteractive: true, transition: ComponentTransition(transition))
|
||||
|
||||
transition.updateFrame(layer: self.attachmentButtonBackground.layer, frame: attachmentButtonFrame)
|
||||
transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(), size: attachmentButtonFrame.size))
|
||||
@@ -3505,7 +3555,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame)
|
||||
self.glassBackgroundContainer.update(size: containerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition))
|
||||
|
||||
return contentHeight
|
||||
// MARK: Swiftgram
|
||||
var toolbarOffset: CGFloat = 0.0
|
||||
toolbarOffset = layoutToolbar(transition: transition, panelHeight: contentHeight, width: width, leftInset: originalLeftInset, rightInset: rightInset, displayBotStartButton: displayBotStartButton)
|
||||
|
||||
return contentHeight + toolbarOffset
|
||||
}
|
||||
|
||||
@objc private func slowModeButtonPressed() {
|
||||
@@ -4046,7 +4100,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
|
||||
let controller = makePeekController(presentationData: presentationData, content: content, sourceView: {
|
||||
return (sourceView, sourceRect)
|
||||
})
|
||||
//strongSelf.peekController = controller
|
||||
@@ -4318,8 +4372,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
let blurTransitionOut: ComponentTransition = transition.isAnimated ? .easeInOut(duration: 0.18) : .immediate
|
||||
let sendButtonBlurOut: CGFloat = 4.0
|
||||
|
||||
var hideMicButton = false
|
||||
var hideMicButtonBackground = false
|
||||
var hideMicButton = SGSimpleSettings.shared.hideRecordingButton
|
||||
var hideMicButtonBackground = SGSimpleSettings.shared.hideRecordingButton
|
||||
|
||||
if self.customRightAction != nil {
|
||||
self.mediaActionButtons.isHidden = true
|
||||
@@ -4397,7 +4451,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
}
|
||||
|
||||
if (hasText || keepSendButtonEnabled && !mediaInputIsActive && !hasSlowModeButton) {
|
||||
if (SGSimpleSettings.shared.hideRecordingButton || hasText || keepSendButtonEnabled && !mediaInputIsActive && !hasSlowModeButton) {
|
||||
if self.sendActionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero {
|
||||
alphaTransition.updateAlpha(node: self.sendActionButtons.sendContainerNode, alpha: 1.0)
|
||||
blurTransitionIn.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: sendButtonBlurOut, toRadius: 0.0)
|
||||
@@ -4522,14 +4576,32 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
var shouldSendMessage = false
|
||||
if self.sendActionButtons.sendButton.supernode != nil && !self.sendActionButtons.sendButton.isHidden && !self.sendActionButtons.sendContainerNode.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
if let context = self.context, context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
|
||||
if modifierFlags.contains(.command) {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
} else {
|
||||
if modifierFlags.isEmpty {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
if shouldSendMessage {
|
||||
self.sendButtonPressed()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn()
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
private func applyUpdateSendButtonIcon() {
|
||||
@@ -4943,6 +5015,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
|
||||
self.updateActivity()
|
||||
|
||||
// MARK: Swiftgram
|
||||
if self.sendWithReturnKey && text == "\n" {
|
||||
self.sendButtonPressed()
|
||||
return false
|
||||
}
|
||||
|
||||
var cleanText = text
|
||||
let removeSequences: [String] = ["\u{202d}", "\u{202c}"]
|
||||
for sequence in removeSequences {
|
||||
@@ -5169,6 +5248,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Swiftgram
|
||||
@objc func attachmentButtonLongPressed(_ gesture: UILongPressGestureRecognizer) {
|
||||
guard gesture.state == .began else { return }
|
||||
}
|
||||
|
||||
@objc func searchLayoutClearButtonPressed() {
|
||||
if let interfaceInteraction = self.interfaceInteraction {
|
||||
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
|
||||
@@ -5477,3 +5561,114 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
||||
return AttachmentInputPanelTransition(inputNode: self, accessoryPanelNode: accessoryPanelNode, menuButtonNode: self.menuButton, menuButtonBackgroundView: self.menuButtonBackgroundView, menuIconNode: self.menuButtonIconNode, menuTextNode: self.menuButtonTextNode, prepareForDismiss: { self.menuButtonIconNode.enqueueState(.app, animated: false) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
extension ChatTextInputPanelNode {
|
||||
|
||||
func initToolbarIfNeeded(context: AccountContext) {
|
||||
guard #available(iOS 13.0, *) else { return }
|
||||
guard SGSimpleSettings.shared.inputToolbar else { return }
|
||||
guard context.sharedContext.immediateSGStatus.status > 1 else { return }
|
||||
guard self.toolbarNode == nil else { return }
|
||||
let toolbarView = ChatToolbarView(
|
||||
onQuote: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesQuote(strongSelf)
|
||||
},
|
||||
onSpoiler: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesSpoiler(strongSelf)
|
||||
},
|
||||
onBold: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesBold(strongSelf)
|
||||
},
|
||||
onItalic: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesItalic(strongSelf)
|
||||
},
|
||||
onMonospace: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesMonospace(strongSelf)
|
||||
},
|
||||
onLink: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesLink(strongSelf)
|
||||
},
|
||||
onStrikethrough: { [weak self]
|
||||
in guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesStrikethrough(strongSelf)
|
||||
},
|
||||
onUnderline: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesUnderline(strongSelf)
|
||||
},
|
||||
onCode: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSelectLastWordIfIdle()
|
||||
strongSelf.formatAttributesCodeBlock(strongSelf)
|
||||
},
|
||||
onNewLine: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.sgSetNewLine()
|
||||
},
|
||||
// TODO(swiftgram): Binding
|
||||
showNewLine: .constant(true), //.constant(self.sendWithReturnKey)
|
||||
onClearFormatting: { [weak self] in
|
||||
guard let strongSelf = self else { return }
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
return (chatTextInputClearFormattingAttributes(current), inputMode)
|
||||
}
|
||||
}
|
||||
)
|
||||
let toolbarHostingController = UIHostingController(rootView: toolbarView)
|
||||
toolbarHostingController.view.backgroundColor = UIColor.clear
|
||||
let toolbarNode = ASDisplayNode { toolbarHostingController.view }
|
||||
self.toolbarNode = toolbarNode
|
||||
// assigning toolbarHostingController bugs responsivness and overrides layout
|
||||
// self.toolbarHostingController = toolbarHostingController
|
||||
|
||||
// Disable "Swipe to go back" gesture when touching scrollview
|
||||
self.view.interactiveTransitionGestureRecognizerTest = { [weak self] point in
|
||||
if let self, let _ = self.toolbarNode?.view.hitTest(point, with: nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.addSubnode(toolbarNode)
|
||||
}
|
||||
|
||||
func layoutToolbar(transition: ContainedViewLayoutTransition, panelHeight: CGFloat, width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, displayBotStartButton: Bool) -> CGFloat {
|
||||
var toolbarHeight: CGFloat = 0.0
|
||||
var toolbarSpacing: CGFloat = 0.0
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
if displayBotStartButton {
|
||||
toolbarNode.view.alpha = 0.0
|
||||
// transition.updateAlpha(node: toolbarNode, alpha: 0.0)
|
||||
/*} else if !self.isFocused {
|
||||
transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { _ in
|
||||
toolbarNode.isHidden = true
|
||||
})*/
|
||||
} else {
|
||||
if !self.isFocused {
|
||||
transition.updateAlpha(node: toolbarNode, alpha: 0.0)
|
||||
} else {
|
||||
toolbarHeight = 44.0
|
||||
toolbarSpacing = 6.0
|
||||
transition.updateFrame(node: toolbarNode, frame: CGRect(origin: CGPoint(x: leftInset, y: panelHeight + toolbarSpacing), size: CGSize(width: width - rightInset - leftInset, height: toolbarHeight)))
|
||||
transition.updateAlpha(node: toolbarNode, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return toolbarHeight + toolbarSpacing
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -6,6 +6,14 @@ import ComponentFlow
|
||||
import GlassBackgroundComponent
|
||||
import AppBundle
|
||||
|
||||
private func neutralInputButtonGlassTint(theme: PresentationTheme) -> GlassBackgroundView.TintColor {
|
||||
if theme.overallDarkAppearance {
|
||||
return .init(kind: .custom, color: UIColor(white: 0.0, alpha: 0.38))
|
||||
} else {
|
||||
return .init(kind: .custom, color: UIColor(white: 1.0, alpha: 0.68))
|
||||
}
|
||||
}
|
||||
|
||||
final class InputIconButtonComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let name: String
|
||||
@@ -87,7 +95,7 @@ final class InputIconButtonComponent: Component {
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: transition)
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: neutralInputButtonGlassTint(theme: component.theme), isInteractive: true, transition: transition)
|
||||
|
||||
self.button.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
|
||||
+10
-2
@@ -7,6 +7,14 @@ import GlassBackgroundComponent
|
||||
import AnimatedTextComponent
|
||||
import StarsParticleEffect
|
||||
|
||||
private func neutralStarReactionGlassTint(theme: PresentationTheme) -> GlassBackgroundView.TintColor {
|
||||
if theme.overallDarkAppearance {
|
||||
return .init(kind: .custom, color: UIColor(white: 0.0, alpha: 0.38))
|
||||
} else {
|
||||
return .init(kind: .custom, color: UIColor(white: 1.0, alpha: 0.68))
|
||||
}
|
||||
}
|
||||
|
||||
final class StarReactionButtonBadgeComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let count: Int
|
||||
@@ -80,7 +88,7 @@ final class StarReactionButtonBadgeComponent: Component {
|
||||
if component.isFilled {
|
||||
backgroundTintColor = .init(kind: .custom, color: UIColor(rgb: 0xFFB10D))
|
||||
} else {
|
||||
backgroundTintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
||||
backgroundTintColor = neutralStarReactionGlassTint(theme: component.theme)
|
||||
}
|
||||
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: backgroundTintColor, isInteractive: true, transition: transition)
|
||||
@@ -338,7 +346,7 @@ final class StarReactionButtonComponent: Component {
|
||||
if component.isFilled {
|
||||
backgroundTintColor = .init(kind: .custom, color: UIColor(rgb: 0xFFB10D))
|
||||
} else {
|
||||
backgroundTintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
||||
backgroundTintColor = neutralStarReactionGlassTint(theme: component.theme)
|
||||
}
|
||||
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: backgroundTintColor, isInteractive: false, transition: transition)
|
||||
|
||||
Reference in New Issue
Block a user