Fixed a few more QA issues, added a minor feature

Updated the document download UI
Minor font tweaks to match settings more closely
Added profile data to the MessageRequestResponse
Fixed the broken tests
This commit is contained in:
Morgan Pretty 2022-10-03 18:23:34 +11:00
parent 93e12a3fcb
commit 0c09f2bfc5
24 changed files with 1081 additions and 961 deletions

View File

@ -671,7 +671,6 @@
FD71160028C8253500B47552 /* UIView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7115FF28C8253500B47552 /* UIView+Combine.swift */; };
FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71160128C8255900B47552 /* UIControl+Combine.swift */; };
FD71160428C95B5600B47552 /* PhotoCollectionPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71160328C95B5600B47552 /* PhotoCollectionPickerViewModel.swift */; };
FD71160C28D00BAE00B47552 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71160B28D00BAE00B47552 /* SessionTests.swift */; };
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161428D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift */; };
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161628D00DA400B47552 /* ThreadSettingsViewModelSpec.swift */; };
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161928D00E1100B47552 /* NotificationContentViewModelSpec.swift */; };
@ -1756,7 +1755,6 @@
FD71160128C8255900B47552 /* UIControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Combine.swift"; sourceTree = "<group>"; };
FD71160328C95B5600B47552 /* PhotoCollectionPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCollectionPickerViewModel.swift; sourceTree = "<group>"; };
FD71160928D00BAE00B47552 /* SessionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
FD71160B28D00BAE00B47552 /* SessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTests.swift; sourceTree = "<group>"; };
FD71161428D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDisappearingMessagesViewModelSpec.swift; sourceTree = "<group>"; };
FD71161628D00DA400B47552 /* ThreadSettingsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModelSpec.swift; sourceTree = "<group>"; };
FD71161928D00E1100B47552 /* NotificationContentViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModelSpec.swift; sourceTree = "<group>"; };
@ -3788,7 +3786,6 @@
children = (
FD71161228D00D5300B47552 /* Conversations */,
FD71161828D00E0100B47552 /* Settings */,
FD71160B28D00BAE00B47552 /* SessionTests.swift */,
);
path = SessionTests;
sourceTree = "<group>";
@ -5758,7 +5755,6 @@
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */,
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
FD71160C28D00BAE00B47552 /* SessionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -5,8 +5,6 @@ import SessionUIKit
import SessionMessagingKit
final class DocumentView: UIView {
private static let iconImageViewSize: CGSize = CGSize(width: 31, height: 40)
// MARK: - Lifecycle
init(attachment: Attachment, textColor: ThemeValue) {
@ -24,18 +22,23 @@ final class DocumentView: UIView {
}
private func setUpViewHierarchy(attachment: Attachment, textColor: ThemeValue) {
// Image view
let imageView = UIImageView(image: UIImage(named: "File")?.withRenderingMode(.alwaysTemplate))
imageView.themeTintColor = textColor
imageView.contentMode = .center
let imageBackgroundView: UIView = UIView()
imageBackgroundView.themeBackgroundColor = .messageBubble_overlay
addSubview(imageBackgroundView)
let iconImageViewSize = DocumentView.iconImageViewSize
imageView.set(.width, to: iconImageViewSize.width)
imageView.set(.height, to: iconImageViewSize.height)
// Image view
let imageView = UIImageView(
image: UIImage(systemName: "doc")?
.withRenderingMode(.alwaysTemplate)
)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.themeTintColor = textColor
imageView.set(.height, to: 22)
// Body label
let titleLabel = UILabel()
titleLabel.font = .systemFont(ofSize: Values.smallFontSize, weight: .light)
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
titleLabel.text = (attachment.sourceFilename ?? "File")
titleLabel.themeTextColor = textColor
titleLabel.lineBreakMode = .byTruncatingTail
@ -51,12 +54,41 @@ final class DocumentView: UIView {
let labelStackView = UIStackView(arrangedSubviews: [ titleLabel, sizeLabel ])
labelStackView.axis = .vertical
// Download image view
let downloadImageView = UIImageView(
image: UIImage(systemName: "arrow.down")?
.withRenderingMode(.alwaysTemplate)
)
downloadImageView.setContentCompressionResistancePriority(.required, for: .horizontal)
downloadImageView.setContentHuggingPriority(.required, for: .horizontal)
downloadImageView.themeTintColor = textColor
downloadImageView.set(.height, to: 16)
// Stack view
let stackView = UIStackView(arrangedSubviews: [ imageView, labelStackView ])
let stackView = UIStackView(
arrangedSubviews: [
imageView,
UIView.spacer(withWidth: 0),
labelStackView,
downloadImageView
]
)
stackView.axis = .horizontal
stackView.spacing = Values.verySmallSpacing
stackView.spacing = Values.mediumSpacing
stackView.alignment = .center
stackView.layoutMargins = UIEdgeInsets(
top: Values.smallSpacing,
leading: Values.mediumSpacing,
bottom: Values.smallSpacing,
trailing: Values.mediumSpacing
)
stackView.isLayoutMarginsRelativeArrangement = true
addSubview(stackView)
stackView.pin(to: self)
imageBackgroundView.pin(.top, to: .top, of: self)
imageBackgroundView.pin(.leading, to: .leading, of: self)
imageBackgroundView.pin(.trailing, to: .trailing, of: imageView, withInset: Values.mediumSpacing)
imageBackgroundView.pin(.bottom, to: .bottom, of: self)
}
}

View File

@ -589,7 +589,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
let inset: CGFloat = 12
let maxWidth = (VisibleMessageCell.getMaxWidth(for: cellViewModel) - 2 * inset)
// Stack view
let stackView = UIStackView(arrangedSubviews: [])
stackView.axis = .vertical
@ -601,6 +601,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
// Body text view
if let body: String = cellViewModel.body, !body.isEmpty { // delegate should always be set at this point
let bodyContainerView: UIView = UIView()
let bodyTappableLabel = VisibleMessageCell.getBodyTappableLabel(
for: cellViewModel,
with: maxWidth,
@ -610,11 +611,16 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
)
self.bodyTappableLabel = bodyTappableLabel
stackView.addArrangedSubview(bodyTappableLabel)
bodyContainerView.addSubview(bodyTappableLabel)
bodyTappableLabel.pin(.top, to: .top, of: bodyContainerView)
bodyTappableLabel.pin(.leading, to: .leading, of: bodyContainerView, withInset: 12)
bodyTappableLabel.pin(.trailing, to: .trailing, of: bodyContainerView, withInset: -12)
bodyTappableLabel.pin(.bottom, to: .bottom, of: bodyContainerView, withInset: -12)
stackView.addArrangedSubview(bodyContainerView)
}
bubbleView.addSubview(stackView)
stackView.pin(to: bubbleView, withInset: inset)
stackView.pin(to: bubbleView)
snContentView.addArrangedSubview(bubbleBackgroundView)
}
}

View File

@ -28,6 +28,8 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
// MARK: - Variables
private let storage: Storage
private let scheduler: ValueObservationScheduler
private let threadId: String
private let config: DisappearingMessagesConfiguration
private var storedSelection: TimeInterval
@ -35,7 +37,14 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
// MARK: - Initialization
init(threadId: String, config: DisappearingMessagesConfiguration) {
init(
storage: Storage = Storage.shared,
scheduling scheduler: ValueObservationScheduler = Storage.defaultPublisherScheduler,
threadId: String,
config: DisappearingMessagesConfiguration
) {
self.storage = storage
self.scheduler = scheduler
self.threadId = threadId
self.config = config
self.storedSelection = (config.isEnabled ? config.durationSeconds : 0)
@ -92,7 +101,7 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
private lazy var _observableSettingsData: ObservableData = ValueObservation
.trackingConstantRegion { [weak self, config = self.config] db -> [SectionModel] in
.trackingConstantRegion { [weak self, config] db -> [SectionModel] in
return [
SectionModel(
model: .content,
@ -127,7 +136,7 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
]
}
.removeDuplicates()
.publisher(in: Storage.shared)
.publisher(in: storage, scheduling: scheduler)
// MARK: - Functions
@ -146,7 +155,7 @@ class ThreadDisappearingMessagesViewModel: SessionTableViewModel<ThreadDisappear
guard self.config != updatedConfig else { return }
Storage.shared.writeAsync { db in
storage.writeAsync { db in
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: threadId) else {
return
}

View File

@ -319,7 +319,6 @@
"vc_settings_notifications_button_title" = "اعلان‌ها";
"vc_settings_recovery_phrase_button_title" = "عبارت بازیابی";
"vc_settings_clear_all_data_button_title" = "پاک کردن اطلاعات";
"NOTIFICATIONS_TITLE" = "Notifiche";
"vc_qr_code_title" = "کد QR";
"vc_qr_code_view_my_qr_code_tab_title" = "مشاهده کد QR من";
"vc_qr_code_view_scan_qr_code_tab_title" = "اسکن کد QR";

View File

@ -9,11 +9,16 @@ import SessionUtilitiesKit
class NotificationContentViewModel: SessionTableViewModel<NoNav, NotificationSettingsViewModel.Section, Preferences.NotificationPreviewType> {
private let storage: Storage
private let scheduler: ValueObservationScheduler
// MARK: - Initialization
init(storage: Storage = Storage.shared) {
init(
storage: Storage = Storage.shared,
scheduling scheduler: ValueObservationScheduler = Storage.defaultPublisherScheduler
) {
self.storage = storage
self.scheduler = scheduler
}
// MARK: - Section
@ -67,7 +72,7 @@ class NotificationContentViewModel: SessionTableViewModel<NoNav, NotificationSet
]
}
.removeDuplicates()
.publisher(in: storage)
.publisher(in: storage, scheduling: scheduler)
// MARK: - Functions

View File

@ -6,7 +6,7 @@ import SessionUtilitiesKit
public enum NoNav: Equatable {}
extension SessionTableViewModel {
public struct NavItem {
public struct NavItem: Equatable {
let id: NavItemId
let image: UIImage?
let style: UIBarButtonItem.Style
@ -65,5 +65,20 @@ extension SessionTableViewModel {
accessibilityIdentifier: accessibilityIdentifier
)
}
// MARK: - Conformance
public static func == (
lhs: SessionTableViewModel<NavItemId, Section, SettingItem>.NavItem,
rhs: SessionTableViewModel<NavItemId, Section, SettingItem>.NavItem
) -> Bool {
return (
lhs.id == rhs.id &&
lhs.image == rhs.image &&
lhs.style == rhs.style &&
lhs.systemItem == rhs.systemItem &&
lhs.accessibilityIdentifier == rhs.accessibilityIdentifier
)
}
}
}

View File

@ -92,7 +92,7 @@ public class SessionCell: UITableViewCell {
private let titleLabel: UILabel = {
let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.font = .boldSystemFont(ofSize: 15)
result.themeTextColor = .textPrimary
result.numberOfLines = 0
result.setCompressionResistanceHorizontalLow()
@ -104,7 +104,7 @@ public class SessionCell: UITableViewCell {
private let subtitleLabel: UILabel = {
let result: UILabel = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.font = .systemFont(ofSize: Values.smallFontSize)
result.font = .systemFont(ofSize: 13)
result.themeTextColor = .textPrimary
result.numberOfLines = 0
result.isHidden = true

View File

@ -238,8 +238,8 @@ extension OpenGroup: CustomStringConvertible, CustomDebugStringConvertible {
"sequenceNumber: \(sequenceNumber)",
"inboxLatestMessageId: \(inboxLatestMessageId)",
"outboxLatestMessageId: \(outboxLatestMessageId)",
"pollFailureCount: \(pollFailureCount))",
"permissions: \(permissions?.toString() ?? "---")"
"pollFailureCount: \(pollFailureCount)",
"permissions: \(permissions?.toString() ?? "---"))"
].joined(separator: ", ")
}
}

View File

@ -152,7 +152,7 @@ public extension Profile {
func toProto() -> SNProtoDataMessage? {
let dataMessageProto = SNProtoDataMessage.builder()
let profileProto = SNProtoDataMessageLokiProfile.builder()
let profileProto = SNProtoLokiProfile.builder()
profileProto.setDisplayName(name)
if let profileKey: OWSAES256Key = profileEncryptionKey, let profilePictureUrl: String = profilePictureUrl {

View File

@ -7,17 +7,21 @@ import SessionUtilitiesKit
public final class MessageRequestResponse: ControlMessage {
private enum CodingKeys: String, CodingKey {
case isApproved
case profile
}
public var isApproved: Bool
public var profile: VisibleMessage.VMProfile?
// MARK: - Initialization
public init(
isApproved: Bool,
profile: VisibleMessage.VMProfile? = nil,
sentTimestampMs: UInt64? = nil
) {
self.isApproved = isApproved
self.profile = profile
super.init(
sentTimestamp: sentTimestampMs
@ -30,6 +34,7 @@ public final class MessageRequestResponse: ControlMessage {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
isApproved = try container.decode(Bool.self, forKey: .isApproved)
profile = try? container.decode(VisibleMessage.VMProfile.self, forKey: .profile)
try super.init(from: decoder)
}
@ -40,6 +45,7 @@ public final class MessageRequestResponse: ControlMessage {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(isApproved, forKey: .isApproved)
try container.encodeIfPresent(profile, forKey: .profile)
}
// MARK: - Proto Conversion
@ -47,11 +53,23 @@ public final class MessageRequestResponse: ControlMessage {
public override class func fromProto(_ proto: SNProtoContent, sender: String) -> MessageRequestResponse? {
guard let messageRequestResponseProto = proto.messageRequestResponse else { return nil }
return MessageRequestResponse(isApproved: messageRequestResponseProto.isApproved)
return MessageRequestResponse(
isApproved: messageRequestResponseProto.isApproved,
profile: VisibleMessage.VMProfile.fromProto(messageRequestResponseProto)
)
}
public override func toProto(_ db: Database) -> SNProtoContent? {
let messageRequestResponseProto = SNProtoMessageRequestResponse.builder(isApproved: isApproved)
let messageRequestResponseProto: SNProtoMessageRequestResponse.SNProtoMessageRequestResponseBuilder
// Profile
if let profile = profile, let profileProto: SNProtoMessageRequestResponse = profile.toProto(isApproved: isApproved) {
messageRequestResponseProto = profileProto.asBuilder()
}
else {
messageRequestResponseProto = SNProtoMessageRequestResponse.builder(isApproved: isApproved)
}
let contentProto = SNProtoContent.builder()
do {
@ -68,7 +86,8 @@ public final class MessageRequestResponse: ControlMessage {
public var description: String {
"""
MessageRequestResponse(
isApproved: \(isApproved)
isApproved: \(isApproved),
profile: \(profile?.description ?? "null")
)
"""
}

View File

@ -3,6 +3,8 @@
import Foundation
import SessionUtilitiesKit
// MARK: - VisibleMessage.VMProfile
public extension VisibleMessage {
struct VMProfile: Codable {
public let displayName: String?
@ -40,7 +42,7 @@ public extension VisibleMessage {
return nil
}
let dataMessageProto = SNProtoDataMessage.builder()
let profileProto = SNProtoDataMessageLokiProfile.builder()
let profileProto = SNProtoLokiProfile.builder()
profileProto.setDisplayName(displayName)
if let profileKey = profileKey, let profilePictureUrl = profilePictureUrl {
@ -56,6 +58,43 @@ public extension VisibleMessage {
}
}
public static func fromProto(_ proto: SNProtoMessageRequestResponse) -> VMProfile? {
guard
let profileProto = proto.profile,
let displayName = profileProto.displayName
else { return nil }
return VMProfile(
displayName: displayName,
profileKey: proto.profileKey,
profilePictureUrl: profileProto.profilePicture
)
}
public func toProto(isApproved: Bool) -> SNProtoMessageRequestResponse? {
guard let displayName = displayName else {
SNLog("Couldn't construct profile proto from: \(self).")
return nil
}
let messageRequestResponseProto = SNProtoMessageRequestResponse.builder(
isApproved: isApproved
)
let profileProto = SNProtoLokiProfile.builder()
profileProto.setDisplayName(displayName)
if let profileKey = profileKey, let profilePictureUrl = profilePictureUrl {
messageRequestResponseProto.setProfileKey(profileKey)
profileProto.setProfilePicture(profilePictureUrl)
}
do {
messageRequestResponseProto.setProfile(try profileProto.build())
return try messageRequestResponseProto.build()
} catch {
SNLog("Couldn't construct profile proto from: \(self).")
return nil
}
}
// MARK: Description
public var description: String {
@ -79,3 +118,12 @@ extension VisibleMessage.VMProfile {
self.profilePictureUrl = profile.profilePictureUrl
}
}
// MARK: - MessageWithProfile
public protocol MessageWithProfile {
var profile: VisibleMessage.VMProfile? { get set }
}
extension VisibleMessage: MessageWithProfile {}
extension MessageRequestResponse: MessageWithProfile {}

View File

@ -127,7 +127,7 @@ public final class VisibleMessage: Message {
let dataMessage: SNProtoDataMessage.SNProtoDataMessageBuilder
// Profile
if let profile = profile, let profileProto = profile.toProto() {
if let profile = profile, let profileProto: SNProtoDataMessage = profile.toProto() {
dataMessage = profileProto.asBuilder()
}
else {

View File

@ -463,6 +463,12 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoMessageRequestResponseBuilder {
let builder = SNProtoMessageRequestResponseBuilder(isApproved: isApproved)
if let _value = profileKey {
builder.setProfileKey(_value)
}
if let _value = profile {
builder.setProfile(_value)
}
return builder
}
@ -482,6 +488,14 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
proto.isApproved = valueParam
}
@objc public func setProfileKey(_ valueParam: Data) {
proto.profileKey = valueParam
}
@objc public func setProfile(_ valueParam: SNProtoLokiProfile) {
proto.profile = valueParam.proto
}
@objc public func build() throws -> SNProtoMessageRequestResponse {
return try SNProtoMessageRequestResponse.parseProto(proto)
}
@ -495,10 +509,24 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
@objc public let isApproved: Bool
@objc public let profile: SNProtoLokiProfile?
@objc public var profileKey: Data? {
guard proto.hasProfileKey else {
return nil
}
return proto.profileKey
}
@objc public var hasProfileKey: Bool {
return proto.hasProfileKey
}
private init(proto: SessionProtos_MessageRequestResponse,
isApproved: Bool) {
isApproved: Bool,
profile: SNProtoLokiProfile?) {
self.proto = proto
self.isApproved = isApproved
self.profile = profile
}
@objc
@ -517,12 +545,18 @@ extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
}
let isApproved = proto.isApproved
var profile: SNProtoLokiProfile? = nil
if proto.hasProfile {
profile = try SNProtoLokiProfile.parseProto(proto.profile)
}
// MARK: - Begin Validation Logic for SNProtoMessageRequestResponse -
// MARK: - End Validation Logic for SNProtoMessageRequestResponse -
let result = SNProtoMessageRequestResponse(proto: proto,
isApproved: isApproved)
isApproved: isApproved,
profile: profile)
return result
}
@ -1194,6 +1228,117 @@ extension SNProtoDataExtractionNotification.SNProtoDataExtractionNotificationBui
#endif
// MARK: - SNProtoLokiProfile
@objc public class SNProtoLokiProfile: NSObject {
// MARK: - SNProtoLokiProfileBuilder
@objc public class func builder() -> SNProtoLokiProfileBuilder {
return SNProtoLokiProfileBuilder()
}
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoLokiProfileBuilder {
let builder = SNProtoLokiProfileBuilder()
if let _value = displayName {
builder.setDisplayName(_value)
}
if let _value = profilePicture {
builder.setProfilePicture(_value)
}
return builder
}
@objc public class SNProtoLokiProfileBuilder: NSObject {
private var proto = SessionProtos_LokiProfile()
@objc fileprivate override init() {}
@objc public func setDisplayName(_ valueParam: String) {
proto.displayName = valueParam
}
@objc public func setProfilePicture(_ valueParam: String) {
proto.profilePicture = valueParam
}
@objc public func build() throws -> SNProtoLokiProfile {
return try SNProtoLokiProfile.parseProto(proto)
}
@objc public func buildSerializedData() throws -> Data {
return try SNProtoLokiProfile.parseProto(proto).serializedData()
}
}
fileprivate let proto: SessionProtos_LokiProfile
@objc public var displayName: String? {
guard proto.hasDisplayName else {
return nil
}
return proto.displayName
}
@objc public var hasDisplayName: Bool {
return proto.hasDisplayName
}
@objc public var profilePicture: String? {
guard proto.hasProfilePicture else {
return nil
}
return proto.profilePicture
}
@objc public var hasProfilePicture: Bool {
return proto.hasProfilePicture
}
private init(proto: SessionProtos_LokiProfile) {
self.proto = proto
}
@objc
public func serializedData() throws -> Data {
return try self.proto.serializedData()
}
@objc public class func parseData(_ serializedData: Data) throws -> SNProtoLokiProfile {
let proto = try SessionProtos_LokiProfile(serializedData: serializedData)
return try parseProto(proto)
}
fileprivate class func parseProto(_ proto: SessionProtos_LokiProfile) throws -> SNProtoLokiProfile {
// MARK: - Begin Validation Logic for SNProtoLokiProfile -
// MARK: - End Validation Logic for SNProtoLokiProfile -
let result = SNProtoLokiProfile(proto: proto)
return result
}
@objc public override var debugDescription: String {
return "\(proto)"
}
}
#if DEBUG
extension SNProtoLokiProfile {
@objc public func serializedDataIgnoringErrors() -> Data? {
return try! self.serializedData()
}
}
extension SNProtoLokiProfile.SNProtoLokiProfileBuilder {
@objc public func buildIgnoringErrors() -> SNProtoLokiProfile? {
return try! self.build()
}
}
#endif
// MARK: - SNProtoDataMessageQuoteQuotedAttachment
@objc public class SNProtoDataMessageQuoteQuotedAttachment: NSObject {
@ -1798,117 +1943,6 @@ extension SNProtoDataMessageReaction.SNProtoDataMessageReactionBuilder {
#endif
// MARK: - SNProtoDataMessageLokiProfile
@objc public class SNProtoDataMessageLokiProfile: NSObject {
// MARK: - SNProtoDataMessageLokiProfileBuilder
@objc public class func builder() -> SNProtoDataMessageLokiProfileBuilder {
return SNProtoDataMessageLokiProfileBuilder()
}
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoDataMessageLokiProfileBuilder {
let builder = SNProtoDataMessageLokiProfileBuilder()
if let _value = displayName {
builder.setDisplayName(_value)
}
if let _value = profilePicture {
builder.setProfilePicture(_value)
}
return builder
}
@objc public class SNProtoDataMessageLokiProfileBuilder: NSObject {
private var proto = SessionProtos_DataMessage.LokiProfile()
@objc fileprivate override init() {}
@objc public func setDisplayName(_ valueParam: String) {
proto.displayName = valueParam
}
@objc public func setProfilePicture(_ valueParam: String) {
proto.profilePicture = valueParam
}
@objc public func build() throws -> SNProtoDataMessageLokiProfile {
return try SNProtoDataMessageLokiProfile.parseProto(proto)
}
@objc public func buildSerializedData() throws -> Data {
return try SNProtoDataMessageLokiProfile.parseProto(proto).serializedData()
}
}
fileprivate let proto: SessionProtos_DataMessage.LokiProfile
@objc public var displayName: String? {
guard proto.hasDisplayName else {
return nil
}
return proto.displayName
}
@objc public var hasDisplayName: Bool {
return proto.hasDisplayName
}
@objc public var profilePicture: String? {
guard proto.hasProfilePicture else {
return nil
}
return proto.profilePicture
}
@objc public var hasProfilePicture: Bool {
return proto.hasProfilePicture
}
private init(proto: SessionProtos_DataMessage.LokiProfile) {
self.proto = proto
}
@objc
public func serializedData() throws -> Data {
return try self.proto.serializedData()
}
@objc public class func parseData(_ serializedData: Data) throws -> SNProtoDataMessageLokiProfile {
let proto = try SessionProtos_DataMessage.LokiProfile(serializedData: serializedData)
return try parseProto(proto)
}
fileprivate class func parseProto(_ proto: SessionProtos_DataMessage.LokiProfile) throws -> SNProtoDataMessageLokiProfile {
// MARK: - Begin Validation Logic for SNProtoDataMessageLokiProfile -
// MARK: - End Validation Logic for SNProtoDataMessageLokiProfile -
let result = SNProtoDataMessageLokiProfile(proto: proto)
return result
}
@objc public override var debugDescription: String {
return "\(proto)"
}
}
#if DEBUG
extension SNProtoDataMessageLokiProfile {
@objc public func serializedDataIgnoringErrors() -> Data? {
return try! self.serializedData()
}
}
extension SNProtoDataMessageLokiProfile.SNProtoDataMessageLokiProfileBuilder {
@objc public func buildIgnoringErrors() -> SNProtoDataMessageLokiProfile? {
return try! self.build()
}
}
#endif
// MARK: - SNProtoDataMessageOpenGroupInvitation
@objc public class SNProtoDataMessageOpenGroupInvitation: NSObject {
@ -2510,7 +2544,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
proto.reaction = valueParam.proto
}
@objc public func setProfile(_ valueParam: SNProtoDataMessageLokiProfile) {
@objc public func setProfile(_ valueParam: SNProtoLokiProfile) {
proto.profile = valueParam.proto
}
@ -2547,7 +2581,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
@objc public let reaction: SNProtoDataMessageReaction?
@objc public let profile: SNProtoDataMessageLokiProfile?
@objc public let profile: SNProtoLokiProfile?
@objc public let openGroupInvitation: SNProtoDataMessageOpenGroupInvitation?
@ -2610,7 +2644,7 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
quote: SNProtoDataMessageQuote?,
preview: [SNProtoDataMessagePreview],
reaction: SNProtoDataMessageReaction?,
profile: SNProtoDataMessageLokiProfile?,
profile: SNProtoLokiProfile?,
openGroupInvitation: SNProtoDataMessageOpenGroupInvitation?,
closedGroupControlMessage: SNProtoDataMessageClosedGroupControlMessage?) {
self.proto = proto
@ -2656,9 +2690,9 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
reaction = try SNProtoDataMessageReaction.parseProto(proto.reaction)
}
var profile: SNProtoDataMessageLokiProfile? = nil
var profile: SNProtoLokiProfile? = nil
if proto.hasProfile {
profile = try SNProtoDataMessageLokiProfile.parseProto(proto.profile)
profile = try SNProtoLokiProfile.parseProto(proto.profile)
}
var openGroupInvitation: SNProtoDataMessageOpenGroupInvitation? = nil

View File

@ -244,11 +244,31 @@ struct SessionProtos_MessageRequestResponse {
/// Clears the value of `isApproved`. Subsequent reads from it will return its default value.
mutating func clearIsApproved() {self._isApproved = nil}
var profileKey: Data {
get {return _profileKey ?? Data()}
set {_profileKey = newValue}
}
/// Returns true if `profileKey` has been explicitly set.
var hasProfileKey: Bool {return self._profileKey != nil}
/// Clears the value of `profileKey`. Subsequent reads from it will return its default value.
mutating func clearProfileKey() {self._profileKey = nil}
var profile: SessionProtos_LokiProfile {
get {return _profile ?? SessionProtos_LokiProfile()}
set {_profile = newValue}
}
/// Returns true if `profile` has been explicitly set.
var hasProfile: Bool {return self._profile != nil}
/// Clears the value of `profile`. Subsequent reads from it will return its default value.
mutating func clearProfile() {self._profile = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _isApproved: Bool? = nil
fileprivate var _profileKey: Data? = nil
fileprivate var _profile: SessionProtos_LokiProfile? = nil
}
struct SessionProtos_Content {
@ -521,6 +541,37 @@ extension SessionProtos_DataExtractionNotification.TypeEnum: CaseIterable {
#endif // swift(>=4.2)
struct SessionProtos_LokiProfile {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var displayName: String {
get {return _displayName ?? String()}
set {_displayName = newValue}
}
/// Returns true if `displayName` has been explicitly set.
var hasDisplayName: Bool {return self._displayName != nil}
/// Clears the value of `displayName`. Subsequent reads from it will return its default value.
mutating func clearDisplayName() {self._displayName = nil}
var profilePicture: String {
get {return _profilePicture ?? String()}
set {_profilePicture = newValue}
}
/// Returns true if `profilePicture` has been explicitly set.
var hasProfilePicture: Bool {return self._profilePicture != nil}
/// Clears the value of `profilePicture`. Subsequent reads from it will return its default value.
mutating func clearProfilePicture() {self._profilePicture = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _displayName: String? = nil
fileprivate var _profilePicture: String? = nil
}
struct SessionProtos_DataMessage {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@ -608,8 +659,8 @@ struct SessionProtos_DataMessage {
/// Clears the value of `reaction`. Subsequent reads from it will return its default value.
mutating func clearReaction() {_uniqueStorage()._reaction = nil}
var profile: SessionProtos_DataMessage.LokiProfile {
get {return _storage._profile ?? SessionProtos_DataMessage.LokiProfile()}
var profile: SessionProtos_LokiProfile {
get {return _storage._profile ?? SessionProtos_LokiProfile()}
set {_uniqueStorage()._profile = newValue}
}
/// Returns true if `profile` has been explicitly set.
@ -910,37 +961,6 @@ struct SessionProtos_DataMessage {
fileprivate var _action: SessionProtos_DataMessage.Reaction.Action? = nil
}
struct LokiProfile {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var displayName: String {
get {return _displayName ?? String()}
set {_displayName = newValue}
}
/// Returns true if `displayName` has been explicitly set.
var hasDisplayName: Bool {return self._displayName != nil}
/// Clears the value of `displayName`. Subsequent reads from it will return its default value.
mutating func clearDisplayName() {self._displayName = nil}
var profilePicture: String {
get {return _profilePicture ?? String()}
set {_profilePicture = newValue}
}
/// Returns true if `profilePicture` has been explicitly set.
var hasProfilePicture: Bool {return self._profilePicture != nil}
/// Clears the value of `profilePicture`. Subsequent reads from it will return its default value.
mutating func clearProfilePicture() {self._profilePicture = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _displayName: String? = nil
fileprivate var _profilePicture: String? = nil
}
struct OpenGroupInvitation {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@ -1702,24 +1722,28 @@ extension SessionProtos_Envelope: SwiftProtobuf.Message, SwiftProtobuf._MessageI
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
if let v = self._source {
} }()
try { if let v = self._source {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._timestamp {
} }()
try { if let v = self._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 5)
}
if let v = self._sourceDevice {
} }()
try { if let v = self._sourceDevice {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7)
}
if let v = self._content {
} }()
try { if let v = self._content {
try visitor.visitSingularBytesField(value: v, fieldNumber: 8)
}
if let v = self._serverTimestamp {
} }()
try { if let v = self._serverTimestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 10)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -1769,12 +1793,16 @@ extension SessionProtos_TypingMessage: SwiftProtobuf.Message, SwiftProtobuf._Mes
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._timestamp {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
}
if let v = self._action {
} }()
try { if let v = self._action {
try visitor.visitSingularEnumField(value: v, fieldNumber: 2)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -1820,12 +1848,16 @@ extension SessionProtos_UnsendRequest: SwiftProtobuf.Message, SwiftProtobuf._Mes
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._timestamp {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
}
if let v = self._author {
} }()
try { if let v = self._author {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -1841,6 +1873,8 @@ extension SessionProtos_MessageRequestResponse: SwiftProtobuf.Message, SwiftProt
static let protoMessageName: String = _protobuf_package + ".MessageRequestResponse"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "isApproved"),
2: .same(proto: "profileKey"),
3: .same(proto: "profile"),
]
public var isInitialized: Bool {
@ -1855,20 +1889,34 @@ extension SessionProtos_MessageRequestResponse: SwiftProtobuf.Message, SwiftProt
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularBoolField(value: &self._isApproved) }()
case 2: try { try decoder.decodeSingularBytesField(value: &self._profileKey) }()
case 3: try { try decoder.decodeSingularMessageField(value: &self._profile) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._isApproved {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._isApproved {
try visitor.visitSingularBoolField(value: v, fieldNumber: 1)
}
} }()
try { if let v = self._profileKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
} }()
try { if let v = self._profile {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
} }()
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SessionProtos_MessageRequestResponse, rhs: SessionProtos_MessageRequestResponse) -> Bool {
if lhs._isApproved != rhs._isApproved {return false}
if lhs._profileKey != rhs._profileKey {return false}
if lhs._profile != rhs._profile {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -1958,30 +2006,34 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._dataMessage {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = _storage._dataMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
if let v = _storage._callMessage {
} }()
try { if let v = _storage._callMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
if let v = _storage._receiptMessage {
} }()
try { if let v = _storage._receiptMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
}
if let v = _storage._typingMessage {
} }()
try { if let v = _storage._typingMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
}
if let v = _storage._configurationMessage {
} }()
try { if let v = _storage._configurationMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
}
if let v = _storage._dataExtractionNotification {
} }()
try { if let v = _storage._dataExtractionNotification {
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
}
if let v = _storage._unsendRequest {
} }()
try { if let v = _storage._unsendRequest {
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
}
if let v = _storage._messageRequestResponse {
} }()
try { if let v = _storage._messageRequestResponse {
try visitor.visitSingularMessageField(value: v, fieldNumber: 10)
}
} }()
}
try unknownFields.traverse(visitor: &visitor)
}
@ -2041,9 +2093,13 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
} }()
if !self.sdps.isEmpty {
try visitor.visitRepeatedStringField(value: self.sdps, fieldNumber: 2)
}
@ -2053,9 +2109,9 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if !self.sdpMids.isEmpty {
try visitor.visitRepeatedStringField(value: self.sdpMids, fieldNumber: 4)
}
if let v = self._uuid {
try { if let v = self._uuid {
try visitor.visitSingularStringField(value: v, fieldNumber: 5)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2108,12 +2164,16 @@ extension SessionProtos_KeyPair: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._publicKey {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._publicKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
}
if let v = self._privateKey {
} }()
try { if let v = self._privateKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2151,12 +2211,16 @@ extension SessionProtos_DataExtractionNotification: SwiftProtobuf.Message, Swift
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
if let v = self._timestamp {
} }()
try { if let v = self._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2175,6 +2239,48 @@ extension SessionProtos_DataExtractionNotification.TypeEnum: SwiftProtobuf._Prot
]
}
extension SessionProtos_LokiProfile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".LokiProfile"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "displayName"),
2: .same(proto: "profilePicture"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self._displayName) }()
case 2: try { try decoder.decodeSingularStringField(value: &self._profilePicture) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._displayName {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
} }()
try { if let v = self._profilePicture {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
} }()
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SessionProtos_LokiProfile, rhs: SessionProtos_LokiProfile) -> Bool {
if lhs._displayName != rhs._displayName {return false}
if lhs._profilePicture != rhs._profilePicture {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".DataMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -2205,7 +2311,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
var _quote: SessionProtos_DataMessage.Quote? = nil
var _preview: [SessionProtos_DataMessage.Preview] = []
var _reaction: SessionProtos_DataMessage.Reaction? = nil
var _profile: SessionProtos_DataMessage.LokiProfile? = nil
var _profile: SessionProtos_LokiProfile? = nil
var _openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation? = nil
var _closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage? = nil
var _syncTarget: String? = nil
@ -2282,48 +2388,52 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._body {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = _storage._body {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
} }()
if !_storage._attachments.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._attachments, fieldNumber: 2)
}
if let v = _storage._group {
try { if let v = _storage._group {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
if let v = _storage._flags {
} }()
try { if let v = _storage._flags {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4)
}
if let v = _storage._expireTimer {
} }()
try { if let v = _storage._expireTimer {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5)
}
if let v = _storage._profileKey {
} }()
try { if let v = _storage._profileKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 6)
}
if let v = _storage._timestamp {
} }()
try { if let v = _storage._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 7)
}
if let v = _storage._quote {
} }()
try { if let v = _storage._quote {
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
}
} }()
if !_storage._preview.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._preview, fieldNumber: 10)
}
if let v = _storage._reaction {
try { if let v = _storage._reaction {
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
}
if let v = _storage._profile {
} }()
try { if let v = _storage._profile {
try visitor.visitSingularMessageField(value: v, fieldNumber: 101)
}
if let v = _storage._openGroupInvitation {
} }()
try { if let v = _storage._openGroupInvitation {
try visitor.visitSingularMessageField(value: v, fieldNumber: 102)
}
if let v = _storage._closedGroupControlMessage {
} }()
try { if let v = _storage._closedGroupControlMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 104)
}
if let v = _storage._syncTarget {
} }()
try { if let v = _storage._syncTarget {
try visitor.visitSingularStringField(value: v, fieldNumber: 105)
}
} }()
}
try unknownFields.traverse(visitor: &visitor)
}
@ -2394,15 +2504,19 @@ extension SessionProtos_DataMessage.Quote: SwiftProtobuf.Message, SwiftProtobuf.
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._id {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._id {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
}
if let v = self._author {
} }()
try { if let v = self._author {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._text {
} }()
try { if let v = self._text {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
} }()
if !self.attachments.isEmpty {
try visitor.visitRepeatedMessageField(value: self.attachments, fieldNumber: 4)
}
@ -2449,18 +2563,22 @@ extension SessionProtos_DataMessage.Quote.QuotedAttachment: SwiftProtobuf.Messag
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._contentType {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._contentType {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._fileName {
} }()
try { if let v = self._fileName {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._thumbnail {
} }()
try { if let v = self._thumbnail {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
if let v = self._flags {
} }()
try { if let v = self._flags {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2509,15 +2627,19 @@ extension SessionProtos_DataMessage.Preview: SwiftProtobuf.Message, SwiftProtobu
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._url {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._url {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._title {
} }()
try { if let v = self._title {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._image {
} }()
try { if let v = self._image {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2562,18 +2684,22 @@ extension SessionProtos_DataMessage.Reaction: SwiftProtobuf.Message, SwiftProtob
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._id {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._id {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
}
if let v = self._author {
} }()
try { if let v = self._author {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._emoji {
} }()
try { if let v = self._emoji {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._action {
} }()
try { if let v = self._action {
try visitor.visitSingularEnumField(value: v, fieldNumber: 4)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2594,44 +2720,6 @@ extension SessionProtos_DataMessage.Reaction.Action: SwiftProtobuf._ProtoNamePro
]
}
extension SessionProtos_DataMessage.LokiProfile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = SessionProtos_DataMessage.protoMessageName + ".LokiProfile"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "displayName"),
2: .same(proto: "profilePicture"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self._displayName) }()
case 2: try { try decoder.decodeSingularStringField(value: &self._profilePicture) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._displayName {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._profilePicture {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SessionProtos_DataMessage.LokiProfile, rhs: SessionProtos_DataMessage.LokiProfile) -> Bool {
if lhs._displayName != rhs._displayName {return false}
if lhs._profilePicture != rhs._profilePicture {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension SessionProtos_DataMessage.OpenGroupInvitation: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = SessionProtos_DataMessage.protoMessageName + ".OpenGroupInvitation"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -2659,12 +2747,16 @@ extension SessionProtos_DataMessage.OpenGroupInvitation: SwiftProtobuf.Message,
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._url {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._url {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._name {
} }()
try { if let v = self._name {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2716,18 +2808,22 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage: SwiftProtobuf.Mes
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
if let v = self._publicKey {
} }()
try { if let v = self._publicKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
}
if let v = self._name {
} }()
try { if let v = self._name {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._encryptionKeyPair {
} }()
try { if let v = self._encryptionKeyPair {
try visitor.visitSingularMessageField(value: v, fieldNumber: 4)
}
} }()
if !self.members.isEmpty {
try visitor.visitRepeatedBytesField(value: self.members, fieldNumber: 5)
}
@ -2737,9 +2833,9 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage: SwiftProtobuf.Mes
if !self.wrappers.isEmpty {
try visitor.visitRepeatedMessageField(value: self.wrappers, fieldNumber: 7)
}
if let v = self._expirationTimer {
try { if let v = self._expirationTimer {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2796,12 +2892,16 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper: Sw
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._publicKey {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._publicKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
}
if let v = self._encryptedKeyPair {
} }()
try { if let v = self._encryptedKeyPair {
try visitor.visitSingularBytesField(value: v, fieldNumber: 2)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2848,21 +2948,25 @@ extension SessionProtos_ConfigurationMessage: SwiftProtobuf.Message, SwiftProtob
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if !self.closedGroups.isEmpty {
try visitor.visitRepeatedMessageField(value: self.closedGroups, fieldNumber: 1)
}
if !self.openGroups.isEmpty {
try visitor.visitRepeatedStringField(value: self.openGroups, fieldNumber: 2)
}
if let v = self._displayName {
try { if let v = self._displayName {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._profilePicture {
} }()
try { if let v = self._profilePicture {
try visitor.visitSingularStringField(value: v, fieldNumber: 4)
}
if let v = self._profileKey {
} }()
try { if let v = self._profileKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 5)
}
} }()
if !self.contacts.isEmpty {
try visitor.visitRepeatedMessageField(value: self.contacts, fieldNumber: 6)
}
@ -2915,24 +3019,28 @@ extension SessionProtos_ConfigurationMessage.ClosedGroup: SwiftProtobuf.Message,
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._publicKey {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._publicKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
}
if let v = self._name {
} }()
try { if let v = self._name {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._encryptionKeyPair {
} }()
try { if let v = self._encryptionKeyPair {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
} }()
if !self.members.isEmpty {
try visitor.visitRepeatedBytesField(value: self.members, fieldNumber: 4)
}
if !self.admins.isEmpty {
try visitor.visitRepeatedBytesField(value: self.admins, fieldNumber: 5)
}
if let v = self._expirationTimer {
try { if let v = self._expirationTimer {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -2985,27 +3093,31 @@ extension SessionProtos_ConfigurationMessage.Contact: SwiftProtobuf.Message, Swi
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._publicKey {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._publicKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
}
if let v = self._name {
} }()
try { if let v = self._name {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._profilePicture {
} }()
try { if let v = self._profilePicture {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._profileKey {
} }()
try { if let v = self._profileKey {
try visitor.visitSingularBytesField(value: v, fieldNumber: 4)
}
if let v = self._isApproved {
} }()
try { if let v = self._isApproved {
try visitor.visitSingularBoolField(value: v, fieldNumber: 5)
}
if let v = self._isBlocked {
} }()
try { if let v = self._isBlocked {
try visitor.visitSingularBoolField(value: v, fieldNumber: 6)
}
if let v = self._didApproveMe {
} }()
try { if let v = self._didApproveMe {
try visitor.visitSingularBoolField(value: v, fieldNumber: 7)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -3048,9 +3160,13 @@ extension SessionProtos_ReceiptMessage: SwiftProtobuf.Message, SwiftProtobuf._Me
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
} }()
if !self.timestamp.isEmpty {
try visitor.visitRepeatedUInt64Field(value: self.timestamp, fieldNumber: 2)
}
@ -3118,42 +3234,46 @@ extension SessionProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftProtobuf.
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._id {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._id {
try visitor.visitSingularFixed64Field(value: v, fieldNumber: 1)
}
if let v = self._contentType {
} }()
try { if let v = self._contentType {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._key {
} }()
try { if let v = self._key {
try visitor.visitSingularBytesField(value: v, fieldNumber: 3)
}
if let v = self._size {
} }()
try { if let v = self._size {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4)
}
if let v = self._thumbnail {
} }()
try { if let v = self._thumbnail {
try visitor.visitSingularBytesField(value: v, fieldNumber: 5)
}
if let v = self._digest {
} }()
try { if let v = self._digest {
try visitor.visitSingularBytesField(value: v, fieldNumber: 6)
}
if let v = self._fileName {
} }()
try { if let v = self._fileName {
try visitor.visitSingularStringField(value: v, fieldNumber: 7)
}
if let v = self._flags {
} }()
try { if let v = self._flags {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8)
}
if let v = self._width {
} }()
try { if let v = self._width {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9)
}
if let v = self._height {
} }()
try { if let v = self._height {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 10)
}
if let v = self._caption {
} }()
try { if let v = self._caption {
try visitor.visitSingularStringField(value: v, fieldNumber: 11)
}
if let v = self._url {
} }()
try { if let v = self._url {
try visitor.visitSingularStringField(value: v, fieldNumber: 101)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -3250,21 +3370,25 @@ extension SessionProtos_GroupContext: SwiftProtobuf.Message, SwiftProtobuf._Mess
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
if let v = _storage._id {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = _storage._id {
try visitor.visitSingularBytesField(value: v, fieldNumber: 1)
}
if let v = _storage._type {
} }()
try { if let v = _storage._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 2)
}
if let v = _storage._name {
} }()
try { if let v = _storage._name {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
} }()
if !_storage._members.isEmpty {
try visitor.visitRepeatedStringField(value: _storage._members, fieldNumber: 4)
}
if let v = _storage._avatar {
try { if let v = _storage._avatar {
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
}
} }()
if !_storage._admins.isEmpty {
try visitor.visitRepeatedStringField(value: _storage._admins, fieldNumber: 6)
}

View File

@ -249,18 +249,22 @@ extension WebSocketProtos_WebSocketRequestMessage: SwiftProtobuf.Message, SwiftP
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._verb {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._verb {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._path {
} }()
try { if let v = self._path {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
}
if let v = self._body {
} }()
try { if let v = self._body {
try visitor.visitSingularBytesField(value: v, fieldNumber: 3)
}
if let v = self._requestID {
} }()
try { if let v = self._requestID {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4)
}
} }()
if !self.headers.isEmpty {
try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5)
}
@ -305,18 +309,22 @@ extension WebSocketProtos_WebSocketResponseMessage: SwiftProtobuf.Message, Swift
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._requestID {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._requestID {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
}
if let v = self._status {
} }()
try { if let v = self._status {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2)
}
if let v = self._message {
} }()
try { if let v = self._message {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
if let v = self._body {
} }()
try { if let v = self._body {
try visitor.visitSingularBytesField(value: v, fieldNumber: 4)
}
} }()
if !self.headers.isEmpty {
try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5)
}
@ -357,15 +365,19 @@ extension WebSocketProtos_WebSocketMessage: SwiftProtobuf.Message, SwiftProtobuf
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._type {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
if let v = self._request {
} }()
try { if let v = self._request {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
if let v = self._response {
} }()
try { if let v = self._response {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
}
} }()
try unknownFields.traverse(visitor: &visitor)
}

View File

@ -43,7 +43,9 @@ message UnsendRequest {
message MessageRequestResponse {
// @required
required bool isApproved = 1; // Whether the request was approved
required bool isApproved = 1; // Whether the request was approved
optional bytes profileKey = 2;
optional LokiProfile profile = 3;
}
message Content {
@ -98,6 +100,11 @@ message DataExtractionNotification {
optional uint64 timestamp = 2;
}
message LokiProfile {
optional string displayName = 1;
optional string profilePicture = 2;
}
message DataMessage {
enum Flags {
@ -147,11 +154,6 @@ message DataMessage {
required Action action = 4;
}
message LokiProfile {
optional string displayName = 1;
optional string profilePicture = 2;
}
message OpenGroupInvitation {
// @required
required string url = 1;

View File

@ -2,6 +2,7 @@
import Foundation
import GRDB
import SignalCoreKit
import SessionUtilitiesKit
extension MessageReceiver {
@ -17,6 +18,24 @@ extension MessageReceiver {
guard message.sender != userPublicKey else { return }
guard let senderId: String = message.sender else { return }
// Update profile if needed (want to do this regardless of whether the message exists or
// not to ensure the profile info gets sync between a users devices at every chance)
if let profile = message.profile {
var contactProfileKey: OWSAES256Key? = nil
let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000)
if let profileKey = profile.profileKey { contactProfileKey = OWSAES256Key(data: profileKey) }
try MessageReceiver.updateProfileIfNeeded(
db,
publicKey: senderId,
name: profile.displayName,
profilePictureUrl: profile.profilePictureUrl,
profileKey: contactProfileKey,
sentTimestamp: messageSentTimestamp
)
}
// Prep the unblinded thread
let unblindedThread: SessionThread = try SessionThread.fetchOrCreate(db, id: senderId, variant: .contact)

View File

@ -109,18 +109,18 @@ public final class MessageSender {
}
// Attach the user's profile if needed
if let message: VisibleMessage = message as? VisibleMessage {
if var messageWithProfile: MessageWithProfile = message as? MessageWithProfile {
let profile: Profile = Profile.fetchOrCreateCurrentUser(db)
if let profileKey: Data = profile.profileEncryptionKey?.keyData, let profilePictureUrl: String = profile.profilePictureUrl {
message.profile = VisibleMessage.VMProfile(
messageWithProfile.profile = VisibleMessage.VMProfile(
displayName: profile.name,
profileKey: profileKey,
profilePictureUrl: profilePictureUrl
)
}
else {
message.profile = VisibleMessage.VMProfile(displayName: profile.name)
messageWithProfile.profile = VisibleMessage.VMProfile(displayName: profile.name)
}
}

View File

@ -76,7 +76,7 @@ class OpenGroupSpec: QuickSpec {
)
expect(openGroup.debugDescription)
.to(equal("OpenGroup(server: \"server\", roomToken: \"room\", id: \"server.room\", publicKey: \"1234\", isActive: true, name: \"name\", roomDescription: null, imageId: null, userCount: 0, infoUpdates: 0, sequenceNumber: 0, inboxLatestMessageId: 0, outboxLatestMessageId: 0, pollFailureCount: 0)"))
.to(equal("OpenGroup(server: \"server\", roomToken: \"room\", id: \"server.room\", publicKey: \"1234\", isActive: true, name: \"name\", roomDescription: null, imageId: null, userCount: 0, infoUpdates: 0, sequenceNumber: 0, inboxLatestMessageId: 0, outboxLatestMessageId: 0, pollFailureCount: 0, permissions: ---)"))
}
}
}

View File

@ -1,427 +1,253 @@
//// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
//
//import Combine
//import Quick
//import Nimble
//
//@testable import Session
//
//class ThreadDisappearingMessagesViewModelSpec: import Combine {
// typealias Item = ConversationDisappearingMessagesViewModel.Item
//
// var disposables: Set<AnyCancellable>!
// var dataChangedCallbackTriggered: Bool = false
// var thread: TSThread!
// var config: OWSDisappearingMessagesConfiguration!
// var contact: Contact!
// var defaultItems: [ConversationDisappearingMessagesViewModel.Item]!
// var defaultItems: [Item]!
// var viewModel: ConversationDisappearingMessagesViewModel!
//
// // MARK: - Configuration
//
// override func setUpWithError() throws {
// dataChangedCallbackTriggered = false
//
// disposables = Set()
// thread = TSContactThread(uniqueId: "TestId")
// config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: "TestId")
// contact = Contact(sessionID: "TestContactId")
// defaultItems = [
// ConversationDisappearingMessagesViewModel.Item(id: 0, title: "Off", isActive: true),
// ConversationDisappearingMessagesViewModel.Item(id: 1, title: "5 seconds", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 2, title: "10 seconds", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 3, title: "30 seconds", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 4, title: "1 minute", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 5, title: "5 minutes", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 6, title: "30 minutes", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 7, title: "1 hour", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 8, title: "6 hours", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 9, title: "12 hours", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 10, title: "1 day", isActive: false),
// ConversationDisappearingMessagesViewModel.Item(id: 11, title: "1 week", isActive: false)
// Item(id: 0, title: "Off", isActive: true),
// Item(id: 1, title: "5 seconds", isActive: false),
// Item(id: 2, title: "10 seconds", isActive: false),
// Item(id: 3, title: "30 seconds", isActive: false),
// Item(id: 4, title: "1 minute", isActive: false),
// Item(id: 5, title: "5 minutes", isActive: false),
// Item(id: 6, title: "30 minutes", isActive: false),
// Item(id: 7, title: "1 hour", isActive: false),
// Item(id: 8, title: "6 hours", isActive: false),
// Item(id: 9, title: "12 hours", isActive: false),
// Item(id: 10, title: "1 day", isActive: false),
// Item(id: 11, title: "1 week", isActive: false)
// ]
//
// viewModel = ConversationDisappearingMessagesViewModel(thread: thread, disappearingMessagesConfiguration: config) { [weak self] in
// self?.dataChangedCallbackTriggered = true
// }
// }
//
// override func tearDownWithError() throws {
// disposables = nil
// dataChangedCallbackTriggered = false
// thread = nil
// config = nil
// contact = nil
// defaultItems = nil
// viewModel = nil
// }
//
// // MARK: - ConversationDisappearingMessagesViewModel.Item
//
// func testItDefaultsToTheExistingValuesWhenUpdatedWithNullValues() throws {
// var item: ConversationDisappearingMessagesViewModel.Item = ConversationDisappearingMessagesViewModel.Item(
// id: 1,
// title: "Test",
// isActive: true
// )
//
// expect(item.isActive).to(beTrue())
//
// item = item.with(isActive: nil)
// expect(item.isActive).to(beTrue())
//
// item = item.with(isActive: false)
// expect(item.isActive).to(beFalse())
// }
//
// // MARK: - Basic Tests
//
// func testItHasTheCorrectTitle() throws {
// expect(self.viewModel.title).to(equal("DISAPPEARING_MESSAGES_SETTINGS_TITLE".localized()))
// }
//
// func testItHasTheCorrectDescriptionForAGroup() throws {
// thread = TSGroupThread(uniqueId: "TestId1")
// config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: "TestId1")
// viewModel = ConversationDisappearingMessagesViewModel(thread: thread, disappearingMessagesConfiguration: config) { [weak self] in
// self?.dataChangedCallbackTriggered = true
// }
//
// expect(self.viewModel.description)
// .to(equal(
// String(format: NSLocalizedString("When enabled, messages between you and %@ will disappear after they have been seen.", comment: ""), arguments: ["the group"])
// ))
// }
//
// func testItHasTheCorrectDescriptionForAKnownContact() throws {
// var hasWrittenToStorage: Bool = false
//
// // TODO: Mock storage
// Storage.write { [weak self] transaction in
// guard let strongSelf = self else { return }
//
// Storage.shared.setContact(strongSelf.contact, using: transaction)
//
// // Need to do these after setting the contact to ensure it's picked up correctly
// strongSelf.thread = TSContactThread(contactSessionID: "TestContactId")
// strongSelf.config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: (strongSelf.thread.uniqueId ?? "TestContactId"))
// strongSelf.viewModel = ConversationDisappearingMessagesViewModel(thread: strongSelf.thread, disappearingMessagesConfiguration: strongSelf.config) {
// self?.dataChangedCallbackTriggered = true
// }
// hasWrittenToStorage = true
// }
//
// // Note: We need this to ensure the test doesn't run before the subsequent 'expect' doesn't
// // run before the viewModel gets recreated in the 'Storage.write'
// expect(hasWrittenToStorage)
// .toEventually(
// beTrue(),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.description)
// .toEventually(
// equal(
// String(format: NSLocalizedString("When enabled, messages between you and %@ will disappear after they have been seen.", comment: ""), arguments: ["anonymous"])
// ),
// timeout: .milliseconds(100)
// )
// }
//
// func testItHasTheCorrectDescriptionForAKnownContactWithADisplayName() throws {
// var hasWrittenToStorage: Bool = false
// contact.nickname = "TestName"
//
// // TODO: Mock storage
// Storage.write { [weak self] transaction in
// guard let strongSelf = self else { return }
//
// Storage.shared.setContact(strongSelf.contact, using: transaction)
//
// // Need to do these after setting the contact to ensure it's picked up correctly
// strongSelf.thread = TSContactThread(contactSessionID: "TestContactId")
// strongSelf.config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: (strongSelf.thread.uniqueId ?? "TestContactId"))
// strongSelf.viewModel = ConversationDisappearingMessagesViewModel(thread: strongSelf.thread, disappearingMessagesConfiguration: strongSelf.config) {
// self?.dataChangedCallbackTriggered = true
// }
// hasWrittenToStorage = true
// }
//
// // Note: We need this to ensure the test doesn't run before the subsequent 'expect' doesn't
// // run before the viewModel gets recreated in the 'Storage.write'
// expect(hasWrittenToStorage)
// .toEventually(
// beTrue(),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.description)
// .toEventually(equal(
// String(format: NSLocalizedString("When enabled, messages between you and %@ will disappear after they have been seen.", comment: ""), arguments: ["TestName"])
// ))
// }
//
// func testItHasTheCorrectDescriptionForAnUnexpectedThreadType() throws {
// var hasWrittenToStorage: Bool = false
// contact.nickname = "TestName"
//
// // TODO: Mock storage
// Storage.write { [weak self] transaction in
// guard let strongSelf = self else { return }
//
// Storage.shared.setContact(strongSelf.contact, using: transaction)
//
// // Need to do these after setting the contact to ensure it's picked up correctly
// strongSelf.thread = TSThread(uniqueId: "TestId1")
// strongSelf.config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: (strongSelf.thread.uniqueId ?? "TestId1"))
// strongSelf.viewModel = ConversationDisappearingMessagesViewModel(thread: strongSelf.thread, disappearingMessagesConfiguration: strongSelf.config) {
// self?.dataChangedCallbackTriggered = true
// }
// hasWrittenToStorage = true
// }
//
// // Note: We need this to ensure the test doesn't run before the subsequent 'expect' doesn't
// // run before the viewModel gets recreated in the 'Storage.write'
// expect(hasWrittenToStorage)
// .toEventually(
// beTrue(),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.description)
// .toEventually(equal(
// String(format: NSLocalizedString("When enabled, messages between you and %@ will disappear after they have been seen.", comment: ""), arguments: ["anonymous"])
// ))
// }
//
// func testItHasTheCorrectNumberOfItems() throws {
// expect(self.viewModel.items.value.count).to(equal(12))
// expect(self.viewModel.items.newest)
// .toEventually(
// haveCount(12),
// timeout: .milliseconds(100)
// )
// }
//
// func testItHasTheCorrectDefaultState() throws {
// expect(self.viewModel.items.value).to(equal(defaultItems))
// expect(self.viewModel.items.newest)
// .toEventually(
// equal(defaultItems),
// timeout: .milliseconds(100)
// )
// }
//
//
// func testItStartsWithTheCorrectItemActiveIfNotDefault() throws {
// config = OWSDisappearingMessagesConfiguration(defaultWithThreadId: "TestId1")
// config.isEnabled = true
// config.durationSeconds = 30
// viewModel = ConversationDisappearingMessagesViewModel(thread: thread, disappearingMessagesConfiguration: config) { [weak self] in
// self?.dataChangedCallbackTriggered = true
// }
//
// var nonDefaultItems: [ConversationDisappearingMessagesViewModel.Item] = defaultItems
// nonDefaultItems[0] = nonDefaultItems[0].with(isActive: false)
// nonDefaultItems[3] = nonDefaultItems[3].with(isActive: true)
// expect(self.viewModel.items.value).to(equal(nonDefaultItems))
// }
//
// // MARK: - Interactions
//
// func testItProvidesTheThreadAndGivenDataWhenAnInteractionOccurs() throws {
// var interactionThread: TSThread? = nil
//
// self.viewModel.interaction.on(0) { thread in
// interactionThread = thread
// }
//
// self.viewModel.interaction.tap(0)
//
// expect(interactionThread).to(equal(self.thread))
// }
//
// func testItRefreshesTheDataCorrectly() throws {
// expect(self.viewModel.items.value.count).to(beGreaterThan(3))
// expect(self.viewModel.items.value[3].id).to(equal(3))
// expect(self.viewModel.items.value[3].isActive).to(beFalse())
//
// config.isEnabled = true
// config.durationSeconds = 30
//
// viewModel.tryRefreshData(for: 3)
//
// expect(self.viewModel.items.value[3].id).to(equal(3))
// expect(self.viewModel.items.value[3].isActive).to(beTrue())
// }
//
// func testItDoesNotSetAnItemToActiveIfTheConfigIsNotEnabled() throws {
// expect(self.viewModel.items.value.count).to(beGreaterThan(3))
// expect(self.viewModel.items.value[3].id).to(equal(3))
// expect(self.viewModel.items.value[3].isActive).to(beFalse())
// var nonDefaultItems: [Item] = defaultItems
// nonDefaultItems[0] = Item(id: nonDefaultItems[0].id, title: nonDefaultItems[0].title, isActive: false)
// nonDefaultItems[3] = Item(id: nonDefaultItems[3].id, title: nonDefaultItems[3].title, isActive: true)
//
// config.durationSeconds = 30
//
// viewModel.tryRefreshData(for: 3)
//
// expect(self.viewModel.items.value[3].id).to(equal(3))
// expect(self.viewModel.items.value[3].isActive).to(beFalse())
// expect(self.viewModel.items.newest)
// .toEventually(
// equal(nonDefaultItems),
// timeout: .milliseconds(100)
// )
// }
//
// func testItUpdatesToADifferentValue() throws {
// expect(self.viewModel.items.value.count).to(beGreaterThan(3))
// expect(self.viewModel.items.value[0].id).to(equal(0))
// expect(self.viewModel.items.value[0].isActive).to(beTrue())
//
// viewModel.interaction.tap(3)
// // MARK: - Interactions
//
// expect(self.viewModel.items.value[0].id)
// func testItSelectsTheItemCorrectly() throws {
// expect(self.viewModel.items.newest)
// .toEventually(
// equal(0),
// satisfyAllOf(
// haveCountGreaterThan(3),
// valueFor(\.id, at: 3, to: equal(3)),
// valueFor(\.isActive, at: 3, to: beFalse())
// ),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.items.value[0].isActive)
//
// viewModel.itemSelected.send(3)
//
// expect(self.viewModel.items.newest)
// .toEventually(
// beFalse(),
// satisfyAllOf(
// valueFor(\.id, at: 3, to: equal(3)),
// valueFor(\.isActive, at: 3, to: beTrue())
// ),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.items.value[3].id)
// }
//
// func testItUpdatesToADifferentValue() throws {
// expect(self.viewModel.items.newest)
// .toEventually(
// equal(3),
// satisfyAllOf(
// haveCountGreaterThan(3),
// valueFor(\.id, at: 0, to: equal(0)),
// valueFor(\.isActive, at: 0, to: beTrue())
// ),
// timeout: .milliseconds(100)
// )
// expect(self.viewModel.items.value[3].isActive)
//
// viewModel.itemSelected.send(3)
//
// expect(self.viewModel.items.newest)
// .toEventually(
// beTrue(),
// satisfyAllOf(
// valueFor(\.id, at: 0, to: equal(0)),
// valueFor(\.isActive, at: 0, to: beFalse()),
// valueFor(\.id, at: 3, to: equal(3)),
// valueFor(\.isActive, at: 3, to: beTrue())
// ),
// timeout: .milliseconds(100)
// )
// }
//
//
// func testItUpdatesTheConfigWhenChangingValue() throws {
// // Note: Default for 'durationSectionds' is OWSDisappearingMessagesConfigurationDefaultExpirationDuration
// // currently set to 86400
// expect(self.config.isEnabled).to(beFalse())
// expect(self.config.durationSeconds).to(equal(86400))
//
// viewModel.interaction.tap(3)
//
// viewModel.items.sink(receiveValue: { _ in }).store(in: &disposables)
// viewModel.itemSelected.send(3)
//
// expect(self.config.isEnabled)
// .toEventually(
// beTrue(),
// timeout: .milliseconds(100)
// )
// expect(self.config.durationSeconds)
// .toEventually(
// equal(30),
// timeout: .milliseconds(100)
// )
// }
//
//
// func testItDisablesTheConfigWhenSetToZero() throws {
// config.isEnabled = true
//
// viewModel.interaction.tap(0)
//
// viewModel.items.sink(receiveValue: { _ in }).store(in: &disposables)
// viewModel.itemSelected.send(0)
//
// expect(self.config.isEnabled)
// .toEventually(
// beFalse(),
// timeout: .milliseconds(100)
// )
// expect(self.config.durationSeconds)
// .toEventually(
// equal(0),
// timeout: .milliseconds(100)
// )
// }
//
//
// func testItDoesNotSaveChangesIfTheConfigHasNotChangedFromItsDefaultState() {
// viewModel.trySaveChanges()
//
//
// // TODO: Mock out Storage.write
// expect(self.dataChangedCallbackTriggered)
// .toEventually(
// beFalse(),
// timeout: .milliseconds(100)
// )
// }
//
//
// func testItDoesSaveChangesIfTheConfigHasChanged() {
// config.isEnabled = true
// config.durationSeconds = 30
//
//
// viewModel.trySaveChanges()
//
//
// // TODO: Mock out Storage.write
// expect(self.dataChangedCallbackTriggered)
// .toEventually(
// beTrue(),
// timeout: .milliseconds(100)
// )
// }
//}
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Combine
import GRDB
import Quick
import Nimble
@testable import Session
class ThreadDisappearingMessagesViewModelSpec: QuickSpec {
typealias ParentType = SessionTableViewModel<ThreadDisappearingMessagesViewModel.NavButton, ThreadDisappearingMessagesViewModel.Section, ThreadDisappearingMessagesViewModel.Item>
// MARK: - Spec
override func spec() {
var mockStorage: Storage!
var dataChangeCancellable: AnyCancellable?
var otherCancellables: [AnyCancellable] = []
var viewModel: ThreadDisappearingMessagesViewModel!
describe("a ThreadDisappearingMessagesViewModel") {
// MARK: - Configuration
beforeEach {
mockStorage = Storage(
customWriter: DatabaseQueue(),
customMigrations: [
SNUtilitiesKit.migrations(),
SNSnodeKit.migrations(),
SNMessagingKit.migrations(),
SNUIKit.migrations()
]
)
mockStorage.write { db in
try SessionThread(
id: "TestId",
variant: .contact
).insert(db)
}
viewModel = ThreadDisappearingMessagesViewModel(
storage: mockStorage,
scheduling: .immediate,
threadId: "TestId",
config: DisappearingMessagesConfiguration.defaultWith("TestId")
)
dataChangeCancellable = viewModel.observableSettingsData
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { viewModel.updateSettings($0) }
)
}
afterEach {
dataChangeCancellable?.cancel()
otherCancellables.forEach { $0.cancel() }
mockStorage = nil
dataChangeCancellable = nil
otherCancellables = []
viewModel = nil
}
// MARK: - Basic Tests
it("has the correct title") {
expect(viewModel.title).to(equal("DISAPPEARING_MESSAGES".localized()))
}
it("has the correct number of items") {
expect(viewModel.settingsData.count)
.to(equal(1))
expect(viewModel.settingsData.first?.elements.count)
.to(equal(12))
}
it("has the correct default state") {
expect(viewModel.settingsData.first?.elements.first)
.to(
equal(
SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(
title: "DISAPPEARING_MESSAGES_OFF".localized()
),
title: "DISAPPEARING_MESSAGES_OFF".localized(),
rightAccessory: .radio(
isSelected: { true }
)
)
)
)
let title: String = NSString.formatDurationSeconds(
UInt32(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1),
useShortFormat: false
)
expect(viewModel.settingsData.first?.elements.last)
.to(
equal(
SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(title: title),
title: title,
rightAccessory: .radio(
isSelected: { false }
)
)
)
)
}
it("starts with the correct item active if not default") {
let config: DisappearingMessagesConfiguration = DisappearingMessagesConfiguration
.defaultWith("TestId")
.with(
isEnabled: true,
durationSeconds: DisappearingMessagesConfiguration.validDurationsSeconds.last
)
mockStorage.write { db in
_ = try config.saved(db)
}
viewModel = ThreadDisappearingMessagesViewModel(
storage: mockStorage,
scheduling: .immediate,
threadId: "TestId",
config: config
)
dataChangeCancellable = viewModel.observableSettingsData
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { viewModel.updateSettings($0) }
)
expect(viewModel.settingsData.first?.elements.first)
.to(
equal(
SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(
title: "DISAPPEARING_MESSAGES_OFF".localized()
),
title: "DISAPPEARING_MESSAGES_OFF".localized(),
rightAccessory: .radio(
isSelected: { false }
)
)
)
)
let title: String = NSString.formatDurationSeconds(
UInt32(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1),
useShortFormat: false
)
expect(viewModel.settingsData.first?.elements.last)
.to(
equal(
SessionCell.Info(
id: ThreadDisappearingMessagesViewModel.Item(title: title),
title: title,
rightAccessory: .radio(
isSelected: { true }
)
)
)
)
}
it("has no right bar button") {
var items: [ParentType.NavItem]?
otherCancellables.append(
viewModel.rightNavItems
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { navItems in items = navItems }
)
)
expect(items).to(equal([]))
}
context("when changed from the previous setting") {
var items: [ParentType.NavItem]?
beforeEach {
otherCancellables.append(
viewModel.rightNavItems
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { navItems in items = navItems }
)
)
viewModel.settingsData.first?.elements.last?.onTap?(nil)
}
it("shows the save button") {
expect(items)
.to(equal([
ParentType.NavItem(
id: .save,
systemItem: .save,
accessibilityIdentifier: "Save button"
)
]))
}
context("and saving") {
it("dismisses the screen") {
var didDismissScreen: Bool = false
otherCancellables.append(
viewModel.dismissScreen
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in didDismissScreen = true }
)
)
items?.first?.action?()
expect(didDismissScreen)
.toEventually(
beTrue(),
timeout: .milliseconds(100)
)
}
it("saves the updated config") {
items?.first?.action?()
let updatedConfig: DisappearingMessagesConfiguration? = mockStorage.read { db in
try DisappearingMessagesConfiguration.fetchOne(db, id: "TestId")
}
expect(updatedConfig?.isEnabled)
.toEventually(
beTrue(),
timeout: .milliseconds(100)
)
expect(updatedConfig?.durationSeconds)
.toEventually(
equal(DisappearingMessagesConfiguration.validDurationsSeconds.last ?? -1),
timeout: .milliseconds(100)
)
}
}
}
}
}
}

View File

@ -1,30 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import XCTest
class SessionTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
// Any test you write for XCTest can be annotated as throws and async.
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -13,6 +13,7 @@ class NotificationContentViewModelSpec: QuickSpec {
override func spec() {
var mockStorage: Storage!
var dataChangeCancellable: AnyCancellable?
var dismissCancellable: AnyCancellable?
var viewModel: NotificationContentViewModel!
describe("a NotificationContentViewModel") {
@ -28,7 +29,7 @@ class NotificationContentViewModelSpec: QuickSpec {
SNUIKit.migrations()
]
)
viewModel = NotificationContentViewModel(storage: mockStorage)
viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate)
dataChangeCancellable = viewModel.observableSettingsData
.receiveOnMain(immediately: true)
.sink(
@ -39,9 +40,11 @@ class NotificationContentViewModelSpec: QuickSpec {
afterEach {
dataChangeCancellable?.cancel()
dismissCancellable?.cancel()
mockStorage = nil
dataChangeCancellable = nil
dismissCancellable = nil
viewModel = nil
}
@ -53,53 +56,37 @@ class NotificationContentViewModelSpec: QuickSpec {
it("has the correct number of items") {
expect(viewModel.settingsData.count)
.toEventually(
equal(1),
timeout: .milliseconds(10)
)
.to(equal(1))
expect(viewModel.settingsData.first?.elements.count)
.toEventually(
equal(3),
timeout: .milliseconds(10)
)
.to(equal(3))
}
it("has the correct default state") {
expect(viewModel.settingsData.first?.elements )
.toEventually(
expect(viewModel.settingsData.first?.elements)
.to(
equal([
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.nameAndPreview,
title: "NOTIFICATIONS_SENDER_AND_MESSAGE".localized(),
action: .listSelection(
isSelected: { true },
storedSelection: true,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(),
rightAccessory: .radio(
isSelected: { true }
)
),
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.nameNoPreview,
title: "NOTIFICATIONS_SENDER_ONLY".localized(),
action: .listSelection(
isSelected: { false },
storedSelection: false,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(),
rightAccessory: .radio(
isSelected: { false }
)
),
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.noNameNoPreview,
title: "NOTIFICATIONS_NONE".localized(),
action: .listSelection(
isSelected: { false },
storedSelection: false,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(),
rightAccessory: .radio(
isSelected: { false }
)
)
]),
timeout: .milliseconds(10)
])
)
}
@ -107,7 +94,7 @@ class NotificationContentViewModelSpec: QuickSpec {
mockStorage.write { db in
db[.preferencesNotificationPreviewType] = Preferences.NotificationPreviewType.nameNoPreview
}
viewModel = NotificationContentViewModel(storage: mockStorage)
viewModel = NotificationContentViewModel(storage: mockStorage, scheduling: .immediate)
dataChangeCancellable = viewModel.observableSettingsData
.receiveOnMain(immediately: true)
.sink(
@ -115,43 +102,56 @@ class NotificationContentViewModelSpec: QuickSpec {
receiveValue: { viewModel.updateSettings($0) }
)
expect(viewModel.settingsData.first?.elements )
.toEventually(
expect(viewModel.settingsData.first?.elements)
.to(
equal([
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.nameAndPreview,
title: "NOTIFICATIONS_SENDER_AND_MESSAGE".localized(),
action: .listSelection(
isSelected: { false },
storedSelection: false,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_AND_CONTENT".localized(),
rightAccessory: .radio(
isSelected: { false }
)
),
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.nameNoPreview,
title: "NOTIFICATIONS_SENDER_ONLY".localized(),
action: .listSelection(
isSelected: { true },
storedSelection: true,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NAME_ONLY".localized(),
rightAccessory: .radio(
isSelected: { true }
)
),
SettingInfo(
SessionCell.Info(
id: Preferences.NotificationPreviewType.noNameNoPreview,
title: "NOTIFICATIONS_NONE".localized(),
action: .listSelection(
isSelected: { false },
storedSelection: false,
shouldAutoSave: true,
selectValue: {}
title: "NOTIFICATIONS_STYLE_CONTENT_OPTION_NO_NAME_OR_CONTENT".localized(),
rightAccessory: .radio(
isSelected: { false }
)
)
]),
timeout: .milliseconds(10)
])
)
}
context("when tapping an item") {
it("updates the saved preference") {
viewModel.settingsData.first?.elements.last?.onTap?(nil)
expect(mockStorage[.preferencesNotificationPreviewType])
.to(equal(Preferences.NotificationPreviewType.noNameNoPreview))
}
it("dismisses the screen") {
var didDismissScreen: Bool = false
dismissCancellable = viewModel.dismissScreen
.receiveOnMain(immediately: true)
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in didDismissScreen = true }
)
viewModel.settingsData.first?.elements.last?.onTap?(nil)
expect(didDismissScreen).to(beTrue())
}
}
}
}
}

View File

@ -26,6 +26,7 @@ public final class Storage {
public static let shared: Storage = Storage()
public private(set) var isValid: Bool = false
public private(set) var hasCompletedMigrations: Bool = false
public static let defaultPublisherScheduler: ValueObservationScheduler = .async(onQueue: .main)
fileprivate var dbWriter: DatabaseWriter?
private var migrator: DatabaseMigrator?
@ -429,12 +430,15 @@ public extension Storage {
// MARK: - Combine Extensions
public extension ValueObservation {
func publisher(in storage: Storage) -> AnyPublisher<Reducer.Value, Error> {
func publisher(
in storage: Storage,
scheduling scheduler: ValueObservationScheduler = Storage.defaultPublisherScheduler
) -> AnyPublisher<Reducer.Value, Error> {
guard storage.isValid, let dbWriter: DatabaseWriter = storage.dbWriter else {
return Fail(error: StorageError.databaseInvalid).eraseToAnyPublisher()
}
return self.publisher(in: dbWriter)
return self.publisher(in: dbWriter, scheduling: scheduler)
.eraseToAnyPublisher()
}
}