- 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>
114 lines
3.3 KiB
Swift
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
|