mirror of
https://github.com/signalapp/Signal-iOS.git
synced 2025-12-05 01:10:41 +00:00
Quote reply icons for polls
This commit is contained in:
@@ -285,7 +285,19 @@ public class QuotedMessageView: ManualStackViewWithLayer {
|
||||
|
||||
switch displayTextValue {
|
||||
case .text(let text):
|
||||
labelText = .text(text)
|
||||
if state.quotedReplyModel.originalContent.isPoll {
|
||||
let pollIcon = SignalSymbol.poll.attributedString(
|
||||
dynamicTypeBaseSize: quotedTextFont.pointSize
|
||||
) + " "
|
||||
let pollPrefix = OWSLocalizedString(
|
||||
"POLL_LABEL",
|
||||
comment: "Label specifying the message type as a poll"
|
||||
) + ": "
|
||||
|
||||
labelText = .attributedText(pollIcon + NSAttributedString(string: pollPrefix + text))
|
||||
} else {
|
||||
labelText = .text(text)
|
||||
}
|
||||
textAlignment = text.naturalTextAlignment
|
||||
case .attributedText(let attributedText):
|
||||
let mutableText = NSMutableAttributedString(attributedString: attributedText)
|
||||
|
||||
@@ -222,7 +222,10 @@ private class QuotedMessageSnippetView: UIView {
|
||||
let label = UILabel()
|
||||
|
||||
let attributedText: NSAttributedString
|
||||
if let displayableQuotedText, !displayableQuotedText.displayTextValue.isEmpty {
|
||||
if let displayableQuotedText,
|
||||
!displayableQuotedText.displayTextValue.isEmpty,
|
||||
!quotedMessage.content.isPoll
|
||||
{
|
||||
let config = HydratedMessageBody.DisplayConfiguration.quotedReply(
|
||||
font: Layout.quotedTextFont,
|
||||
textColor: .fixed(.Signal.label)
|
||||
@@ -271,6 +274,35 @@ private class QuotedMessageSnippetView: UIView {
|
||||
.foregroundColor: UIColor.Signal.secondaryLabel
|
||||
]
|
||||
)
|
||||
} else if quotedMessage.content.isPoll {
|
||||
switch quotedMessage.content {
|
||||
case .poll(let pollQuestion):
|
||||
let pollIcon = SignalSymbol.poll.attributedString(dynamicTypeBaseSize: Layout.fileTypeFont.pointSize) + " "
|
||||
let pollPrefix = OWSLocalizedString(
|
||||
"POLL_LABEL",
|
||||
comment: "Label specifying the message type as a poll"
|
||||
) + ": "
|
||||
|
||||
attributedText = pollIcon + NSAttributedString(
|
||||
string: pollPrefix + pollQuestion,
|
||||
attributes: [
|
||||
.font: Layout.fileTypeFont,
|
||||
.foregroundColor: UIColor.Signal.secondaryLabel
|
||||
]
|
||||
)
|
||||
default:
|
||||
owsFailDebug("Quoted message is poll but there's no poll")
|
||||
attributedText = NSAttributedString(
|
||||
string: NSLocalizedString(
|
||||
"QUOTED_REPLY_TYPE_ATTACHMENT",
|
||||
comment: "Indicates this message is a quoted reply to an attachment of unknown type."
|
||||
),
|
||||
attributes: [
|
||||
.font: Layout.fileTypeFont,
|
||||
.foregroundColor: UIColor.Signal.secondaryLabel
|
||||
]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
attributedText = NSAttributedString(
|
||||
string: NSLocalizedString(
|
||||
@@ -479,7 +511,7 @@ private class QuotedMessageSnippetView: UIView {
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
thumbnailView = imageView
|
||||
|
||||
case .payment, .text, .viewOnce, .contactShare, .storyReactionEmoji:
|
||||
case .payment, .text, .viewOnce, .contactShare, .storyReactionEmoji, .poll:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -581,7 +613,7 @@ private class QuotedMessageSnippetView: UIView {
|
||||
return (attachment.mimeType, reference.renderingFlag == .shouldLoop)
|
||||
case .edit(_, _, let innerContent):
|
||||
return mimeTypeAndIsLooping(innerContent)
|
||||
case .giftBadge, .text, .payment, .attachmentStub, .viewOnce, .contactShare, .storyReactionEmoji:
|
||||
case .giftBadge, .text, .payment, .attachmentStub, .viewOnce, .contactShare, .storyReactionEmoji, .poll:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -635,7 +667,7 @@ private class QuotedMessageSnippetView: UIView {
|
||||
return reference.sourceFilename
|
||||
case .edit(_, _, let innerContent):
|
||||
return sourceFilenameForSnippet(innerContent)
|
||||
case .giftBadge, .text, .payment, .contactShare, .viewOnce, .storyReactionEmoji:
|
||||
case .giftBadge, .text, .payment, .contactShare, .viewOnce, .storyReactionEmoji, .poll:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +598,8 @@ class BackupArchiveTSMessageContentsArchiver: BackupArchiveProtoStreamWriter {
|
||||
quote.type = .giftBadge
|
||||
} else if quotedMessage.isTargetMessageViewOnce {
|
||||
quote.type = .viewOnce
|
||||
} else if quotedMessage.isPoll {
|
||||
quote.type = .poll
|
||||
} else {
|
||||
guard didArchiveText || didArchiveAttachments else {
|
||||
// NORMAL-type quotes must have either text or attachments, lest
|
||||
|
||||
@@ -12,6 +12,7 @@ extension DraftQuotedReplyModel {
|
||||
public let originalMessageAuthorAddress: SignalServiceAddress
|
||||
public let originalMessageIsGiftBadge: Bool
|
||||
public let originalMessageIsViewOnce: Bool
|
||||
public let originalMessageIsPoll: Bool
|
||||
public let threadUniqueId: String
|
||||
|
||||
public let quoteBody: MessageBody?
|
||||
|
||||
@@ -34,6 +34,8 @@ public class DraftQuotedReplyModel {
|
||||
/// The original message is a story reaction emoji
|
||||
case storyReactionEmoji(String)
|
||||
|
||||
case poll(String)
|
||||
|
||||
// MARK: - Attachment types
|
||||
|
||||
/// The original message had an attachment, but it could not
|
||||
@@ -84,6 +86,15 @@ public class DraftQuotedReplyModel {
|
||||
}
|
||||
}
|
||||
|
||||
public var isPoll: Bool {
|
||||
switch self {
|
||||
case .poll:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var isRemotelySourced: Bool {
|
||||
switch self {
|
||||
case .edit(_, let quotedMessage, _):
|
||||
@@ -231,6 +242,9 @@ public class DraftQuotedReplyModel {
|
||||
emoji
|
||||
)
|
||||
return MessageBody(text: text, ranges: .empty)
|
||||
case .poll(let pollQuestion):
|
||||
// Poll question should be the message body of the draft reply.
|
||||
return MessageBody(text: pollQuestion, ranges: .empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +262,7 @@ extension DraftQuotedReplyModel: Equatable {
|
||||
extension DraftQuotedReplyModel.Content: Equatable {
|
||||
public static func == (lhs: DraftQuotedReplyModel.Content, rhs: DraftQuotedReplyModel.Content) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.giftBadge, .giftBadge), (.viewOnce, .viewOnce):
|
||||
case (.giftBadge, .giftBadge), (.viewOnce, .viewOnce), (.poll, .poll):
|
||||
return true
|
||||
case let (.payment(lhsBody), .payment(rhsBody)):
|
||||
return lhsBody == rhsBody
|
||||
@@ -281,6 +295,7 @@ extension DraftQuotedReplyModel.Content: Equatable {
|
||||
(.attachment, _),
|
||||
(.edit, _),
|
||||
(.storyReactionEmoji, _),
|
||||
(.poll, _),
|
||||
(_, .giftBadge),
|
||||
(_, .payment),
|
||||
(_, .text),
|
||||
@@ -289,7 +304,8 @@ extension DraftQuotedReplyModel.Content: Equatable {
|
||||
(_, .attachmentStub),
|
||||
(_, .attachment),
|
||||
(_, .edit),
|
||||
(_, .storyReactionEmoji):
|
||||
(_, .storyReactionEmoji),
|
||||
(_, .poll):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodySource: .remote,
|
||||
receivedQuotedAttachmentInfo: nil,
|
||||
isGiftBadge: true,
|
||||
isTargetMessageViewOnce: false
|
||||
isTargetMessageViewOnce: false,
|
||||
isPoll: false
|
||||
))
|
||||
}
|
||||
|
||||
@@ -168,7 +169,8 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodySource: .remote,
|
||||
receivedQuotedAttachmentInfo: attachmentInfo?.info,
|
||||
isGiftBadge: false,
|
||||
isTargetMessageViewOnce: false
|
||||
isTargetMessageViewOnce: false,
|
||||
isPoll: false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -214,13 +216,15 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodySource: .local,
|
||||
receivedQuotedAttachmentInfo: nil,
|
||||
isGiftBadge: false,
|
||||
isTargetMessageViewOnce: true
|
||||
isTargetMessageViewOnce: true,
|
||||
isPoll: false
|
||||
))
|
||||
}
|
||||
|
||||
let body: String?
|
||||
let bodyRanges: MessageBodyRanges?
|
||||
var isGiftBadge: Bool
|
||||
var isPoll: Bool
|
||||
|
||||
if originalMessage is OWSPaymentMessage {
|
||||
// This really should recalculate the string from payment metadata.
|
||||
@@ -228,16 +232,19 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
body = quoteProto.text
|
||||
bodyRanges = nil
|
||||
isGiftBadge = false
|
||||
isPoll = false
|
||||
} else if let messageBody = originalMessage.body?.nilIfEmpty {
|
||||
body = messageBody
|
||||
bodyRanges = originalMessage.bodyRanges
|
||||
isGiftBadge = false
|
||||
isPoll = originalMessage.isPoll
|
||||
} else if let contactName = originalMessage.contactShare?.name.displayName.nilIfEmpty {
|
||||
// Contact share bodies are special-cased in OWSQuotedReplyModel
|
||||
// We need to account for that here.
|
||||
body = "👤 " + contactName
|
||||
bodyRanges = nil
|
||||
isGiftBadge = false
|
||||
isPoll = false
|
||||
} else if let storyReactionEmoji = originalMessage.storyReactionEmoji?.nilIfEmpty {
|
||||
let formatString: String = {
|
||||
if (authorAddress.isLocalAddress) {
|
||||
@@ -255,10 +262,12 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
body = String(format: formatString, storyReactionEmoji)
|
||||
bodyRanges = nil
|
||||
isGiftBadge = false
|
||||
isPoll = false
|
||||
} else {
|
||||
isGiftBadge = originalMessage.giftBadge != nil
|
||||
body = nil
|
||||
bodyRanges = nil
|
||||
isPoll = false
|
||||
}
|
||||
|
||||
let attachmentBuilder = self.attachmentBuilder(
|
||||
@@ -285,7 +294,8 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodySource: .local,
|
||||
receivedQuotedAttachmentInfo: attachmentInfo?.info,
|
||||
isGiftBadge: isGiftBadge,
|
||||
isTargetMessageViewOnce: false
|
||||
isTargetMessageViewOnce: false,
|
||||
isPoll: isPoll
|
||||
)
|
||||
}
|
||||
|
||||
@@ -536,6 +546,14 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
return createDraftReply(content: .storyReactionEmoji(storyReactionEmoji))
|
||||
}
|
||||
|
||||
if originalMessage.isPoll {
|
||||
guard let body = originalMessage.body else {
|
||||
owsFailDebug("Poll message has no question body.")
|
||||
return nil
|
||||
}
|
||||
return createDraftReply(content: .poll(body))
|
||||
}
|
||||
|
||||
return createTextDraftReplyOrNil()
|
||||
}
|
||||
|
||||
@@ -627,6 +645,7 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
originalMessageAuthorAddress: draft.originalMessageAuthorAddress,
|
||||
originalMessageIsGiftBadge: draft.content.isGiftBadge,
|
||||
originalMessageIsViewOnce: draft.content.isViewOnce,
|
||||
originalMessageIsPoll: draft.content.isPoll,
|
||||
threadUniqueId: draft.threadUniqueId,
|
||||
quoteBody: draft.bodyForSending,
|
||||
attachment: nil,
|
||||
@@ -692,6 +711,7 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
originalMessageAuthorAddress: draft.originalMessageAuthorAddress,
|
||||
originalMessageIsGiftBadge: draft.content.isGiftBadge,
|
||||
originalMessageIsViewOnce: draft.content.isViewOnce,
|
||||
originalMessageIsPoll: draft.content.isPoll,
|
||||
threadUniqueId: draft.threadUniqueId,
|
||||
quoteBody: draft.bodyForSending,
|
||||
attachment: quoteAttachment,
|
||||
@@ -728,7 +748,8 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodySource: .remote,
|
||||
receivedQuotedAttachmentInfo: nil,
|
||||
isGiftBadge: false,
|
||||
isTargetMessageViewOnce: false
|
||||
isTargetMessageViewOnce: false,
|
||||
isPoll: false
|
||||
))
|
||||
}
|
||||
|
||||
@@ -742,7 +763,8 @@ public class QuotedReplyManagerImpl: QuotedReplyManager {
|
||||
bodyRanges: body?.ranges,
|
||||
quotedAttachmentForSending: attachmentInfo?.info,
|
||||
isGiftBadge: draft.originalMessageIsGiftBadge,
|
||||
isTargetMessageViewOnce: draft.originalMessageIsViewOnce
|
||||
isTargetMessageViewOnce: draft.originalMessageIsViewOnce,
|
||||
isPoll: draft.originalMessageIsPoll
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ open class QuotedReplyManagerMock: QuotedReplyManager {
|
||||
originalMessageAuthorAddress: draft.originalMessageAuthorAddress,
|
||||
originalMessageIsGiftBadge: draft.content.isGiftBadge,
|
||||
originalMessageIsViewOnce: draft.content.isViewOnce,
|
||||
originalMessageIsPoll: draft.content.isPoll,
|
||||
threadUniqueId: draft.threadUniqueId,
|
||||
quoteBody: draft.bodyForSending,
|
||||
attachment: nil,
|
||||
|
||||
@@ -120,7 +120,8 @@ typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) {
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
quotedAttachmentForSending:(nullable OWSAttachmentInfo *)attachmentInfo
|
||||
isGiftBadge:(BOOL)isGiftBadge
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce;
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce
|
||||
isPoll:(BOOL)isPoll;
|
||||
|
||||
// used when receiving quoted messages. Do not call directly outside AttachmentManager.
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
@@ -130,7 +131,8 @@ typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) {
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
receivedQuotedAttachmentInfo:(nullable OWSAttachmentInfo *)attachmentInfo
|
||||
isGiftBadge:(BOOL)isGiftBadge
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce;
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce
|
||||
isPoll:(BOOL)isPoll;
|
||||
|
||||
// used when restoring quoted messages from backups
|
||||
+ (instancetype)quotedMessageFromBackupWithTargetMessageTimestamp:(nullable NSNumber *)timestamp
|
||||
|
||||
@@ -106,6 +106,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
receivedQuotedAttachmentInfo:(nullable OWSAttachmentInfo *)attachmentInfo
|
||||
isGiftBadge:(BOOL)isGiftBadge
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce
|
||||
isPoll:(BOOL)isPoll
|
||||
{
|
||||
OWSAssertDebug(authorAddress.isValid);
|
||||
|
||||
@@ -122,6 +123,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_quotedAttachment = attachmentInfo;
|
||||
_isGiftBadge = isGiftBadge;
|
||||
_isTargetMessageViewOnce = isTargetMessageViewOnce;
|
||||
_isPoll = isPoll;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -134,6 +136,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
quotedAttachmentForSending:(nullable OWSAttachmentInfo *)attachmentInfo
|
||||
isGiftBadge:(BOOL)isGiftBadge
|
||||
isTargetMessageViewOnce:(BOOL)isTargetMessageViewOnce
|
||||
isPoll:(BOOL)isPoll
|
||||
{
|
||||
OWSAssertDebug(authorAddress.isValid);
|
||||
|
||||
@@ -150,6 +153,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_quotedAttachment = attachmentInfo;
|
||||
_isGiftBadge = isGiftBadge;
|
||||
_isTargetMessageViewOnce = isTargetMessageViewOnce;
|
||||
_isPoll = isPoll;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -203,7 +207,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
bodySource:bodySource
|
||||
receivedQuotedAttachmentInfo:attachmentInfo
|
||||
isGiftBadge:isGiftBadge
|
||||
isTargetMessageViewOnce:isTargetMessageViewOnce];
|
||||
isTargetMessageViewOnce:isTargetMessageViewOnce
|
||||
isPoll:isPoll];
|
||||
|
||||
// TODO (KC): add polls prefix for backup quoted messages when its implemented
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public enum SignalSymbol: Character {
|
||||
case play = "\u{E067}"
|
||||
case playSquare = "\u{E068}"
|
||||
case playRectangle = "\u{E069}"
|
||||
case poll = "\u{E082}"
|
||||
case reply = "\u{E06D}"
|
||||
case safetyNumber = "\u{E06F}"
|
||||
case timer = "\u{E073}"
|
||||
@@ -147,6 +148,8 @@ public enum SignalSymbol: Character {
|
||||
case light
|
||||
case regular
|
||||
case bold
|
||||
case medium
|
||||
case thin
|
||||
|
||||
fileprivate var fontName: String {
|
||||
switch self {
|
||||
@@ -156,6 +159,10 @@ public enum SignalSymbol: Character {
|
||||
return "SignalSymbols-Regular"
|
||||
case .bold:
|
||||
return "SignalSymbols-Bold"
|
||||
case .medium:
|
||||
return "SignalSymbols-Medium"
|
||||
case .thin:
|
||||
return "SignalSymbols-Thin"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
SignalUI/Fonts/SignalSymbols-Medium.otf
Normal file
BIN
SignalUI/Fonts/SignalSymbols-Medium.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
SignalUI/Fonts/SignalSymbols-Thin.otf
Normal file
BIN
SignalUI/Fonts/SignalSymbols-Thin.otf
Normal file
Binary file not shown.
@@ -68,6 +68,8 @@ public class QuotedReplyModel {
|
||||
/// Used if the story has expired; we do not retain a copy.
|
||||
case expiredStory
|
||||
|
||||
case poll(String)
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
public var isGiftBadge: Bool {
|
||||
@@ -88,6 +90,15 @@ public class QuotedReplyModel {
|
||||
}
|
||||
}
|
||||
|
||||
public var isPoll: Bool {
|
||||
switch self {
|
||||
case .poll:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var attachmentMimeType: String? {
|
||||
switch self {
|
||||
case .text(_):
|
||||
@@ -106,6 +117,8 @@ public class QuotedReplyModel {
|
||||
return nil
|
||||
case .expiredStory:
|
||||
return nil
|
||||
case .poll:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +140,8 @@ public class QuotedReplyModel {
|
||||
return nil
|
||||
case .expiredStory:
|
||||
return nil
|
||||
case .poll:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +179,8 @@ public class QuotedReplyModel {
|
||||
),
|
||||
ranges: .empty
|
||||
)
|
||||
case .poll(let pollQuestion):
|
||||
return MessageBody(text: pollQuestion, ranges: .empty)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +202,8 @@ public class QuotedReplyModel {
|
||||
return nil
|
||||
case .expiredStory:
|
||||
return nil
|
||||
case .poll:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +226,8 @@ public class QuotedReplyModel {
|
||||
return true
|
||||
case .expiredStory:
|
||||
return false
|
||||
case .poll:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +365,14 @@ public class QuotedReplyModel {
|
||||
return buildQuotedReplyModel(originalContent: .giftBadge)
|
||||
}
|
||||
|
||||
if quotedMessage.isPoll {
|
||||
guard let pollQuestion = originalMessageBody?.text else {
|
||||
owsFailDebug("Quoted message is poll but no question found")
|
||||
return buildQuotedReplyModel(originalContent: .text(originalMessageBody))
|
||||
}
|
||||
return buildQuotedReplyModel(originalContent: .poll(pollQuestion))
|
||||
}
|
||||
|
||||
if quotedMessage.isTargetMessageViewOnce {
|
||||
return buildQuotedReplyModel(originalContent: .text(.init(
|
||||
text: OWSLocalizedString(
|
||||
@@ -480,7 +509,8 @@ extension QuotedReplyModel.OriginalContent: Equatable {
|
||||
return false
|
||||
case (.expiredStory, .expiredStory):
|
||||
return true
|
||||
|
||||
case (.poll, .poll):
|
||||
return true
|
||||
case
|
||||
(.text, _),
|
||||
(.giftBadge, _),
|
||||
@@ -489,7 +519,8 @@ extension QuotedReplyModel.OriginalContent: Equatable {
|
||||
(.attachment, _),
|
||||
(.mediaStory, _),
|
||||
(.textStory, _),
|
||||
(.expiredStory, _):
|
||||
(.expiredStory, _),
|
||||
(.poll, _):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user