Reactions: Add more button (#1314)

* Add expand/collapse UI for reactions

- Adds a CollapsibleFlowLayout for controlling the layout
- Adds tests for  this layout and some mocks for testing layouts generally
- Improves the rendering of the reaction buttons which were not pixel perfect
- Adds the UI for the expand collapse buttons including the count of hidden items in the collapsed state.

* Add comment for reactionsCollapsed binding.

* Add the add more button and change the layout to be reactions specific and account for the add more button specifics.

* Update CollapsibleReactionLayout.swift

* Rename lines to rows, to hopefully ease the incoming merge.

* Add LayoutValueKey to organise subviews by item type.

- create ReactionLayoutItemType to tag subviews with ReactionLayoutItem
- Add LayoutValueKey support to the layout mocks
- Fix some mistakes in the merge
- Make the test code a bit more DRY
- Make the animation of the addMore and collapse buttons consistent

* Fix naming of the add more button label

* Address comments and fix padding

- Fix typos
- Use pdf/svg for icon
- Fix incorrect padding from a bad merge

* Fix "Add more" button padding to match designs

* Update snapshot test reference images.

* Fix naming, add Preserves Vector Data
This commit is contained in:
David Langley
2023-07-14 17:32:32 +01:00
committed by GitHub
parent a44a60cb0a
commit b31af77686
20 changed files with 415 additions and 141 deletions
@@ -24,25 +24,36 @@ struct TimelineReactionsView: View {
private static let verticalSpacing: CGFloat = 4
@EnvironmentObject private var context: RoomScreenViewModel.Context
@Environment(\.layoutDirection) private var layoutDirection: LayoutDirection
@Namespace private var animation
let itemID: TimelineItemIdentifier
let reactions: [AggregatedReaction]
@Binding var collapsed: Bool
var body: some View {
CollapsibleFlowLayout(itemSpacing: 4, rowSpacing: 4, collapsed: collapsed, rowsBeforeCollapsible: 2) {
CollapsibleReactionLayout(itemSpacing: 4, rowSpacing: 4, collapsed: collapsed, rowsBeforeCollapsible: 2) {
ForEach(reactions, id: \.self) { reaction in
TimelineReactionButton(itemID: itemID, reaction: reaction) { key in
TimelineReactionButton(reaction: reaction) { key in
context.send(viewAction: .toggleReaction(key: key, itemID: itemID))
} showReactionSummary: { key in
context.send(viewAction: .reactionSummary(itemID: itemID, key: key))
}
.reactionLayoutItem(.reaction)
}
Button {
collapsed.toggle()
} label: {
TimelineCollapseButtonLabel(collapsed: collapsed)
}
.reactionLayoutItem(.expandCollapse)
.animation(.easeOut, value: collapsed)
Button {
context.send(viewAction: .displayEmojiPicker(itemID: itemID))
} label: {
TimelineReactionAddMoreButtonLabel()
}
.animation(.easeOut, value: collapsed)
.reactionLayoutItem(.addMore)
}
.coordinateSpace(name: Self.flowCoordinateSpace)
}
@@ -54,15 +65,11 @@ struct TimelineReactionButtonLabel<Content: View>: View {
@ViewBuilder var content: () -> Content
var body: some View {
HStack(spacing: 4) {
content()
}
.padding(.vertical, 6)
.padding(.horizontal, 8)
.background(backgroundShape.inset(by: 1).fill(overlayBackgroundColor))
.overlay(backgroundShape.inset(by: 2.0).strokeBorder(overlayBorderColor))
.overlay(backgroundShape.strokeBorder(Color.compound.bgCanvasDefault, lineWidth: 2))
.accessibilityElement(children: .combine)
content()
.background(backgroundShape.inset(by: 1).fill(overlayBackgroundColor))
.overlay(backgroundShape.inset(by: 2.0).strokeBorder(overlayBorderColor))
.overlay(backgroundShape.strokeBorder(Color.compound.bgCanvasDefault, lineWidth: 2))
.accessibilityElement(children: .combine)
}
var backgroundShape: some InsettableShape {
@@ -84,6 +91,8 @@ struct TimelineCollapseButtonLabel: View {
var body: some View {
TimelineReactionButtonLabel {
Text(collapsed ? L10n.screenRoomReactionsShowMore : L10n.screenRoomReactionsShowLess)
.padding(.vertical, 6)
.padding(.horizontal, 8)
.layoutPriority(1)
.drawingGroup()
.font(.compound.bodyMD)
@@ -93,7 +102,6 @@ struct TimelineCollapseButtonLabel: View {
}
struct TimelineReactionButton: View {
let itemID: TimelineItemIdentifier
let reaction: AggregatedReaction
let toggleReaction: (String) -> Void
let showReactionSummary: (String) -> Void
@@ -110,13 +118,17 @@ struct TimelineReactionButton: View {
var label: some View {
TimelineReactionButtonLabel(isHighlighted: reaction.isHighlighted) {
Text(reaction.key)
.font(.compound.bodyMD)
if reaction.count > 1 {
Text(String(reaction.count))
HStack(spacing: 4) {
Text(reaction.key)
.font(.compound.bodyMD)
.foregroundColor(textColor)
if reaction.count > 1 {
Text(String(reaction.count))
.font(.compound.bodyMD)
.foregroundColor(textColor)
}
}
.padding(.vertical, 6)
.padding(.horizontal, 8)
}
}
@@ -125,6 +137,22 @@ struct TimelineReactionButton: View {
}
}
struct TimelineReactionAddMoreButtonLabel: View {
@ScaledMetric private var addMoreButtonIconSize = 16
var body: some View {
TimelineReactionButtonLabel {
Image(asset: Asset.Images.timelineReactionAddMore)
.resizable()
.frame(width: addMoreButtonIconSize, height: addMoreButtonIconSize)
// Vertical sizing is done by the layout so that the add more button
// matches the height of the text based buttons.
.padding(.horizontal, 10)
.frame(maxHeight: .infinity, alignment: .center)
}
}
}
struct TimelineReactionViewPreviewsContainer: View {
@State private var collapseState1 = false
@State private var collapseState2 = true