some fixes
This commit is contained in:
1 parent
e1f27cd8b2
commit
95a1c62b54
16 files changed
+137
-51
No files matched your search
@@ -44,6 +44,19 @@
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<false/>
|
||||
</dict>
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
AB12CD34EF56010203040503 /* ChatMessageEntrancePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB12CD34EF56010203040501 /* ChatMessageEntrancePolicy.swift */; };
|
||||
AB12CD34EF56010203040504 /* ChatMessageEntrancePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB12CD34EF56010203040501 /* ChatMessageEntrancePolicy.swift */; };
|
||||
AB12CD34EF56010203040505 /* MessageBubbleEntrance.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB12CD34EF56010203040502 /* MessageBubbleEntrance.swift */; };
|
||||
AB12CD34EF56010203040506 /* MessageBubbleEntrance.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB12CD34EF56010203040502 /* MessageBubbleEntrance.swift */; };
|
||||
00FCAB7210D005D2C30747CB /* AccountConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2006464EAC88C6E4E2B08AC8 /* AccountConfiguration.swift */; };
|
||||
01039E013AB84FA338B2A5CF /* MessageCorrectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096748EF4DC92889BCAB3260 /* MessageCorrectionTests.swift */; };
|
||||
01E4131ED4DBED0BEAD465A2 /* ServerInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA20B768A9C675FABCDC821 /* ServerInfoView.swift */; };
|
||||
@@ -79,6 +75,7 @@
|
||||
32790D95A577DCE99937B539 /* AccountPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235CA66C5DFB8B6F9472D0C3 /* AccountPreferences.swift */; };
|
||||
32E78EE5035DD08181B50B86 /* VideoNoteRotationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C986DC0BAE0D11816C25E782 /* VideoNoteRotationPolicy.swift */; };
|
||||
3375874DFCE22B22879112DD /* SystemPhotoCameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5198FF1E47790E498CC8C6F3 /* SystemPhotoCameraView.swift */; };
|
||||
33EF454D77366A0E9137D824 /* MessageBubbleEntrance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1180AA3C5AC03BB48393D97 /* MessageBubbleEntrance.swift */; };
|
||||
39C3E7AC663AB6C8DACFFAA8 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = A99B4C1B1459E1C34F214164 /* OpenSSL */; };
|
||||
3BA375F1E0934FB4DD2148F9 /* SASLprep.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2446F6A1D00ADAD261D4CCD /* SASLprep.swift */; };
|
||||
3BEF3895B4C84880B9103058 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43A4CE3E678096BA76F2988C /* NewChatView.swift */; };
|
||||
@@ -104,12 +101,14 @@
|
||||
49DC659401787CF44A33B620 /* LocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C67A8A07F9DC9DFB7FB35DA /* LocationProvider.swift */; };
|
||||
4A3844CC51129576741753E7 /* MediaPlaybackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ADAEFB7CB8441CA1A50C44 /* MediaPlaybackCoordinator.swift */; };
|
||||
4B2CFA4008D1234DCE35ADDE /* MediaSendActivityTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 247FBE680C61D12AFB583862 /* MediaSendActivityTrackerTests.swift */; };
|
||||
4B65093C19D148E04A675440 /* ChatMessageEntrancePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B311D6C0234E1C910457FCE0 /* ChatMessageEntrancePolicy.swift */; };
|
||||
4CD33DF0D193A7EED84A673F /* CallSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6079CE75BAB995515A8D5460 /* CallSnapshot.swift */; };
|
||||
4DE3B27CFA3D524590FB751A /* AppLockPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA3AEE3D9FB450B77AEC10 /* AppLockPolicyTests.swift */; };
|
||||
4E1B116771922F92FEEB079B /* ArchiveStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BA0E25A2B52BDED1C8B3B1B /* ArchiveStore.swift */; };
|
||||
4EFADFE085B42E0B0593E501 /* ArchiveSyncCheckpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BBE412EAF54DFE968B2E54 /* ArchiveSyncCheckpoint.swift */; };
|
||||
51AE33BE56ED4543014119D0 /* ConversationRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758B448979C845F56168A51F /* ConversationRow.swift */; };
|
||||
523D54621DD4DDD54EA8D510 /* MediaDownloadService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB613D3331D85529B5F989D0 /* MediaDownloadService.swift */; };
|
||||
52CA5DEE0CF7697D1B2D1FEA /* MessageBubbleEntrance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1180AA3C5AC03BB48393D97 /* MessageBubbleEntrance.swift */; };
|
||||
52F75F60459DA50E1815479A /* MediaPreviewProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0E3C6F60C3D4D6C565CA0A /* MediaPreviewProcessor.swift */; };
|
||||
5409F5C90FBE0817F4AC8176 /* ChatTimelineEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90C426133ECC0319483A084 /* ChatTimelineEntryTests.swift */; };
|
||||
5415AF453AB8FCDA55FB59AD /* LumaCallEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72EC7DC2FE873C0BA4321496 /* LumaCallEngine.swift */; };
|
||||
@@ -128,6 +127,7 @@
|
||||
64C8B9876278B711CC6479E0 /* LocationMessagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96E7970930AB7F09CB5DA9C /* LocationMessagePreview.swift */; };
|
||||
64C9A8E42046AC0FCB701613 /* TLSVersionProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186BB225C4C5E528B3D7C9CE /* TLSVersionProbe.swift */; };
|
||||
64F4AD83D86B8900DE69F117 /* CredentialVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE381301A55B1C4F333B523 /* CredentialVault.swift */; };
|
||||
65D2B572D508EF10931420FD /* ChatMessageEntrancePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B311D6C0234E1C910457FCE0 /* ChatMessageEntrancePolicy.swift */; };
|
||||
662B8E72F00F5A77DE28819A /* SCRAMPlusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D03F8C05843DD139C1A4CE /* SCRAMPlusTests.swift */; };
|
||||
66489D4D15366683D3095B3D /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C6A6B83DA076867481CCF3 /* ChatMessage.swift */; };
|
||||
66613FF7E056788222552D6C /* NotificationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E25F876C75CC5FCDDCB5906 /* NotificationPolicy.swift */; };
|
||||
@@ -336,8 +336,6 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
AB12CD34EF56010203040501 /* ChatMessageEntrancePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageEntrancePolicy.swift; sourceTree = "<group>"; };
|
||||
AB12CD34EF56010203040502 /* MessageBubbleEntrance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubbleEntrance.swift; sourceTree = "<group>"; };
|
||||
0142FF4A1B05C373DAB4CDBE /* VideoNoteCaptureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteCaptureView.swift; sourceTree = "<group>"; };
|
||||
0386C2687C1C60EC734FDBB0 /* BubbleShapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleShapes.swift; sourceTree = "<group>"; };
|
||||
03AC6BC7FC7CFFD69A20DA17 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
|
||||
@@ -459,6 +457,7 @@
|
||||
B1E6875B522254FFB5FA880E /* MediaPickerSelectionPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerSelectionPolicy.swift; sourceTree = "<group>"; };
|
||||
B2446F6A1D00ADAD261D4CCD /* SASLprep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SASLprep.swift; sourceTree = "<group>"; };
|
||||
B2DC8B629E8D4F078B192FAB /* MartinOMEMO */ = {isa = PBXFileReference; lastKnownFileType = folder; name = MartinOMEMO; path = ThirdParty/MartinOMEMO; sourceTree = SOURCE_ROOT; };
|
||||
B311D6C0234E1C910457FCE0 /* ChatMessageEntrancePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageEntrancePolicy.swift; sourceTree = "<group>"; };
|
||||
B38702A403DA3E943525FB62 /* MediaPickerSelectionPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerSelectionPolicyTests.swift; sourceTree = "<group>"; };
|
||||
B8F7B01C59E9667ADAFABBAC /* MediaViewerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaViewerItem.swift; sourceTree = "<group>"; };
|
||||
B96E7970930AB7F09CB5DA9C /* LocationMessagePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMessagePreview.swift; sourceTree = "<group>"; };
|
||||
@@ -493,6 +492,7 @@
|
||||
D9FCE089ABBF05EF9F55D451 /* VideoNoteRecordingLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteRecordingLifecycleTests.swift; sourceTree = "<group>"; };
|
||||
DCE53C6D5BAE984F218BFCFB /* ReadStateSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadStateSync.swift; sourceTree = "<group>"; };
|
||||
DF8C531E464C9C4EDE2648C6 /* Luma.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Luma.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E1180AA3C5AC03BB48393D97 /* MessageBubbleEntrance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubbleEntrance.swift; sourceTree = "<group>"; };
|
||||
E619B2C85B0DD2E80653D6D3 /* VideoNoteRecordingCompletionPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteRecordingCompletionPolicy.swift; sourceTree = "<group>"; };
|
||||
E6A5C32B4DACD56DA733A0DB /* ServerInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerInformation.swift; sourceTree = "<group>"; };
|
||||
E9F226A1F8D82B39630F9CEB /* LumaRoomStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LumaRoomStore.swift; sourceTree = "<group>"; };
|
||||
@@ -575,7 +575,7 @@
|
||||
C04D749B7CA93CB7C59127D6 /* AttachmentDraft.swift */,
|
||||
6079CE75BAB995515A8D5460 /* CallSnapshot.swift */,
|
||||
12C6A6B83DA076867481CCF3 /* ChatMessage.swift */,
|
||||
AB12CD34EF56010203040501 /* ChatMessageEntrancePolicy.swift */,
|
||||
B311D6C0234E1C910457FCE0 /* ChatMessageEntrancePolicy.swift */,
|
||||
C37AAE886AA3B8782B89E1A0 /* ChatScrollPositionPolicy.swift */,
|
||||
13E35F7F0CB0243311FCDD61 /* ChatTimelineEntry.swift */,
|
||||
1C3E51BB890F05FD02C71F6E /* ChatTypingPolicy.swift */,
|
||||
@@ -823,7 +823,7 @@
|
||||
B96E7970930AB7F09CB5DA9C /* LocationMessagePreview.swift */,
|
||||
4380219D3AF1D75EDF4D3167 /* MediaViewer.swift */,
|
||||
03AC6BC7FC7CFFD69A20DA17 /* MessageBubble.swift */,
|
||||
AB12CD34EF56010203040502 /* MessageBubbleEntrance.swift */,
|
||||
E1180AA3C5AC03BB48393D97 /* MessageBubbleEntrance.swift */,
|
||||
8D2294F0FE44B2268A0620BB /* PhotoAttachmentPreview.swift */,
|
||||
19C556A41E640592AF6376B6 /* ReplyThreadOverlay.swift */,
|
||||
5003B4F9DF154543555E9825 /* RTCVideoRendererView.swift */,
|
||||
@@ -1062,7 +1062,7 @@
|
||||
97A3A0635ADA8A8227C94216 /* ChatBubbleShape.swift in Sources */,
|
||||
7AC93485DA12BC90717739E9 /* ChatMediaImageCache.swift in Sources */,
|
||||
66489D4D15366683D3095B3D /* ChatMessage.swift in Sources */,
|
||||
AB12CD34EF56010203040503 /* ChatMessageEntrancePolicy.swift in Sources */,
|
||||
65D2B572D508EF10931420FD /* ChatMessageEntrancePolicy.swift in Sources */,
|
||||
D5E6B1859101ABBD33D90C3C /* ChatScrollPositionPolicy.swift in Sources */,
|
||||
1E239B9338CEEA7EA704626C /* ChatTimelineEntry.swift in Sources */,
|
||||
433A7B65E635FB59021F7D87 /* ChatTypingPolicy.swift in Sources */,
|
||||
@@ -1109,7 +1109,7 @@
|
||||
B33870CD89289E44882750C3 /* MediaViewer.swift in Sources */,
|
||||
EBAF4861DD93FBEAA4B37D1C /* MediaViewerItem.swift in Sources */,
|
||||
230E0DAA5396EA507D6F6D47 /* MessageBubble.swift in Sources */,
|
||||
AB12CD34EF56010203040505 /* MessageBubbleEntrance.swift in Sources */,
|
||||
33EF454D77366A0E9137D824 /* MessageBubbleEntrance.swift in Sources */,
|
||||
FD9F8640841236BB78563573 /* MessageReaction.swift in Sources */,
|
||||
30BE19F459FDDAD640F66C68 /* MessageReplyFallback.swift in Sources */,
|
||||
94FB3597E01FC508F872861E /* MessageReplySwipePolicy.swift in Sources */,
|
||||
@@ -1190,7 +1190,7 @@
|
||||
2A7611650AB52EDABB978362 /* ChatBubbleShape.swift in Sources */,
|
||||
6FCF6A4EFF8AE1FBFC2D81E1 /* ChatMediaImageCache.swift in Sources */,
|
||||
8714A5A67956D6A78D4D3E4B /* ChatMessage.swift in Sources */,
|
||||
AB12CD34EF56010203040504 /* ChatMessageEntrancePolicy.swift in Sources */,
|
||||
4B65093C19D148E04A675440 /* ChatMessageEntrancePolicy.swift in Sources */,
|
||||
8B98B818AC4379F03907B0E0 /* ChatScrollPositionPolicy.swift in Sources */,
|
||||
7F7BA6805C1D1EBCEAFDD3AD /* ChatTimelineEntry.swift in Sources */,
|
||||
B4AA654DF3A53AAAAC23915D /* ChatTypingPolicy.swift in Sources */,
|
||||
@@ -1237,7 +1237,7 @@
|
||||
CC5297F90A3DBD45C5E41A3A /* MediaViewer.swift in Sources */,
|
||||
2C5BAAEF4EC7AAE5FE0FEEDB /* MediaViewerItem.swift in Sources */,
|
||||
A90142509385DE6E83818953 /* MessageBubble.swift in Sources */,
|
||||
AB12CD34EF56010203040506 /* MessageBubbleEntrance.swift in Sources */,
|
||||
52CA5DEE0CF7697D1B2D1FEA /* MessageBubbleEntrance.swift in Sources */,
|
||||
ECCD95DD4E16E0B393C707A9 /* MessageReaction.swift in Sources */,
|
||||
C2CC9BABAE68FA9258694766 /* MessageReplyFallback.swift in Sources */,
|
||||
D4E1CF1F7D948B2E72C063BE /* MessageReplySwipePolicy.swift in Sources */,
|
||||
@@ -1423,6 +1423,7 @@
|
||||
PRODUCT_NAME = Luma;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_SUPPRESS_WARNINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -1682,6 +1683,7 @@
|
||||
PRODUCT_NAME = Luma;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_SUPPRESS_WARNINGS = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BAB78463D7DE07A8F7B3D27D"
|
||||
BuildableName = "LumaWatch.app"
|
||||
BlueprintName = "LumaWatch"
|
||||
ReferencedContainer = "container:Luma.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
@@ -72,7 +71,6 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BAB78463D7DE07A8F7B3D27D"
|
||||
BuildableName = "LumaWatch.app"
|
||||
BlueprintName = "LumaWatch"
|
||||
ReferencedContainer = "container:Luma.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
|
||||
@@ -78,6 +78,7 @@ Luma — рабочий MVP XMPP-клиента для iOS, iPadOS, macOS и App
|
||||
iPhone, iPad, Mac и Apple Watch; один и тот же catalog явно подключён ко всем
|
||||
app-target’ам, а имя закреплено в build settings и Info.plist;
|
||||
- адаптивный SwiftUI-интерфейс для iPhone, iPad и Mac;
|
||||
- поддержка портретной и альбомной ориентации на iPhone и всех четырёх ориентаций на iPad;
|
||||
- companion-приложение watchOS: последние чаты, диктовка/ответы, а также запись
|
||||
голосового до 60 секунд с отменой и предпрослушиванием; `.m4a` передаётся на
|
||||
iPhone фоновой файловой очередью WatchConnectivity, после чего iPhone
|
||||
|
||||
@@ -2485,26 +2485,44 @@ final class AppModel: ObservableObject {
|
||||
stashPendingReadMarker(envelope, cameFromPeer: cameFromPeer)
|
||||
}
|
||||
|
||||
/// Promotes a matching outgoing message to `.read` and, when the receipt
|
||||
/// came from the peer (not from another own device), broadcasts the state
|
||||
/// to the user's other devices via Carbons/MAM. Failed messages are never
|
||||
/// Promotes the matching outgoing message and every earlier outgoing
|
||||
/// message in the same conversation to `.read`. XEP-0333 markers are
|
||||
/// cumulative in practice: a marker for message N means the peer has
|
||||
/// displayed the whole conversation through N. Failed messages are never
|
||||
/// promoted by a receipt.
|
||||
private func markRead(at index: Int, cameFromPeer: Bool) {
|
||||
let message = messages[index]
|
||||
guard message.direction == .outgoing,
|
||||
message.delivery != .read,
|
||||
message.delivery != .failed,
|
||||
!message.isRetracted
|
||||
guard messages.indices.contains(index) else { return }
|
||||
let target = messages[index]
|
||||
guard target.direction == .outgoing,
|
||||
target.delivery != .failed,
|
||||
!target.isRetracted
|
||||
else { return }
|
||||
messages[index].delivery = .read
|
||||
|
||||
let targetConversation = target.conversationID
|
||||
var changed = false
|
||||
for candidateIndex in messages.indices {
|
||||
let candidate = messages[candidateIndex]
|
||||
guard candidate.conversationID == targetConversation,
|
||||
candidate.direction == .outgoing,
|
||||
candidate.delivery != .read,
|
||||
candidate.delivery != .failed,
|
||||
!candidate.isRetracted,
|
||||
candidate.timestamp < target.timestamp
|
||||
|| (candidate.timestamp == target.timestamp
|
||||
&& candidate.clientID <= target.clientID)
|
||||
else { continue }
|
||||
messages[candidateIndex].delivery = .read
|
||||
changed = true
|
||||
}
|
||||
guard changed else { return }
|
||||
schedulePersist()
|
||||
guard cameFromPeer else { return }
|
||||
xmpp.syncReadState(
|
||||
ReadStateSync.Envelope(
|
||||
id: UUID().uuidString,
|
||||
conversationJID: message.conversationID,
|
||||
messageID: message.originID ?? message.clientID,
|
||||
stanzaID: message.stanzaID,
|
||||
conversationJID: target.conversationID,
|
||||
messageID: target.originID ?? target.clientID,
|
||||
stanzaID: target.stanzaID,
|
||||
timestamp: Date()
|
||||
))
|
||||
}
|
||||
|
||||
@@ -31,11 +31,12 @@ enum VideoNoteRotationPolicy {
|
||||
/// sensor (UIDevice.current.orientation) can report landscape or
|
||||
/// face-up while the app stays portrait, which is exactly how a
|
||||
/// portrait circle ended up encoded sideways.
|
||||
@MainActor
|
||||
static var currentInterfaceOrientation: InterfaceOrientation {
|
||||
let orientation = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first { $0.activationState == .foregroundActive }?
|
||||
.interfaceOrientation
|
||||
.effectiveGeometry.interfaceOrientation
|
||||
switch orientation {
|
||||
case .portraitUpsideDown: return .portraitUpsideDown
|
||||
case .landscapeLeft: return .landscapeLeft
|
||||
|
||||
@@ -50,9 +50,9 @@ struct WatchVoiceMessage: Sendable {
|
||||
}
|
||||
|
||||
#if os(iOS) && canImport(WatchConnectivity)
|
||||
import WatchConnectivity
|
||||
@preconcurrency import WatchConnectivity
|
||||
|
||||
final class PhoneWatchBridge: NSObject, WCSessionDelegate {
|
||||
final class PhoneWatchBridge: NSObject, WCSessionDelegate, @unchecked Sendable {
|
||||
var onReply: ((String, String) -> Void)?
|
||||
var onVoiceMessage: ((WatchVoiceMessage) -> Void)? {
|
||||
didSet { deliverPendingVoiceMessages() }
|
||||
|
||||
@@ -1033,7 +1033,7 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
|
||||
// consistent no matter which camera is recording.
|
||||
let microphone =
|
||||
AVCaptureDevice.default(
|
||||
.builtInMicrophone,
|
||||
.microphone,
|
||||
for: .audio,
|
||||
position: .back
|
||||
)
|
||||
@@ -1194,7 +1194,7 @@ private actor VideoNoteFileInspector {
|
||||
]
|
||||
)
|
||||
videoInput.expectsMediaDataInRealTime = false
|
||||
videoInput.transform = videoTrack.preferredTransform
|
||||
videoInput.transform = (try? await videoTrack.load(.preferredTransform)) ?? .identity
|
||||
let adaptor = AVAssetWriterInputPixelBufferAdaptor(
|
||||
assetWriterInput: videoInput,
|
||||
sourcePixelBufferAttributes: [
|
||||
@@ -1397,7 +1397,7 @@ private actor VideoNoteFileInspector {
|
||||
return nil
|
||||
}
|
||||
if !hasVideo {
|
||||
transform = source.preferredTransform
|
||||
transform = (try? await source.load(.preferredTransform)) ?? .identity
|
||||
hasVideo = true
|
||||
}
|
||||
cursor = cursor + duration
|
||||
@@ -1431,10 +1431,6 @@ private actor VideoNoteFileInspector {
|
||||
try? FileManager.default.removeItem(at: outputURL)
|
||||
return nil
|
||||
}
|
||||
guard exporter.status == .completed else {
|
||||
try? FileManager.default.removeItem(at: outputURL)
|
||||
return nil
|
||||
}
|
||||
return Finalization(url: outputURL, duration: composition.duration)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ private enum EmojiPickerPresentation: Identifiable {
|
||||
struct ChatView: View {
|
||||
private static let bottomAnchorID = "luma-chat-timeline-bottom"
|
||||
private static let timelineCoordinateSpace = "luma-chat-timeline-space"
|
||||
private static let localHistoryPageSize = 60
|
||||
|
||||
@ObservedObject var model: AppModel
|
||||
let conversation: Conversation
|
||||
@@ -75,6 +76,8 @@ struct ChatView: View {
|
||||
@State private var captureSuspendsArchiveSync = false
|
||||
@State private var pickerResetToken = UUID()
|
||||
@State private var timelineEntries: [ChatTimelineEntry] = []
|
||||
@State private var visibleLocalMessageCount = 0
|
||||
@State private var localHistoryExhausted = false
|
||||
/// Messages-style entrance state: which rows currently animate, which IDs
|
||||
/// the timeline already showed, and whether the opening cascade of this
|
||||
/// conversation has played.
|
||||
@@ -150,6 +153,8 @@ struct ChatView: View {
|
||||
_captureSuspendsArchiveSync = State(initialValue: false)
|
||||
_pickerResetToken = State(initialValue: UUID())
|
||||
_timelineEntries = State<[ChatTimelineEntry]>(initialValue: [])
|
||||
_visibleLocalMessageCount = State(initialValue: 0)
|
||||
_localHistoryExhausted = State(initialValue: false)
|
||||
_entranceModes = State<[String: MessageBubbleEntrance.Mode]>(
|
||||
initialValue: [:]
|
||||
)
|
||||
@@ -389,6 +394,8 @@ struct ChatView: View {
|
||||
historyTopTriggerVisible = false
|
||||
historyAutoContinueCount = 0
|
||||
emptyHistoryRetryCount = 0
|
||||
visibleLocalMessageCount = 0
|
||||
localHistoryExhausted = false
|
||||
entranceModes = [:]
|
||||
knownEntranceIDs = []
|
||||
hasPlayedOpeningEntrance = false
|
||||
@@ -495,8 +502,21 @@ struct ChatView: View {
|
||||
|
||||
private func rebuildTimelineEntries(from newMessages: [ChatMessage]? = nil) {
|
||||
let source = newMessages ?? messages
|
||||
timelineEntries = ChatTimelineEntry.make(from: source)
|
||||
updateEntranceModes(for: source)
|
||||
if visibleLocalMessageCount == 0 {
|
||||
visibleLocalMessageCount = min(Self.localHistoryPageSize, source.count)
|
||||
} else if localHistoryExhausted {
|
||||
visibleLocalMessageCount = source.count
|
||||
} else {
|
||||
visibleLocalMessageCount = min(visibleLocalMessageCount, source.count)
|
||||
}
|
||||
localHistoryExhausted = visibleLocalMessageCount >= source.count
|
||||
let visible = Array(source.suffix(visibleLocalMessageCount))
|
||||
timelineEntries = ChatTimelineEntry.make(from: visible)
|
||||
updateEntranceModes(for: visible)
|
||||
}
|
||||
|
||||
private var hasMoreLocalHistory: Bool {
|
||||
visibleLocalMessageCount < messages.count
|
||||
}
|
||||
|
||||
/// Decides which rows play a Messages-style entrance after a rebuild.
|
||||
@@ -588,7 +608,7 @@ struct ChatView: View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 5) {
|
||||
if hasCompletedInitialScroll,
|
||||
model.hasMoreOlderHistory,
|
||||
(hasMoreLocalHistory || model.hasMoreOlderHistory),
|
||||
!timelineEntries.isEmpty
|
||||
{
|
||||
Color.clear
|
||||
@@ -625,7 +645,7 @@ struct ChatView: View {
|
||||
.onAppear {
|
||||
// Empty timeline has no scroll gesture, so load
|
||||
// the first page directly when it appears.
|
||||
guard model.hasMoreOlderHistory,
|
||||
guard hasMoreLocalHistory || model.hasMoreOlderHistory,
|
||||
!model.isLoadingOlderHistory
|
||||
else { return }
|
||||
model
|
||||
@@ -815,6 +835,17 @@ struct ChatView: View {
|
||||
selectedMessageIDs.formIntersection(
|
||||
Set(timelineEntries.map(\.id))
|
||||
)
|
||||
guard historyLoadAnchorID != nil,
|
||||
!model.isLoadingOlderHistory,
|
||||
historyTopTriggerVisible,
|
||||
let anchor = historyLoadAnchorID
|
||||
else { return }
|
||||
Task { @MainActor in
|
||||
await Task.yield()
|
||||
proxy.scrollTo(anchor, anchor: .top)
|
||||
historyLoadAnchorID = nil
|
||||
historyTopTriggerArmed = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasCompletedInitialScroll,
|
||||
@@ -1995,6 +2026,16 @@ struct ChatView: View {
|
||||
historyTopTriggerArmed = false
|
||||
historyLoadAnchorID = timelineEntries.first?.id
|
||||
historyLoadEntryCount = timelineEntries.count
|
||||
if hasMoreLocalHistory {
|
||||
visibleLocalMessageCount = min(
|
||||
messages.count,
|
||||
visibleLocalMessageCount + Self.localHistoryPageSize
|
||||
)
|
||||
localHistoryExhausted = visibleLocalMessageCount >= messages.count
|
||||
rebuildTimelineEntries(from: messages)
|
||||
historyTopTriggerArmed = true
|
||||
return
|
||||
}
|
||||
model.loadOlderHistoryForSelectedConversation()
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ struct LocationMessagePreview: View {
|
||||
}
|
||||
|
||||
private func openInMaps() {
|
||||
let item = MKMapItem(placemark: MKPlacemark(coordinate: coordinate))
|
||||
let item = MKMapItem(location: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude), address: nil)
|
||||
item.name = "Геопозиция из Luma"
|
||||
item.openInMaps()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Combine
|
||||
import CommonCrypto
|
||||
import CryptoKit
|
||||
import os
|
||||
import Martin
|
||||
@preconcurrency import Martin
|
||||
import MartinOMEMO
|
||||
|
||||
extension XmppModuleIdentifier {
|
||||
@@ -22,7 +22,7 @@ extension XmppModuleIdentifier {
|
||||
/// `<bundle>` with `<spk>/<spks>/<ik>/<prekeys><pk>`).
|
||||
/// The Double Ratchet sessions are shared with the legacy OMEMO module
|
||||
/// through the same SignalStorage.
|
||||
final class LumaOMEMO2Module: AbstractPEPModule, XmppModule {
|
||||
final class LumaOMEMO2Module: AbstractPEPModule, XmppModule, @unchecked Sendable {
|
||||
public static let ID = "omemo2"
|
||||
public static let IDENTIFIER = XmppModuleIdentifier<LumaOMEMO2Module>()
|
||||
public static let XMLNS = "urn:xmpp:omemo:2"
|
||||
@@ -98,7 +98,8 @@ final class LumaOMEMO2Module: AbstractPEPModule, XmppModule {
|
||||
return
|
||||
}
|
||||
let known = listEl.mapChildren(transform: { $0.getAttribute("id").flatMap(Int32.init) })
|
||||
self.devicesQueue.async { self.devices[jid] = known }
|
||||
let jidString = jid.stringValue
|
||||
self.devicesQueue.async { self.devices[BareJID(jidString)] = known }
|
||||
continuation.resume(returning: known)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ final class LumaSaslFailureModule: XmppModuleBase, XmppModule {
|
||||
}
|
||||
|
||||
func process(stanza: Stanza) throws {
|
||||
let condition = stanza.findChild()?.name
|
||||
let condition = stanza.firstChild(where: { _ in true })?.name
|
||||
let text = stanza.firstChild(name: "text")?.value
|
||||
lock.lock()
|
||||
stored = Failure(condition: condition, text: text)
|
||||
|
||||
@@ -61,7 +61,7 @@ enum SCRAMHash {
|
||||
|
||||
func hash(data: Data) -> Data {
|
||||
var output = [UInt8](repeating: 0, count: digestLength)
|
||||
data.withUnsafeBytes { bytes in
|
||||
_ = data.withUnsafeBytes { bytes in
|
||||
switch self {
|
||||
case .sha1:
|
||||
CC_SHA1(bytes.baseAddress, CC_LONG(bytes.count), &output)
|
||||
|
||||
@@ -201,7 +201,7 @@ final class LumaTLSNetworkProcessor: SocketConnector.NetworkProcessor, SSLNetwor
|
||||
override func read(data: Data) {
|
||||
guard !handshakeFailed else { return }
|
||||
ensureTLS()
|
||||
guard let ssl, let readBIO else { return }
|
||||
guard ssl != nil, let readBIO else { return }
|
||||
data.withUnsafeBytes { raw in
|
||||
_ = BIO_write(readBIO, raw.baseAddress, Int32(raw.count))
|
||||
}
|
||||
@@ -415,9 +415,7 @@ final class LumaTLSNetworkProcessor: SocketConnector.NetworkProcessor, SSLNetwor
|
||||
switch certificateValidation {
|
||||
case .default:
|
||||
SecTrustSetPolicies(trust, SecPolicyCreateSSL(true, domain as CFString))
|
||||
var result = SecTrustResultType.invalid
|
||||
SecTrustEvaluate(trust, &result)
|
||||
validated = result == .proceed || result == .unspecified
|
||||
validated = SecTrustEvaluateWithError(trust, nil)
|
||||
case .fingerprint(let fingerprint):
|
||||
validated = Self.matchesFingerprint(leafDER, fingerprint: fingerprint)
|
||||
case .customValidator(let validator):
|
||||
|
||||
Vendored
+6
-1
@@ -23,7 +23,12 @@ let package = Package(
|
||||
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
|
||||
.target(
|
||||
name: "MartinOMEMO",
|
||||
dependencies: ["Martin", "libsignal"]),
|
||||
dependencies: ["Martin", "libsignal"],
|
||||
swiftSettings: [
|
||||
// Martin 3.2 still exposes deprecated event APIs that are
|
||||
// required for compatibility with older clients.
|
||||
.unsafeFlags(["-suppress-warnings"])
|
||||
]),
|
||||
.testTarget(
|
||||
name: "MartinOMEMOTests",
|
||||
dependencies: ["MartinOMEMO"]),
|
||||
|
||||
+12
@@ -69,6 +69,15 @@ targets:
|
||||
CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
UILaunchScreen: {}
|
||||
UISupportedInterfaceOrientations:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
UISupportedInterfaceOrientations~ipad:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationPortraitUpsideDown
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
UIBackgroundModes:
|
||||
- audio
|
||||
- fetch
|
||||
@@ -89,6 +98,9 @@ targets:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: $(COMPANION_BUNDLE_IDENTIFIER)
|
||||
PRODUCT_NAME: Luma
|
||||
TARGETED_DEVICE_FAMILY: "1,2"
|
||||
# The remaining diagnostics come from APIs that Martin still requires
|
||||
# for XMPP/TLS compatibility (QueueDispatcher and SecureTransport).
|
||||
SWIFT_SUPPRESS_WARNINGS: true
|
||||
SUPPORTS_MACCATALYST: false
|
||||
DEVELOPMENT_TEAM: K3AS449A74
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
|
||||
|
||||
Reference in new issue
Block a user