diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 712875dea..032fc2c3a 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -346,6 +346,7 @@ 5375902175B2FEA2949D7D74 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */; }; 53A55748D5F587C9061F98BF /* ServerConfigurationScreenViewStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */; }; 53A59720F4729D9BBFFB7CAB /* NotificationSettingsEditScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD9CB3B9DFA353AB2B7CD9F8 /* NotificationSettingsEditScreenCoordinator.swift */; }; + 53A795964991B06A672B4AAD /* CallNotificationRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584509A363ADF6244DFDB96A /* CallNotificationRoomTimelineView.swift */; }; 53C1E7F6A7D6409D89F36ED7 /* AggregatedReactionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69CB8242D69B7E4D0B32E18D /* AggregatedReactionMock.swift */; }; 53DEF39F0C4DE02E3FC56D91 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 800631D7250B7F93195035F1 /* KeychainAccess */; }; 53F1196F9C69512306A2693F /* TextRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */; }; @@ -955,6 +956,7 @@ E4B07FF075C99D04D9AF792D /* AppLockSetupPINScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B410B32B72C90BF94E481F33 /* AppLockSetupPINScreenModels.swift */; }; E4F924DECC66389C1C810550 /* AuthenticationStartScreenBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D685B4DB38BB5BD87C956A /* AuthenticationStartScreenBackgroundImage.swift */; }; E58F1F3276E98A93F7D39219 /* RoomPollsHistoryScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */; }; + E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */; }; E5F4C992845388B50BABACAA /* ServerSelectionScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */; }; E62EC30B39354A391E32A126 /* AudioRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */; }; E67418DACEDBC29E988E6ACD /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; }; @@ -1488,6 +1490,7 @@ 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConsentState.swift; sourceTree = ""; }; 57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItem.swift; sourceTree = ""; }; 57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLocator.swift; sourceTree = ""; }; + 584509A363ADF6244DFDB96A /* CallNotificationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineView.swift; sourceTree = ""; }; 584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = ""; }; 58D295F0081084F38DB20893 /* RoomNotificationSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelTests.swift; sourceTree = ""; }; @@ -2023,6 +2026,7 @@ E1573D28C8A9FB6399D0EEFB /* SecureBackupLogoutConfirmationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenCoordinator.swift; sourceTree = ""; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = ""; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; + E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = ""; }; E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = ""; }; E26C69EC1157D71CC61ADAE4 /* ScaledPaddingModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledPaddingModifier.swift; sourceTree = ""; }; E2B1CC9AA154F4D5435BF60A /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; @@ -4404,6 +4408,7 @@ isa = PBXGroup; children = ( E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */, + E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */, A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */, 5351EBD7A0B9610548E4B7B2 /* EncryptedRoomTimelineItem.swift */, 5281C5CDC4A712265A0B5FBF /* PollRoomTimelineItem.swift */, @@ -4478,6 +4483,7 @@ children = ( FC2D505742FDA21FCDC4C18A /* AudioRoomTimelineView.swift */, CA8F098AE48D958B4257EB24 /* CallInviteRoomTimelineView.swift */, + 584509A363ADF6244DFDB96A /* CallNotificationRoomTimelineView.swift */, 6E2656184491C505700D2405 /* CollapsibleRoomTimelineView.swift */, 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */, 56C1BCB9E83B09A45387FCA2 /* EncryptedRoomTimelineView.swift */, @@ -5940,6 +5946,8 @@ 6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */, 01B63F1A04A276B39AC17014 /* CallInviteRoomTimelineItem.swift in Sources */, F7048AD79361405AA95F2B3B /* CallInviteRoomTimelineView.swift in Sources */, + E5AB28123E2488F97E953AC0 /* CallNotificationRoomTimelineItem.swift in Sources */, + 53A795964991B06A672B4AAD /* CallNotificationRoomTimelineView.swift in Sources */, D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */, 763D69741D58D2B650BC1FC9 /* CallScreenCoordinator.swift in Sources */, B7C9E07F4F9CCC8DD7156A20 /* CallScreenModels.swift in Sources */, @@ -7335,7 +7343,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.0.7; + version = 1.0.8; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7c5e31efc..c8e3a4817 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -139,8 +139,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "5cc3a5c9990caa5b1cffdb4c7f5f0332d7bab290", - "version" : "1.0.7" + "revision" : "464227df09ff5ab4d00e432df131f779ba2f7ced", + "version" : "1.0.8" } }, { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/CallNotificationRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CallNotificationRoomTimelineView.swift new file mode 100644 index 000000000..4452c056e --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CallNotificationRoomTimelineView.swift @@ -0,0 +1,78 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Compound +import Foundation +import SwiftUI + +struct CallNotificationRoomTimelineView: View { + @EnvironmentObject private var context: RoomScreenViewModel.Context + + let timelineItem: CallNotificationRoomTimelineItem + + var body: some View { + HStack(spacing: 12) { + LoadableAvatarImage(url: timelineItem.sender.avatarURL, + name: timelineItem.sender.displayName ?? timelineItem.sender.id, + contentID: timelineItem.sender.id, + avatarSize: .user(on: .timeline), + imageProvider: context.imageProvider) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 0) { + Text(timelineItem.sender.disambiguatedDisplayName ?? timelineItem.sender.id) + .font(.compound.bodyLGSemibold) + .foregroundColor(.compound.textPrimary) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + + Label(title: { Text(L10n.commonCallStarted) }, + icon: { CompoundIcon(\.videoCallSolid, size: .medium, relativeTo: .compound.bodyMD) }) + .font(.compound.bodyMD) + .foregroundColor(.compound.textSecondary) + .labelStyle(.custom(spacing: 4)) + } + + Spacer() + + Text(timelineItem.timestamp) + .font(.compound.bodyXS) + .foregroundColor(.compound.textSecondary) + } + .padding(12) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(.compound.borderInteractiveSecondary, lineWidth: 1) + ) + .padding(16) + } +} + +struct CallNotificationRoomTimelineView_Previews: PreviewProvider, TestablePreview { + static let viewModel = RoomScreenViewModel.mock + + static var previews: some View { + body.environmentObject(viewModel.context) + } + + static var body: some View { + CallNotificationRoomTimelineView(timelineItem: .init(id: .random, + timestamp: "Now", + isEditable: false, + canBeRepliedTo: false, + sender: .init(id: "Bob"))) + } +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift index a9ecc032a..c7fcc54c3 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift @@ -68,6 +68,8 @@ struct RoomEventStringBuilder { return prefix(L10n.commonPollSummary(question), with: displayName) case .callInvite: return prefix(L10n.commonCallInvite, with: displayName) + case .callNotify: + return prefix(L10n.commonCallStarted, with: displayName) } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/CallNotificationRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/CallNotificationRoomTimelineItem.swift new file mode 100644 index 000000000..492fb4d2d --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/CallNotificationRoomTimelineItem.swift @@ -0,0 +1,28 @@ +// +// Copyright 2024 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct CallNotificationRoomTimelineItem: RoomTimelineItemProtocol, Equatable { + let id: TimelineItemIdentifier + let timestamp: String + let isEditable: Bool + let canBeRepliedTo: Bool + + let sender: TimelineItemSender + + var properties = RoomTimelineItemProperties() +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 04c2c0019..dcd238a57 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -75,6 +75,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { return buildPollTimelineItem(question, kind, maxSelections, answers, votes, endTime, eventItemProxy, isOutgoing, edited) case .callInvite: return buildCallInviteTimelineItem(for: eventItemProxy) + case .callNotify: + return buildCallNotificationTimelineItem(for: eventItemProxy) } } @@ -438,6 +440,14 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { sender: eventItemProxy.sender) } + private func buildCallNotificationTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol { + CallNotificationRoomTimelineItem(id: eventItemProxy.id, + timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), + isEditable: eventItemProxy.isEditable, + canBeRepliedTo: eventItemProxy.canBeRepliedTo, + sender: eventItemProxy.sender) + } + private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] { reactions.map { reaction in let senders = reaction.senders diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift index 32ba1125c..b5fd2dccd 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemView.swift @@ -76,6 +76,8 @@ struct RoomTimelineItemView: View { VoiceMessageRoomTimelineView(timelineItem: item, playerState: context?.viewState.audioPlayerStateProvider?(item.id) ?? AudioPlayerState(id: .timelineItemIdentifier(item.id), duration: 0)) case .callInvite(let item): CallInviteRoomTimelineView(timelineItem: item) + case .callNotification(let item): + CallNotificationRoomTimelineView(timelineItem: item) } } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift index e013043b6..99bd864c8 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemViewState.swift @@ -66,6 +66,7 @@ enum RoomTimelineItemType: Equatable { case poll(PollRoomTimelineItem) case voice(VoiceMessageRoomTimelineItem) case callInvite(CallInviteRoomTimelineItem) + case callNotification(CallNotificationRoomTimelineItem) init(item: RoomTimelineItemProtocol) { switch item { @@ -111,6 +112,8 @@ enum RoomTimelineItemType: Equatable { self = .voice(item) case let item as CallInviteRoomTimelineItem: self = .callInvite(item) + case let item as CallNotificationRoomTimelineItem: + self = .callNotification(item) default: fatalError("Unknown timeline item") } @@ -138,7 +141,8 @@ enum RoomTimelineItemType: Equatable { .location(let item as RoomTimelineItemProtocol), .poll(let item as RoomTimelineItemProtocol), .voice(let item as RoomTimelineItemProtocol), - .callInvite(let item as RoomTimelineItemProtocol): + .callInvite(let item as RoomTimelineItemProtocol), + .callNotification(let item as RoomTimelineItemProtocol): return item.id } } diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-en-GB.1.png new file mode 100644 index 000000000..061a8a929 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ee8b4a10bb5c075e073c13b41ee7ed91f05d0185a861a15e522a0fd95bc74d +size 81770 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-pseudo.1.png new file mode 100644 index 000000000..430d04d9b --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c417400b4ce675df3ec0406129c9686e4dd9f76c3965548502d129f7e5004119 +size 85067 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-en-GB.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-en-GB.1.png new file mode 100644 index 000000000..85ae85e21 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:463ad464df991675929a9840cd5fdd52e5ac3a918ccec2365d0a0f324c57e5ad +size 40115 diff --git a/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-pseudo.1.png b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-pseudo.1.png new file mode 100644 index 000000000..6edd0a3b5 --- /dev/null +++ b/PreviewTests/__Snapshots__/PreviewTests/test_callNotificationRoomTimelineView-iPhone-15-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ea024d9a548098c44911bb5e23cd444eeeadb73e16fc977b7cb8405a9cfd588 +size 42955 diff --git a/project.yml b/project.yml index 1db0296f3..00241558f 100644 --- a/project.yml +++ b/project.yml @@ -49,7 +49,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 1.0.7 + exactVersion: 1.0.8 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios