Add task completion animations, subscription trials, and quiet debug console

- Completion animations: play user-selected animation on task card after completing,
  with DataManager guard to prevent race condition during animation playback.
  Works in both AllTasksView and ResidenceDetailView. Animation preference
  persisted via @AppStorage and configurable from Settings.
- Subscription: add trial fields (trialStart, trialEnd, trialActive) and
  subscriptionSource to model, cross-platform purchase guard, trial banner
  in upgrade prompt, and platform-aware subscription management in profile.
- Analytics: disable PostHog SDK debug logging and remove console print
  statements to reduce debug console noise.
- Documents: remove redundant nested do-catch blocks in ViewModel wrapper.
- Widgets: add debounced timeline reloads and thread-safe file I/O queue.
- Onboarding: fix animation leak on disappear, remove unused state vars.
- Remove unused files (ContentView, StateFlowExtensions, CustomView).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-05 11:35:08 -06:00
parent c5f2bee83f
commit 98dbacdea0
73 changed files with 1770 additions and 529 deletions

View File

@@ -3,8 +3,11 @@ import SwiftUI
struct AnimationTestingView: View {
@Environment(\.dismiss) private var dismiss
// Animation selection
@State private var selectedAnimation: TaskAnimationType = .implode
// Animation selection (persisted)
@StateObject private var animationPreference = AnimationPreference.shared
private var selectedAnimation: TaskAnimationType {
get { animationPreference.selectedAnimation }
}
// Fake task data
@State private var columns: [TestColumn] = TestColumn.defaultColumns
@@ -30,7 +33,7 @@ struct AnimationTestingView: View {
resetButton
}
}
.navigationTitle("Animation Testing")
.navigationTitle("Completion Animation")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
@@ -59,13 +62,13 @@ struct AnimationTestingView: View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: AppSpacing.xs) {
ForEach(TaskAnimationType.allCases) { animation in
ForEach(TaskAnimationType.selectableCases) { animation in
AnimationChip(
animation: animation,
isSelected: selectedAnimation == animation,
onSelect: {
withAnimation(.easeInOut(duration: 0.2)) {
selectedAnimation = animation
animationPreference.selectedAnimation = animation
}
}
)
@@ -135,6 +138,17 @@ struct AnimationTestingView: View {
animatingTaskId = task.id
// No animation: instant move
if selectedAnimation == .none {
if let taskIndex = columns[currentIndex].tasks.firstIndex(where: { $0.id == task.id }) {
columns[currentIndex].tasks.remove(at: taskIndex)
}
columns[currentIndex + 1].tasks.insert(task, at: 0)
animatingTaskId = nil
animationPhase = .idle
return
}
// Extended timing animations: shrink card, show checkmark, THEN move task
if selectedAnimation.needsExtendedTiming {
// Phase 1: Start shrinking

View File

@@ -3,6 +3,7 @@ import SwiftUI
// MARK: - Animation Type Enum
enum TaskAnimationType: String, CaseIterable, Identifiable {
case none = "None"
case implode = "Implode"
case firework = "Firework"
case starburst = "Starburst"
@@ -12,6 +13,7 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
var icon: String {
switch self {
case .none: return "minus.circle"
case .implode: return "checkmark.circle"
case .firework: return "sparkle"
case .starburst: return "sun.max.fill"
@@ -21,6 +23,7 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
var description: String {
switch self {
case .none: return "No animation, instant move"
case .implode: return "Sucks into center, becomes checkmark"
case .firework: return "Explodes into colorful sparks"
case .starburst: return "Radiating rays from checkmark"
@@ -29,7 +32,17 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
}
/// All celebration animations need extended timing for checkmark display
var needsExtendedTiming: Bool { true }
var needsExtendedTiming: Bool {
switch self {
case .none: return false
default: return true
}
}
/// Selectable animation types (excludes "none" from picker in testing view)
static var selectableCases: [TaskAnimationType] {
allCases
}
}
// MARK: - Animation Phase
@@ -159,6 +172,8 @@ extension View {
@ViewBuilder
func taskAnimation(type: TaskAnimationType, phase: AnimationPhase) -> some View {
switch type {
case .none:
self
case .implode:
self.implodeAnimation(phase: phase)
case .firework: