Optimize subscription tier management and empty state logic

Changes:
- Make currentTier a computed property from StoreKit instead of stored value
- Initialize StoreKit at app launch and refresh on foreground for ready subscription status
- Fix empty state logic: show empty state when limit > 0, upgrade prompt when limit = 0
- Add subscription status display in Profile screen (iOS/Android)
- Add upgrade prompts to Residences and Tasks screens for free tier users
- Improve SubscriptionHelper with better tier checking logic

iOS:
- SubscriptionCache: currentTier now computed from StoreKit.purchasedProductIDs
- StoreKitManager: add refreshSubscriptionStatus() method
- AppDelegate: initialize StoreKit at launch and refresh on app active
- ContractorsListView: use shouldShowUpgradePrompt(currentCount:limitKey:)
- WarrantiesTabContent/DocumentsTabContent: same empty state fix
- ProfileTabView: display current tier and limitations status
- ResidencesListView/ResidenceDetailView: add upgrade prompts for free users
- AllTasksView: add upgrade prompt for free users

Android:
- ContractorsScreen/DocumentsScreen: fix empty state logic
- ProfileScreen: display subscription status and limits
- ResidencesScreen/ResidenceDetailScreen: add upgrade prompts
- UpgradeFeatureScreen: improve UI layout

Shared:
- APILayer: add getSubscriptionStatus() method
- SubscriptionHelper: add hasAccessToFeature() utility
- Remove duplicate subscription checking logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-24 18:59:09 -06:00
parent d92a4fd4f1
commit ce1ca0f0ce
23 changed files with 728 additions and 120 deletions

View File

@@ -4,20 +4,28 @@ import ComposeApp
struct AllTasksView: View {
@StateObject private var taskViewModel = TaskViewModel()
@StateObject private var residenceViewModel = ResidenceViewModel()
@StateObject private var subscriptionCache = SubscriptionCacheWrapper.shared
@State private var tasksResponse: TaskColumnsResponse?
@State private var isLoadingTasks = false
@State private var tasksError: String?
@State private var showAddTask = false
@State private var showEditTask = false
@State private var showingUpgradePrompt = false
@State private var selectedTaskForEdit: TaskDetail?
@State private var selectedTaskForComplete: TaskDetail?
@State private var selectedTaskForArchive: TaskDetail?
@State private var showArchiveConfirmation = false
@State private var selectedTaskForCancel: TaskDetail?
@State private var showCancelConfirmation = false
// Count total tasks across all columns
private var totalTaskCount: Int {
guard let response = tasksResponse else { return 0 }
return response.columns.reduce(0) { $0 + $1.tasks.count }
}
private var hasNoTasks: Bool {
guard let response = tasksResponse else { return true }
return response.columns.allSatisfy { $0.tasks.isEmpty }
@@ -46,6 +54,9 @@ struct AllTasksView: View {
loadAllTasks()
}
}
.sheet(isPresented: $showingUpgradePrompt) {
UpgradePromptView(triggerKey: "add_11th_task", isPresented: $showingUpgradePrompt)
}
.alert("Archive Task", isPresented: $showArchiveConfirmation) {
Button("Cancel", role: .cancel) {
selectedTaskForArchive = nil
@@ -129,7 +140,12 @@ struct AllTasksView: View {
.multilineTextAlignment(.center)
Button(action: {
showAddTask = true
// Check if we should show upgrade prompt before adding
if subscriptionCache.shouldShowUpgradePrompt(currentCount: totalTaskCount, limitKey: "tasks") {
showingUpgradePrompt = true
} else {
showAddTask = true
}
}) {
HStack(spacing: 8) {
Image(systemName: "plus")
@@ -224,7 +240,12 @@ struct AllTasksView: View {
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showAddTask = true
// Check if we should show upgrade prompt before adding
if subscriptionCache.shouldShowUpgradePrompt(currentCount: totalTaskCount, limitKey: "tasks") {
showingUpgradePrompt = true
} else {
showAddTask = true
}
}) {
Image(systemName: "plus")
}