Reaction highlights (#1145)

* Highlight own reactions

- Highlight own reactions on timeline and improve style.
- Highlight reactions on message bottom sheet only when it our own.

* Improve overlay background colours

* pin to new sdk version

* Remove the red used for debug.

* Fix padding, overlap and only display count if > 1.

- Fix reaction padding and overlap of reaction and bubble.
- Only display count if greater than 1.
- Return EmptyView for TimelineDeliveryStatus.cancelled.

* Fix colors as per design recommendations.

* Add Snapshot tests for reactions and documentation.

- Add UITests for the timeline including reactions.
- Add the reference snapshots.
- Add documentation on how to run snapshot tests and update the reference snapshots.

* Fix Package.resolved
This commit is contained in:
David Langley
2023-06-25 13:24:48 +01:00
committed by GitHub
parent 41772b57eb
commit 13decc668a
12 changed files with 64 additions and 13 deletions
+8
View File
@@ -57,6 +57,14 @@ Git LFS is used to store UI test snapshots. `swift run tools setup-project` will
git lfs install git lfs install
``` ```
### Snapshot Tests
If you make changes to the UI you may cause existing UI Snapshot tests to fail. You can run the snapshot tests using `UITests` target. To update the reference snapshots, delete them from `element-x-ios/UITests/Sources/__Snapshots__/Application` and run the tests again.
These are the devices we store snapshots for that you will need to run against:
- iPhone 14
- iPad (9th generation)
### Githooks ### Githooks
The project uses its own shared githooks stored in the .githooks folder, you will need to configure git to use such folder, this is already done if you have run the setup tool with `swift run tools setup-project` otherwise you would need to run: The project uses its own shared githooks stored in the .githooks folder, you will need to configure git to use such folder, this is already done if you have run the setup tool with `swift run tools setup-project` otherwise you would need to run:
@@ -86,9 +86,8 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
} }
private var messageBubbleWithReactions: some View { private var messageBubbleWithReactions: some View {
// Figma has a spacing of -4 but it doesn't take into account // Figma overlaps reactions by 3
// the centre aligned stroke width so we use -5 here VStack(alignment: alignment, spacing: -3) {
VStack(alignment: alignment, spacing: -5) {
messageBubble messageBubble
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
@@ -44,23 +44,36 @@ struct TimelineReactionButton: View {
HStack(spacing: 4) { HStack(spacing: 4) {
Text(reaction.key) Text(reaction.key)
.font(.compound.bodyMD) .font(.compound.bodyMD)
Text(String(reaction.count)) if reaction.count > 1 {
.font(.compound.bodyMD) Text(String(reaction.count))
.foregroundColor(.compound.textSecondary) .font(.compound.bodyMD)
.foregroundColor(textColor)
}
} }
.padding(.vertical, 6) .padding(.vertical, 6)
.padding(.horizontal, 8) .padding(.horizontal, 8)
.background( .background(backgroundShape.fill(overlayBackgroundColor))
backgroundShape .overlay(backgroundShape.strokeBorder(overlayBorderColor))
.strokeBorder(reaction.isHighlighted ? Color.compound.textSecondary : .compound.bgCanvasDefault, lineWidth: 2) .padding(2)
.background(reaction.isHighlighted ? Color.compound.textPrimary.opacity(0.1) : .compound._bgReactionButton, in: backgroundShape) .overlay(backgroundShape.strokeBorder(Color.compound.bgCanvasDefault, lineWidth: 2))
)
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
} }
var backgroundShape: some InsettableShape { var backgroundShape: some InsettableShape {
RoundedRectangle(cornerRadius: 12, style: .continuous) RoundedRectangle(cornerRadius: 12, style: .continuous)
} }
var textColor: Color {
reaction.isHighlighted ? Color.compound.textPrimary : .compound.textSecondary
}
var overlayBackgroundColor: Color {
reaction.isHighlighted ? Color.compound.bgSubtlePrimary : .compound.bgSubtleSecondary
}
var overlayBorderColor: Color {
reaction.isHighlighted ? Color.compound.borderInteractivePrimary : .clear
}
} }
struct TimelineReactionView_Previews: PreviewProvider { struct TimelineReactionView_Previews: PreviewProvider {
@@ -163,7 +163,8 @@ public struct TimelineItemMenu: View {
} }
private func reactionBackgroundColor(for emoji: String) -> Color { private func reactionBackgroundColor(for emoji: String) -> Color {
if item.properties.reactions.first(where: { $0.key == emoji }) != nil { if let reaction = item.properties.reactions.first(where: { $0.key == emoji }),
reaction.isHighlighted {
return .compound.bgActionPrimaryRest return .compound.bgActionPrimaryRest
} else { } else {
return .clear return .clear
@@ -322,7 +322,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] { private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
reactions.map { reaction in reactions.map { reaction in
let isHighlighted = false // reaction.details.contains(where: { $0.sender.id == userID }) let isHighlighted = reaction.senders.contains(where: { $0 == userID })
return AggregatedReaction(key: reaction.key, count: Int(reaction.count), isHighlighted: isHighlighted) return AggregatedReaction(key: reaction.key, count: Int(reaction.count), isHighlighted: isHighlighted)
} }
} }
@@ -198,6 +198,17 @@ class MockScreen: Identifiable {
let coordinator = RoomScreenCoordinator(parameters: parameters) let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .roomSmallTimelineWithReactions:
let navigationStackCoordinator = NavigationStackCoordinator()
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.default
let parameters = RoomScreenCoordinatorParameters(roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
timelineController: timelineController,
mediaProvider: MockMediaProvider(),
emojiProvider: EmojiProvider())
let coordinator = RoomScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .roomSmallTimelineWithReadReceipts: case .roomSmallTimelineWithReadReceipts:
ServiceLocator.shared.settings.readReceiptsEnabled = true ServiceLocator.shared.settings.readReceiptsEnabled = true
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
@@ -36,6 +36,7 @@ enum UITestsScreenIdentifier: String {
case roomPlainNoAvatar case roomPlainNoAvatar
case roomEncryptedWithAvatar case roomEncryptedWithAvatar
case roomSmallTimeline case roomSmallTimeline
case roomSmallTimelineWithReactions
case roomSmallTimelineWithReadReceipts case roomSmallTimelineWithReadReceipts
case roomSmallTimelineIncomingAndSmallPagination case roomSmallTimelineIncomingAndSmallPagination
case roomSmallTimelineLargePagination case roomSmallTimelineLargePagination
+6
View File
@@ -161,6 +161,12 @@ class RoomScreenUITests: XCTestCase {
try await app.assertScreenshot(.roomSmallTimelineWithReadReceipts) try await app.assertScreenshot(.roomSmallTimelineWithReadReceipts)
} }
func testTimelineReactions() async throws {
let app = Application.launch(.roomSmallTimelineWithReactions)
try await app.assertScreenshot(.roomSmallTimelineWithReactions)
}
// MARK: - Helper Methods // MARK: - Helper Methods
private func performOperation(_ operation: UITestsSignal, using client: UITestsSignalling.Client) async throws { private func performOperation(_ operation: UITestsSignal, using client: UITestsSignalling.Client) async throws {
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b77e85ff9c5f5b1d888c97af89a511f788aa9e74eb0d609659c7d81e2b89b94
size 231654
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c6127923272006fe70a164fca243c1683d32d0227c8066f6794957567724a41
size 328078
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bfc0399661f579abcba48d2d003e710e1f54b235186d26d5fb7f6d3ffcbe235a
size 232688
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae1da33e7d2bdddd25617f63d797c2b14c0d646645f19348d2aea2a1729a0950
size 328669