Make admin name tappable
This commit is contained in:
parent
58cdc81e23
commit
82fab30fcd
@ -385,7 +385,7 @@ public class CVComponentBodyText: CVComponentBase, CVComponent {
|
||||
lineBreakMode: .byWordWrapping,
|
||||
numberOfLines: 0,
|
||||
cacheKey: textViewConfig.cacheKey,
|
||||
items: bodyTextState.items,
|
||||
items: textViewConfig.linkItems,
|
||||
linkifyStyle: textViewConfig.linkifyStyle,
|
||||
)
|
||||
}
|
||||
@ -462,7 +462,7 @@ public class CVComponentBodyText: CVComponentBase, CVComponent {
|
||||
case .oversizeTextDownloading:
|
||||
return bodyTextLabelConfig(labelConfig: labelConfigForOversizeTextDownloading)
|
||||
case .remotelyDeleted:
|
||||
return bodyTextLabelConfig(labelConfig: labelConfigForRemotelyDeleted)
|
||||
return bodyTextLabelConfig(textViewConfig: textViewConfigForRemotelyDeleted)
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,35 +475,101 @@ public class CVComponentBodyText: CVComponentBase, CVComponent {
|
||||
)
|
||||
}
|
||||
|
||||
private var labelConfigForRemotelyDeleted: CVLabelConfig {
|
||||
var text: String
|
||||
private func buildAdminDeleteAttributedString(deleteAuthor: CVComponentState.DeleteAuthor) -> NSAttributedString {
|
||||
let format = OWSLocalizedString(
|
||||
"DELETED_BY_ADMIN",
|
||||
comment: "Text indicating the message was remotely deleted by an admin. Embeds {{admin display name}}",
|
||||
)
|
||||
|
||||
let attributedString = NSAttributedString.make(
|
||||
fromFormat: format,
|
||||
attributedFormatArgs: [
|
||||
.string(
|
||||
deleteAuthor.displayName,
|
||||
attributes: [.font: textMessageFont.bold(), .foregroundColor: deleteAuthor.groupColor],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
private func rangeOfFirstSubstring(
|
||||
in attributedString: NSAttributedString,
|
||||
withColor color: UIColor,
|
||||
) -> NSRange? {
|
||||
var matchingRange: NSRange?
|
||||
attributedString.enumerateAttribute(
|
||||
.foregroundColor,
|
||||
in: NSRange(location: 0, length: attributedString.length),
|
||||
options: [],
|
||||
) { value, range, stop in
|
||||
if
|
||||
let currentColor = value as? UIColor,
|
||||
currentColor == color
|
||||
{
|
||||
matchingRange = range
|
||||
stop.pointee = true
|
||||
}
|
||||
}
|
||||
return matchingRange
|
||||
}
|
||||
|
||||
private var textViewConfigForRemotelyDeleted: CVTextViewConfig {
|
||||
let text = NSMutableAttributedString()
|
||||
text.append(
|
||||
SignalSymbol.xCircle.attributedString(
|
||||
dynamicTypeBaseSize: textMessageFont.pointSize,
|
||||
) + " ",
|
||||
)
|
||||
|
||||
var linkItems: [CVTextLabel.Item] = []
|
||||
switch bodyText {
|
||||
case .remotelyDeleted(let deleteAuthor):
|
||||
if let deleteAuthor {
|
||||
// TODO: make attributed string with icon and tappable display name.
|
||||
let format = OWSLocalizedString(
|
||||
"DELETED_BY_ADMIN",
|
||||
comment: "Text indicating the message was remotely deleted by an admin. Embeds {{admin display name}}",
|
||||
)
|
||||
text = String(format: format, deleteAuthor)
|
||||
let attributedString = buildAdminDeleteAttributedString(deleteAuthor: deleteAuthor)
|
||||
text.append(attributedString)
|
||||
|
||||
if
|
||||
let tapItemRange = rangeOfFirstSubstring(
|
||||
in: text,
|
||||
withColor: deleteAuthor.groupColor,
|
||||
)
|
||||
{
|
||||
linkItems.append(.deleteAuthor(deleteAuthorItem: CVTextLabel.DeleteAuthorItem(
|
||||
deleteAuthorAci: deleteAuthor.aci,
|
||||
range: tapItemRange,
|
||||
)))
|
||||
} else {
|
||||
owsFailDebug("Admin delete is missing tappable range")
|
||||
}
|
||||
} else {
|
||||
fallthrough
|
||||
}
|
||||
default:
|
||||
text = (
|
||||
let remoteDeleteNoAuthorMessage = (
|
||||
isIncoming
|
||||
? OWSLocalizedString("THIS_MESSAGE_WAS_DELETED", comment: "text indicating the message was remotely deleted")
|
||||
: OWSLocalizedString("YOU_DELETED_THIS_MESSAGE", comment: "text indicating the message was remotely deleted by you"),
|
||||
? NSAttributedString(string: OWSLocalizedString("THIS_MESSAGE_WAS_DELETED", comment: "text indicating the message was remotely deleted"))
|
||||
: NSAttributedString(string: OWSLocalizedString("YOU_DELETED_THIS_MESSAGE", comment: "text indicating the message was remotely deleted by you")),
|
||||
)
|
||||
text.append(remoteDeleteNoAuthorMessage)
|
||||
}
|
||||
return CVLabelConfig(
|
||||
text: .text(text),
|
||||
displayConfig: .forUnstyledText(font: textMessageFont.italic(), textColor: bodyTextColor),
|
||||
font: textMessageFont.italic(),
|
||||
textColor: bodyTextColor,
|
||||
numberOfLines: 0,
|
||||
lineBreakMode: .byWordWrapping,
|
||||
textAlignment: .center,
|
||||
|
||||
let displayConfiguration = HydratedMessageBody.DisplayConfiguration.messageBubble(
|
||||
isIncoming: isIncoming,
|
||||
revealedSpoilerIds: revealedSpoilerIds,
|
||||
searchRanges: .matchedRanges([]),
|
||||
)
|
||||
|
||||
return CVTextViewConfig(
|
||||
text: .attributedText(text),
|
||||
font: textMessageFont,
|
||||
textColor: UIColor.Signal.secondaryLabel,
|
||||
textAlignment: .natural,
|
||||
displayConfiguration: displayConfiguration,
|
||||
linkifyStyle: linkifyStyle,
|
||||
linkItems: linkItems,
|
||||
matchedSearchRanges: [],
|
||||
)
|
||||
}
|
||||
|
||||
@ -792,7 +858,7 @@ extension CVComponentBodyText: CVAccessibilityComponent {
|
||||
case .oversizeTextDownloading:
|
||||
return labelConfigForOversizeTextDownloading.text.accessibilityDescription
|
||||
case .remotelyDeleted:
|
||||
return labelConfigForRemotelyDeleted.text.accessibilityDescription
|
||||
return textViewConfigForRemotelyDeleted.text.accessibilityDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
import MobileCoin
|
||||
public import SignalServiceKit
|
||||
import LibSignalClient
|
||||
import SignalUI
|
||||
|
||||
public enum CVAttachment: Equatable {
|
||||
@ -121,6 +122,12 @@ public struct CVComponentState: Equatable {
|
||||
let avatarDataSource: ConversationAvatarDataSource
|
||||
}
|
||||
|
||||
struct DeleteAuthor: Equatable {
|
||||
let displayName: String
|
||||
let aci: Aci
|
||||
let groupColor: UIColor
|
||||
}
|
||||
|
||||
let senderAvatar: SenderAvatar?
|
||||
|
||||
enum BodyText: Equatable {
|
||||
@ -133,7 +140,7 @@ public struct CVComponentState: Equatable {
|
||||
|
||||
// We use the "body text" component to
|
||||
// render the "remotely deleted" indicator.
|
||||
case remotelyDeleted(deleteAuthorName: String?)
|
||||
case remotelyDeleted(deleteAuthor: DeleteAuthor?)
|
||||
|
||||
var displayableText: DisplayableText? {
|
||||
switch self {
|
||||
@ -1188,7 +1195,7 @@ private extension CVComponentState.Builder {
|
||||
}
|
||||
|
||||
/// If the message was deleted remotely by an admin, display the admin's name.
|
||||
private func displayNameForDeleteMessage(message: TSMessage) -> String? {
|
||||
private func displayNameAndColorForDeleteMessage(message: TSMessage) -> CVComponentState.DeleteAuthor? {
|
||||
let adminDeleteManager = DependenciesBridge.shared.adminDeleteManager
|
||||
|
||||
guard
|
||||
@ -1208,10 +1215,16 @@ private extension CVComponentState.Builder {
|
||||
return nil
|
||||
} else {
|
||||
// Only display admin name if non self-delete.
|
||||
return SSKEnvironment.shared.contactManagerRef.displayName(
|
||||
let displayName = SSKEnvironment.shared.contactManagerRef.displayName(
|
||||
for: SignalServiceAddress(authorAci),
|
||||
tx: transaction,
|
||||
).resolvedValue()
|
||||
let groupNameColor = GroupNameColors.forThread(thread).color(for: authorAci)
|
||||
return CVComponentState.DeleteAuthor(
|
||||
displayName: displayName,
|
||||
aci: authorAci,
|
||||
groupColor: groupNameColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1223,8 +1236,8 @@ private extension CVComponentState.Builder {
|
||||
if message.wasRemotelyDeleted {
|
||||
// If the message has been remotely deleted, suppress everything else.
|
||||
|
||||
let deleteAuthor = displayNameForDeleteMessage(message: message)
|
||||
self.bodyText = .remotelyDeleted(deleteAuthorName: deleteAuthor)
|
||||
let remoteDeleteAuthor = displayNameAndColorForDeleteMessage(message: message)
|
||||
self.bodyText = .remotelyDeleted(deleteAuthor: remoteDeleteAuthor)
|
||||
return build()
|
||||
}
|
||||
|
||||
|
||||
@ -67,6 +67,8 @@ extension ConversationViewController {
|
||||
didTapOrLongPressUnrevealedSpoiler(unrevealedSpoilerItem)
|
||||
case .referencedUser(let referencedUserItem):
|
||||
owsFailDebug("Should never have a referenced user item in body text, but tapped \(referencedUserItem)")
|
||||
case .deleteAuthor(let deleteAuthor):
|
||||
didTapOrLongPressDeleteAuthor(aci: deleteAuthor.deleteAuthorAci)
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +106,8 @@ extension ConversationViewController {
|
||||
didTapOrLongPressUnrevealedSpoiler(unrevealedSpoilerItem)
|
||||
case .referencedUser(let referencedUserItem):
|
||||
owsFailDebug("Should never have a referenced user item in body text, but long pressed \(referencedUserItem)")
|
||||
case .deleteAuthor(let deleteAuthor):
|
||||
didTapOrLongPressDeleteAuthor(aci: deleteAuthor.deleteAuthorAci)
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,4 +461,14 @@ extension ConversationViewController {
|
||||
deletedInteractionIds: [],
|
||||
)
|
||||
}
|
||||
|
||||
// Taps and long presses do the same thing.
|
||||
private func didTapOrLongPressDeleteAuthor(aci: Aci) {
|
||||
ProfileSheetSheetCoordinator(
|
||||
address: SignalServiceAddress(aci),
|
||||
groupViewHelper: nil,
|
||||
spoilerState: SpoilerRenderState(),
|
||||
)
|
||||
.presentAppropriateSheet(from: self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,6 +365,8 @@ class LongTextViewController: OWSViewController {
|
||||
)
|
||||
self.loadContent()
|
||||
return
|
||||
case .deleteAuthor:
|
||||
owsFailDebug("delete author should not appear in long message body")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +103,7 @@ public enum SignalSymbol: Character {
|
||||
case videoFill = "\u{E077}"
|
||||
case viewOnce = "\u{E078}"
|
||||
case viewOnceSlash = "\u{E079}"
|
||||
case xCircle = "\u{E1EE}"
|
||||
|
||||
// MARK: Localized symbols
|
||||
|
||||
|
||||
@ -55,11 +55,24 @@ public class CVTextLabel: NSObject {
|
||||
|
||||
// MARK: -
|
||||
|
||||
public struct DeleteAuthorItem: Equatable {
|
||||
public let deleteAuthorAci: Aci
|
||||
public let range: NSRange
|
||||
|
||||
public init(deleteAuthorAci: Aci, range: NSRange) {
|
||||
self.deleteAuthorAci = deleteAuthorAci
|
||||
self.range = range
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
public enum Item: Equatable, CustomStringConvertible {
|
||||
case dataItem(dataItem: TextCheckingDataItem)
|
||||
case mention(mentionItem: MentionItem)
|
||||
case referencedUser(referencedUserItem: ReferencedUserItem)
|
||||
case unrevealedSpoiler(UnrevealedSpoilerItem)
|
||||
case deleteAuthor(deleteAuthorItem: DeleteAuthorItem)
|
||||
|
||||
public var range: NSRange {
|
||||
switch self {
|
||||
@ -71,6 +84,8 @@ public class CVTextLabel: NSObject {
|
||||
return referencedUserItem.range
|
||||
case .unrevealedSpoiler(let item):
|
||||
return item.range
|
||||
case .deleteAuthor(let deleteAuthorItem):
|
||||
return deleteAuthorItem.range
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,6 +99,8 @@ public class CVTextLabel: NSObject {
|
||||
return ".referencedUser"
|
||||
case .unrevealedSpoiler:
|
||||
return ".unrevealedSpoiler"
|
||||
case .deleteAuthor:
|
||||
return ".deleteAuthor"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,7 +280,7 @@ public class CVTextLabel: NSObject {
|
||||
let range = item.range
|
||||
|
||||
switch item {
|
||||
case .mention, .referencedUser, .unrevealedSpoiler:
|
||||
case .mention, .referencedUser, .unrevealedSpoiler, .deleteAuthor:
|
||||
// Do nothing; these are already styled.
|
||||
continue
|
||||
case .dataItem(let dataItem):
|
||||
@ -468,6 +485,9 @@ public class CVTextLabel: NSObject {
|
||||
case .unrevealedSpoiler:
|
||||
// Don't apply anything for spoilers.
|
||||
return
|
||||
case .deleteAuthor:
|
||||
// Don't apply anything for delete author
|
||||
return
|
||||
}
|
||||
|
||||
setNeedsDisplay()
|
||||
@ -659,6 +679,9 @@ extension CVTextLabel.Label: UIDragInteractionDelegate {
|
||||
case .unrevealedSpoiler:
|
||||
// Dragging is not applicable for spoilers.
|
||||
return []
|
||||
case .deleteAuthor:
|
||||
// Dragging is not applicable for admin delete author.
|
||||
return []
|
||||
case .dataItem(let dataItem):
|
||||
animate(selectedItem: selectedItem)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user