Files
Reflect/Shared/Services/InsightsExporter.swift
Trey T 1f040ab676 v1.1 polish: accessibility, error logging, localization, and code quality sweep
- Wrap 30+ production print() statements in #if DEBUG guards across 18 files
- Add VoiceOver labels, hints, and traits to Watch app, Live Activities, widgets
- Add .accessibilityAddTraits(.isButton) to 15+ onTapGesture views
- Add text alternatives for color-only indicators (progress dots, mood circles)
- Localize raw string literals in NoteEditorView, EntryDetailView, widgets
- Replace 25+ silent try? with do/catch + AppLogger error logging
- Replace hardcoded font sizes with semantic Dynamic Type fonts
- Fix FIXME in IconPickerView (log icon change errors)
- Extract magic animation delays to named constants across 8 files
- Add widget empty state "Log your first mood!" messaging
- Hide decorative images from VoiceOver, add labels to ColorPickers
- Remove stale TODO in Color+Codable (alpha change deferred for migration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:09:14 -05:00

114 lines
3.3 KiB
Swift

//
// InsightsExporter.swift
// Reflect
//
// Debug utility to export insights view screenshots with sample AI data.
//
#if DEBUG
import SwiftUI
import UIKit
import os.log
/// Exports insights view screenshots for App Store marketing
@MainActor
class InsightsExporter {
// MARK: - Screen Sizes (iPhone 15 Pro Max @ 3x)
/// Full screen size for insights export
static let screenSize = CGSize(width: 430, height: 932)
// MARK: - Export All Insights Screenshots
/// Exports insights screenshots in light and dark mode
/// - Returns: URL to the export directory, or nil if failed
static func exportInsightsScreenshots() async -> URL? {
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let exportPath = documentsPath.appendingPathComponent("InsightsExports", isDirectory: true)
// Clean and create export directory
try? FileManager.default.removeItem(at: exportPath)
do {
try FileManager.default.createDirectory(at: exportPath, withIntermediateDirectories: true)
} catch {
AppLogger.export.error("Failed to create insights export directory: \(error)")
return nil
}
var totalExported = 0
// Export with default tint and emoji pack
let moodTint: MoodTints = .Default
let imagePack: MoodImages = .Emoji
// Export light mode
await exportInsightsView(
colorScheme: .light,
moodTint: moodTint,
imagePack: imagePack,
to: exportPath,
name: "insights_light"
)
totalExported += 1
// Export dark mode
await exportInsightsView(
colorScheme: .dark,
moodTint: moodTint,
imagePack: imagePack,
to: exportPath,
name: "insights_dark"
)
totalExported += 1
print("✨ Total \(totalExported) insights screenshots exported to: \(exportPath.path)")
return exportPath
}
// MARK: - Export Single Screenshot
private static func exportInsightsView(
colorScheme: ColorScheme,
moodTint: MoodTints,
imagePack: MoodImages,
to folder: URL,
name: String
) async {
let content = ExportableInsightsView(
colorScheme: colorScheme,
moodTint: moodTint,
imagePack: imagePack
)
let view = ExportableInsightsContainer(
width: screenSize.width,
height: screenSize.height,
colorScheme: colorScheme
) {
content
}
await renderAndSave(view: view, to: folder, name: name)
}
// MARK: - Render and Save
private static func renderAndSave<V: View>(view: V, to folder: URL, name: String) async {
let renderer = ImageRenderer(content: view.frame(width: screenSize.width, height: screenSize.height))
renderer.scale = 3.0 // 3x for high res
if let image = renderer.uiImage {
let url = folder.appendingPathComponent("\(name).png")
if let data = image.pngData() {
do {
try data.write(to: url)
} catch {
AppLogger.export.error("Failed to write insights image '\(name)': \(error)")
}
}
}
}
}
#endif