basic support for release notes chat
This commit is contained in:
parent
c75e957b68
commit
218b7ffdbc
@ -64,6 +64,7 @@
|
||||
045B40922ECE406A002D3F9A /* ConversationViewController+PinnedMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045B40912ECE4060002D3F9A /* ConversationViewController+PinnedMessages.swift */; };
|
||||
045B40952ECF98C1002D3F9A /* PinnedMessagesDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045B40942ECF98BB002D3F9A /* PinnedMessagesDetailsViewController.swift */; };
|
||||
046926092E8EBAAE00B1FC74 /* TSInfoMessage+Polls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046926082E8EBAA800B1FC74 /* TSInfoMessage+Polls.swift */; };
|
||||
0477BE322FA4FC41002F9B47 /* TSReleaseNotesThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477BE312FA4FC38002F9B47 /* TSReleaseNotesThread.swift */; };
|
||||
047A6DD02E00B5720048EDF4 /* BackupKeyReminderMegaphoneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 047A6DCF2E00B5640048EDF4 /* BackupKeyReminderMegaphoneTests.swift */; };
|
||||
0480F0002E57C51A006CBB29 /* BackupsEnabledNotificationMegaphone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0480EFFF2E57C513006CBB29 /* BackupsEnabledNotificationMegaphone.swift */; };
|
||||
0484CECE2F44B7BE009AB2CB /* AdminDeleteRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0484CECD2F44B7BB009AB2CB /* AdminDeleteRecord.swift */; };
|
||||
@ -4217,6 +4218,7 @@
|
||||
045B40912ECE4060002D3F9A /* ConversationViewController+PinnedMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationViewController+PinnedMessages.swift"; sourceTree = "<group>"; };
|
||||
045B40942ECF98BB002D3F9A /* PinnedMessagesDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessagesDetailsViewController.swift; sourceTree = "<group>"; };
|
||||
046926082E8EBAA800B1FC74 /* TSInfoMessage+Polls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+Polls.swift"; sourceTree = "<group>"; };
|
||||
0477BE312FA4FC38002F9B47 /* TSReleaseNotesThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSReleaseNotesThread.swift; sourceTree = "<group>"; };
|
||||
047A6DCF2E00B5640048EDF4 /* BackupKeyReminderMegaphoneTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupKeyReminderMegaphoneTests.swift; sourceTree = "<group>"; };
|
||||
0480EFFF2E57C513006CBB29 /* BackupsEnabledNotificationMegaphone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupsEnabledNotificationMegaphone.swift; sourceTree = "<group>"; };
|
||||
0484CECD2F44B7BB009AB2CB /* AdminDeleteRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminDeleteRecord.swift; sourceTree = "<group>"; };
|
||||
@ -15165,6 +15167,7 @@
|
||||
F9C5C9EF289453B100548EEE /* TSGroupThread+OWS.swift */,
|
||||
880FB40528CD205F00FA1C10 /* TSGroupThread.swift */,
|
||||
F9C5C9E5289453B100548EEE /* TSPrivateStoryThread.swift */,
|
||||
0477BE312FA4FC38002F9B47 /* TSReleaseNotesThread.swift */,
|
||||
F9C5C9E9289453B100548EEE /* TSThread+OWS.swift */,
|
||||
);
|
||||
path = Threads;
|
||||
@ -20052,6 +20055,7 @@
|
||||
F9C5CCCB289453B300548EEE /* TSPrivateStoryThread.swift in Sources */,
|
||||
F9C5CBE4289453B300548EEE /* TSQuotedMessage.m in Sources */,
|
||||
6615553F2ABA5A7500AA302B /* TSRegistrationState.swift in Sources */,
|
||||
0477BE322FA4FC41002F9B47 /* TSReleaseNotesThread.swift in Sources */,
|
||||
66C2B1312A05D28A008DDE72 /* TSRequest.swift in Sources */,
|
||||
6691E7EF2996E8FB0032A68A /* TSRequestOWSURLSessionMock.swift in Sources */,
|
||||
F9C5CCCF289453B300548EEE /* TSThread+OWS.swift in Sources */,
|
||||
|
||||
@ -494,6 +494,7 @@ public struct CVComponentState: Equatable {
|
||||
let mutualGroupsText: NSAttributedString?
|
||||
let threadType: SafetyTipsType
|
||||
let shouldShowSafetyTipsButton: Bool
|
||||
let isOfficialChat: Bool
|
||||
}
|
||||
|
||||
let avatarDataSource: ConversationAvatarDataSource?
|
||||
|
||||
@ -195,6 +195,16 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
componentDelegate.didTapNameEducation(type: safetySection.threadType)
|
||||
}
|
||||
innerViews.append(nameNotVerifiedButton)
|
||||
} else if safetySection.isOfficialChat {
|
||||
innerViews.append(UIView.spacer(withHeight: vSpacingNotVerifiedLabel))
|
||||
|
||||
let officialLabel = componentView.officialLabel
|
||||
let officialLabelConfig = officialLabelConfig()
|
||||
officialLabelConfig.applyForRendering(label: officialLabel)
|
||||
officialLabel.backgroundColor = UIColor.Signal.officialLabelBackground
|
||||
officialLabel.layer.cornerRadius = 14
|
||||
officialLabel.layer.masksToBounds = true
|
||||
innerViews.append(officialLabel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -405,6 +415,29 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
)
|
||||
}
|
||||
|
||||
private func officialLabelConfig() -> CVLabelConfig {
|
||||
let symbol = SignalSymbol.checkCircle.attributedString(dynamicTypeBaseSize: UIFont.dynamicTypeCalloutClamped.pointSize)
|
||||
let notVerifiedString = NSAttributedString.composed(
|
||||
of: [
|
||||
symbol,
|
||||
SignalSymbol.LeadingCharacter.space.rawValue,
|
||||
OWSLocalizedString("RELEASE_NOTES_CHANNEL_OFFICIAL_LABEL", comment: "Label displayed in thread details of the release notes chat"),
|
||||
],
|
||||
)
|
||||
return CVLabelConfig(
|
||||
text: .attributedText(notVerifiedString),
|
||||
displayConfig: .forUnstyledText(
|
||||
font: .dynamicTypeCallout.medium(),
|
||||
textColor: UIColor.Signal.officialLabel,
|
||||
),
|
||||
font: .dynamicTypeCallout.medium(),
|
||||
textColor: UIColor.Signal.officialLabel,
|
||||
numberOfLines: 0,
|
||||
lineBreakMode: .byWordWrapping,
|
||||
textAlignment: .center,
|
||||
)
|
||||
}
|
||||
|
||||
private var safetyTipsButtonLabelConfig: CVLabelConfig {
|
||||
CVLabelConfig.unstyledText(
|
||||
OWSLocalizedString(
|
||||
@ -449,6 +482,11 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
transaction: transaction,
|
||||
avatarBuilder: avatarBuilder,
|
||||
)
|
||||
} else if let releaseNotesThread = thread as? TSReleaseNotesThread {
|
||||
return buildComponentState(
|
||||
releaseNotesThread: releaseNotesThread,
|
||||
transaction: transaction,
|
||||
)
|
||||
} else {
|
||||
owsFailDebug("Invalid thread.")
|
||||
return CVComponentState.ThreadDetails(
|
||||
@ -564,6 +602,30 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
)
|
||||
}
|
||||
|
||||
private static func buildComponentState(
|
||||
releaseNotesThread: TSReleaseNotesThread,
|
||||
transaction: DBReadTransaction,
|
||||
) -> CVComponentState.ThreadDetails {
|
||||
|
||||
let titleText = OWSLocalizedString(
|
||||
"RELEASE_NOTES_CHANNEL_NAME",
|
||||
comment: "Display name for the release notes channel",
|
||||
)
|
||||
|
||||
let safetySection = Self.buildReleaseNotesSafetySection(from: releaseNotesThread, tx: transaction)
|
||||
|
||||
return CVComponentState.ThreadDetails(
|
||||
avatarDataSource: .asset(avatar: AvatarBuilder.releaseNotesIcon(), badge: nil),
|
||||
isAvatarBlurred: false,
|
||||
isAvatarBeingDownloaded: false,
|
||||
titleText: titleText,
|
||||
shouldShowVerifiedBadge: true,
|
||||
shouldShowContactIcon: false,
|
||||
safetySection: safetySection,
|
||||
groupDescriptionText: nil,
|
||||
)
|
||||
}
|
||||
|
||||
private let vSpacingTitle: CGFloat = 8
|
||||
private let vSpacingNotVerifiedLabel: CGFloat = 6
|
||||
private let vSpacingSafetyButton: CGFloat = 16
|
||||
@ -622,6 +684,14 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
)
|
||||
let notVerifiedSizeWithPadding = CGSize(width: notVerifiedSize.width + hPaddingNotVerifiedButton * 2, height: notVerifiedSize.height + vPaddingNotVerifiedButton * 2)
|
||||
innerSubviewInfos.append(notVerifiedSizeWithPadding.asManualSubviewInfo)
|
||||
} else if safetySection.isOfficialChat {
|
||||
innerSubviewInfos.append(CGSize(square: vSpacingNotVerifiedLabel).asManualSubviewInfo)
|
||||
let officialLabelSize = CVText.measureLabel(
|
||||
config: officialLabelConfig(),
|
||||
maxWidth: maxContentWidth,
|
||||
)
|
||||
let officialLabelSizeWithPadding = CGSize(width: officialLabelSize.width + hPaddingNotVerifiedButton * 2, height: officialLabelSize.height + vPaddingNotVerifiedButton * 2)
|
||||
innerSubviewInfos.append(officialLabelSizeWithPadding.asManualSubviewInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,6 +829,7 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
fileprivate let bioLabel = CVLabel()
|
||||
|
||||
fileprivate let profileNamesEducationButton = OWSRoundedButton()
|
||||
fileprivate let officialLabel = CVLabel()
|
||||
|
||||
fileprivate let reviewCarefullyLabel = CVLabel()
|
||||
fileprivate let detailsButton = CVButton()
|
||||
@ -817,6 +888,20 @@ public class CVComponentThreadDetails: CVComponentBase, CVRootComponent {
|
||||
}
|
||||
|
||||
extension CVComponentThreadDetails {
|
||||
private static func buildReleaseNotesSafetySection(
|
||||
from releaseNotesThread: TSReleaseNotesThread,
|
||||
tx: DBReadTransaction,
|
||||
) -> CVComponentState.ThreadDetails.SafetySection {
|
||||
return .init(
|
||||
shouldShowProfileNamesEducation: false,
|
||||
detailsText: NSAttributedString(string: OWSLocalizedString("RELEASE_NOTES_DETAILS", comment: "Details text for the thread details view of the release notes channel")),
|
||||
mutualGroupsText: nil,
|
||||
threadType: .contact,
|
||||
shouldShowSafetyTipsButton: false,
|
||||
isOfficialChat: true,
|
||||
)
|
||||
}
|
||||
|
||||
private static func buildGroupsSafetySection(
|
||||
from groupThread: TSGroupThread,
|
||||
threadAssociatedData: ThreadAssociatedData,
|
||||
@ -945,6 +1030,7 @@ extension CVComponentThreadDetails {
|
||||
mutualGroupsText: nil,
|
||||
threadType: .group,
|
||||
shouldShowSafetyTipsButton: shouldShowUnknownThreadWarning && groupThread.hasPendingMessageRequest(transaction: tx),
|
||||
isOfficialChat: false,
|
||||
)
|
||||
}
|
||||
|
||||
@ -976,6 +1062,7 @@ extension CVComponentThreadDetails {
|
||||
),
|
||||
threadType: .contact,
|
||||
shouldShowSafetyTipsButton: false,
|
||||
isOfficialChat: false,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1079,6 +1166,7 @@ extension CVComponentThreadDetails {
|
||||
]),
|
||||
threadType: .contact,
|
||||
shouldShowSafetyTipsButton: isMessageRequest,
|
||||
isOfficialChat: false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +151,12 @@ class ConversationHeaderView: UIView {
|
||||
|
||||
func configure(threadViewModel: ThreadViewModel) {
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.dataSource = .thread(threadViewModel.threadRecord)
|
||||
if threadViewModel.threadRecord.isReleaseNotesThread {
|
||||
config.dataSource = .asset(avatar: AvatarBuilder.releaseNotesIcon(), badge: nil)
|
||||
} else {
|
||||
config.dataSource = .thread(threadViewModel.threadRecord)
|
||||
}
|
||||
|
||||
config.storyConfiguration = .autoUpdate()
|
||||
config.applyConfigurationSynchronously()
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ enum CVCBottomViewType: Equatable {
|
||||
case notRegistered
|
||||
case notLinked
|
||||
case groupEnded
|
||||
case releaseNotes
|
||||
}
|
||||
|
||||
protocol ConversationBottomBar: UIView {
|
||||
@ -78,6 +79,9 @@ public extension ConversationViewController {
|
||||
case .normal:
|
||||
break
|
||||
}
|
||||
if thread.isReleaseNotesThread {
|
||||
return .releaseNotes
|
||||
}
|
||||
if appExpiry.isExpired(now: Date()) {
|
||||
return .appExpired
|
||||
}
|
||||
@ -207,6 +211,13 @@ public extension ConversationViewController {
|
||||
)
|
||||
requestView = groupEndedView
|
||||
bottomView = groupEndedView
|
||||
case .releaseNotes:
|
||||
let releaseNotesView = BlockingErrorBottomPanelView(
|
||||
text: NSAttributedString(string: OWSLocalizedString("RELEASE_NOTES_BOTTOM_BAR_LABEL", comment: "Bottom bar label for the release notes thread")),
|
||||
onTap: {},
|
||||
)
|
||||
requestView = releaseNotesView
|
||||
bottomView = releaseNotesView
|
||||
}
|
||||
|
||||
bottomBarContainer.removeAllSubviews()
|
||||
|
||||
@ -54,10 +54,10 @@ class ConversationViewModel {
|
||||
return false
|
||||
}
|
||||
return !identityManager.groupContainsUnverifiedMember(groupThread.uniqueId, tx: tx)
|
||||
|
||||
case let contactThread as TSContactThread:
|
||||
return identityManager.verificationState(for: contactThread.contactAddress, tx: tx) == .verified
|
||||
|
||||
case is TSReleaseNotesThread:
|
||||
return false
|
||||
default:
|
||||
owsFailDebug("Showing conversation for unexpected thread type.")
|
||||
return false
|
||||
|
||||
12
Signal/Images.xcassets/signal-logo-release-notes.imageset/Contents.json
vendored
Normal file
12
Signal/Images.xcassets/signal-logo-release-notes.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "signal-logo-release-notes.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Signal/Images.xcassets/signal-logo-release-notes.imageset/signal-logo-release-notes.pdf
vendored
Normal file
BIN
Signal/Images.xcassets/signal-logo-release-notes.imageset/signal-logo-release-notes.pdf
vendored
Normal file
Binary file not shown.
@ -39,6 +39,11 @@ class DebugUIMisc: DebugUIPage {
|
||||
let viewController = LineWrappingStackViewTestController()
|
||||
UIApplication.shared.frontmostViewController!.present(viewController, animated: true)
|
||||
}),
|
||||
OWSTableItem(title: "Create Release Notes Thread", actionBlock: {
|
||||
let _ = SSKEnvironment.shared.databaseStorageRef.write { tx in
|
||||
TSReleaseNotesThread.createReleaseNotes(transaction: tx)
|
||||
}
|
||||
}),
|
||||
]
|
||||
return OWSTableSection(title: name, items: items)
|
||||
}
|
||||
|
||||
@ -360,7 +360,11 @@ class ChatListCell: UITableViewCell, ReusableTableViewCell {
|
||||
owsAssertDebug(avatarView == nil, "ChatListCell.configure without prior reset called")
|
||||
avatarView = ConversationAvatarView(sizeClass: .fiftySix, localUserDisplayMode: .noteToSelf, useAutolayout: true)
|
||||
avatarView?.updateWithSneakyTransactionIfNecessary({ config in
|
||||
config.dataSource = .thread(configuration.thread)
|
||||
if configuration.thread.isReleaseNotesThread {
|
||||
config.dataSource = .asset(avatar: AvatarBuilder.releaseNotesIcon(), badge: nil)
|
||||
} else {
|
||||
config.dataSource = .thread(configuration.thread)
|
||||
}
|
||||
if asyncAvatarLoadingAllowed, cellContentToken.shouldLoadAvatarAsync {
|
||||
config.usePlaceholderImages()
|
||||
} else {
|
||||
|
||||
@ -7738,6 +7738,18 @@
|
||||
/* Error message when sending a verification code via voice call failed, but resending via sms might succeed. */
|
||||
"REGISTRATION_VOICE_CODE_FAILED_TRY_SMS_ERROR" = "We couldn't send you a verification code via voice call. Try receiving your code via sms instead.";
|
||||
|
||||
/* Bottom bar label for the release notes thread */
|
||||
"RELEASE_NOTES_BOTTOM_BAR_LABEL" = "The only official chat from Signal";
|
||||
|
||||
/* Display name for the release notes channel */
|
||||
"RELEASE_NOTES_CHANNEL_NAME" = "Signal";
|
||||
|
||||
/* Label displayed in thread details of the release notes chat */
|
||||
"RELEASE_NOTES_CHANNEL_OFFICIAL_LABEL" = "Official Chat";
|
||||
|
||||
/* Details text for the thread details view of the release notes channel */
|
||||
"RELEASE_NOTES_DETAILS" = "The only official chat from Signal. Keep up to date with news and release notes";
|
||||
|
||||
/* Button below the warning to fix a corrupted username. */
|
||||
"REMINDER_VIEW_USERNAME_CORRUPTED_FIX_BUTTON" = "Fix now";
|
||||
|
||||
|
||||
@ -593,6 +593,53 @@ public class AvatarBuilder {
|
||||
return formattedAbbreviation
|
||||
}
|
||||
|
||||
public static func releaseNotesIcon() -> UIImage? {
|
||||
let iconSize = CGSize(square: 74.0)
|
||||
let embeddedImageSize = CGSize(square: 46.0)
|
||||
|
||||
let image = UIImage(named: "signal-logo-release-notes")!.withTintColor(.white)
|
||||
|
||||
let renderer = UIGraphicsImageRenderer(size: iconSize)
|
||||
let finalImage = renderer.image { context in
|
||||
let rect = CGRect(origin: .zero, size: iconSize)
|
||||
let circlePath = UIBezierPath(ovalIn: rect)
|
||||
|
||||
context.cgContext.addPath(circlePath.cgPath)
|
||||
context.cgContext.clip()
|
||||
|
||||
let colors = [
|
||||
UIColor(red: 0.23, green: 0.27, blue: 0.99, alpha: 1).cgColor,
|
||||
UIColor(red: 0.12, green: 0.16, blue: 0.99, alpha: 1).cgColor,
|
||||
|
||||
] as CFArray
|
||||
|
||||
let locations: [CGFloat] = [0.0, 1.0]
|
||||
|
||||
let gradient = CGGradient(
|
||||
colorsSpace: CGColorSpaceCreateDeviceRGB(),
|
||||
colors: colors,
|
||||
locations: locations,
|
||||
)!
|
||||
|
||||
context.cgContext.drawLinearGradient(
|
||||
gradient,
|
||||
start: CGPoint(x: rect.midX, y: rect.minY),
|
||||
end: CGPoint(x: rect.midX, y: rect.maxY),
|
||||
options: [],
|
||||
)
|
||||
|
||||
let centerOffset = iconSize.width / 2 - embeddedImageSize.width / 2
|
||||
let imageRect = CGRect(
|
||||
x: centerOffset,
|
||||
y: centerOffset,
|
||||
width: embeddedImageSize.width,
|
||||
height: embeddedImageSize.height,
|
||||
)
|
||||
image.withRenderingMode(.alwaysTemplate).draw(in: imageRect)
|
||||
}
|
||||
return finalImage
|
||||
}
|
||||
|
||||
// MARK: - Content
|
||||
|
||||
private enum AvatarContentType: Equatable {
|
||||
|
||||
@ -83,6 +83,9 @@ public class BackupArchiveChatArchiver: BackupArchiveProtoStreamWriter {
|
||||
context.gv1ThreadIds.insert(thread.uniqueThreadIdentifier)
|
||||
// Skip gv1 threads; count as success.
|
||||
result = .success
|
||||
} else if thread.isReleaseNotesThread {
|
||||
// TODO: [KC] implement release notes in backups
|
||||
result = .success
|
||||
} else {
|
||||
result = .completeFailure(.fatalArchiveError(.unrecognizedThreadType))
|
||||
}
|
||||
|
||||
@ -1385,6 +1385,8 @@ extension ContactManager {
|
||||
return .contactThread(displayName(for: thread.contactAddress, tx: tx))
|
||||
case let thread as TSGroupThread:
|
||||
return .groupThread(thread.groupNameOrDefault)
|
||||
case _ as TSReleaseNotesThread:
|
||||
return .releaseNotes
|
||||
default:
|
||||
owsFailDebug("Unexpected thread type: \(type(of: thread))")
|
||||
return nil
|
||||
@ -1421,6 +1423,7 @@ public enum ThreadDisplayName {
|
||||
case noteToSelf
|
||||
case contactThread(DisplayName)
|
||||
case groupThread(String)
|
||||
case releaseNotes
|
||||
|
||||
public func resolvedValue() -> String {
|
||||
switch self {
|
||||
@ -1430,6 +1433,8 @@ public enum ThreadDisplayName {
|
||||
return displayName.resolvedValue()
|
||||
case .groupThread(let groupName):
|
||||
return groupName
|
||||
case .releaseNotes:
|
||||
return OWSLocalizedString("RELEASE_NOTES_CHANNEL_NAME", comment: "Display name for the release notes channel")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ open class TSThread: NSObject, SDSCodableModel, InheritableRecord {
|
||||
case SDSRecordType.contactThread.rawValue: TSContactThread.self
|
||||
case SDSRecordType.groupThread.rawValue: TSGroupThread.self
|
||||
case SDSRecordType.privateStoryThread.rawValue: TSPrivateStoryThread.self
|
||||
case SDSRecordType.releaseNotesThread.rawValue: TSReleaseNotesThread.self
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,6 +315,8 @@ public class BlockingManager {
|
||||
return _isGroupIdBlocked(groupThread.groupModel.groupId, tx: transaction)
|
||||
} else if thread is TSPrivateStoryThread {
|
||||
return false
|
||||
} else if thread.isReleaseNotesThread {
|
||||
return false
|
||||
} else {
|
||||
owsFailDebug("Invalid thread: \(type(of: thread))")
|
||||
return false
|
||||
|
||||
@ -84,4 +84,5 @@ public enum SDSRecordType: UInt, CaseIterable {
|
||||
case paymentActivationRequestFinishedMessage = 77
|
||||
case incomingArchivedPaymentMessage = 78
|
||||
case outgoingArchivedPaymentMessage = 79
|
||||
case releaseNotesThread = 80
|
||||
}
|
||||
|
||||
49
SignalServiceKit/Threads/TSReleaseNotesThread.swift
Normal file
49
SignalServiceKit/Threads/TSReleaseNotesThread.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents the Release Notes thread.
|
||||
public final class TSReleaseNotesThread: TSThread {
|
||||
override public class var recordType: SDSRecordType { .releaseNotesThread }
|
||||
|
||||
@objc
|
||||
public class var releaseNotesUniqueId: String {
|
||||
"00000000-0000-5000-8000-00000000000A"
|
||||
}
|
||||
|
||||
public class func createReleaseNotes(transaction: DBWriteTransaction) -> TSReleaseNotesThread {
|
||||
let releaseNotes = TSReleaseNotesThread(uniqueId: releaseNotesUniqueId)
|
||||
releaseNotes.shouldThreadBeVisible = true
|
||||
releaseNotes.anyInsert(transaction: transaction)
|
||||
return releaseNotes
|
||||
}
|
||||
|
||||
override func deepCopy() -> TSThread {
|
||||
return TSReleaseNotesThread(
|
||||
id: self.id,
|
||||
uniqueId: self.uniqueId,
|
||||
creationDate: self.creationDate,
|
||||
editTargetTimestamp: self.editTargetTimestamp,
|
||||
isArchivedObsolete: self.isArchivedObsolete,
|
||||
isMarkedUnreadObsolete: self.isMarkedUnreadObsolete,
|
||||
lastDraftInteractionRowId: self.lastDraftInteractionRowId,
|
||||
lastDraftUpdateTimestamp: self.lastDraftUpdateTimestamp,
|
||||
lastInteractionRowId: self.lastInteractionRowId,
|
||||
lastSentStoryTimestamp: self.lastSentStoryTimestamp,
|
||||
mentionNotificationMode: self.mentionNotificationMode,
|
||||
messageDraft: self.messageDraft,
|
||||
messageDraftBodyRanges: self.messageDraftBodyRanges,
|
||||
mutedUntilTimestampObsolete: self.mutedUntilTimestampObsolete,
|
||||
shouldThreadBeVisible: self.shouldThreadBeVisible,
|
||||
storyViewMode: self.storyViewMode,
|
||||
)
|
||||
}
|
||||
|
||||
@objc
|
||||
override public func recipientAddresses(with tx: DBReadTransaction) -> [SignalServiceAddress] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,10 @@ public extension TSThread {
|
||||
return groupThread.groupModel.groupsVersion == .V2
|
||||
}
|
||||
|
||||
var isReleaseNotesThread: Bool {
|
||||
self is TSReleaseNotesThread
|
||||
}
|
||||
|
||||
var groupModelIfGroupThread: TSGroupModel? {
|
||||
guard let groupThread = self as? TSGroupThread else {
|
||||
return nil
|
||||
|
||||
@ -178,6 +178,20 @@ extension UIColor.Signal {
|
||||
)
|
||||
}
|
||||
|
||||
public static var officialLabel: UIColor {
|
||||
UIColor(
|
||||
light: UIColor(rgbHex: 0x2934FD),
|
||||
dark: UIColor(rgbHex: 0xC5C7F5),
|
||||
)
|
||||
}
|
||||
|
||||
public static var officialLabelBackground: UIColor {
|
||||
UIColor(
|
||||
light: UIColor(rgbHex: 0x2934FD).withAlphaComponent(0.12),
|
||||
dark: UIColor(rgbHex: 0x424585),
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Background
|
||||
|
||||
public static var background: UIColor {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user