diff --git a/Vault/Sources/TestHelpers/SnapshotTesting/View+TestDeviceSize.swift b/Vault/Sources/TestHelpers/SnapshotTesting/View+TestDeviceSize.swift index de654f296..9d2b3c2e4 100644 --- a/Vault/Sources/TestHelpers/SnapshotTesting/View+TestDeviceSize.swift +++ b/Vault/Sources/TestHelpers/SnapshotTesting/View+TestDeviceSize.swift @@ -6,10 +6,23 @@ import SwiftUI extension View { /// Fixed width at the test device size, height whatever the content wants. /// + /// Pins the regular vertical size class: UIKit reports compact for + /// short frames, which would otherwise flip size-class-aware layouts + /// into their landscape form for any test using a small height. + /// /// - parameter height: the height to make the view (defaults to 1000pts) public func framedForTest(height: CGFloat = 1000) -> some View { // iPhone 14 width frame(width: 390, height: height) + .environment(\.verticalSizeClass, .regular) + } + + /// Landscape phone frame with the compact vertical size class the + /// system would report for it. + public func framedForLandscapeTest() -> some View { + // iPhone 14 landscape + frame(width: 844, height: 390) + .environment(\.verticalSizeClass, .compact) } } diff --git a/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift b/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift index bc8856f30..c227fa6e3 100644 --- a/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift +++ b/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift @@ -15,6 +15,7 @@ public struct VaultItemFeedView< @Environment(VaultInjector.self) private var injector @Environment(VaultDataModel.self) private var dataModel + @Environment(\.verticalSizeClass) private var verticalSizeClass @State private var state: VaultItemFeedState public init( @@ -79,50 +80,117 @@ public struct VaultItemFeedView< .autocorrectionDisabled() .textInputAutocapitalization(.never) .safeAreaInset(edge: .bottom, spacing: 0) { - VStack(spacing: 8) { - if dataModel.allTags.isNotEmpty { - tagFilterBar + Group { + if verticalSizeClass == .compact { + compactFeedBar + } else { + regularFeedBar } - - bottomBar - .padding(.horizontal) } - .padding(.vertical, 8) + .padding(.vertical, 6) .animation(.snappy, value: state.isEditing) .animation(.snappy, value: dataModel.isSearching) - .animation(.snappy, value: dataModel.itemsFilteringByTags) + // Filter changes get a much shorter spring than the rest: the + // default one fades the filter name and Clear button out over + // ~0.4s while the glass capsule morphs, which reads as the bar + // lagging behind the tap rather than animating with it. + .animation(.snappy(duration: 0.2), value: dataModel.itemsFilteringByTags) .animation(.snappy, value: dataModel.allTags.isEmpty) } } - /// Horizontally scrolling row of tag filters, presented above the bottom bar. + /// Plain regular glass lets tile text show straight through the bar, so + /// a wash of the background colour sits behind the glass to keep it + /// legible over busy content while keeping the glass edge and lensing. + /// + /// This is a backdrop rather than `Glass.tint` because a tinted glass + /// renders as an empty image under `CALayer.render(in:)`, which blanks + /// every snapshot test that includes the feed. + private var feedBarWash: Color { + Color(.systemBackground).opacity(0.85) + } + + /// Tag filters stacked above the status bar, for regular-height layouts. + private var regularFeedBar: some View { + VStack(spacing: 8) { + if dataModel.allTags.isNotEmpty { + tagFilterBar + } + + bottomBar + .padding(.horizontal) + } + } + + /// Tag filters and the status bar side by side, for compact-height + /// layouts (iPhone landscape) where two rows would crowd out the grid. + private var compactFeedBar: some View { + HStack(spacing: 8) { + if dataModel.allTags.isNotEmpty { + tagFilterBar + } else { + // Keep the bar trailing where it sits when tags are present. + Spacer() + } + + // Hugging its content collapses the bar's internal spacer so the + // tag row takes whatever width is left. + bottomBar + .fixedSize(horizontal: true, vertical: false) + } + .padding(.horizontal) + } + + /// Horizontally scrolling row of tag filters. private var tagFilterBar: some View { ScrollView(.horizontal, showsIndicators: false) { - HStack { - ForEach(dataModel.allTags) { tag in - // `TagPillView` draws its own capsule — filled when - // selected, outlined when not — which reads far more - // clearly than tinting a bordered button both ways. - // The toggle keeps the button trait and selected state - // that a bare tap gesture would not expose. - Toggle(isOn: filterBinding(for: tag)) { - TagPillView( - tag: tag, - isSelected: dataModel.itemsFilteringByTags.contains(tag.id), - ) - } - .id(tag) - } + // The container lives inside the scroll view on purpose: glass + // renders at the container's level, so a container outside the + // scroll view would let pills draw past its clip. + GlassEffectContainer { + pillRow + // The container clips to its bounds, and the pill stroke + // straddles the capsule edge, so give it room above and + // below or the top and bottom of the border get shaved. + .padding(.vertical, 2) } - .toggleStyle(.button) - .buttonStyle(.plain) - .font(.footnote) - .padding(.horizontal) } - .scrollClipDisabled() + // Side by side with the bar the scroll view no longer spans the + // screen, so it must clip or pills would slide underneath the bar. + .scrollClipDisabled(verticalSizeClass != .compact) .transition(.move(edge: .bottom).combined(with: .opacity)) } + private var pillRow: some View { + HStack { + ForEach(dataModel.allTags) { tag in + // `TagPillView` draws its own capsule — filled when + // selected, outlined when not — which reads far more + // clearly than tinting a bordered button both ways. + // Glass beneath it keeps the pill legible over whatever + // scrolls past. The toggle keeps the button trait and + // selected state that a bare tap gesture would not expose. + Toggle(isOn: filterBinding(for: tag)) { + TagPillView( + tag: tag, + isSelected: dataModel.itemsFilteringByTags.contains(tag.id), + ) + .glassEffect(.regular.interactive(), in: .capsule) + .background(feedBarWash, in: .capsule) + } + .id(tag) + } + } + .toggleStyle(.button) + .buttonStyle(.plain) + .controlSize(.small) + .font(.footnote) + // Beside the bar the scroll view clips, so the row keeps a hair of + // inset or the first pill's stroke is shaved at the leading edge; + // stacked, it spans the screen and carries the normal margin. + .padding(.horizontal, verticalSizeClass == .compact ? 2 : 16) + } + /// Item count and the feed-level actions. private var bottomBar: some View { HStack { @@ -132,45 +200,64 @@ public struct VaultItemFeedView< Spacer() - if dataModel.items.isNotEmpty { + // The buttons are what give the row its height, so an invisible + // zero-width Edit button always sits behind them: the bar stays + // the same height whether the feed is empty, filtered to nothing, + // or full, without reserving any width when they are gone. + ZStack(alignment: .trailing) { + editButton + .hidden() + .frame(width: 0) + HStack(spacing: 8) { + // Clear follows the filter, not the results, so a filter + // that matches nothing can still be cleared from here. if dataModel.itemsFilteringByTags.isNotEmpty, !state.isEditing { - Button { - dataModel.itemsFilteringByTags.removeAll() - } label: { - Label("Clear", systemImage: "tag.slash.fill") - } - .buttonStyle(.bordered) - .tint(.secondary) + clearButton } - Button { - state.isEditing.toggle() - } label: { - Label( - state.isEditing ? "Done" : "Edit", - systemImage: state.isEditing ? "checkmark" : "pencil", - ) + if dataModel.items.isNotEmpty { + editButton } - .buttonStyle(.borderedProminent) } - .buttonBorderShape(.capsule) - .controlSize(.small) - .font(.footnote) - .lineLimit(1) - .fixedSize() } + .buttonBorderShape(.capsule) + .controlSize(.small) + .font(.footnote) + .lineLimit(1) + .fixedSize() } - .frame(minHeight: 44) - // The status row carries its own surface; the tag pills above stay - // outside it, sitting directly on the content. - .padding(.vertical, 8) - .padding(.horizontal, 12) - .background(Color.primary.opacity(0.05)) - .clipShape(RoundedRectangle(cornerRadius: 12)) + // Glass keeps the row legible over the grid scrolling beneath it + // without the heavy, opaque panel a flat fill would need. + .padding(.vertical, 6) + .padding(.horizontal, 14) + .glassEffect(.regular, in: .capsule) + .background(feedBarWash, in: .capsule) .transition(.move(edge: .bottom).combined(with: .opacity)) } + private var clearButton: some View { + Button { + dataModel.itemsFilteringByTags.removeAll() + } label: { + Label("Clear", systemImage: "tag.slash.fill") + } + .buttonStyle(.bordered) + .tint(.secondary) + } + + private var editButton: some View { + Button { + state.isEditing.toggle() + } label: { + Label( + state.isEditing ? "Done" : "Edit", + systemImage: state.isEditing ? "checkmark" : "pencil", + ) + } + .buttonStyle(.borderedProminent) + } + @ViewBuilder private var statusLabel: some View { if state.isEditing { @@ -289,9 +376,19 @@ public struct VaultItemFeedView< #Preview { let store = VaultStoreStub() + let tagStore = VaultTagStoreStub() + let workTag = Identifier() + tagStore.retrieveTagsHandler = { + [ + VaultItemTag(id: workTag, name: "work"), + VaultItemTag(id: .init(), name: "personal", color: .tagDefault), + VaultItemTag(id: .init(), name: "archive", color: .gray), + VaultItemTag(id: .init(), name: "family", color: .init(color: .purple)), + ] + } let dataModel = VaultDataModel( vaultStore: store, - vaultTagStore: VaultTagStoreStub(), + vaultTagStore: tagStore, vaultImporter: VaultStoreImporterMock(), vaultDeleter: VaultStoreDeleterMock(), vaultKillphraseDeleter: VaultStoreKillphraseDeleterMock(), @@ -303,34 +400,37 @@ public struct VaultItemFeedView< searchPassphraseRehashService: nil, backupEventLogger: BackupEventLoggerMock(), ) - store.retrieveHandler = { _ in .init(items: [ - .init( - metadata: .init( - id: Identifier(), - created: Date(), - updated: Date(), - relativeOrder: .min, - userDescription: "My Cool Code", - tags: [], - visibility: .always, - searchableLevel: .full, - searchPassphrase: nil, - killphrase: nil, - lockState: .notLocked, - color: VaultItemColor(color: .green), - showInQuickType: true, - previewMode: .titleAndFirstLine, - ), - item: .otpCode(.init( - type: .totp(), - data: .init( - secret: .empty(), - accountName: "example@example.com", - issuer: "i", + // Enough tiles to scroll under the bar, so the glass has content behind it. + let colors: [Color] = [.green, .orange, .blue, .pink, .teal, .indigo, .red, .mint, .brown, .cyan] + store.retrieveHandler = { _ in + .init(items: colors.enumerated().map { index, color in + .init( + metadata: .init( + id: Identifier(), + created: Date(), + updated: Date(), + relativeOrder: .min, + userDescription: "My Cool Code \(index + 1)", + tags: index.isMultiple(of: 2) ? [workTag] : [], + visibility: .always, + searchableLevel: .full, + searchPassphrase: nil, + killphrase: nil, + lockState: .notLocked, + color: VaultItemColor(color: color), + showInQuickType: true, + previewMode: .titleAndFirstLine, ), - )), - ), - ]) + item: .otpCode(.init( + type: .totp(), + data: .init( + secret: .empty(), + accountName: "example@example.com", + issuer: "i", + ), + )), + ) + }) } return VaultItemFeedView( localSettings: .init(defaults: .init(userDefaults: .standard)), @@ -338,15 +438,29 @@ public struct VaultItemFeedView< state: VaultItemFeedState(), ) .environment(dataModel) + .environment(VaultInjector( + clock: EpochClockMock(currentTime: 30), + intervalTimer: IntervalTimerImpl(), + backupEventLogger: BackupEventLoggerMock(), + vaultKeyDeriverFactory: VaultKeyDeriverFactoryImpl(), + encryptedVaultDecoder: EncryptedVaultDecoderMock(), + autoBackupService: AutoBackupServiceMock(status: .disabled, configuration: .init()), + defaults: Defaults(userDefaults: .standard), + fileManager: .default, + )) } private struct GenericGenerator: VaultItemPreviewViewGenerator { func makeVaultPreviewView( item _: VaultItem.Payload, - metadata _: VaultItem.Metadata, + metadata: VaultItem.Metadata, behaviour _: VaultItemViewBehaviour, ) -> some View { - Text("Code") + // Solid tiles so the glass bar has something to blur behind it. + Text(metadata.userDescription) + .foregroundStyle(.white) + .frame(maxWidth: .infinity, minHeight: 160) + .background(metadata.color?.color ?? .gray, in: .rect(cornerRadius: 12)) } func clearViewCache() async { diff --git a/Vault/Sources/VaultiOS/Views/General/TagPillView.swift b/Vault/Sources/VaultiOS/Views/General/TagPillView.swift index b0cebe13e..aeb54ab63 100644 --- a/Vault/Sources/VaultiOS/Views/General/TagPillView.swift +++ b/Vault/Sources/VaultiOS/Views/General/TagPillView.swift @@ -6,13 +6,24 @@ struct TagPillView: View { var tag: VaultItemTag var isSelected: Bool = false + @Environment(\.controlSize) private var controlSize + + /// Small controls get tighter insets so a row of pills sits level with + /// small capsule buttons. + private var isCompact: Bool { + switch controlSize { + case .mini, .small: true + default: false + } + } + var body: some View { - HStack(alignment: .center, spacing: 12) { + HStack(alignment: .center, spacing: isCompact ? 8 : 12) { TagIconView(iconName: tag.iconName) Text(tag.name.isBlank ? "Tag" : tag.name) } - .padding(.horizontal, 12) - .padding(.vertical, 8) + .padding(.horizontal, isCompact ? 10 : 12) + .padding(.vertical, isCompact ? 5 : 8) .background( Capsule(style: .circular) .fill(tag.fillColor(isSelected: isSelected)) diff --git a/Vault/Tests/VaultiOSAutofillTests/__Snapshots__/VaultAutofillCodeSelectorViewSnapshotTests/layout.1.png b/Vault/Tests/VaultiOSAutofillTests/__Snapshots__/VaultAutofillCodeSelectorViewSnapshotTests/layout.1.png index 976fb687e..227671597 100644 Binary files a/Vault/Tests/VaultiOSAutofillTests/__Snapshots__/VaultAutofillCodeSelectorViewSnapshotTests/layout.1.png and b/Vault/Tests/VaultiOSAutofillTests/__Snapshots__/VaultAutofillCodeSelectorViewSnapshotTests/layout.1.png differ diff --git a/Vault/Tests/VaultiOSTests/VaultItemFeedViewSnapshotTests.swift b/Vault/Tests/VaultiOSTests/VaultItemFeedViewSnapshotTests.swift index c4eda88dd..c2734202c 100644 --- a/Vault/Tests/VaultiOSTests/VaultItemFeedViewSnapshotTests.swift +++ b/Vault/Tests/VaultiOSTests/VaultItemFeedViewSnapshotTests.swift @@ -328,11 +328,162 @@ final class VaultItemFeedViewSnapshotTests { assertSnapshot(of: sut, as: .image) } + + /// In compact height the tag row and the status bar share one row so the + /// grid keeps as much of the short screen as possible. + @Test + func landscape_collapsesToSingleRow() async { + let store = VaultStoreStub() + let tagStore = VaultTagStoreStub() + let tag1Id = Identifier() + tagStore.retrieveTagsHandler = { + [ + VaultItemTag(id: tag1Id, name: "work"), + VaultItemTag(id: .init(), name: "personal", color: .tagDefault), + VaultItemTag(id: .init(), name: "archive", color: .gray), + ] + } + store.retrieveHandler = { _ in + .init(items: [uniqueVaultItem(), uniqueVaultItem()]) + } + let dataModel = anyVaultDataModel(vaultStore: store, vaultTagStore: tagStore) + await dataModel.reloadData() + + let sut = makeSUT(dataModel: dataModel) + .framedForLandscapeTest() + + dataModel.itemsFilteringByTags = [tag1Id] + + assertSnapshot(of: sut, as: .image) + } + + /// Without tags the compact row is just the status bar, hugging the + /// trailing edge where it sits when tags are present. + @Test + func landscape_noTags_barOnly() async { + let store = VaultStoreStub() + store.retrieveHandler = { _ in + .init(items: [uniqueVaultItem()]) + } + let dataModel = anyVaultDataModel(vaultStore: store) + await dataModel.reloadData() + + let sut = makeSUT(dataModel: dataModel) + .framedForLandscapeTest() + + assertSnapshot(of: sut, as: .image) + } + + /// With more pills than fit beside the bar, the row must clip at its own + /// edge rather than let pills run on underneath the bar. + @Test + func landscape_overflowingTagsClipBeforeBar() async { + let store = VaultStoreStub() + let tagStore = VaultTagStoreStub() + tagStore.retrieveTagsHandler = { + ["work", "personal", "archive", "family", "finance", "travel", "health", "projects"] + .map { VaultItemTag(id: .init(), name: $0) } + } + store.retrieveHandler = { _ in + .init(items: [uniqueVaultItem()]) + } + let dataModel = anyVaultDataModel(vaultStore: store, vaultTagStore: tagStore) + await dataModel.reloadData() + + let sut = makeSUT(dataModel: dataModel) + .framedForLandscapeTest() + .background(Color.gray) + + assertSnapshot(of: sut, as: .image) + } + + /// The buttons size the bar, so hiding them on an empty feed must not + /// change its height. Compare the bar's top edge across the pair. + @Test + func bar_keepsHeightWithoutEditButton() async { + let (empty, populated) = await makeEmptyAndPopulatedDataModels() + + assertSnapshot( + of: makeSUT(dataModel: empty).framedForTest(height: 240).background(Color.gray), + as: .image, + named: "noItems", + ) + assertSnapshot( + of: makeSUT(dataModel: populated).framedForTest(height: 240).background(Color.gray), + as: .image, + named: "withItems", + ) + } + + /// A filter that matches nothing hides Edit but must keep Clear, or the + /// only way out is scrolling back to the pill; the bar keeps its height. + @Test + func bar_filterWithNoResultsKeepsClear() async { + let store = VaultStoreStub() + let tagStore = VaultTagStoreStub() + let tag1Id = Identifier() + tagStore.retrieveTagsHandler = { + [VaultItemTag(id: tag1Id, name: "work"), VaultItemTag(id: .init(), name: "personal")] + } + store.retrieveHandler = { query in + query.filterTags.isEmpty ? .init(items: [uniqueVaultItem()]) : .init(items: []) + } + let dataModel = anyVaultDataModel(vaultStore: store, vaultTagStore: tagStore) + await dataModel.reloadData() + + dataModel.itemsFilteringByTags = [tag1Id] + await dataModel.reloadItems() + + let sut = makeSUT(dataModel: dataModel) + .framedForTest(height: 240) + .background(Color.gray) + + assertSnapshot(of: sut, as: .image) + } + + /// Same guarantee for the single-row landscape layout, where the pills + /// sit beside the bar and would show any height change. + @Test + func landscape_barKeepsHeightWithoutEditButton() async { + let (empty, populated) = await makeEmptyAndPopulatedDataModels() + + assertSnapshot( + of: makeSUT(dataModel: empty).framedForLandscapeTest().background(Color.gray), + as: .image, + named: "noItems", + ) + assertSnapshot( + of: makeSUT(dataModel: populated).framedForLandscapeTest().background(Color.gray), + as: .image, + named: "withItems", + ) + } } // MARK: - Helpers extension VaultItemFeedViewSnapshotTests { + /// Two feeds sharing one tag, one with nothing to edit and one with an + /// item, so a pair of snapshots differs only by the bar's buttons. + private func makeEmptyAndPopulatedDataModels() async -> (empty: VaultDataModel, populated: VaultDataModel) { + let tagStore = VaultTagStoreStub() + tagStore.retrieveTagsHandler = { + [VaultItemTag(id: .init(), name: "work"), VaultItemTag(id: .init(), name: "personal")] + } + + let emptyStore = VaultStoreStub() + emptyStore.retrieveHandler = { _ in .init(items: []) } + let empty = anyVaultDataModel(vaultStore: emptyStore, vaultTagStore: tagStore) + await empty.reloadData() + + let populatedStore = VaultStoreStub() + populatedStore.retrieveHandler = { _ in .init(items: [uniqueVaultItem()]) } + let populated = anyVaultDataModel(vaultStore: populatedStore, vaultTagStore: tagStore) + await populated.reloadData() + + return (empty, populated) + } + private func makeSUT( dataModel: VaultDataModel, state: VaultItemFeedState = VaultItemFeedState(), diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_filterWithNoResultsKeepsClear.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_filterWithNoResultsKeepsClear.1.png new file mode 100644 index 000000000..600ed86ec Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_filterWithNoResultsKeepsClear.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.noItems.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.noItems.png new file mode 100644 index 000000000..f0faf784e Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.noItems.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.withItems.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.withItems.png new file mode 100644 index 000000000..b9318a5b6 Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/bar_keepsHeightWithoutEditButton.withItems.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.noItems.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.noItems.png new file mode 100644 index 000000000..7a095e92f Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.noItems.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.withItems.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.withItems.png new file mode 100644 index 000000000..588a8373b Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_barKeepsHeightWithoutEditButton.withItems.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_collapsesToSingleRow.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_collapsesToSingleRow.1.png new file mode 100644 index 000000000..e4d65de39 Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_collapsesToSingleRow.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_noTags_barOnly.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_noTags_barOnly.1.png new file mode 100644 index 000000000..4a8ad0135 Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_noTags_barOnly.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_overflowingTagsClipBeforeBar.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_overflowingTagsClipBeforeBar.1.png new file mode 100644 index 000000000..78367862c Binary files /dev/null and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/landscape_overflowingTagsClipBeforeBar.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_multipleCodesAtMediumSize.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_multipleCodesAtMediumSize.1.png index 520ff3333..9781a798d 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_multipleCodesAtMediumSize.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_multipleCodesAtMediumSize.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_noCodes.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_noCodes.1.png index 873301050..0eafdf909 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_noCodes.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_noCodes.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_singleCodeAtMediumSize.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_singleCodeAtMediumSize.1.png index 6f5e83e08..e46e5acbf 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_singleCodeAtMediumSize.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/layout_singleCodeAtMediumSize.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_includesTagsIfTheyExistInTheVaultStore.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_includesTagsIfTheyExistInTheVaultStore.1.png index b0e60a88c..0a893e75b 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_includesTagsIfTheyExistInTheVaultStore.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_includesTagsIfTheyExistInTheVaultStore.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_tagsBeingFiltered.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_tagsBeingFiltered.1.png index ed6bdb040..58c51e7d5 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_tagsBeingFiltered.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/searchBar_tagsBeingFiltered.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_clearButtonVisible.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_clearButtonVisible.1.png index 7f36c1ec1..170129934 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_clearButtonVisible.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_clearButtonVisible.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleFiltersFallBackToCount.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleFiltersFallBackToCount.1.png index bcd68f5ea..4d1982dee 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleFiltersFallBackToCount.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleFiltersFallBackToCount.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleTagsFiltered.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleTagsFiltered.1.png index 57a771a72..e9761868a 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleTagsFiltered.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_multipleTagsFiltered.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_narrowWidth_buttonsDoNotWrap.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_narrowWidth_buttonsDoNotWrap.1.png index 49dfcf697..0d56a6502 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_narrowWidth_buttonsDoNotWrap.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_narrowWidth_buttonsDoNotWrap.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithNoResults.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithNoResults.1.png index 873301050..0eafdf909 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithNoResults.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithNoResults.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithResults.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithResults.1.png index a46935f97..aaa92ae13 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithResults.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_searchingWithResults.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_singleFilterIsNamed.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_singleFilterIsNamed.1.png index 4afad1e55..aa94000d8 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_singleFilterIsNamed.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_singleFilterIsNamed.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_tagFilteringInEditMode.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_tagFilteringInEditMode.1.png index eed2d4c84..4f398c26b 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_tagFilteringInEditMode.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/unifiedBar_tagFilteringInEditMode.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.editing.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.editing.png index ec399e601..dc5ad7a7f 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.editing.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.editing.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.notEditing.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.notEditing.png index 6f5e83e08..e46e5acbf 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.notEditing.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultItemFeedViewSnapshotTests/viewState_toggleEditingMode.notEditing.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultMainNavigationViewSnapshotTests/layout.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultMainNavigationViewSnapshotTests/layout.1.png index 1977c9754..279380de2 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/VaultMainNavigationViewSnapshotTests/layout.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/VaultMainNavigationViewSnapshotTests/layout.1.png differ