mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
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:
@@ -21,22 +21,13 @@ import XCTest
|
||||
final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
func testFlowLayoutWithExpandAndCollapse() {
|
||||
let containerSize = CGSize(width: 250, height: 400)
|
||||
var flowLayout = CollapsibleFlowLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
var flowLayout = CollapsibleReactionLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
|
||||
var placedViews: [CGRect] = []
|
||||
let placedViewsCallback = { rect in
|
||||
placedViews.append(rect)
|
||||
}
|
||||
let subviews: [LayoutSubviewMock] = [
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
// The expand/collapse button
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback)
|
||||
]
|
||||
let subviews = createReactionLayoutSubviews(with: Array(repeating: CGSize(width: 100, height: 50), count: 6), placedPositionCallback: placedViewsCallback)
|
||||
let subviewsMock = LayoutSubviewsMock(subviews: subviews)
|
||||
var a: () = ()
|
||||
var size = flowLayout.sizeThatFits(proposal: ProposedViewSize(containerSize), subviews: subviewsMock, cache: &a)
|
||||
@@ -45,7 +36,7 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
XCTAssertEqual(size, CGSize(width: 205, height: 105))
|
||||
flowLayout.placeSubviews(in: CGRect(origin: .zero, size: size), proposal: ProposedViewSize(containerSize), subviews: subviewsMock, cache: &a)
|
||||
|
||||
// 3 items are hidden in the collapsed state (put in the centre with zero size)
|
||||
// 4 items are hidden in the collapsed state (put in the centre with zero size)
|
||||
var targetPlacements: [CGRect] = [
|
||||
CGRect(x: 0, y: 25, width: 100, height: 50),
|
||||
CGRect(x: 105, y: 25, width: 100, height: 50),
|
||||
@@ -53,6 +44,7 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
CGRect(x: 105, y: 80, width: 100, height: 50),
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0),
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0),
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0),
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0)
|
||||
]
|
||||
XCTAssertEqual(placedViews, targetPlacements)
|
||||
@@ -74,25 +66,22 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
CGRect(x: 105, y: 80, width: 100, height: 50),
|
||||
CGRect(x: 0, y: 135, width: 100, height: 50),
|
||||
CGRect(x: 105, y: 135, width: 100, height: 50),
|
||||
CGRect(x: 0, y: 190, width: 100, height: 50)
|
||||
CGRect(x: 0, y: 190, width: 100, height: 50),
|
||||
CGRect(x: 105.0, y: 190, width: 100, height: 50)
|
||||
]
|
||||
XCTAssertEqual(placedViews, targetPlacements)
|
||||
}
|
||||
|
||||
func testFlowLayoutWithExpandButtonIsHidden() {
|
||||
func testFlowLayoutWithExpandButtonAndAddMoreIsHidden() {
|
||||
let containerSize = CGSize(width: 250, height: 400)
|
||||
let flowLayout = CollapsibleFlowLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
let flowLayout = CollapsibleReactionLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
|
||||
var placedViews: [CGRect] = []
|
||||
let placedViewsCallback = { rect in
|
||||
placedViews.append(rect)
|
||||
}
|
||||
let subviews: [LayoutSubviewMock] = [
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback)
|
||||
]
|
||||
|
||||
let subviews = createReactionLayoutSubviews(with: Array(repeating: CGSize(width: 100, height: 50), count: 3), placedPositionCallback: placedViewsCallback)
|
||||
let subviewsMock = LayoutSubviewsMock(subviews: subviews)
|
||||
var a: () = ()
|
||||
let size = flowLayout.sizeThatFits(proposal: ProposedViewSize(containerSize), subviews: subviewsMock, cache: &a)
|
||||
@@ -104,7 +93,9 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
CGRect(x: 0, y: 25, width: 100, height: 50),
|
||||
CGRect(x: 105, y: 25, width: 100, height: 50),
|
||||
CGRect(x: 0, y: 80, width: 100, height: 50),
|
||||
// Button is hidden
|
||||
// Add more button
|
||||
CGRect(x: 105.0, y: 80, width: 100, height: 50),
|
||||
// Expand/Collapse button is hidden
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0)
|
||||
]
|
||||
XCTAssertEqual(placedViews, targetPlacements)
|
||||
@@ -112,16 +103,13 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
|
||||
func testFlowLayoutEmptyState() {
|
||||
let containerSize = CGSize(width: 250, height: 400)
|
||||
let flowLayout = CollapsibleFlowLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
let flowLayout = CollapsibleReactionLayout(itemSpacing: 5, rowSpacing: 5, rowsBeforeCollapsible: 2)
|
||||
|
||||
var placedViews: [CGRect] = []
|
||||
let placedViewsCallback = { rect in
|
||||
placedViews.append(rect)
|
||||
}
|
||||
let subviews: [LayoutSubviewMock] = [
|
||||
// No subviews to layout just the expand/collapse button
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50), placedPositionCallback: placedViewsCallback)
|
||||
]
|
||||
let subviews = createReactionLayoutSubviews(with: [], placedPositionCallback: placedViewsCallback)
|
||||
let subviewsMock = LayoutSubviewsMock(subviews: subviews)
|
||||
var a: () = ()
|
||||
let size = flowLayout.sizeThatFits(proposal: ProposedViewSize(containerSize), subviews: subviewsMock, cache: &a)
|
||||
@@ -130,8 +118,25 @@ final class CollapsibleFlowLayoutTests: XCTestCase {
|
||||
flowLayout.placeSubviews(in: CGRect(origin: .zero, size: size), proposal: ProposedViewSize(containerSize), subviews: subviewsMock, cache: &a)
|
||||
|
||||
let targetPlacements: [CGRect] = [
|
||||
// both buttons are not displayed
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0),
|
||||
CGRect(x: -10000, y: -10000, width: 0, height: 0)
|
||||
]
|
||||
XCTAssertEqual(placedViews, targetPlacements)
|
||||
}
|
||||
|
||||
func createReactionLayoutSubviews(with sizes: [CGSize], placedPositionCallback: @escaping (CGRect) -> Void) -> [LayoutSubviewMock] {
|
||||
sizes.map { size in
|
||||
LayoutSubviewMock(size: size,
|
||||
layoutValues: [String(describing: ReactionLayoutItemType.self): ReactionLayoutItem.reaction],
|
||||
placedPositionCallback: placedPositionCallback)
|
||||
} + [
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50),
|
||||
layoutValues: [String(describing: ReactionLayoutItemType.self): ReactionLayoutItem.expandCollapse],
|
||||
placedPositionCallback: placedPositionCallback),
|
||||
LayoutSubviewMock(size: CGSize(width: 100, height: 50),
|
||||
layoutValues: [String(describing: ReactionLayoutItemType.self): ReactionLayoutItem.addMore],
|
||||
placedPositionCallback: placedPositionCallback)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,9 +60,16 @@ struct LayoutSubviewsMock: Equatable, RandomAccessCollection {
|
||||
/// A mock of the SwiftUI `LayoutSubview` struct
|
||||
struct LayoutSubviewMock: FlowLayoutSubview, Equatable {
|
||||
var size: CGSize
|
||||
|
||||
var layoutValues = [String: Any]()
|
||||
var placedPositionCallback: (CGRect) -> Void
|
||||
|
||||
subscript<K>(key: K.Type) -> K.Value where K: LayoutValueKey {
|
||||
guard let value = layoutValues[String(describing: key.self)] as? K.Value else {
|
||||
fatalError("There is no value for the provided layout key.")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func sizeThatFits(_ proposal: ProposedViewSize) -> CGSize {
|
||||
size
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user