diff --git a/Vault/Sources/VaultiOS/Views/Backup/BackupImportFlowView.swift b/Vault/Sources/VaultiOS/Views/Backup/BackupImportFlowView.swift index 6aa7f1c4d..be760bce3 100644 --- a/Vault/Sources/VaultiOS/Views/Backup/BackupImportFlowView.swift +++ b/Vault/Sources/VaultiOS/Views/Backup/BackupImportFlowView.swift @@ -96,8 +96,8 @@ struct BackupImportFlowView: View { .foregroundStyle(Color.red) } } - .animation(.easeOut, value: viewModel.importState) - .animation(.easeOut, value: viewModel.payloadState) + .animation(.snappy, value: viewModel.importState) + .animation(.snappy, value: viewModel.payloadState) .navigationTitle(Text("Import")) .navigationBarTitleDisplayMode(.inline) .navigationDestination(for: VaultApplicationPayload.self) { payload in @@ -177,8 +177,8 @@ struct BackupImportFlowView: View { successSection } } - .animation(.easeOut, value: viewModel.importState) - .animation(.easeOut, value: viewModel.payloadState) + .animation(.snappy, value: viewModel.importState) + .animation(.snappy, value: viewModel.payloadState) .toolbar { if viewModel.importState.isFinished { ToolbarItem(placement: .primaryAction) { diff --git a/Vault/Sources/VaultiOS/Views/Backup/BackupKeyChangeView.swift b/Vault/Sources/VaultiOS/Views/Backup/BackupKeyChangeView.swift index 968ccfa83..20dd957bc 100644 --- a/Vault/Sources/VaultiOS/Views/Backup/BackupKeyChangeView.swift +++ b/Vault/Sources/VaultiOS/Views/Backup/BackupKeyChangeView.swift @@ -29,7 +29,7 @@ struct BackupKeyChangeView: View { .navigationTitle(Text("Backup Password")) .navigationBarTitleDisplayMode(.inline) .interactiveDismissDisabled(viewModel.newPassword.isLoading) - .animation(.easeOut, value: viewModel.newlyEnteredPassword.isNotEmpty) + .animation(.snappy, value: viewModel.newlyEnteredPassword.isNotEmpty) .task { await viewModel.onAppear() } @@ -113,7 +113,7 @@ struct BackupKeyChangeView: View { } footer: { Text("Enter a new password to generate an encryption key.") } - .animation(.easeOut, value: viewModel.newlyEnteredPassword) + .animation(.snappy, value: viewModel.newlyEnteredPassword) } // MARK: - Generate Section diff --git a/Vault/Sources/VaultiOS/Views/Backup/BackupKeyDecryptorView.swift b/Vault/Sources/VaultiOS/Views/Backup/BackupKeyDecryptorView.swift index 28e2cb6c1..8821a8351 100644 --- a/Vault/Sources/VaultiOS/Views/Backup/BackupKeyDecryptorView.swift +++ b/Vault/Sources/VaultiOS/Views/Backup/BackupKeyDecryptorView.swift @@ -75,6 +75,6 @@ struct BackupKeyDecryptorView: View { } .disabled(!viewModel.canAttemptDecryption || viewModel.isDecrypting) } - .animation(.easeOut, value: viewModel.canAttemptDecryption) + .animation(.snappy, value: viewModel.canAttemptDecryption) } } diff --git a/Vault/Sources/VaultiOS/Views/Detail/EncryptedItemDetailView.swift b/Vault/Sources/VaultiOS/Views/Detail/EncryptedItemDetailView.swift index 425a7a22a..a4cd2c4ef 100644 --- a/Vault/Sources/VaultiOS/Views/Detail/EncryptedItemDetailView.swift +++ b/Vault/Sources/VaultiOS/Views/Detail/EncryptedItemDetailView.swift @@ -108,7 +108,7 @@ struct EncryptedItemDetailView: View { } .disabled(!viewModel.canStartDecryption) } - .animation(.easeOut, value: viewModel.state) + .animation(.snappy, value: viewModel.state) } private func decryptRow(@ViewBuilder content: @escaping () -> some View) -> some View { diff --git a/Vault/Sources/VaultiOS/Views/Detail/OTP/OTPCodeDetailView.swift b/Vault/Sources/VaultiOS/Views/Detail/OTP/OTPCodeDetailView.swift index 0c6a09534..d459f2f65 100644 --- a/Vault/Sources/VaultiOS/Views/Detail/OTP/OTPCodeDetailView.swift +++ b/Vault/Sources/VaultiOS/Views/Detail/OTP/OTPCodeDetailView.swift @@ -99,7 +99,7 @@ struct OTPCodeDetailView: .navigationTitle(viewModel.strings.title) .navigationBarTitleDisplayMode(.inline) .interactiveDismissDisabled(viewModel.editingModel.isDirty) - .animation(.easeOut, value: viewModel.isInEditMode) + .animation(.snappy, value: viewModel.isInEditMode) .onReceive(viewModel.isFinishedPublisher()) { dismiss() } diff --git a/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift b/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift index 77bc71848..92b799deb 100644 --- a/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift +++ b/Vault/Sources/VaultiOS/Views/Feed/VaultItemFeedView.swift @@ -67,7 +67,7 @@ public struct VaultItemFeedView< .scrollTargetLayout() .padding(.horizontal) .padding(.bottom) - .animation(.easeOut(duration: 0.1), value: dataModel.itemsFilteringByTags) + .animation(.snappy, value: dataModel.itemsFilteringByTags) } else { ContentUnavailableView { Label(localized(key: "codeFeed.noCodes.title"), systemImage: "key.horizontal") diff --git a/Vault/Sources/VaultiOS/Views/General/Shimmer.swift b/Vault/Sources/VaultiOS/Views/General/Shimmer.swift index 0b539a187..7656d17f9 100644 --- a/Vault/Sources/VaultiOS/Views/General/Shimmer.swift +++ b/Vault/Sources/VaultiOS/Views/General/Shimmer.swift @@ -128,13 +128,33 @@ extension View { bandSize: CGFloat = 0.3, ) -> some View { if active { - modifier(Shimmer(animation: animation, gradient: gradient, bandSize: bandSize)) + modifier(ShimmerIfMotionAllowed(animation: animation, gradient: gradient, bandSize: bandSize)) } else { self } } } +/// Applies ``Shimmer`` only when the system is not asking for reduced motion. +/// +/// The shimmer repeats forever, and is applied to every card in the feed at once while +/// editing, so it is exactly the kind of motion Reduce Motion exists to suppress. +private struct ShimmerIfMotionAllowed: ViewModifier { + var animation: Animation + var gradient: Gradient + var bandSize: CGFloat + + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + func body(content: Content) -> some View { + if reduceMotion { + content + } else { + content.modifier(Shimmer(animation: animation, gradient: gradient, bandSize: bandSize)) + } + } +} + #Preview("Scenarios", traits: .sizeThatFitsLayout) { Group { Text("SwiftUI Shimmer").preferredColorScheme(.light) diff --git a/Vault/Sources/VaultiOS/Views/General/TagPillView.swift b/Vault/Sources/VaultiOS/Views/General/TagPillView.swift index 0dbe6c4bd..b0cebe13e 100644 --- a/Vault/Sources/VaultiOS/Views/General/TagPillView.swift +++ b/Vault/Sources/VaultiOS/Views/General/TagPillView.swift @@ -18,7 +18,7 @@ struct TagPillView: View { .fill(tag.fillColor(isSelected: isSelected)) .stroke(tag.strokeColor(isSelected: isSelected), lineWidth: 1), ) - .foregroundColor(tag.strokeColor(isSelected: isSelected)) + .foregroundStyle(tag.strokeColor(isSelected: isSelected)) } } diff --git a/Vault/Sources/VaultiOS/Views/Previews/OTP/HOTPCodePreviewView.swift b/Vault/Sources/VaultiOS/Views/Previews/OTP/HOTPCodePreviewView.swift index 785f85c17..9635b1fba 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/OTP/HOTPCodePreviewView.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/OTP/HOTPCodePreviewView.swift @@ -27,8 +27,8 @@ struct HOTPCodePreviewView: View { timerSection } .padding(16) - .animation(.easeOut, value: behaviour) - .animation(.easeOut, value: canLoadNextCode) + .animation(.snappy, value: behaviour) + .animation(.snappy, value: canLoadNextCode) .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(1, contentMode: .fill) .shimmering(active: isEditing) @@ -104,7 +104,7 @@ struct HOTPCodePreviewView: View { .fontWeight(.heavy) .minimumScaleFactor(0.5) .lineLimit(1) - .foregroundColor(isEditing ? .white : .primary) + .foregroundStyle(isEditing ? .white : .primary) .frame(maxWidth: .infinity, alignment: .leading) } @@ -121,7 +121,7 @@ struct HOTPCodePreviewView: View { buttonView .disabled(!canLoadNextCode) } - .animation(.easeOut, value: canLoadNextCode) + .animation(.snappy, value: canLoadNextCode) } private var canLoadNextCode: Bool { diff --git a/Vault/Sources/VaultiOS/Views/Previews/OTP/OTPCodeButtonView.swift b/Vault/Sources/VaultiOS/Views/Previews/OTP/OTPCodeButtonView.swift index 354580bf1..17c3b2f48 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/OTP/OTPCodeButtonView.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/OTP/OTPCodeButtonView.swift @@ -18,7 +18,7 @@ struct OTPCodeButtonView: View { } .foregroundStyle(viewModel.incrementError != nil ? .red : .accentColor) .disabled(isDisabled) - .animation(.easeOut, value: isDisabled) + .animation(.snappy, value: isDisabled) } var isDisabled: Bool { diff --git a/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPCodePreviewView.swift b/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPCodePreviewView.swift index 0526177eb..19f0d89a4 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPCodePreviewView.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/OTP/TOTPCodePreviewView.swift @@ -35,7 +35,7 @@ struct TOTPCodePreviewView: View { .clipShape(RoundedRectangle(cornerRadius: 6)) } .padding(16) - .animation(.easeOut, value: behaviour) + .animation(.snappy, value: behaviour) .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(1, contentMode: .fill) .shimmering(active: isEditing) @@ -94,7 +94,7 @@ struct TOTPCodePreviewView: View { .fontWeight(.heavy) .minimumScaleFactor(0.5) .lineLimit(1) - .foregroundColor(isEditing ? .white : .primary) + .foregroundStyle(isEditing ? .white : .primary) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Vault/Sources/VaultiOS/Views/Previews/PreviewErrorIcon.swift b/Vault/Sources/VaultiOS/Views/Previews/PreviewErrorIcon.swift index 6ace1c499..026efee1e 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/PreviewErrorIcon.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/PreviewErrorIcon.swift @@ -4,6 +4,6 @@ import SwiftUI struct PreviewErrorIcon: View { var body: some View { Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.red) + .foregroundStyle(.red) } } diff --git a/Vault/Sources/VaultiOS/Views/Previews/VaultCardModifier.swift b/Vault/Sources/VaultiOS/Views/Previews/VaultCardModifier.swift index cb83a18a5..27919faa9 100644 --- a/Vault/Sources/VaultiOS/Views/Previews/VaultCardModifier.swift +++ b/Vault/Sources/VaultiOS/Views/Previews/VaultCardModifier.swift @@ -28,7 +28,7 @@ struct VaultCardModifier: ViewModifier { private var backgroundColor: Color { switch configuration.style { - case .prominent: Color.blue + case .prominent: Color.accentColor case .secondary: Color(UIColor.secondarySystemBackground) } } diff --git a/Vault/Sources/VaultiOS/Views/ThirdParty/ThirdPartyLibraryRowView.swift b/Vault/Sources/VaultiOS/Views/ThirdParty/ThirdPartyLibraryRowView.swift index a8df64c30..78bc7a0d5 100644 --- a/Vault/Sources/VaultiOS/Views/ThirdParty/ThirdPartyLibraryRowView.swift +++ b/Vault/Sources/VaultiOS/Views/ThirdParty/ThirdPartyLibraryRowView.swift @@ -7,11 +7,11 @@ struct ThirdPartyLibraryRowView: View { VStack(alignment: .leading, spacing: 2) { Text(library.name) .font(.body) - .foregroundColor(.primary) + .foregroundStyle(.primary) .lineLimit(1) Text(library.url.absoluteString) .font(.caption) - .foregroundColor(.secondary) + .foregroundStyle(.secondary) .lineLimit(1) } } diff --git a/Vault/Sources/VaultiOS/Views/TimerBar/CodeStateTimerBarView.swift b/Vault/Sources/VaultiOS/Views/TimerBar/CodeStateTimerBarView.swift index c9970a179..f03834281 100644 --- a/Vault/Sources/VaultiOS/Views/TimerBar/CodeStateTimerBarView.swift +++ b/Vault/Sources/VaultiOS/Views/TimerBar/CodeStateTimerBarView.swift @@ -17,7 +17,7 @@ struct CodeStateTimerBarView: View { .shimmering(active: isShimmering) } } - .animation(.easeOut, value: behaviour) + .animation(.snappy, value: behaviour) } private var isShimmering: Bool { diff --git a/Vault/Sources/VaultiOS/Views/TimerBar/CodeTimerHorizontalBarView.swift b/Vault/Sources/VaultiOS/Views/TimerBar/CodeTimerHorizontalBarView.swift index c69c219a2..18dad208c 100644 --- a/Vault/Sources/VaultiOS/Views/TimerBar/CodeTimerHorizontalBarView.swift +++ b/Vault/Sources/VaultiOS/Views/TimerBar/CodeTimerHorizontalBarView.swift @@ -6,8 +6,8 @@ import VaultFeed @MainActor struct CodeTimerHorizontalBarView: View { var timerState: OTPCodeTimerPeriodState - var color: Color = .blue - var backgroundColor: Color = .init(UIColor.systemGray2).opacity(0.3) + var color: Color = .accentColor + var backgroundColor: Color = .init(.quaternarySystemFill) @State private var currentFractionCompleted = 1.0 @Environment(\.scenePhase) private var scenePhase diff --git a/Vault/Sources/VaultiOS/Views/TimerBar/HorizontalTimerProgressBarView.swift b/Vault/Sources/VaultiOS/Views/TimerBar/HorizontalTimerProgressBarView.swift index 3b41a9805..f8de77689 100644 --- a/Vault/Sources/VaultiOS/Views/TimerBar/HorizontalTimerProgressBarView.swift +++ b/Vault/Sources/VaultiOS/Views/TimerBar/HorizontalTimerProgressBarView.swift @@ -4,7 +4,7 @@ import SwiftUI struct HorizontalTimerProgressBarView: View { var fractionCompleted: Double var color: Color - var backgroundColor: Color = .init(UIColor.systemGray6) + var backgroundColor: Color = .init(.quaternarySystemFill) @Environment(\.redactionReasons) private var redactionReasons diff --git a/Vault/Sources/VaultiOSShared/OTPCodeTextView.swift b/Vault/Sources/VaultiOSShared/OTPCodeTextView.swift index 06b0beffc..a0190577b 100644 --- a/Vault/Sources/VaultiOSShared/OTPCodeTextView.swift +++ b/Vault/Sources/VaultiOSShared/OTPCodeTextView.swift @@ -28,7 +28,7 @@ public struct OTPCodeTextView: View { .transition(.blurReplace(.downUp)) case let .error(_, digits): placeholderCode(digits: digits) - .foregroundColor(.red) + .foregroundStyle(.red) .transition(.blurReplace(.downUp)) case let .visible(code): makeCodeView(text: code) diff --git a/Vault/Sources/VaultiOSWidgets/Views/OTPWidgetSmallView.swift b/Vault/Sources/VaultiOSWidgets/Views/OTPWidgetSmallView.swift index 7becdaefa..73ae90164 100644 --- a/Vault/Sources/VaultiOSWidgets/Views/OTPWidgetSmallView.swift +++ b/Vault/Sources/VaultiOSWidgets/Views/OTPWidgetSmallView.swift @@ -60,7 +60,7 @@ struct OTPWidgetSmallView: View { .fontWeight(.heavy) .minimumScaleFactor(0.5) .lineLimit(1) - .foregroundColor(.primary) + .foregroundStyle(.primary) .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_empty.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_empty.1.png index 7212272b2..b45ccac56 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_empty.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_empty.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_halfFull.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_halfFull.1.png index fd4300012..5e58a373c 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_halfFull.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/layout_halfFull.1.png differ diff --git a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/redactedPrivacy_showsProgressStill.1.png b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/redactedPrivacy_showsProgressStill.1.png index fd4300012..5e58a373c 100644 Binary files a/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/redactedPrivacy_showsProgressStill.1.png and b/Vault/Tests/VaultiOSTests/__Snapshots__/HorizontalTimerProgressBarViewSnapshotTests/redactedPrivacy_showsProgressStill.1.png differ