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:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user