From 879e7b0499638c78240dffd121f085d61f49c5a8 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 14 Sep 2022 10:43:58 +0100 Subject: [PATCH 1/2] Add timeline properties for edited and reactions. Not yet visible, waiting for timeline API. --- ElementX.xcodeproj/project.pbxproj | 32 +++++++ .../SwiftUI/Layout/AlignedScrollView.swift | 37 +++++++++ .../SwiftUI/Layout/ViewFrameReader.swift | 5 +- .../Screens/RoomScreen/RoomScreenModels.swift | 1 + .../RoomScreen/RoomScreenViewModel.swift | 3 + .../Screens/RoomScreen/View/RoomScreen.swift | 4 +- .../Style/TimelineItemBubbledStylerView.swift | 38 +++++++-- .../Style/TimelineItemPlainStylerView.swift | 26 +++++- .../Supplementary/TimelineReactionsView.swift | 83 +++++++++++++++++++ .../View/Timeline/EmoteRoomTimelineView.swift | 6 +- .../View/Timeline/FormattedBodyText.swift | 8 ++ .../View/Timeline/ImageRoomTimelineView.swift | 9 +- .../Timeline/NoticeRoomTimelineView.swift | 6 +- .../View/Timeline/TextRoomTimelineView.swift | 7 +- .../RoomScreen/View/TimelineItemList.swift | 5 +- .../RoomScreen/View/TimelineView.swift | 7 +- .../Timeline/MockRoomTimelineController.swift | 14 +++- .../AggregratedReaction.swift | 27 ++++++ .../EventBasedTimelineItemProtocol.swift | 2 + .../Items/EmoteRoomTimelineItem.swift | 2 + .../Items/ImageRoomTimelineItem.swift | 2 + .../Items/NoticeRoomTimelineItem.swift | 2 + .../Items/RoomTimelineItemProperties.swift | 25 ++++++ .../Items/TextRoomTimelineItem.swift | 2 + .../RoomTimelineItemFactory.swift | 12 ++- changelog.d/111.wip | 1 + 26 files changed, 329 insertions(+), 37 deletions(-) create mode 100644 ElementX/Sources/Other/SwiftUI/Layout/AlignedScrollView.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift create mode 100644 ElementX/Sources/Services/Timeline/TimeLineItemContent/AggregratedReaction.swift create mode 100644 ElementX/Sources/Services/Timeline/TimelineItems/Items/RoomTimelineItemProperties.swift create mode 100644 changelog.d/111.wip diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 53c5777a67..5c676c0575 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -95,6 +95,7 @@ 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; }; 388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */; }; 38C76D586404C1FDED095F3A /* LoginModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B01468022EC826CB2FD2C0 /* LoginModels.swift */; }; + 39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */; }; 3A64A93A651A3CB8774ADE8E /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; }; 3B770CB4DED51CC362C66D47 /* SettingsModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4990FDBDA96B88E214F92F48 /* SettingsModels.swift */; }; 3C549A0BF39F8A854D45D9FD /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; }; @@ -164,6 +165,7 @@ 6FC10A00D268FCD48B631E37 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */; }; 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; }; 706F79A39BDB32F592B8C2C7 /* UIKitBackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCD9116ADDE820E4E30F92 /* UIKitBackgroundTask.swift */; }; + 7096FA3AC218D914E88BFB70 /* AggregratedReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */; }; 72F6E890820FF606A7E276C8 /* SplashScreenPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A534A4619D8FEFB6439FCC /* SplashScreenPageView.swift */; }; 7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; }; 744C029EB6C43429926A0499 /* AnalyticsPromptViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A86C95340248A8B7BA9A43 /* AnalyticsPromptViewModelProtocol.swift */; }; @@ -224,6 +226,7 @@ 992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; }; 99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; }; 9AC5F8142413862A9E3A2D98 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; }; + 9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */; }; 9B8DE1D424E37581C7D99CCC /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC7CCC6DE5FA623E31BA8546 /* RoomTimelineControllerProtocol.swift */; }; 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */; }; 9BE7A9CF6C593251D734B461 /* MockServerSelectionScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A20AE75FF4FF35B1FF6CA7 /* MockServerSelectionScreenState.swift */; }; @@ -280,6 +283,7 @@ C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; }; C7B251DC896C0867C51B616D /* AnalyticsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */; }; C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; }; + C8E82786DE1B6A400DA9BA25 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */; }; CB137BFB3E083C33E398A6CB /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; }; CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */; }; CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; }; @@ -429,6 +433,7 @@ 2583416C8974272ADBADDBE1 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-TW"; path = "zh-TW.lproj/Localizable.stringsdict"; sourceTree = ""; }; 26C4D226FCD20BAC53F1E092 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/Localizable.strings; sourceTree = ""; }; 28959C7DB36C7688A01D4045 /* BugReportViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelProtocol.swift; sourceTree = ""; }; + 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = ""; }; 28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = ""; }; 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = ""; }; 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; @@ -444,6 +449,7 @@ 32CE6D4FF64C9A3C18619224 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; 3340ABAE3A4647E80163AE18 /* TemplateViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewModelTests.swift; sourceTree = ""; }; 33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 36322DD0D4E29D31B0945ADC /* EventBriefFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactory.swift; sourceTree = ""; }; 3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -593,6 +599,7 @@ 874A1842477895F199567BD7 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; 878B7C1885486FB4BE41631D /* iw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = iw; path = iw.lproj/Localizable.stringsdict; sourceTree = ""; }; 885D8C42DD17625B5261BEFF /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; + 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedScrollView.swift; sourceTree = ""; }; 8888D13645C04AC9818F5778 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; 8A9AE4967817E9608E22EB44 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; @@ -775,6 +782,7 @@ F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetectorTests.swift; sourceTree = ""; }; F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBugReportService.swift; sourceTree = ""; }; + F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = ""; }; F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = ""; }; F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1187,6 +1195,14 @@ path = ErrorHandling; sourceTree = ""; }; + 5A7A7D6D373D411C8C48B881 /* TimeLineItemContent */ = { + isa = PBXGroup; + children = ( + F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */, + ); + path = TimeLineItemContent; + sourceTree = ""; + }; 605F8221E52991786397FCC9 /* View */ = { isa = PBXGroup; children = ( @@ -1295,6 +1311,7 @@ F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */, 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */, 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */, + 289FA233E896FBC5956C67E0 /* RoomTimelineItemProperties.swift */, A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */, F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */, ); @@ -1336,6 +1353,7 @@ 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */, 874A1842477895F199567BD7 /* TimelineView.swift */, A312471EA62EFB0FD94E60DC /* Style */, + CCD48459CA34A1928EC7A26A /* Supplementary */, B7D3886505ECC85A06DA8258 /* Timeline */, ); path = View; @@ -1650,9 +1668,18 @@ path = UITests; sourceTree = ""; }; + CCD48459CA34A1928EC7A26A /* Supplementary */ = { + isa = PBXGroup; + children = ( + 351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */, + ); + path = Supplementary; + sourceTree = ""; + }; CE2FBFD64A89F5DBE4EB30DB /* Layout */ = { isa = PBXGroup; children = ( + 8872E9C5E91E9F2BFC4EBCCA /* AlignedScrollView.swift */, 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */, 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */, EFF7BF82A950B91BC5469E91 /* ViewFrameReader.swift */, @@ -1792,6 +1819,7 @@ CC7CCC6DE5FA623E31BA8546 /* RoomTimelineControllerProtocol.swift */, 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */, 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */, + 5A7A7D6D373D411C8C48B881 /* TimeLineItemContent */, 95BE1C7CB2C80344FF0BE724 /* TimelineItems */, ); path = Timeline; @@ -2223,7 +2251,9 @@ D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */, 4D23C56053013437C35E511E /* ActivityIndicatorPresenterType.swift in Sources */, FC6B7436C3A5B3D0565227D5 /* ActivityIndicatorView.swift in Sources */, + 7096FA3AC218D914E88BFB70 /* AggregratedReaction.swift in Sources */, A50849766F056FD1DB942DEA /* AlertInfo.swift in Sources */, + 39929D29B265C3F6606047DE /* AlignedScrollView.swift in Sources */, A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */, F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */, 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */, @@ -2360,6 +2390,7 @@ 9B8DE1D424E37581C7D99CCC /* RoomTimelineControllerProtocol.swift in Sources */, 4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */, 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */, + C8E82786DE1B6A400DA9BA25 /* RoomTimelineItemProperties.swift in Sources */, 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */, 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */, 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */, @@ -2415,6 +2446,7 @@ 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */, 4D970CB606276717B43E2332 /* TimelineItemList.swift in Sources */, F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */, + 9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */, ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */, 69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */, FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */, diff --git a/ElementX/Sources/Other/SwiftUI/Layout/AlignedScrollView.swift b/ElementX/Sources/Other/SwiftUI/Layout/AlignedScrollView.swift new file mode 100644 index 0000000000..15c358fa3f --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Layout/AlignedScrollView.swift @@ -0,0 +1,37 @@ +// +// Copyright 2022 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 SwiftUI + +/// A horizontal `ScrollView` that will pad the start of it's content +/// when the alignment is set to `.trailing`. +struct AlignedScrollView: View { + let alignment: HorizontalAlignment + var showsIndicators = true + + @ViewBuilder let content: () -> Content + + @State private var scrollViewFrame: CGRect = .zero + + var body: some View { + ScrollView(.horizontal, showsIndicators: showsIndicators) { + content() + .frame(minWidth: scrollViewFrame.width, + alignment: Alignment(horizontal: alignment, vertical: .center)) + } + .background(ViewFrameReader(frame: $scrollViewFrame)) + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift b/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift index 19c411d7fa..9a9d08ea23 100644 --- a/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift +++ b/ElementX/Sources/Other/SwiftUI/Layout/ViewFrameReader.swift @@ -35,8 +35,9 @@ struct ViewFrameReader: View { .preference(key: FramePreferenceKey.self, value: geometry.frame(in: .local)) } - .onPreferenceChange(FramePreferenceKey.self) { - frame = $0 + .onPreferenceChange(FramePreferenceKey.self) { newValue in + guard frame != newValue else { return } + frame = newValue } } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index a3f5fec8f7..907701bedf 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -31,6 +31,7 @@ enum RoomScreenViewAction { case itemDisappeared(id: String) case linkClicked(url: URL) case sendMessage + case sendReaction(key: String, eventID: String) } struct RoomScreenViewState: BindableState { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index e2c062040b..347eb70c73 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -89,6 +89,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol await timelineController.sendMessage(state.bindings.composerText) state.bindings.composerText = "" + case .sendReaction(let key, _): + #warning("Reaction implementation awaiting SDK support.") + MXLog.warning("React with \(key) failed. Not implemented.") } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 7ed2818bbb..eec91f0d5b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -21,7 +21,9 @@ struct RoomScreen: View { var body: some View { VStack(spacing: 0.0) { - TimelineView(context: context) + TimelineView() + .environmentObject(context) + MessageComposer(text: $context.composerText, disabled: context.viewState.sendButtonDisabled) { sendMessage() } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index 085e0a3768..3e9fa902ef 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -18,6 +18,8 @@ import Foundation import SwiftUI struct TimelineItemBubbledStylerView: View { + @EnvironmentObject private var context: RoomScreenViewModel.Context + let timelineItem: EventBasedTimelineItemProtocol @ViewBuilder let content: () -> Content @@ -25,7 +27,7 @@ struct TimelineItemBubbledStylerView: View { @ScaledMetric private var minBubbleWidth = 44 var body: some View { - VStack(alignment: timelineItem.isOutgoing ? .trailing : .leading, spacing: -5) { + VStack(alignment: alignment, spacing: -5) { if !timelineItem.isOutgoing { header .zIndex(1) @@ -33,18 +35,18 @@ struct TimelineItemBubbledStylerView: View { if timelineItem.isOutgoing { HStack { Spacer() - styledContent + styledContentWithReactions } .padding(.trailing, 16) .padding(.leading, 51) } else { - styledContent + styledContentWithReactions .padding(.leading, 16) .padding(.trailing, 51) } } } - + @ViewBuilder private var header: some View { if timelineItem.shouldShowSenderDetails { @@ -62,6 +64,22 @@ struct TimelineItemBubbledStylerView: View { } } } + + private var styledContentWithReactions: some View { + // Figma has a spacing of -4 but it doesn't take into account + // the centre aligned stroke width so we use -5 here + VStack(alignment: alignment, spacing: -5) { + styledContent + + if !timelineItem.properties.reactions.isEmpty { + TimelineReactionsView(reactions: timelineItem.properties.reactions, + alignment: alignment) { key in + context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id)) + } + .padding(.horizontal, 12) + } + } + } @ViewBuilder var styledContent: some View { @@ -84,9 +102,11 @@ struct TimelineItemBubbledStylerView: View { content() .frame(minWidth: minBubbleWidth, alignment: .leading) - Text(timelineItem.timestamp) - .foregroundColor(Color.element.tertiaryContent) - .font(.element.caption2) + if timelineItem.properties.isEdited { + Text(ElementL10n.editedSuffix) + .font(.element.caption2) + .foregroundColor(.element.tertiaryContent) + } } .padding(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8)) .clipped() @@ -103,6 +123,10 @@ struct TimelineItemBubbledStylerView: View { let opacity = colorScheme == .light ? 0.06 : 0.15 return timelineItem.isOutgoing ? .element.accent.opacity(opacity) : .element.system } + + private var alignment: HorizontalAlignment { + timelineItem.isOutgoing ? .trailing : .leading + } } struct TimelineItemBubbledStylerView_Previews: PreviewProvider { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift index 25ef5457ce..a9eb1e93a7 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift @@ -18,13 +18,19 @@ import Foundation import SwiftUI struct TimelineItemPlainStylerView: View { + @EnvironmentObject private var context: RoomScreenViewModel.Context + let timelineItem: EventBasedTimelineItemProtocol @ViewBuilder let content: () -> Content var body: some View { VStack(alignment: .leading) { header - content() + + VStack(alignment: .leading, spacing: 4) { + content() + supplementaryViews + } } } @@ -44,6 +50,24 @@ struct TimelineItemPlainStylerView: View { } } } + + @ViewBuilder + private var supplementaryViews: some View { + VStack { + if timelineItem.properties.isEdited { + Text(ElementL10n.editedSuffix) + .font(.element.footnote) + .foregroundColor(.element.tertiaryContent) + } + + if !timelineItem.properties.reactions.isEmpty { + TimelineReactionsView(reactions: timelineItem.properties.reactions, + alignment: .leading) { key in + context.send(viewAction: .sendReaction(key: key, eventID: timelineItem.id)) + } + } + } + } } struct TimelineItemPlainStylerView_Previews: PreviewProvider { diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift new file mode 100644 index 0000000000..5d788cb599 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift @@ -0,0 +1,83 @@ +// +// Copyright 2022 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 SwiftUI + +struct TimelineReactionsView: View { + let reactions: [AggregatedReaction] + let alignment: HorizontalAlignment + let action: (String) -> Void + + var body: some View { + AlignedScrollView(alignment: alignment, showsIndicators: false) { + HStack { + ForEach(reactions, id: \.self) { reaction in + TimelineReactionButton(reaction: reaction, action: action) + } + } + } + } +} + +struct TimelineReactionButton: View { + let reaction: AggregatedReaction + let action: (String) -> Void + + var body: some View { + Button { action(reaction.key) } label: { label } + } + + var label: some View { + HStack(spacing: 4) { + Text(reaction.key) + .font(.element.caption1) + Text(String(reaction.count)) + .font(.element.caption1) + .foregroundColor(.element.secondaryContent) + } + .padding(.vertical, 5) + .padding(.horizontal, 6) + .background( + Capsule() + .strokeBorder(reaction.isHighlighted ? Color.element.accent : .element.background, lineWidth: 2) + .background(reaction.isHighlighted ? Color.element.accent.opacity(0.1) : .element.system, in: Capsule()) + ) + .accessibilityElement(children: .combine) + } +} + +struct TimelineReactionView_Previews: PreviewProvider { + static var previews: some View { + VStack { + TimelineReactionButton(reaction: AggregatedReaction(key: "👍", count: 5, isHighlighted: true)) { _ in } + TimelineReactionButton(reaction: AggregatedReaction(key: "👏", count: 1, isHighlighted: false)) { _ in } + TimelineReactionButton(reaction: AggregatedReaction(key: "🎉", count: 20, isHighlighted: false)) { _ in } + + TimelineReactionsView(reactions: [ + AggregatedReaction(key: "😅", count: 1, isHighlighted: true), + AggregatedReaction(key: "🤷‍♂️", count: 1, isHighlighted: false), + AggregatedReaction(key: "🎨", count: 6, isHighlighted: true), + AggregatedReaction(key: "🎉", count: 8, isHighlighted: false), + AggregatedReaction(key: "🤯", count: 15, isHighlighted: true), + AggregatedReaction(key: "🫣", count: 1, isHighlighted: false), + AggregatedReaction(key: "🚀", count: 3, isHighlighted: true), + AggregatedReaction(key: "😇", count: 2, isHighlighted: false), + AggregatedReaction(key: "🤭", count: 9, isHighlighted: true), + AggregatedReaction(key: "🫤", count: 10, isHighlighted: false) + ], alignment: .leading) { _ in } + } + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index 8bb4e75ac5..cbb256ee3f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -27,8 +27,7 @@ struct EmoteRoomTimelineView: View { if let attributedComponents = timelineItem.attributedComponents { FormattedBodyText(attributedComponents: attributedComponents) } else { - Text(timelineItem.text) - .foregroundColor(.element.primaryContent) + FormattedBodyText(text: timelineItem.text) } } } @@ -61,6 +60,7 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: true, isOutgoing: false, - senderId: senderId) + senderId: senderId, + properties: RoomTimelineItemProperties()) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index 0226f86b44..442136a004 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -43,6 +43,12 @@ struct FormattedBodyText: View { } } +extension FormattedBodyText { + init(text: String) { + attributedComponents = [.init(attributedString: AttributedString(text), isBlockquote: false)] + } +} + struct FormattedBodyText_Previews: PreviewProvider { static var previews: some View { body.preferredColorScheme(.light) @@ -74,6 +80,7 @@ struct FormattedBodyText_Previews: PreviewProvider { ] let attributedStringBuilder = AttributedStringBuilder() + VStack(alignment: .leading, spacing: 24.0) { ForEach(htmlStrings, id: \.self) { htmlString in let attributedString = attributedStringBuilder.fromHTML(htmlString) @@ -83,6 +90,7 @@ struct FormattedBodyText_Previews: PreviewProvider { .fixedSize() } } + FormattedBodyText(text: "Some plain text that's not an attributed component.") } } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift index 1fa7b3d6c8..143801a90f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift @@ -73,7 +73,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { isOutgoing: false, senderId: "Bob", source: nil, - image: UIImage(systemName: "photo"))) + image: UIImage(systemName: "photo"), + properties: RoomTimelineItemProperties())) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Some other image", @@ -82,7 +83,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { isOutgoing: false, senderId: "Bob", source: nil, - image: nil)) + image: nil, + properties: RoomTimelineItemProperties())) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Blurhashed image", @@ -93,7 +95,8 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { source: nil, image: nil, aspectRatio: 0.7, - blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) + blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", + properties: RoomTimelineItemProperties())) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index 5ca22d2a9e..3a997ab041 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -27,8 +27,7 @@ struct NoticeRoomTimelineView: View { if let attributedComponents = timelineItem.attributedComponents { FormattedBodyText(attributedComponents: attributedComponents) } else { - Text(timelineItem.text) - .foregroundColor(.element.primaryContent) + FormattedBodyText(text: timelineItem.text) } } } @@ -62,6 +61,7 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: true, isOutgoing: false, - senderId: senderId) + senderId: senderId, + properties: RoomTimelineItemProperties()) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index 45d4bf8227..55fe96094a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -25,9 +25,7 @@ struct TextRoomTimelineView: View { if let attributedComponents = timelineItem.attributedComponents { FormattedBodyText(attributedComponents: attributedComponents) } else { - Text(timelineItem.text) - .font(.body) - .multilineTextAlignment(.leading) + FormattedBodyText(text: timelineItem.text) } } .id(timelineItem.id) @@ -78,6 +76,7 @@ struct TextRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: shouldShowSenderDetails, isOutgoing: isOutgoing, - senderId: senderId) + senderId: senderId, + properties: RoomTimelineItemProperties()) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift index e49c9551a0..d776fbd315 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemList.swift @@ -24,7 +24,7 @@ struct TimelineItemList: View { @State private var hasPendingChanges = false @ObservedObject private var settings = ElementSettings.shared - @ObservedObject var context: RoomScreenViewModel.Context + @EnvironmentObject var context: RoomScreenViewModel.Context let bottomVisiblePublisher: PassthroughSubject let scrollToBottomPublisher: PassthroughSubject @@ -163,6 +163,7 @@ struct TimelineItemList_Previews: PreviewProvider { timelineViewFactory: RoomTimelineViewFactory(), roomName: nil) - TimelineItemList(context: viewModel.context, bottomVisiblePublisher: PassthroughSubject(), scrollToBottomPublisher: PassthroughSubject()) + TimelineItemList(bottomVisiblePublisher: PassthroughSubject(), scrollToBottomPublisher: PassthroughSubject()) + .environmentObject(viewModel.context) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift index 55bf45db49..20bbb4175d 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineView.swift @@ -25,11 +25,9 @@ struct TimelineView: View { @State private var scrollToBottomPublisher = PassthroughSubject() @State private var scollToBottomButtonVisible = false - @ObservedObject var context: RoomScreenViewModel.Context - var body: some View { ZStack(alignment: .bottomTrailing) { - TimelineItemList(context: context, bottomVisiblePublisher: bottomVisiblePublisher, scrollToBottomPublisher: scrollToBottomPublisher) + TimelineItemList(bottomVisiblePublisher: bottomVisiblePublisher, scrollToBottomPublisher: scrollToBottomPublisher) scrollToBottomButton } } @@ -63,6 +61,7 @@ struct TimelineView_Previews: PreviewProvider { timelineViewFactory: RoomTimelineViewFactory(), roomName: nil) - TimelineView(context: viewModel.context) + TimelineView() + .environmentObject(viewModel.context) } } diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift index 3acd03ca63..31c972030f 100644 --- a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift @@ -30,14 +30,18 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { shouldShowSenderDetails: true, isOutgoing: false, senderId: "", - senderDisplayName: "Some user with a really long long long long long display name"), + senderDisplayName: "Some user with a really long long long long long display name", + properties: RoomTimelineItemProperties(isEdited: true)), TextRoomTimelineItem(id: UUID().uuidString, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, isOutgoing: false, senderId: "", - senderDisplayName: "Alice"), + senderDisplayName: "Alice", + properties: RoomTimelineItemProperties(reactions: [ + AggregatedReaction(key: "🙌", count: 1, isHighlighted: true) + ])), SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"), TextRoomTimelineItem(id: UUID().uuidString, @@ -46,7 +50,11 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol { shouldShowSenderDetails: false, isOutgoing: true, senderId: "", - senderDisplayName: "Bob")] + senderDisplayName: "Bob", + properties: RoomTimelineItemProperties(reactions: [ + AggregatedReaction(key: "🙏", count: 1, isHighlighted: false), + AggregatedReaction(key: "😁", count: 3, isHighlighted: false) + ]))] func paginateBackwards(_ count: UInt) async -> Result { .failure(.generic) diff --git a/ElementX/Sources/Services/Timeline/TimeLineItemContent/AggregratedReaction.swift b/ElementX/Sources/Services/Timeline/TimeLineItemContent/AggregratedReaction.swift new file mode 100644 index 0000000000..1cb39ebd75 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimeLineItemContent/AggregratedReaction.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 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 + +/// Represents all reactions of the same type for a single event. +struct AggregatedReaction: Equatable, Hashable { + /// The reaction that was sent. + let key: String + /// The number of times this reactions was sent. + let count: Int + /// Whether to highlight the reaction, indicating that the current user sent this reaction. + let isHighlighted: Bool +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift index 604089bc7c..c224856f5e 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -26,4 +26,6 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { var senderId: String { get } var senderDisplayName: String? { get set } var senderAvatar: UIImage? { get set } + + var properties: RoomTimelineItemProperties { get } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift index 8ea6daeb4d..74372a4d81 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift @@ -28,4 +28,6 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa let senderId: String var senderDisplayName: String? var senderAvatar: UIImage? + + let properties: RoomTimelineItemProperties } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift index e0fb461d89..888607fcb4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift @@ -35,4 +35,6 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa var height: CGFloat? var aspectRatio: CGFloat? var blurhash: String? + + let properties: RoomTimelineItemProperties } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift index 48b0c1be5c..9da589c0f0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift @@ -28,4 +28,6 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equ let senderId: String var senderDisplayName: String? var senderAvatar: UIImage? + + let properties: RoomTimelineItemProperties } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/RoomTimelineItemProperties.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/RoomTimelineItemProperties.swift new file mode 100644 index 0000000000..d4cf07a528 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/RoomTimelineItemProperties.swift @@ -0,0 +1,25 @@ +// +// Copyright 2022 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 + +/// Properties of a matrix event that are common between all timeline items. +struct RoomTimelineItemProperties: Equatable { + /// Whether the item has been edited. + var isEdited = false + /// The aggregated reactions that have been sent for this item. + var reactions: [AggregatedReaction] = [] +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift index 02fe654452..8903c833d6 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift @@ -28,4 +28,6 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat let senderId: String var senderDisplayName: String? var senderAvatar: UIImage? + + let properties: RoomTimelineItemProperties } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 04305bcec4..8441153267 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -67,7 +67,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage) + senderAvatar: avatarImage, + properties: RoomTimelineItemProperties()) } private func buildImageTimelineItemFromMessage(_ message: ImageRoomMessage, @@ -94,7 +95,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { width: message.width, height: message.height, aspectRatio: aspectRatio, - blurhash: message.blurhash) + blurhash: message.blurhash, + properties: RoomTimelineItemProperties()) } private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage, @@ -113,7 +115,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage) + senderAvatar: avatarImage, + properties: RoomTimelineItemProperties()) } private func buildEmoteTimelineItemFromMessage(_ message: EmoteRoomMessage, @@ -132,6 +135,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage) + senderAvatar: avatarImage, + properties: RoomTimelineItemProperties()) } } diff --git a/changelog.d/111.wip b/changelog.d/111.wip new file mode 100644 index 0000000000..5699da3d37 --- /dev/null +++ b/changelog.d/111.wip @@ -0,0 +1 @@ +Add isEdited and reactions properties to timeline items. From ee33153f17fb380d24d989c0769fa104df91bfc8 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 14 Sep 2022 11:41:57 +0100 Subject: [PATCH 2/2] Add default values for timeline item properties. --- .../View/Timeline/EmoteRoomTimelineView.swift | 3 +-- .../View/Timeline/ImageRoomTimelineView.swift | 9 +++------ .../View/Timeline/NoticeRoomTimelineView.swift | 3 +-- .../View/Timeline/TextRoomTimelineView.swift | 3 +-- .../TimelineItems/Items/EmoteRoomTimelineItem.swift | 2 +- .../TimelineItems/Items/ImageRoomTimelineItem.swift | 2 +- .../TimelineItems/Items/NoticeRoomTimelineItem.swift | 2 +- .../TimelineItems/Items/TextRoomTimelineItem.swift | 2 +- .../TimelineItems/RoomTimelineItemFactory.swift | 12 ++++-------- 9 files changed, 14 insertions(+), 24 deletions(-) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index cbb256ee3f..696bfaa26a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -60,7 +60,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: true, isOutgoing: false, - senderId: senderId, - properties: RoomTimelineItemProperties()) + senderId: senderId) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift index 143801a90f..1fa7b3d6c8 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift @@ -73,8 +73,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { isOutgoing: false, senderId: "Bob", source: nil, - image: UIImage(systemName: "photo"), - properties: RoomTimelineItemProperties())) + image: UIImage(systemName: "photo"))) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Some other image", @@ -83,8 +82,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { isOutgoing: false, senderId: "Bob", source: nil, - image: nil, - properties: RoomTimelineItemProperties())) + image: nil)) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: UUID().uuidString, text: "Blurhashed image", @@ -95,8 +93,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { source: nil, image: nil, aspectRatio: 0.7, - blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW", - properties: RoomTimelineItemProperties())) + blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index 3a997ab041..5f67c7b9ea 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -61,7 +61,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: true, isOutgoing: false, - senderId: senderId, - properties: RoomTimelineItemProperties()) + senderId: senderId) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index 55fe96094a..418df38922 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -76,7 +76,6 @@ struct TextRoomTimelineView_Previews: PreviewProvider { timestamp: timestamp, shouldShowSenderDetails: shouldShowSenderDetails, isOutgoing: isOutgoing, - senderId: senderId, - properties: RoomTimelineItemProperties()) + senderId: senderId) } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift index 74372a4d81..9df1dc26e2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/EmoteRoomTimelineItem.swift @@ -29,5 +29,5 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa var senderDisplayName: String? var senderAvatar: UIImage? - let properties: RoomTimelineItemProperties + var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift index 888607fcb4..aacfe56c40 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift @@ -36,5 +36,5 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa var aspectRatio: CGFloat? var blurhash: String? - let properties: RoomTimelineItemProperties + var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift index 9da589c0f0..4dc98e5b1c 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/NoticeRoomTimelineItem.swift @@ -29,5 +29,5 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equ var senderDisplayName: String? var senderAvatar: UIImage? - let properties: RoomTimelineItemProperties + var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift index 8903c833d6..2d739df0c4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift @@ -29,5 +29,5 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat var senderDisplayName: String? var senderAvatar: UIImage? - let properties: RoomTimelineItemProperties + var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 8441153267..04305bcec4 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -67,8 +67,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage, - properties: RoomTimelineItemProperties()) + senderAvatar: avatarImage) } private func buildImageTimelineItemFromMessage(_ message: ImageRoomMessage, @@ -95,8 +94,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { width: message.width, height: message.height, aspectRatio: aspectRatio, - blurhash: message.blurhash, - properties: RoomTimelineItemProperties()) + blurhash: message.blurhash) } private func buildNoticeTimelineItemFromMessage(_ message: NoticeRoomMessage, @@ -115,8 +113,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage, - properties: RoomTimelineItemProperties()) + senderAvatar: avatarImage) } private func buildEmoteTimelineItemFromMessage(_ message: EmoteRoomMessage, @@ -135,7 +132,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { isOutgoing: isOutgoing, senderId: message.sender, senderDisplayName: displayName, - senderAvatar: avatarImage, - properties: RoomTimelineItemProperties()) + senderAvatar: avatarImage) } }