Merge branch 'dev' into add-documents-section
This commit is contained in:
commit
f913b7a168
3
Podfile
3
Podfile
|
@ -66,6 +66,9 @@ abstract_target 'GlobalDependencies' do
|
||||||
|
|
||||||
pod 'Quick'
|
pod 'Quick'
|
||||||
pod 'Nimble'
|
pod 'Nimble'
|
||||||
|
|
||||||
|
# Need to include this for the tests because otherwise it won't actually build
|
||||||
|
pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,6 @@ SPEC CHECKSUMS:
|
||||||
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
||||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
||||||
|
|
||||||
PODFILE CHECKSUM: f0857369c4831b2e5c1946345e76e493f3286805
|
PODFILE CHECKSUM: 0e694576fbda3c10bbc762998183d97142b85896
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.11.3
|
||||||
|
|
|
@ -646,6 +646,10 @@
|
||||||
FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; };
|
FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5B12554AF9800555489 /* VisibleMessage+Profile.swift */; };
|
||||||
FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; };
|
FD245C6C2850669200B966DD /* MessageReceiveJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A31225574F5200338F3E /* MessageReceiveJob.swift */; };
|
||||||
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; };
|
FD245C6D285066A400B966DD /* NotifyPushServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */; };
|
||||||
|
FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */; };
|
||||||
|
FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */; };
|
||||||
|
FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */; };
|
||||||
|
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */; };
|
||||||
FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; };
|
FD3AABE928306BBD00E5099A /* ThreadPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */; };
|
||||||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
|
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
|
||||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
||||||
|
@ -1687,6 +1691,10 @@
|
||||||
FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||||
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
|
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
|
||||||
FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||||
|
FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_FixDeletedMessageReadState.swift; sourceTree = "<group>"; };
|
||||||
|
FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _006_FixHiddenModAdminSupport.swift; sourceTree = "<group>"; };
|
||||||
|
FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedTableAlteration.swift; sourceTree = "<group>"; };
|
||||||
|
FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentitySpec.swift; sourceTree = "<group>"; };
|
||||||
FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = "<group>"; };
|
FD3AABE828306BBD00E5099A /* ThreadPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPickerViewModel.swift; sourceTree = "<group>"; };
|
||||||
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
|
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
|
||||||
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
|
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
|
||||||
|
@ -3460,6 +3468,8 @@
|
||||||
FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */,
|
FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */,
|
||||||
FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */,
|
FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */,
|
||||||
FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */,
|
FDF40CDD2897A1BC006A0CC4 /* _004_RemoveLegacyYDB.swift */,
|
||||||
|
FD37EA0C28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift */,
|
||||||
|
FD37EA0E28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift */,
|
||||||
);
|
);
|
||||||
path = Migrations;
|
path = Migrations;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3526,6 +3536,7 @@
|
||||||
FD17D7B727F51ECA00122BE0 /* Migration.swift */,
|
FD17D7B727F51ECA00122BE0 /* Migration.swift */,
|
||||||
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */,
|
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */,
|
||||||
FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */,
|
FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */,
|
||||||
|
FD37EA1028AB34B3003AE748 /* TypedTableAlteration.swift */,
|
||||||
FD7162DA281B6C440060647B /* TypedTableAlias.swift */,
|
FD7162DA281B6C440060647B /* TypedTableAlias.swift */,
|
||||||
FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */,
|
FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */,
|
||||||
);
|
);
|
||||||
|
@ -3573,6 +3584,22 @@
|
||||||
path = LegacyDatabase;
|
path = LegacyDatabase;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FD37EA1228AB3F60003AE748 /* Database */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD37EA1328AB42C1003AE748 /* Models */,
|
||||||
|
);
|
||||||
|
path = Database;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FD37EA1328AB42C1003AE748 /* Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD37EA1428AB42CB003AE748 /* IdentitySpec.swift */,
|
||||||
|
);
|
||||||
|
path = Models;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FD3C905D27E410DB00CD579F /* Common Networking */ = {
|
FD3C905D27E410DB00CD579F /* Common Networking */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3662,6 +3689,7 @@
|
||||||
FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */ = {
|
FD83B9B027CF200A005E1583 /* SessionUtilitiesKitTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FD37EA1228AB3F60003AE748 /* Database */,
|
||||||
FD83B9B927CF20A5005E1583 /* General */,
|
FD83B9B927CF20A5005E1583 /* General */,
|
||||||
);
|
);
|
||||||
path = SessionUtilitiesKitTests;
|
path = SessionUtilitiesKitTests;
|
||||||
|
@ -5021,6 +5049,7 @@
|
||||||
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
||||||
C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */,
|
C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */,
|
||||||
C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */,
|
C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Utilities.swift in Sources */,
|
||||||
|
FD37EA1128AB34B3003AE748 /* TypedTableAlteration.swift in Sources */,
|
||||||
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
||||||
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
|
||||||
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
C3D9E35E25675F640040E4F3 /* OWSFileSystem.m in Sources */,
|
||||||
|
@ -5128,6 +5157,7 @@
|
||||||
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */,
|
7BFD1A8C2747150E00FB91B9 /* TurnServerInfo.swift in Sources */,
|
||||||
FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */,
|
FDC4386527B4DE7600C60D73 /* RoomPollInfo.swift in Sources */,
|
||||||
FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */,
|
FD245C6B2850667400B966DD /* VisibleMessage+Profile.swift in Sources */,
|
||||||
|
FD37EA0F28AB3330003AE748 /* _006_FixHiddenModAdminSupport.swift in Sources */,
|
||||||
C3D9E3BF25676AD70040E4F3 /* (null) in Sources */,
|
C3D9E3BF25676AD70040E4F3 /* (null) in Sources */,
|
||||||
B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */,
|
B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */,
|
||||||
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */,
|
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */,
|
||||||
|
@ -5197,6 +5227,7 @@
|
||||||
FD716E682850318E00C96BF4 /* CallMode.swift in Sources */,
|
FD716E682850318E00C96BF4 /* CallMode.swift in Sources */,
|
||||||
FD09799527FE7B8E00936362 /* Interaction.swift in Sources */,
|
FD09799527FE7B8E00936362 /* Interaction.swift in Sources */,
|
||||||
FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */,
|
FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */,
|
||||||
|
FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */,
|
||||||
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */,
|
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */,
|
||||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */,
|
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */,
|
||||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
|
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */,
|
||||||
|
@ -5448,6 +5479,7 @@
|
||||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||||
|
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
||||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -6826,7 +6858,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 366;
|
CURRENT_PROJECT_VERSION = 369;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -6865,7 +6897,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.0.0;
|
MARKETING_VERSION = 2.0.2;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
|
@ -6898,7 +6930,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 366;
|
CURRENT_PROJECT_VERSION = 369;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -6937,7 +6969,7 @@
|
||||||
"$(SRCROOT)",
|
"$(SRCROOT)",
|
||||||
);
|
);
|
||||||
LLVM_LTO = NO;
|
LLVM_LTO = NO;
|
||||||
MARKETING_VERSION = 2.0.0;
|
MARKETING_VERSION = 2.0.2;
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
|
||||||
PRODUCT_NAME = Session;
|
PRODUCT_NAME = Session;
|
||||||
|
|
|
@ -369,9 +369,15 @@ extension ConversationVC:
|
||||||
body: text,
|
body: text,
|
||||||
timestampMs: sentTimestampMs,
|
timestampMs: sentTimestampMs,
|
||||||
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text),
|
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: threadId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db),
|
||||||
linkPreviewUrl: linkPreviewDraft?.urlString
|
linkPreviewUrl: linkPreviewDraft?.urlString
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
|
|
||||||
// If there is a LinkPreview and it doesn't match an existing one then add it now
|
// If there is a LinkPreview and it doesn't match an existing one then add it now
|
||||||
if
|
if
|
||||||
let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft,
|
let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft,
|
||||||
|
@ -459,7 +465,13 @@ extension ConversationVC:
|
||||||
variant: .standardOutgoing,
|
variant: .standardOutgoing,
|
||||||
body: text,
|
body: text,
|
||||||
timestampMs: sentTimestampMs,
|
timestampMs: sentTimestampMs,
|
||||||
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text)
|
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: text),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: threadId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db)
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
|
|
||||||
try MessageSender.send(
|
try MessageSender.send(
|
||||||
|
|
|
@ -435,6 +435,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
|
|
||||||
return try Interaction
|
return try Interaction
|
||||||
.filter(Interaction.Columns.wasRead == false)
|
.filter(Interaction.Columns.wasRead == false)
|
||||||
|
.filter(
|
||||||
|
// Exclude outgoing and deleted messages from the count
|
||||||
|
Interaction.Columns.variant != Interaction.Variant.standardOutgoing &&
|
||||||
|
Interaction.Columns.variant != Interaction.Variant.standardIncomingDeleted
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
// Only count mentions if 'onlyNotifyForMentions' is set
|
// Only count mentions if 'onlyNotifyForMentions' is set
|
||||||
thread[.onlyNotifyForMentions] == false ||
|
thread[.onlyNotifyForMentions] == false ||
|
||||||
|
|
|
@ -436,7 +436,13 @@ class NotificationActionHandler {
|
||||||
variant: .standardOutgoing,
|
variant: .standardOutgoing,
|
||||||
body: replyText,
|
body: replyText,
|
||||||
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
||||||
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText)
|
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: replyText),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: threadId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db)
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
|
|
||||||
try Interaction.markAsRead(
|
try Interaction.markAsRead(
|
||||||
|
|
|
@ -240,7 +240,7 @@ public final class FullConversationCell: UITableViewCell {
|
||||||
profile: cellViewModel.profile,
|
profile: cellViewModel.profile,
|
||||||
additionalProfile: cellViewModel.additionalProfile,
|
additionalProfile: cellViewModel.additionalProfile,
|
||||||
threadVariant: cellViewModel.threadVariant,
|
threadVariant: cellViewModel.threadVariant,
|
||||||
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) },
|
openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
|
||||||
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
|
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ public final class FullConversationCell: UITableViewCell {
|
||||||
profile: cellViewModel.profile,
|
profile: cellViewModel.profile,
|
||||||
additionalProfile: cellViewModel.additionalProfile,
|
additionalProfile: cellViewModel.additionalProfile,
|
||||||
threadVariant: cellViewModel.threadVariant,
|
threadVariant: cellViewModel.threadVariant,
|
||||||
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) },
|
openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
|
||||||
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
|
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@ public final class FullConversationCell: UITableViewCell {
|
||||||
profile: cellViewModel.profile,
|
profile: cellViewModel.profile,
|
||||||
additionalProfile: cellViewModel.additionalProfile,
|
additionalProfile: cellViewModel.additionalProfile,
|
||||||
threadVariant: cellViewModel.threadVariant,
|
threadVariant: cellViewModel.threadVariant,
|
||||||
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) },
|
openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
|
||||||
useFallbackPicture: (
|
useFallbackPicture: (
|
||||||
cellViewModel.threadVariant == .openGroup &&
|
cellViewModel.threadVariant == .openGroup &&
|
||||||
cellViewModel.openGroupProfilePictureData == nil
|
cellViewModel.openGroupProfilePictureData == nil
|
||||||
|
|
|
@ -256,7 +256,8 @@ enum MockDataGenerator {
|
||||||
_ = try! GroupMember(
|
_ = try! GroupMember(
|
||||||
groupId: randomGroupPublicKey,
|
groupId: randomGroupPublicKey,
|
||||||
profileId: memberId,
|
profileId: memberId,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
)
|
)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
@ -264,7 +265,8 @@ enum MockDataGenerator {
|
||||||
_ = try! GroupMember(
|
_ = try! GroupMember(
|
||||||
groupId: randomGroupPublicKey,
|
groupId: randomGroupPublicKey,
|
||||||
profileId: adminId,
|
profileId: adminId,
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
)
|
)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ public enum SNMessagingKit { // Just to make the external API nice
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
_004_RemoveLegacyYDB.self
|
_004_RemoveLegacyYDB.self
|
||||||
|
],
|
||||||
|
[
|
||||||
|
_005_FixDeletedMessageReadState.self,
|
||||||
|
_006_FixHiddenModAdminSupport.self
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -650,7 +650,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: threadId,
|
groupId: threadId,
|
||||||
profileId: memberId,
|
profileId: memberId,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
if !validProfileIds.contains(memberId) {
|
if !validProfileIds.contains(memberId) {
|
||||||
|
@ -662,7 +663,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: threadId,
|
groupId: threadId,
|
||||||
profileId: adminId,
|
profileId: adminId,
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
if !validProfileIds.contains(adminId) {
|
if !validProfileIds.contains(adminId) {
|
||||||
|
@ -674,7 +676,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: threadId,
|
groupId: threadId,
|
||||||
profileId: zombieId,
|
profileId: zombieId,
|
||||||
role: .zombie
|
role: .zombie,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
if !validProfileIds.contains(zombieId) {
|
if !validProfileIds.contains(zombieId) {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
/// This migration fixes a bug where certain message variants could incorrectly be counted as unread messages
|
||||||
|
enum _005_FixDeletedMessageReadState: Migration {
|
||||||
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
|
static let identifier: String = "FixDeletedMessageReadState"
|
||||||
|
static let needsConfigSync: Bool = false
|
||||||
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
static func migrate(_ db: Database) throws {
|
||||||
|
_ = try Interaction
|
||||||
|
.filter(
|
||||||
|
Interaction.Columns.variant == Interaction.Variant.standardIncomingDeleted ||
|
||||||
|
Interaction.Columns.variant == Interaction.Variant.standardOutgoing ||
|
||||||
|
Interaction.Columns.variant == Interaction.Variant.infoDisappearingMessagesUpdate
|
||||||
|
)
|
||||||
|
.updateAll(db, Interaction.Columns.wasRead.set(to: true))
|
||||||
|
|
||||||
|
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
/// This migration fixes an issue where hidden mods/admins weren't getting recognised as mods/admins, it reset's the `info_updates`
|
||||||
|
/// for open groups so they will fully re-fetch their mod/admin lists
|
||||||
|
enum _006_FixHiddenModAdminSupport: Migration {
|
||||||
|
static let target: TargetMigrations.Identifier = .messagingKit
|
||||||
|
static let identifier: String = "FixHiddenModAdminSupport"
|
||||||
|
static let needsConfigSync: Bool = false
|
||||||
|
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||||
|
|
||||||
|
static func migrate(_ db: Database) throws {
|
||||||
|
try db.alter(table: GroupMember.self) { t in
|
||||||
|
t.add(.isHidden, .boolean)
|
||||||
|
.notNull()
|
||||||
|
.defaults(to: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When modifying OpenGroup behaviours we should always look to reset the `infoUpdates`
|
||||||
|
// value for all OpenGroups to ensure they all have the correct state for newly
|
||||||
|
// added/changed fields
|
||||||
|
_ = try OpenGroup
|
||||||
|
.updateAll(db, OpenGroup.Columns.infoUpdates.set(to: 0))
|
||||||
|
|
||||||
|
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||||
|
}
|
||||||
|
}
|
|
@ -208,7 +208,7 @@ public class SMKDisappearingMessagesConfiguration: NSObject {
|
||||||
body: config.messageInfoString(with: nil),
|
body: config.messageInfoString(with: nil),
|
||||||
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000))
|
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000))
|
||||||
)
|
)
|
||||||
.saved(db)
|
.inserted(db)
|
||||||
|
|
||||||
try MessageSender.send(
|
try MessageSender.send(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -17,6 +17,7 @@ public struct GroupMember: Codable, Equatable, FetchableRecord, PersistableRecor
|
||||||
case groupId
|
case groupId
|
||||||
case profileId
|
case profileId
|
||||||
case role
|
case role
|
||||||
|
case isHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Role: Int, Codable, DatabaseValueConvertible {
|
public enum Role: Int, Codable, DatabaseValueConvertible {
|
||||||
|
@ -29,6 +30,7 @@ public struct GroupMember: Codable, Equatable, FetchableRecord, PersistableRecor
|
||||||
public let groupId: String
|
public let groupId: String
|
||||||
public let profileId: String
|
public let profileId: String
|
||||||
public let role: Role
|
public let role: Role
|
||||||
|
public let isHidden: Bool
|
||||||
|
|
||||||
// MARK: - Relationships
|
// MARK: - Relationships
|
||||||
|
|
||||||
|
@ -49,11 +51,13 @@ public struct GroupMember: Codable, Equatable, FetchableRecord, PersistableRecor
|
||||||
public init(
|
public init(
|
||||||
groupId: String,
|
groupId: String,
|
||||||
profileId: String,
|
profileId: String,
|
||||||
role: Role
|
role: Role,
|
||||||
|
isHidden: Bool
|
||||||
) {
|
) {
|
||||||
self.groupId = groupId
|
self.groupId = groupId
|
||||||
self.profileId = profileId
|
self.profileId = profileId
|
||||||
self.role = role
|
self.role = role
|
||||||
|
self.isHidden = isHidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
|
||||||
self.body = body
|
self.body = body
|
||||||
self.timestampMs = timestampMs
|
self.timestampMs = timestampMs
|
||||||
self.receivedAtTimestampMs = receivedAtTimestampMs
|
self.receivedAtTimestampMs = receivedAtTimestampMs
|
||||||
self.wasRead = wasRead
|
self.wasRead = (wasRead && variant.canBeUnread)
|
||||||
self.hasMention = hasMention
|
self.hasMention = hasMention
|
||||||
self.expiresInSeconds = expiresInSeconds
|
self.expiresInSeconds = expiresInSeconds
|
||||||
self.expiresStartedAtMs = expiresStartedAtMs
|
self.expiresStartedAtMs = expiresStartedAtMs
|
||||||
|
@ -304,7 +304,7 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu
|
||||||
default: return timestampMs
|
default: return timestampMs
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
self.wasRead = wasRead
|
self.wasRead = (wasRead && variant.canBeUnread)
|
||||||
self.hasMention = hasMention
|
self.hasMention = hasMention
|
||||||
self.expiresInSeconds = expiresInSeconds
|
self.expiresInSeconds = expiresInSeconds
|
||||||
self.expiresStartedAtMs = expiresStartedAtMs
|
self.expiresStartedAtMs = expiresStartedAtMs
|
||||||
|
@ -497,8 +497,6 @@ public extension Interaction {
|
||||||
.filter(Interaction.Columns.threadId == threadId)
|
.filter(Interaction.Columns.threadId == threadId)
|
||||||
.filter(Interaction.Columns.timestampMs <= interactionInfo.timestampMs)
|
.filter(Interaction.Columns.timestampMs <= interactionInfo.timestampMs)
|
||||||
.filter(Interaction.Columns.wasRead == false)
|
.filter(Interaction.Columns.wasRead == false)
|
||||||
// The `wasRead` flag doesn't apply to `standardOutgoing` or `standardIncomingDeleted`
|
|
||||||
.filter(Columns.variant != Variant.standardOutgoing && Columns.variant != Variant.standardIncomingDeleted)
|
|
||||||
let interactionIdsToMarkAsRead: [Int64] = try interactionQuery
|
let interactionIdsToMarkAsRead: [Int64] = try interactionQuery
|
||||||
.select(.id)
|
.select(.id)
|
||||||
.asRequest(of: Int64.self)
|
.asRequest(of: Int64.self)
|
||||||
|
@ -600,7 +598,7 @@ public extension Interaction {
|
||||||
body: nil,
|
body: nil,
|
||||||
timestampMs: timestampMs,
|
timestampMs: timestampMs,
|
||||||
receivedAtTimestampMs: receivedAtTimestampMs,
|
receivedAtTimestampMs: receivedAtTimestampMs,
|
||||||
wasRead: wasRead,
|
wasRead: (wasRead && Variant.standardIncomingDeleted.canBeUnread),
|
||||||
hasMention: hasMention,
|
hasMention: hasMention,
|
||||||
expiresInSeconds: expiresInSeconds,
|
expiresInSeconds: expiresInSeconds,
|
||||||
expiresStartedAtMs: expiresStartedAtMs,
|
expiresStartedAtMs: expiresStartedAtMs,
|
||||||
|
|
|
@ -233,9 +233,15 @@ public class SMKOpenGroup: NSObject {
|
||||||
authorId: userId,
|
authorId: userId,
|
||||||
variant: .standardOutgoing,
|
variant: .standardOutgoing,
|
||||||
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: userId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db),
|
||||||
linkPreviewUrl: urlString
|
linkPreviewUrl: urlString
|
||||||
)
|
)
|
||||||
.saved(db)
|
.inserted(db)
|
||||||
|
|
||||||
try MessageSender.send(
|
try MessageSender.send(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -422,17 +422,41 @@ public final class OpenGroupManager: NSObject {
|
||||||
_ = try GroupMember(
|
_ = try GroupMember(
|
||||||
groupId: threadId,
|
groupId: threadId,
|
||||||
profileId: adminId,
|
profileId: adminId,
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
).saved(db)
|
).saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try roomDetails.hiddenAdmins
|
||||||
|
.defaulting(to: [])
|
||||||
|
.forEach { adminId in
|
||||||
|
_ = try GroupMember(
|
||||||
|
groupId: threadId,
|
||||||
|
profileId: adminId,
|
||||||
|
role: .admin,
|
||||||
|
isHidden: true
|
||||||
|
).saved(db)
|
||||||
|
}
|
||||||
|
|
||||||
try roomDetails.moderators.forEach { moderatorId in
|
try roomDetails.moderators.forEach { moderatorId in
|
||||||
_ = try GroupMember(
|
_ = try GroupMember(
|
||||||
groupId: threadId,
|
groupId: threadId,
|
||||||
profileId: moderatorId,
|
profileId: moderatorId,
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).saved(db)
|
).saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try roomDetails.hiddenModerators
|
||||||
|
.defaulting(to: [])
|
||||||
|
.forEach { moderatorId in
|
||||||
|
_ = try GroupMember(
|
||||||
|
groupId: threadId,
|
||||||
|
profileId: moderatorId,
|
||||||
|
role: .moderator,
|
||||||
|
isHidden: true
|
||||||
|
).saved(db)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db.afterNextTransactionCommit { db in
|
db.afterNextTransactionCommit { db in
|
||||||
|
@ -498,13 +522,13 @@ public final class OpenGroupManager: NSObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let seqNo: Int64? = messages.map { $0.seqNo }.max()
|
||||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||||
.filter { $0.deleted != true }
|
.filter { $0.deleted != true }
|
||||||
.sorted { lhs, rhs in lhs.id < rhs.id }
|
.sorted { lhs, rhs in lhs.id < rhs.id }
|
||||||
var messageServerIdsToRemove: [Int64] = messages
|
var messageServerIdsToRemove: [Int64] = messages
|
||||||
.filter { $0.deleted == true }
|
.filter { $0.deleted == true }
|
||||||
.map { $0.id }
|
.map { $0.id }
|
||||||
let seqNo: Int64? = sortedMessages.map { $0.seqNo }.max()
|
|
||||||
|
|
||||||
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
||||||
if let seqNo: Int64 = seqNo {
|
if let seqNo: Int64 = seqNo {
|
||||||
|
|
|
@ -193,6 +193,7 @@ extension MessageReceiver {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.inserted(db)
|
.inserted(db)
|
||||||
|
|
||||||
try MessageSender
|
try MessageSender
|
||||||
.sendNonDurably(
|
.sendNonDurably(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -89,7 +89,8 @@ extension MessageReceiver {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: groupPublicKey,
|
groupId: groupPublicKey,
|
||||||
profileId: memberId,
|
profileId: memberId,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
).save(db)
|
).save(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +98,8 @@ extension MessageReceiver {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: groupPublicKey,
|
groupId: groupPublicKey,
|
||||||
profileId: adminId,
|
profileId: adminId,
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
).save(db)
|
).save(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +256,8 @@ extension MessageReceiver {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: id,
|
groupId: id,
|
||||||
profileId: memberId,
|
profileId: memberId,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,7 +443,8 @@ extension MessageReceiver {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: id,
|
groupId: id,
|
||||||
profileId: sender,
|
profileId: sender,
|
||||||
role: .zombie
|
role: .zombie,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,8 @@ extension MessageSender {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: groupPublicKey,
|
groupId: groupPublicKey,
|
||||||
profileId: adminId,
|
profileId: adminId,
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +49,8 @@ extension MessageSender {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: groupPublicKey,
|
groupId: groupPublicKey,
|
||||||
profileId: memberId,
|
profileId: memberId,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +376,8 @@ extension MessageSender {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: closedGroup.id,
|
groupId: closedGroup.id,
|
||||||
profileId: member,
|
profileId: member,
|
||||||
role: .standard
|
role: .standard,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,8 @@ public extension SessionThreadViewModel {
|
||||||
// MARK: --SessionThreadViewModel
|
// MARK: --SessionThreadViewModel
|
||||||
|
|
||||||
public extension SessionThreadViewModel {
|
public extension SessionThreadViewModel {
|
||||||
|
/// **Note:** This query **will not** include deleted incoming messages in it's unread count (they should never be marked as unread
|
||||||
|
/// but including this warning just in case there is a discrepancy)
|
||||||
static func baseQuery(
|
static func baseQuery(
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
filterSQL: SQL,
|
filterSQL: SQL,
|
||||||
|
@ -610,6 +612,8 @@ public extension SessionThreadViewModel {
|
||||||
// MARK: - ConversationVC
|
// MARK: - ConversationVC
|
||||||
|
|
||||||
public extension SessionThreadViewModel {
|
public extension SessionThreadViewModel {
|
||||||
|
/// **Note:** This query **will** include deleted incoming messages in it's unread count (they should never be marked as unread
|
||||||
|
/// but including this warning just in case there is a discrepancy)
|
||||||
static func conversationQuery(threadId: String, userPublicKey: String) -> AdaptedFetchRequest<SQLRequest<SessionThreadViewModel>> {
|
static func conversationQuery(threadId: String, userPublicKey: String) -> AdaptedFetchRequest<SQLRequest<SessionThreadViewModel>> {
|
||||||
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
|
let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
|
||||||
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
let contact: TypedTableAlias<Contact> = TypedTableAlias()
|
||||||
|
|
|
@ -37,7 +37,7 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
describe("a Capability") {
|
describe("a Capability") {
|
||||||
context("when initializing") {
|
context("when initializing") {
|
||||||
it("succeeeds with a valid case") {
|
it("succeeeds with a valid case") {
|
||||||
let capability: OpenGroupAPI.Capabilities.Capability = OpenGroupAPI.Capabilities.Capability(
|
let capability: Capability.Variant = Capability.Variant(
|
||||||
from: "sogs"
|
from: "sogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("wraps an unknown value in the unsupported case") {
|
it("wraps an unknown value in the unsupported case") {
|
||||||
let capability: OpenGroupAPI.Capabilities.Capability = OpenGroupAPI.Capabilities.Capability(
|
let capability: Capability.Variant = Capability.Variant(
|
||||||
from: "test"
|
from: "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,12 +55,12 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
|
|
||||||
context("when accessing the rawValue") {
|
context("when accessing the rawValue") {
|
||||||
it("provides known cases exactly") {
|
it("provides known cases exactly") {
|
||||||
expect(OpenGroupAPI.Capabilities.Capability.sogs.rawValue).to(equal("sogs"))
|
expect(Capability.Variant.sogs.rawValue).to(equal("sogs"))
|
||||||
expect(OpenGroupAPI.Capabilities.Capability.blind.rawValue).to(equal("blind"))
|
expect(Capability.Variant.blind.rawValue).to(equal("blind"))
|
||||||
}
|
}
|
||||||
|
|
||||||
it("provides the wrapped value for unsupported cases") {
|
it("provides the wrapped value for unsupported cases") {
|
||||||
expect(OpenGroupAPI.Capabilities.Capability.unsupported("test").rawValue).to(equal("test"))
|
expect(Capability.Variant.unsupported("test").rawValue).to(equal("test"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,14 +68,14 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
it("decodes known cases exactly") {
|
it("decodes known cases exactly") {
|
||||||
expect(
|
expect(
|
||||||
try? JSONDecoder().decode(
|
try? JSONDecoder().decode(
|
||||||
OpenGroupAPI.Capabilities.Capability.self,
|
Capability.Variant.self,
|
||||||
from: "\"sogs\"".data(using: .utf8)!
|
from: "\"sogs\"".data(using: .utf8)!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.to(equal(.sogs))
|
.to(equal(.sogs))
|
||||||
expect(
|
expect(
|
||||||
try? JSONDecoder().decode(
|
try? JSONDecoder().decode(
|
||||||
OpenGroupAPI.Capabilities.Capability.self,
|
Capability.Variant.self,
|
||||||
from: "\"blind\"".data(using: .utf8)!
|
from: "\"blind\"".data(using: .utf8)!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -85,7 +85,7 @@ class CapabilitiesSpec: QuickSpec {
|
||||||
it("decodes unknown cases into the unsupported case") {
|
it("decodes unknown cases into the unsupported case") {
|
||||||
expect(
|
expect(
|
||||||
try? JSONDecoder().decode(
|
try? JSONDecoder().decode(
|
||||||
OpenGroupAPI.Capabilities.Capability.self,
|
Capability.Variant.self,
|
||||||
from: "\"test\"".data(using: .utf8)!
|
from: "\"test\"".data(using: .utf8)!
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -76,7 +76,7 @@ class OpenGroupSpec: QuickSpec {
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(openGroup.debugDescription)
|
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)"))
|
.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)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1237,6 +1237,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
sender: "testSender",
|
sender: "testSender",
|
||||||
posted: 321,
|
posted: 321,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 10,
|
seqNo: 10,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -1605,6 +1606,7 @@ class OpenGroupAPISpec: QuickSpec {
|
||||||
sender: "testSender",
|
sender: "testSender",
|
||||||
posted: 321,
|
posted: 321,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 10,
|
seqNo: 10,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
|
|
@ -186,6 +186,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: "05\(TestConstants.publicKey)",
|
sender: "05\(TestConstants.publicKey)",
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 124,
|
seqNo: 124,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -1277,7 +1278,12 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
defaultWrite: nil,
|
defaultWrite: nil,
|
||||||
upload: false,
|
upload: false,
|
||||||
defaultUpload: nil,
|
defaultUpload: nil,
|
||||||
details: TestCapabilitiesAndRoomApi.roomData.with(moderators: ["TestMod"], admins: [])
|
details: TestCapabilitiesAndRoomApi.roomData.with(
|
||||||
|
moderators: ["TestMod"],
|
||||||
|
hiddenModerators: [],
|
||||||
|
admins: [],
|
||||||
|
hiddenAdmins: []
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -1308,7 +1314,67 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
server: "testServer"
|
server: "testServer"
|
||||||
),
|
),
|
||||||
profileId: "TestMod",
|
profileId: "TestMod",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
it("updates for hidden moderators") {
|
||||||
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
||||||
|
|
||||||
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
||||||
|
token: "testRoom",
|
||||||
|
activeUsers: 10,
|
||||||
|
admin: false,
|
||||||
|
globalAdmin: false,
|
||||||
|
moderator: false,
|
||||||
|
globalModerator: false,
|
||||||
|
read: false,
|
||||||
|
defaultRead: nil,
|
||||||
|
defaultAccessible: nil,
|
||||||
|
write: false,
|
||||||
|
defaultWrite: nil,
|
||||||
|
upload: false,
|
||||||
|
defaultUpload: nil,
|
||||||
|
details: TestCapabilitiesAndRoomApi.roomData.with(
|
||||||
|
moderators: [],
|
||||||
|
hiddenModerators: ["TestMod2"],
|
||||||
|
admins: [],
|
||||||
|
hiddenAdmins: []
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mockStorage.write { db in
|
||||||
|
try OpenGroupManager.handlePollInfo(
|
||||||
|
db,
|
||||||
|
pollInfo: testPollInfo,
|
||||||
|
publicKey: TestConstants.publicKey,
|
||||||
|
for: "testRoom",
|
||||||
|
on: "testServer",
|
||||||
|
dependencies: dependencies
|
||||||
|
) { didComplete = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
||||||
|
expect(
|
||||||
|
mockStorage.read { db in
|
||||||
|
try GroupMember
|
||||||
|
.filter(GroupMember.Columns.groupId == OpenGroup.idFor(
|
||||||
|
roomToken: "testRoom",
|
||||||
|
server: "testServer"
|
||||||
|
))
|
||||||
|
.fetchOne(db)
|
||||||
|
}
|
||||||
|
).to(equal(
|
||||||
|
GroupMember(
|
||||||
|
groupId: OpenGroup.idFor(
|
||||||
|
roomToken: "testRoom",
|
||||||
|
server: "testServer"
|
||||||
|
),
|
||||||
|
profileId: "TestMod2",
|
||||||
|
role: .moderator,
|
||||||
|
isHidden: true
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1368,7 +1434,12 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
defaultWrite: nil,
|
defaultWrite: nil,
|
||||||
upload: false,
|
upload: false,
|
||||||
defaultUpload: nil,
|
defaultUpload: nil,
|
||||||
details: TestCapabilitiesAndRoomApi.roomData.with(moderators: [], admins: ["TestAdmin"])
|
details: TestCapabilitiesAndRoomApi.roomData.with(
|
||||||
|
moderators: [],
|
||||||
|
hiddenModerators: [],
|
||||||
|
admins: ["TestAdmin"],
|
||||||
|
hiddenAdmins: []
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
|
@ -1399,7 +1470,67 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
server: "testServer"
|
server: "testServer"
|
||||||
),
|
),
|
||||||
profileId: "TestAdmin",
|
profileId: "TestAdmin",
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
|
)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
it("updates for hidden admins") {
|
||||||
|
var didComplete: Bool = false // Prevent multi-threading test bugs
|
||||||
|
|
||||||
|
testPollInfo = OpenGroupAPI.RoomPollInfo(
|
||||||
|
token: "testRoom",
|
||||||
|
activeUsers: 10,
|
||||||
|
admin: false,
|
||||||
|
globalAdmin: false,
|
||||||
|
moderator: false,
|
||||||
|
globalModerator: false,
|
||||||
|
read: false,
|
||||||
|
defaultRead: nil,
|
||||||
|
defaultAccessible: nil,
|
||||||
|
write: false,
|
||||||
|
defaultWrite: nil,
|
||||||
|
upload: false,
|
||||||
|
defaultUpload: nil,
|
||||||
|
details: TestCapabilitiesAndRoomApi.roomData.with(
|
||||||
|
moderators: [],
|
||||||
|
hiddenModerators: [],
|
||||||
|
admins: [],
|
||||||
|
hiddenAdmins: ["TestAdmin2"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mockStorage.write { db in
|
||||||
|
try OpenGroupManager.handlePollInfo(
|
||||||
|
db,
|
||||||
|
pollInfo: testPollInfo,
|
||||||
|
publicKey: TestConstants.publicKey,
|
||||||
|
for: "testRoom",
|
||||||
|
on: "testServer",
|
||||||
|
dependencies: dependencies
|
||||||
|
) { didComplete = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
|
||||||
|
expect(
|
||||||
|
mockStorage.read { db in
|
||||||
|
try GroupMember
|
||||||
|
.filter(GroupMember.Columns.groupId == OpenGroup.idFor(
|
||||||
|
roomToken: "testRoom",
|
||||||
|
server: "testServer"
|
||||||
|
))
|
||||||
|
.fetchOne(db)
|
||||||
|
}
|
||||||
|
).to(equal(
|
||||||
|
GroupMember(
|
||||||
|
groupId: OpenGroup.idFor(
|
||||||
|
roomToken: "testRoom",
|
||||||
|
server: "testServer"
|
||||||
|
),
|
||||||
|
profileId: "TestAdmin2",
|
||||||
|
role: .admin,
|
||||||
|
isHidden: true
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1978,6 +2109,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: nil,
|
sender: nil,
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 124,
|
seqNo: 124,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2039,6 +2171,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: nil,
|
sender: nil,
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 124,
|
seqNo: 124,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2071,6 +2204,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: "05\(TestConstants.publicKey)",
|
sender: "05\(TestConstants.publicKey)",
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 124,
|
seqNo: 124,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2114,6 +2248,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: "05\(TestConstants.publicKey)",
|
sender: "05\(TestConstants.publicKey)",
|
||||||
posted: 122,
|
posted: 122,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 123,
|
seqNo: 123,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2152,6 +2287,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: "05\(TestConstants.publicKey)",
|
sender: "05\(TestConstants.publicKey)",
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 123,
|
seqNo: 123,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2180,6 +2316,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
sender: "05\(TestConstants.publicKey)",
|
sender: "05\(TestConstants.publicKey)",
|
||||||
posted: 123,
|
posted: 123,
|
||||||
edited: nil,
|
edited: nil,
|
||||||
|
deleted: nil,
|
||||||
seqNo: 123,
|
seqNo: 123,
|
||||||
whisper: false,
|
whisper: false,
|
||||||
whisperMods: false,
|
whisperMods: false,
|
||||||
|
@ -2328,6 +2465,10 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
mockSodium
|
mockSodium
|
||||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||||
.thenReturn(Data(hex: testDirectMessage.sender.removingIdPrefixIfNeeded()).bytes)
|
.thenReturn(Data(hex: testDirectMessage.sender.removingIdPrefixIfNeeded()).bytes)
|
||||||
|
|
||||||
|
mockSodium
|
||||||
|
.when { $0.sessionId(any(), matchesBlindedId: any(), serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||||
|
.thenReturn(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("updates the inbox latest message id") {
|
it("updates the inbox latest message id") {
|
||||||
|
@ -2422,6 +2563,10 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
mockSodium
|
mockSodium
|
||||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||||
.thenReturn(Data(hex: testDirectMessage.recipient.removingIdPrefixIfNeeded()).bytes)
|
.thenReturn(Data(hex: testDirectMessage.recipient.removingIdPrefixIfNeeded()).bytes)
|
||||||
|
|
||||||
|
mockSodium
|
||||||
|
.when { $0.sessionId(any(), matchesBlindedId: any(), serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||||
|
.thenReturn(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("updates the outbox latest message id") {
|
it("updates the outbox latest message id") {
|
||||||
|
@ -2602,7 +2747,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "05\(TestConstants.publicKey)",
|
profileId: "05\(TestConstants.publicKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2621,7 +2767,48 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "05\(TestConstants.publicKey)",
|
profileId: "05\(TestConstants.publicKey)",
|
||||||
role: .admin
|
role: .admin,
|
||||||
|
isHidden: false
|
||||||
|
).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
OpenGroupManager.isUserModeratorOrAdmin(
|
||||||
|
"05\(TestConstants.publicKey)",
|
||||||
|
for: "testRoom",
|
||||||
|
on: "testServer",
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
).to(beTrue())
|
||||||
|
}
|
||||||
|
|
||||||
|
it("returns true if the moderator is hidden") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try GroupMember(
|
||||||
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
|
profileId: "05\(TestConstants.publicKey)",
|
||||||
|
role: .moderator,
|
||||||
|
isHidden: true
|
||||||
|
).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
OpenGroupManager.isUserModeratorOrAdmin(
|
||||||
|
"05\(TestConstants.publicKey)",
|
||||||
|
for: "testRoom",
|
||||||
|
on: "testServer",
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
).to(beTrue())
|
||||||
|
}
|
||||||
|
|
||||||
|
it("returns true if the admin is hidden") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try GroupMember(
|
||||||
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
|
profileId: "05\(TestConstants.publicKey)",
|
||||||
|
role: .admin,
|
||||||
|
isHidden: true
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2672,7 +2859,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "00\(otherKey)",
|
profileId: "00\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
try Identity(variant: .ed25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
||||||
|
@ -2709,7 +2897,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "15\(otherKey)",
|
profileId: "15\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2766,7 +2955,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "05\(otherKey)",
|
profileId: "05\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
||||||
|
@ -2805,7 +2995,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "15\(otherKey)",
|
profileId: "15\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
|
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
|
||||||
|
@ -2911,7 +3102,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "05\(otherKey)",
|
profileId: "05\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: otherKey)!).save(db)
|
||||||
|
@ -2951,7 +3143,8 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
try GroupMember(
|
try GroupMember(
|
||||||
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
groupId: OpenGroup.idFor(roomToken: "testRoom", server: "testServer"),
|
||||||
profileId: "00\(otherKey)",
|
profileId: "00\(otherKey)",
|
||||||
role: .moderator
|
role: .moderator,
|
||||||
|
isHidden: false
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
|
try Identity(variant: .x25519PublicKey, data: Data.data(fromHex: TestConstants.publicKey)!).save(db)
|
||||||
|
@ -2977,6 +3170,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
context("when getting the default rooms if needed") {
|
context("when getting the default rooms if needed") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
class TestRoomsApi: TestOnionRequestAPI {
|
class TestRoomsApi: TestOnionRequestAPI {
|
||||||
|
static let capabilitiesData: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: nil)
|
||||||
static let roomsData: [OpenGroupAPI.Room] = [
|
static let roomsData: [OpenGroupAPI.Room] = [
|
||||||
TestCapabilitiesAndRoomApi.roomData,
|
TestCapabilitiesAndRoomApi.roomData,
|
||||||
OpenGroupAPI.Room(
|
OpenGroupAPI.Room(
|
||||||
|
@ -3009,7 +3203,26 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
]
|
]
|
||||||
|
|
||||||
override class var mockResponse: Data? {
|
override class var mockResponse: Data? {
|
||||||
return try! JSONEncoder().encode(roomsData)
|
let responses: [Data] = [
|
||||||
|
try! JSONEncoder().encode(
|
||||||
|
OpenGroupAPI.BatchSubResponse(
|
||||||
|
code: 200,
|
||||||
|
headers: [:],
|
||||||
|
body: capabilitiesData,
|
||||||
|
failedToParseBody: false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
try! JSONEncoder().encode(
|
||||||
|
OpenGroupAPI.BatchSubResponse(
|
||||||
|
code: 200,
|
||||||
|
headers: [:],
|
||||||
|
body: roomsData,
|
||||||
|
failedToParseBody: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
dependencies = dependencies.with(onionApi: TestRoomsApi.self)
|
||||||
|
@ -3178,6 +3391,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
|
|
||||||
it("fetches the image for any rooms with images") {
|
it("fetches the image for any rooms with images") {
|
||||||
class TestRoomsApi: TestOnionRequestAPI {
|
class TestRoomsApi: TestOnionRequestAPI {
|
||||||
|
static let capabilitiesData: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: nil)
|
||||||
static let roomsData: [OpenGroupAPI.Room] = [
|
static let roomsData: [OpenGroupAPI.Room] = [
|
||||||
OpenGroupAPI.Room(
|
OpenGroupAPI.Room(
|
||||||
token: "test2",
|
token: "test2",
|
||||||
|
@ -3209,7 +3423,26 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
]
|
]
|
||||||
|
|
||||||
override class var mockResponse: Data? {
|
override class var mockResponse: Data? {
|
||||||
return try! JSONEncoder().encode(roomsData)
|
let responses: [Data] = [
|
||||||
|
try! JSONEncoder().encode(
|
||||||
|
OpenGroupAPI.BatchSubResponse(
|
||||||
|
code: 200,
|
||||||
|
headers: [:],
|
||||||
|
body: capabilitiesData,
|
||||||
|
failedToParseBody: false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
try! JSONEncoder().encode(
|
||||||
|
OpenGroupAPI.BatchSubResponse(
|
||||||
|
code: 200,
|
||||||
|
headers: [:],
|
||||||
|
body: roomsData,
|
||||||
|
failedToParseBody: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let testDate: Date = Date(timeIntervalSince1970: 1234567890)
|
let testDate: Date = Date(timeIntervalSince1970: 1234567890)
|
||||||
|
@ -3218,7 +3451,9 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
date: testDate
|
date: testDate
|
||||||
)
|
)
|
||||||
|
|
||||||
OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)
|
OpenGroupManager
|
||||||
|
.getDefaultRoomsIfNeeded(using: dependencies)
|
||||||
|
.retainUntilComplete()
|
||||||
|
|
||||||
expect(mockUserDefaults)
|
expect(mockUserDefaults)
|
||||||
.toEventually(
|
.toEventually(
|
||||||
|
@ -3674,7 +3909,12 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
// MARK: - Room Convenience Extensions
|
// MARK: - Room Convenience Extensions
|
||||||
|
|
||||||
extension OpenGroupAPI.Room {
|
extension OpenGroupAPI.Room {
|
||||||
func with(moderators: [String], admins: [String]) -> OpenGroupAPI.Room {
|
func with(
|
||||||
|
moderators: [String],
|
||||||
|
hiddenModerators: [String],
|
||||||
|
admins: [String],
|
||||||
|
hiddenAdmins: [String]
|
||||||
|
) -> OpenGroupAPI.Room {
|
||||||
return OpenGroupAPI.Room(
|
return OpenGroupAPI.Room(
|
||||||
token: self.token,
|
token: self.token,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
|
@ -3689,11 +3929,11 @@ extension OpenGroupAPI.Room {
|
||||||
admin: self.admin,
|
admin: self.admin,
|
||||||
globalAdmin: self.globalAdmin,
|
globalAdmin: self.globalAdmin,
|
||||||
admins: admins,
|
admins: admins,
|
||||||
hiddenAdmins: self.hiddenAdmins,
|
hiddenAdmins: hiddenAdmins,
|
||||||
moderator: self.moderator,
|
moderator: self.moderator,
|
||||||
globalModerator: self.globalModerator,
|
globalModerator: self.globalModerator,
|
||||||
moderators: moderators,
|
moderators: moderators,
|
||||||
hiddenModerators: self.hiddenModerators,
|
hiddenModerators: hiddenModerators,
|
||||||
read: self.read,
|
read: self.read,
|
||||||
defaultRead: self.defaultRead,
|
defaultRead: self.defaultRead,
|
||||||
defaultAccessible: self.defaultAccessible,
|
defaultAccessible: self.defaultAccessible,
|
||||||
|
|
|
@ -94,7 +94,7 @@ final class SimplifiedConversationCell: UITableViewCell {
|
||||||
profile: cellViewModel.profile,
|
profile: cellViewModel.profile,
|
||||||
additionalProfile: cellViewModel.additionalProfile,
|
additionalProfile: cellViewModel.additionalProfile,
|
||||||
threadVariant: cellViewModel.threadVariant,
|
threadVariant: cellViewModel.threadVariant,
|
||||||
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) },
|
openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
|
||||||
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil),
|
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil),
|
||||||
showMultiAvatarForClosedGroup: true
|
showMultiAvatarForClosedGroup: true
|
||||||
)
|
)
|
||||||
|
|
|
@ -236,6 +236,12 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
body: body,
|
body: body,
|
||||||
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
timestampMs: Int64(floor(Date().timeIntervalSince1970 * 1000)),
|
||||||
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body),
|
hasMention: Interaction.isUserMentioned(db, threadId: threadId, body: body),
|
||||||
|
expiresInSeconds: try? DisappearingMessagesConfiguration
|
||||||
|
.select(.durationSeconds)
|
||||||
|
.filter(id: threadId)
|
||||||
|
.filter(DisappearingMessagesConfiguration.Columns.isEnabled == true)
|
||||||
|
.asRequest(of: TimeInterval.self)
|
||||||
|
.fetchOne(db),
|
||||||
linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil)
|
linkPreviewUrl: (isSharingUrl ? attachments.first?.linkPreviewDraft?.urlString : nil)
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
|
|
||||||
|
|
|
@ -129,14 +129,16 @@ public extension Identity {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func fetchHexEncodedSeed() -> String? {
|
static func fetchHexEncodedSeed(_ db: Database? = nil) -> String? {
|
||||||
return Storage.shared.read { db in
|
guard let db: Database = db else {
|
||||||
guard let data: Data = try? Identity.fetchOne(db, id: .seed)?.data else {
|
return Storage.shared.read { db in fetchHexEncodedSeed(db) }
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.toHexString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let data: Data = try? Identity.fetchOne(db, id: .seed)?.data else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.toHexString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
/// This is a convenience wrapper around the GRDB `TableAlteration` class which allows for shorthand
|
||||||
|
/// when creating tables
|
||||||
|
public class TypedTableAlteration<T> where T: TableRecord, T: ColumnExpressible {
|
||||||
|
let alteration: TableAlteration
|
||||||
|
|
||||||
|
init(alteration: TableAlteration) {
|
||||||
|
self.alteration = alteration
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult public func add(_ key: T.Columns, _ type: Database.ColumnType? = nil) -> ColumnDefinition {
|
||||||
|
return alteration.add(column: key.name, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func rename(column: String, to key: T.Columns) {
|
||||||
|
alteration.rename(column: column, to: key.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func drop(_ key: T.Columns) {
|
||||||
|
return alteration.drop(column: key.name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,17 @@ public extension Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func alter<T>(
|
||||||
|
table: T.Type,
|
||||||
|
body: (TypedTableAlteration<T>) -> Void
|
||||||
|
) throws where T: TableRecord, T: ColumnExpressible {
|
||||||
|
try alter(table: T.databaseTableName) { tableAlteration in
|
||||||
|
let typedAlteration: TypedTableAlteration<T> = TypedTableAlteration(alteration: tableAlteration)
|
||||||
|
|
||||||
|
body(typedAlteration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeFTS5Pattern<T>(rawPattern: String, forTable table: T.Type) throws -> FTS5Pattern where T: TableRecord, T: ColumnExpressible {
|
func makeFTS5Pattern<T>(rawPattern: String, forTable table: T.Type) throws -> FTS5Pattern where T: TableRecord, T: ColumnExpressible {
|
||||||
return try makeFTS5Pattern(rawPattern: rawPattern, forTable: table.databaseTableName)
|
return try makeFTS5Pattern(rawPattern: rawPattern, forTable: table.databaseTableName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ public extension Data {
|
||||||
case (0x42, 0x4d): return .bmp
|
case (0x42, 0x4d): return .bmp
|
||||||
case (0x4D, 0x4D): return .tiff // Motorola byte order TIFF
|
case (0x4D, 0x4D): return .tiff // Motorola byte order TIFF
|
||||||
case (0x49, 0x49): return .tiff // Intel byte order TIFF
|
case (0x49, 0x49): return .tiff // Intel byte order TIFF
|
||||||
|
case (0x52, 0x49): return .webp // First two letters of WebP
|
||||||
|
|
||||||
default: return .unknown
|
default: return .unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +115,9 @@ public extension Data {
|
||||||
mimeType == OWSMimeTypeImageBmp1 ||
|
mimeType == OWSMimeTypeImageBmp1 ||
|
||||||
mimeType == OWSMimeTypeImageBmp2
|
mimeType == OWSMimeTypeImageBmp2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case .webp:
|
||||||
|
return (mimeType == nil || mimeType == OWSMimeTypeImageWebp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,5 @@ public enum ImageFormat {
|
||||||
case tiff
|
case tiff
|
||||||
case jpeg
|
case jpeg
|
||||||
case bmp
|
case bmp
|
||||||
|
case webp
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,7 +328,7 @@ typedef struct {
|
||||||
// Intel byte order TIFF
|
// Intel byte order TIFF
|
||||||
return ImageFormat_Tiff;
|
return ImageFormat_Tiff;
|
||||||
} else if (byte0 == 0x52 && byte1 == 0x49) {
|
} else if (byte0 == 0x52 && byte1 == 0x49) {
|
||||||
// First two letters of RIFF tag.
|
// First two letters of WebP tag.
|
||||||
return ImageFormat_Webp;
|
return ImageFormat_Webp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
import Quick
|
||||||
|
import Nimble
|
||||||
|
|
||||||
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
|
class IdentitySpec: QuickSpec {
|
||||||
|
// MARK: - Spec
|
||||||
|
|
||||||
|
override func spec() {
|
||||||
|
var mockStorage: Storage!
|
||||||
|
|
||||||
|
describe("an Identity") {
|
||||||
|
beforeEach {
|
||||||
|
mockStorage = Storage(
|
||||||
|
customWriter: DatabaseQueue(),
|
||||||
|
customMigrations: [
|
||||||
|
SNUtilitiesKit.migrations()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly retrieves the user user public key") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .x25519PublicKey, data: "Test1".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
expect(Identity.fetchUserPublicKey(db))
|
||||||
|
.to(equal("Test1".data(using: .utf8)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly retrieves the user private key") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .x25519PrivateKey, data: "Test2".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
expect(Identity.fetchUserPrivateKey(db))
|
||||||
|
.to(equal("Test2".data(using: .utf8)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly retrieves the user key pair") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
||||||
|
try Identity(variant: .x25519PrivateKey, data: "Test4".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
let keyPair = Identity.fetchUserKeyPair(db)
|
||||||
|
|
||||||
|
expect(keyPair?.publicKey)
|
||||||
|
.to(equal("Test3".data(using: .utf8)?.bytes))
|
||||||
|
expect(keyPair?.secretKey)
|
||||||
|
.to(equal("Test4".data(using: .utf8)?.bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly determines if the user exists") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .x25519PublicKey, data: "Test3".data(using: .utf8)!).insert(db)
|
||||||
|
try Identity(variant: .x25519PrivateKey, data: "Test4".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
expect(Identity.userExists(db))
|
||||||
|
.to(equal(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly retrieves the user ED25519 key pair") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .ed25519PublicKey, data: "Test5".data(using: .utf8)!).insert(db)
|
||||||
|
try Identity(variant: .ed25519SecretKey, data: "Test6".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
let keyPair = Identity.fetchUserEd25519KeyPair(db)
|
||||||
|
|
||||||
|
expect(keyPair?.publicKey)
|
||||||
|
.to(equal("Test5".data(using: .utf8)?.bytes))
|
||||||
|
expect(keyPair?.secretKey)
|
||||||
|
.to(equal("Test6".data(using: .utf8)?.bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("correctly retrieves the hex encoded seed") {
|
||||||
|
mockStorage.write { db in
|
||||||
|
try Identity(variant: .seed, data: "Test7".data(using: .utf8)!).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStorage.read { db in
|
||||||
|
expect(Identity.fetchHexEncodedSeed(db))
|
||||||
|
.to(equal("5465737437"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,8 +19,61 @@ public final class ProfilePictureView: UIView {
|
||||||
|
|
||||||
// MARK: - Components
|
// MARK: - Components
|
||||||
|
|
||||||
private lazy var imageView = getImageView()
|
private lazy var imageContainerView: UIView = {
|
||||||
private lazy var additionalImageView = getImageView()
|
let result: UIView = UIView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.clipsToBounds = true
|
||||||
|
result.backgroundColor = Colors.unimportant
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var imageView: UIImageView = {
|
||||||
|
let result: UIImageView = UIImageView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.contentMode = .scaleAspectFill
|
||||||
|
result.isHidden = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var animatedImageView: YYAnimatedImageView = {
|
||||||
|
let result: YYAnimatedImageView = YYAnimatedImageView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.contentMode = .scaleAspectFill
|
||||||
|
result.isHidden = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var additionalImageContainerView: UIView = {
|
||||||
|
let result: UIView = UIView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.clipsToBounds = true
|
||||||
|
result.backgroundColor = Colors.unimportant
|
||||||
|
result.layer.cornerRadius = (Values.smallProfilePictureSize / 2)
|
||||||
|
result.isHidden = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var additionalImageView: UIImageView = {
|
||||||
|
let result: UIImageView = UIImageView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.contentMode = .scaleAspectFill
|
||||||
|
result.isHidden = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var additionalAnimatedImageView: YYAnimatedImageView = {
|
||||||
|
let result: YYAnimatedImageView = YYAnimatedImageView()
|
||||||
|
result.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
result.contentMode = .scaleAspectFill
|
||||||
|
result.isHidden = true
|
||||||
|
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
|
@ -35,27 +88,33 @@ public final class ProfilePictureView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setUpViewHierarchy() {
|
private func setUpViewHierarchy() {
|
||||||
// Set up image view
|
|
||||||
addSubview(imageView)
|
|
||||||
imageView.pin(.leading, to: .leading, of: self)
|
|
||||||
imageView.pin(.top, to: .top, of: self)
|
|
||||||
|
|
||||||
let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
|
let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
|
||||||
imageViewWidthConstraint = imageView.set(.width, to: imageViewSize)
|
|
||||||
imageViewHeightConstraint = imageView.set(.height, to: imageViewSize)
|
|
||||||
|
|
||||||
// Set up additional image view
|
|
||||||
addSubview(additionalImageView)
|
|
||||||
additionalImageView.pin(.trailing, to: .trailing, of: self)
|
|
||||||
additionalImageView.pin(.bottom, to: .bottom, of: self)
|
|
||||||
|
|
||||||
let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
|
let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
|
||||||
additionalImageViewWidthConstraint = additionalImageView.set(.width, to: additionalImageViewSize)
|
|
||||||
additionalImageViewHeightConstraint = additionalImageView.set(.height, to: additionalImageViewSize)
|
addSubview(imageContainerView)
|
||||||
additionalImageView.layer.cornerRadius = additionalImageViewSize / 2
|
addSubview(additionalImageContainerView)
|
||||||
|
|
||||||
|
imageContainerView.pin(.leading, to: .leading, of: self)
|
||||||
|
imageContainerView.pin(.top, to: .top, of: self)
|
||||||
|
imageViewWidthConstraint = imageContainerView.set(.width, to: imageViewSize)
|
||||||
|
imageViewHeightConstraint = imageContainerView.set(.height, to: imageViewSize)
|
||||||
|
additionalImageContainerView.pin(.trailing, to: .trailing, of: self)
|
||||||
|
additionalImageContainerView.pin(.bottom, to: .bottom, of: self)
|
||||||
|
additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: additionalImageViewSize)
|
||||||
|
additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: additionalImageViewSize)
|
||||||
|
|
||||||
|
imageContainerView.addSubview(imageView)
|
||||||
|
imageContainerView.addSubview(animatedImageView)
|
||||||
|
additionalImageContainerView.addSubview(additionalImageView)
|
||||||
|
additionalImageContainerView.addSubview(additionalAnimatedImageView)
|
||||||
|
|
||||||
|
imageView.pin(to: imageContainerView)
|
||||||
|
animatedImageView.pin(to: imageContainerView)
|
||||||
|
additionalImageView.pin(to: additionalImageContainerView)
|
||||||
|
additionalAnimatedImageView.pin(to: additionalImageContainerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Remove this once we refactor the ConversationVC to Swift (use the HomeViewModel approach)
|
// FIXME: Remove this once we refactor the OWSConversationSettingsViewController to Swift (use the HomeViewModel approach)
|
||||||
@objc(updateForThreadId:)
|
@objc(updateForThreadId:)
|
||||||
public func update(forThreadId threadId: String?) {
|
public func update(forThreadId threadId: String?) {
|
||||||
guard
|
guard
|
||||||
|
@ -74,7 +133,7 @@ public final class ProfilePictureView: UIView {
|
||||||
profile: viewModel.profile,
|
profile: viewModel.profile,
|
||||||
additionalProfile: viewModel.additionalProfile,
|
additionalProfile: viewModel.additionalProfile,
|
||||||
threadVariant: viewModel.threadVariant,
|
threadVariant: viewModel.threadVariant,
|
||||||
openGroupProfilePicture: viewModel.openGroupProfilePictureData.map { UIImage(data: $0) },
|
openGroupProfilePictureData: viewModel.openGroupProfilePictureData,
|
||||||
useFallbackPicture: (
|
useFallbackPicture: (
|
||||||
viewModel.threadVariant == .openGroup &&
|
viewModel.threadVariant == .openGroup &&
|
||||||
viewModel.openGroupProfilePictureData == nil
|
viewModel.openGroupProfilePictureData == nil
|
||||||
|
@ -88,7 +147,7 @@ public final class ProfilePictureView: UIView {
|
||||||
profile: Profile? = nil,
|
profile: Profile? = nil,
|
||||||
additionalProfile: Profile? = nil,
|
additionalProfile: Profile? = nil,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
openGroupProfilePicture: UIImage? = nil,
|
openGroupProfilePictureData: Data? = nil,
|
||||||
useFallbackPicture: Bool = false,
|
useFallbackPicture: Bool = false,
|
||||||
showMultiAvatarForClosedGroup: Bool = false
|
showMultiAvatarForClosedGroup: Bool = false
|
||||||
) {
|
) {
|
||||||
|
@ -101,20 +160,38 @@ public final class ProfilePictureView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
imageView.contentMode = .center
|
imageView.contentMode = .center
|
||||||
imageView.backgroundColor = UIColor(rgbHex: 0x353535)
|
imageView.isHidden = false
|
||||||
imageView.layer.cornerRadius = (self.size / 2)
|
animatedImageView.isHidden = true
|
||||||
|
imageContainerView.backgroundColor = UIColor(rgbHex: 0x353535)
|
||||||
|
imageContainerView.layer.cornerRadius = (self.size / 2)
|
||||||
imageViewWidthConstraint.constant = self.size
|
imageViewWidthConstraint.constant = self.size
|
||||||
imageViewHeightConstraint.constant = self.size
|
imageViewHeightConstraint.constant = self.size
|
||||||
additionalImageView.isHidden = true
|
additionalImageContainerView.isHidden = true
|
||||||
|
animatedImageView.image = nil
|
||||||
additionalImageView.image = nil
|
additionalImageView.image = nil
|
||||||
additionalImageView.layer.cornerRadius = (self.size / 2)
|
additionalAnimatedImageView.image = nil
|
||||||
|
additionalImageView.isHidden = true
|
||||||
|
additionalAnimatedImageView.isHidden = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard !publicKey.isEmpty || openGroupProfilePicture != nil else { return }
|
guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return }
|
||||||
|
|
||||||
func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage, isTappable: Bool) {
|
func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) {
|
||||||
if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile), let image: YYImage = YYImage(data: profileData) {
|
if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) {
|
||||||
return (image, true)
|
let format: ImageFormat = profileData.guessedImageFormat
|
||||||
|
|
||||||
|
let image: UIImage? = (format == .gif || format == .webp ?
|
||||||
|
nil :
|
||||||
|
UIImage(data: profileData)
|
||||||
|
)
|
||||||
|
let animatedImage: YYImage? = (format != .gif && format != .webp ?
|
||||||
|
nil :
|
||||||
|
YYImage(data: profileData)
|
||||||
|
)
|
||||||
|
|
||||||
|
if image != nil || animatedImage != nil {
|
||||||
|
return (image, animatedImage, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -124,6 +201,7 @@ public final class ProfilePictureView: UIView {
|
||||||
.defaulting(to: publicKey),
|
.defaulting(to: publicKey),
|
||||||
size: size
|
size: size
|
||||||
),
|
),
|
||||||
|
nil,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -147,56 +225,75 @@ public final class ProfilePictureView: UIView {
|
||||||
imageViewHeightConstraint.constant = targetSize
|
imageViewHeightConstraint.constant = targetSize
|
||||||
additionalImageViewWidthConstraint.constant = targetSize
|
additionalImageViewWidthConstraint.constant = targetSize
|
||||||
additionalImageViewHeightConstraint.constant = targetSize
|
additionalImageViewHeightConstraint.constant = targetSize
|
||||||
additionalImageView.isHidden = false
|
additionalImageContainerView.isHidden = false
|
||||||
|
|
||||||
if let additionalProfile: Profile = additionalProfile {
|
if let additionalProfile: Profile = additionalProfile {
|
||||||
additionalImageView.image = getProfilePicture(
|
let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture(
|
||||||
of: targetSize,
|
of: targetSize,
|
||||||
for: additionalProfile.id,
|
for: additionalProfile.id,
|
||||||
profile: additionalProfile
|
profile: additionalProfile
|
||||||
).image
|
)
|
||||||
|
|
||||||
|
// Set the images and show the appropriate imageView (non-animated should be
|
||||||
|
// visible if there is no image)
|
||||||
|
additionalImageView.image = image
|
||||||
|
additionalAnimatedImageView.image = animatedImage
|
||||||
|
additionalImageView.isHidden = (animatedImage != nil)
|
||||||
|
additionalAnimatedImageView.isHidden = (animatedImage == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
targetSize = self.size
|
targetSize = self.size
|
||||||
imageViewWidthConstraint.constant = targetSize
|
imageViewWidthConstraint.constant = targetSize
|
||||||
imageViewHeightConstraint.constant = targetSize
|
imageViewHeightConstraint.constant = targetSize
|
||||||
additionalImageView.isHidden = true
|
additionalImageContainerView.isHidden = true
|
||||||
additionalImageView.image = nil
|
additionalImageView.image = nil
|
||||||
|
additionalImageView.isHidden = true
|
||||||
|
additionalAnimatedImageView.image = nil
|
||||||
|
additionalAnimatedImageView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the image
|
// Set the image
|
||||||
if let openGroupProfilePicture: UIImage = openGroupProfilePicture {
|
if let openGroupProfilePictureData: Data = openGroupProfilePictureData {
|
||||||
imageView.image = openGroupProfilePicture
|
let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat
|
||||||
|
|
||||||
|
let image: UIImage? = (format == .gif || format == .webp ?
|
||||||
|
nil :
|
||||||
|
UIImage(data: openGroupProfilePictureData)
|
||||||
|
)
|
||||||
|
let animatedImage: YYImage? = (format != .gif && format != .webp ?
|
||||||
|
nil :
|
||||||
|
YYImage(data: openGroupProfilePictureData)
|
||||||
|
)
|
||||||
|
|
||||||
|
imageView.image = image
|
||||||
|
animatedImageView.image = animatedImage
|
||||||
|
imageView.isHidden = (animatedImage != nil)
|
||||||
|
animatedImageView.isHidden = (animatedImage == nil)
|
||||||
hasTappableProfilePicture = true
|
hasTappableProfilePicture = true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let (image, isTappable): (UIImage, Bool) = getProfilePicture(
|
let (image, animatedImage, isTappable): (UIImage?, YYImage?, Bool) = getProfilePicture(
|
||||||
of: targetSize,
|
of: targetSize,
|
||||||
for: publicKey,
|
for: publicKey,
|
||||||
profile: profile
|
profile: profile
|
||||||
)
|
)
|
||||||
imageView.image = image
|
imageView.image = image
|
||||||
|
animatedImageView.image = animatedImage
|
||||||
|
imageView.isHidden = (animatedImage != nil)
|
||||||
|
animatedImageView.isHidden = (animatedImage == nil)
|
||||||
hasTappableProfilePicture = isTappable
|
hasTappableProfilePicture = isTappable
|
||||||
}
|
}
|
||||||
|
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.backgroundColor = Colors.unimportant
|
animatedImageView.contentMode = .scaleAspectFill
|
||||||
imageView.layer.cornerRadius = (targetSize / 2)
|
imageContainerView.backgroundColor = Colors.unimportant
|
||||||
additionalImageView.layer.cornerRadius = (targetSize / 2)
|
imageContainerView.layer.cornerRadius = (targetSize / 2)
|
||||||
|
additionalImageContainerView.layer.cornerRadius = (targetSize / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
private func getImageView() -> YYAnimatedImageView {
|
|
||||||
let result = YYAnimatedImageView()
|
|
||||||
result.layer.masksToBounds = true
|
|
||||||
result.backgroundColor = Colors.unimportant
|
|
||||||
result.contentMode = .scaleAspectFill
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc public func getProfilePicture() -> UIImage? {
|
@objc public func getProfilePicture() -> UIImage? {
|
||||||
return (hasTappableProfilePicture ? imageView.image : nil)
|
return (hasTappableProfilePicture ? imageView.image : nil)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue