Files
Reflect/Shared/Views/Sharing/SharingStylePickerView.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

268 lines
9.0 KiB
Swift

//
// SharingStylePickerView.swift
// Reflect
//
// A horizontal pager that lets users swipe between design variations
// for a sharing template, then export the selected design.
//
import SwiftUI
struct SharingDesign: Identifiable {
let id = UUID()
let name: String
let shareView: AnyView
let image: () -> UIImage
}
struct SharePickerData: Identifiable {
let id = UUID()
let title: String
let designs: [SharingDesign]
}
struct SharingStylePickerView: View {
let title: String
let designs: [SharingDesign]
@State private var selectedIndex = 0
@StateObject private var shareImage = ShareImageStateViewModel()
@Environment(\.presentationMode) var presentationMode
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
private var textColor: Color { theme.currentTheme.labelColor }
private var safeIndex: Int {
guard !designs.isEmpty else { return 0 }
return min(selectedIndex, designs.count - 1)
}
var body: some View {
if designs.isEmpty {
Text("No designs available")
.foregroundColor(textColor)
} else {
VStack(spacing: 0) {
// Title bar
HStack {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Exit")
.font(.headline)
.foregroundColor(.red)
}
Spacer()
Text(title)
.font(.headline)
.foregroundColor(textColor)
Spacer()
// Invisible placeholder for symmetry
Text("Exit")
.font(.headline)
.hidden()
}
.padding(.horizontal)
.padding(.vertical, 12)
// Pager showing scaled-down shareViews
TabView(selection: $selectedIndex) {
ForEach(Array(designs.enumerated()), id: \.offset) { index, design in
design.shareView
.scaleEffect(0.45, anchor: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
// Design name label
Text(designs[safeIndex].name)
.font(.title3)
.fontWeight(.semibold)
.foregroundColor(textColor)
.padding(.top, 8)
.animation(.none, value: selectedIndex)
// Share button
Button(action: {
let img = designs[safeIndex].image()
shareImage.selectedShareImage = img
shareImage.showSheet = true
}) {
Text("Share")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Color.green)
.cornerRadius(14)
}
.padding(.horizontal, 24)
.padding(.top, 12)
.padding(.bottom, 24)
}
.background(theme.currentTheme.bg.edgesIgnoringSafeArea(.all))
.sheet(isPresented: $shareImage.showSheet) {
if let uiImage = shareImage.selectedShareImage {
ShareSheet(photo: uiImage)
}
}
}
}
}
// MARK: - Longest Streak Picker (mood selection + style picker)
struct LongestStreakPickerView: View {
let startDate: Date
let endDate: Date
@State private var selectedMood: Mood = .great
@State private var streakEntries: [MoodEntryModel] = []
@State private var selectedIndex = 0
@StateObject private var shareImage = ShareImageStateViewModel()
@Environment(\.presentationMode) var presentationMode
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
private var textColor: Color { theme.currentTheme.labelColor }
private var designs: [SharingDesign] {
[
SharingDesign(
name: "Gradient Bar",
shareView: AnyView(LongestStreakV2(streakEntries: streakEntries, selectedMood: selectedMood)),
image: { LongestStreakV2(streakEntries: streakEntries, selectedMood: selectedMood).image }
),
SharingDesign(
name: "Dark Badge",
shareView: AnyView(LongestStreakV3(streakEntries: streakEntries, selectedMood: selectedMood)),
image: { LongestStreakV3(streakEntries: streakEntries, selectedMood: selectedMood).image }
),
]
}
var body: some View {
VStack(spacing: 0) {
// Title bar with mood picker
HStack {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Exit")
.font(.headline)
.foregroundColor(.red)
}
Spacer()
Menu {
ForEach(Mood.allValues) { mood in
Button(mood.strValue) {
selectedMood = mood
recomputeStreak()
}
}
} label: {
HStack(spacing: 6) {
Text(selectedMood.strValue)
.font(.headline)
.foregroundColor(textColor)
Image(systemName: "chevron.down")
.font(.caption)
.foregroundColor(textColor.opacity(0.6))
}
}
Spacer()
Text("Exit")
.font(.headline)
.hidden()
}
.padding(.horizontal)
.padding(.vertical, 12)
// Pager
TabView(selection: $selectedIndex) {
ForEach(Array(designs.enumerated()), id: \.offset) { index, design in
design.shareView
.scaleEffect(0.45, anchor: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
// Design name
Text(designs[selectedIndex].name)
.font(.title3)
.fontWeight(.semibold)
.foregroundColor(textColor)
.padding(.top, 8)
.animation(.none, value: selectedIndex)
// Share button
Button(action: {
let img = designs[selectedIndex].image()
shareImage.selectedShareImage = img
shareImage.showSheet = true
}) {
Text("Share")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(Color.green)
.cornerRadius(14)
}
.padding(.horizontal, 24)
.padding(.top, 12)
.padding(.bottom, 24)
}
.background(theme.currentTheme.bg.edgesIgnoringSafeArea(.all))
.sheet(isPresented: $shareImage.showSheet) {
if let uiImage = shareImage.selectedShareImage {
ShareSheet(photo: uiImage)
}
}
.onAppear { recomputeStreak() }
}
@MainActor
private func recomputeStreak() {
let allEntries = DataController.shared.getData(
startDate: startDate,
endDate: endDate,
includedDays: [1, 2, 3, 4, 5, 6, 7]
)
var splitArrays = createSubArrays(fromMoodEntries: allEntries, splitOn: selectedMood)
splitArrays.sort { $0.count > $1.count }
streakEntries = splitArrays.first ?? []
}
private func createSubArrays(fromMoodEntries: [MoodEntryModel], splitOn: Mood) -> [[MoodEntryModel]] {
var splitArrays = [[MoodEntryModel]]()
var currentSplit = [MoodEntryModel]()
for entry in fromMoodEntries {
if entry.mood == splitOn {
currentSplit.append(entry)
} else {
splitArrays.append(currentSplit)
currentSplit.removeAll()
}
}
splitArrays.append(currentSplit)
return splitArrays
}
}