Fix build errors, resolve all warnings, and improve code quality
Widget Extension Fixes: - Create standalone WidgetDataProvider for widget data isolation - Add WIDGET_EXTENSION compiler flag for conditional compilation - Fix DataController references in widget-shared files - Sync widget version numbers with main app (23, 1.0.2) - Add WidgetBackground color to asset catalog Warning Resolutions: - Fix UIScreen.main deprecation in BGView and SharingListView - Fix Text '+' concatenation deprecation in PurchaseButtonView and SettingsTabView - Fix exhaustive switch in BiometricAuthManager (add .none case) - Fix var to let in ExportService (3 instances) - Fix unused result warning in NoteEditorView - Fix ForEach duplicate ID warnings in MonthView and YearView Code Quality Improvements: - Wrap bypassSubscription in #if DEBUG for security - Rename StupidAssCustomWidgetObservableObject to CustomWidgetStateViewModel - Add @MainActor to IconViewModel - Replace fatalError with graceful fallback in SharedModelContainer - Add [weak self] to closures in DayViewViewModel - Add OSLog-based AppLogger for production logging - Add ImageCache with NSCache for memory efficiency - Add AccessibilityHelpers with Reduce Motion support - Create DataControllerProtocol for dependency injection - Update .gitignore with secrets exclusions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
119
Shared/Utilities/AccessibilityHelpers.swift
Normal file
119
Shared/Utilities/AccessibilityHelpers.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// AccessibilityHelpers.swift
|
||||
// Feels
|
||||
//
|
||||
// Accessibility utilities for supporting VoiceOver, Dynamic Type, and Reduce Motion.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Reduce Motion Support
|
||||
|
||||
/// Environment key for accessing reduce motion preference
|
||||
struct ReduceMotionKey: EnvironmentKey {
|
||||
static let defaultValue: Bool = false
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var reduceMotion: Bool {
|
||||
get { self[ReduceMotionKey.self] }
|
||||
set { self[ReduceMotionKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
/// View modifier that respects reduce motion preference
|
||||
struct ReduceMotionModifier: ViewModifier {
|
||||
@Environment(\.accessibilityReduceMotion) var reduceMotion
|
||||
|
||||
let animation: Animation?
|
||||
let reducedAnimation: Animation?
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.animation(reduceMotion ? reducedAnimation : animation, value: UUID())
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Applies animation only when reduce motion is disabled
|
||||
func accessibleAnimation(_ animation: Animation? = .default, reduced: Animation? = nil) -> some View {
|
||||
modifier(ReduceMotionModifier(animation: animation, reducedAnimation: reduced))
|
||||
}
|
||||
|
||||
/// Wraps content in withAnimation respecting reduce motion
|
||||
func withAccessibleAnimation<V: Equatable>(_ animation: Animation? = .default, value: V, action: @escaping () -> Void) -> some View {
|
||||
self.onChange(of: value) { _, _ in
|
||||
if UIAccessibility.isReduceMotionEnabled {
|
||||
action()
|
||||
} else {
|
||||
withAnimation(animation) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Accessibility Helpers
|
||||
|
||||
extension View {
|
||||
/// Adds accessibility label with optional hint
|
||||
func accessibleMoodCell(mood: Mood, date: Date) -> some View {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
|
||||
return self
|
||||
.accessibilityLabel("\(mood.strValue) on \(formatter.string(from: date))")
|
||||
.accessibilityHint("Double tap to edit mood")
|
||||
}
|
||||
|
||||
/// Makes a button accessible with custom label
|
||||
func accessibleButton(label: String, hint: String? = nil) -> some View {
|
||||
self
|
||||
.accessibilityLabel(label)
|
||||
.accessibilityHint(hint ?? "")
|
||||
.accessibilityAddTraits(.isButton)
|
||||
}
|
||||
|
||||
/// Groups related elements for VoiceOver
|
||||
func accessibilityGrouped(label: String) -> some View {
|
||||
self
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(label)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Dynamic Type Support
|
||||
|
||||
extension Font {
|
||||
/// Returns a scalable font that respects Dynamic Type
|
||||
static func scalable(_ style: Font.TextStyle, weight: Font.Weight = .regular) -> Font {
|
||||
Font.system(style, design: .rounded).weight(weight)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Ensures minimum touch target size for accessibility (44x44 points)
|
||||
func accessibleTouchTarget() -> some View {
|
||||
self.frame(minWidth: 44, minHeight: 44)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Accessibility Announcements
|
||||
|
||||
struct AccessibilityAnnouncement {
|
||||
/// Announces a message to VoiceOver users
|
||||
static func announce(_ message: String) {
|
||||
UIAccessibility.post(notification: .announcement, argument: message)
|
||||
}
|
||||
|
||||
/// Notifies VoiceOver that screen content has changed
|
||||
static func screenChanged() {
|
||||
UIAccessibility.post(notification: .screenChanged, argument: nil)
|
||||
}
|
||||
|
||||
/// Notifies VoiceOver that layout has changed
|
||||
static func layoutChanged(focusElement: Any? = nil) {
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: focusElement)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user