1.1.0 feature work: Monthly Check-in, Charts, Goals, Share, Reviews

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-02-11 23:37:11 +01:00
parent daaca95913
commit 4761e2e5c8
19 changed files with 1118 additions and 90 deletions
@@ -1,6 +1,7 @@
import SwiftUI
struct MonthlyCheckInView: View {
@Environment(\.openURL) private var openURL
@EnvironmentObject var accountStore: AccountStore
@StateObject private var viewModel = MonthlyCheckInViewModel()
let referenceDate: Date
@@ -13,6 +14,8 @@ struct MonthlyCheckInView: View {
@State private var editingSnapshot: Snapshot?
@State private var addingSource: InvestmentSource?
@State private var didApplyDuplicate = false
@State private var showAchievementSatisfactionDialog = false
@State private var showAppStoreReviewAlert = false
init(referenceDate: Date = Date(), duplicatePrevious: Bool = false) {
self.referenceDate = referenceDate
@@ -56,7 +59,7 @@ struct MonthlyCheckInView: View {
}
private var canAddNewCheckIn: Bool {
lastCompletionDate == nil || checkInProgress >= 0.7
true
}
var body: some View {
@@ -119,11 +122,36 @@ struct MonthlyCheckInView: View {
.onChange(of: selectedMood) { _, newValue in
MonthlyCheckInStore.setMood(newValue, for: referenceDate)
}
.confirmationDialog(
"How much are you enjoying Portfolio Journal?",
isPresented: $showAchievementSatisfactionDialog,
titleVisibility: .visible
) {
ForEach(1...5, id: \.self) { value in
Button("\(value) Star\(value > 1 ? "s" : "")") {
if value == 5 {
showAppStoreReviewAlert = true
}
}
}
Button("Not Now", role: .cancel) {}
} message: {
Text("Congrats on your new achievement! Your feedback helps us improve.")
}
.alert("Would you like to leave an App Store review?", isPresented: $showAppStoreReviewAlert) {
Button("Not Now", role: .cancel) {}
Button("Write a Review") {
ReviewPromptService.shared.markStoreReviewCompleted()
openURL(ReviewPromptService.appStoreWriteReviewURL())
}
} message: {
Text("Thanks for the 5 stars. It really helps other investors discover the app.")
}
}
private var headerCard: some View {
VStack(alignment: .leading, spacing: 8) {
Text("This Month")
Text(monthLabel)
.font(.headline)
if let date = lastCompletionDate {
@@ -162,6 +190,7 @@ struct MonthlyCheckInView: View {
}
Button {
let previousUnlockedAchievementKeys = unlockedAchievementKeys()
let now = Date()
let completionDate = referenceDate.isSameMonth(as: now)
? now
@@ -169,6 +198,12 @@ struct MonthlyCheckInView: View {
MonthlyCheckInStore.setCompletionDate(completionDate, for: referenceDate)
ReviewPromptService.shared.recordMonthlyCheckInCompleted()
viewModel.refresh()
let newlyUnlockedAchievementKeys = unlockedAchievementKeys().subtracting(previousUnlockedAchievementKeys)
if ReviewPromptService.shared.shouldAskForAchievementSatisfaction(
newlyUnlockedAchievementKeys: newlyUnlockedAchievementKeys
) {
showAchievementSatisfactionDialog = true
}
} label: {
Text("Mark Check-in Complete")
.font(.subheadline.weight(.semibold))
@@ -178,12 +213,6 @@ struct MonthlyCheckInView: View {
.cornerRadius(AppConstants.UI.cornerRadius)
}
.disabled(!canAddNewCheckIn)
if !canAddNewCheckIn {
Text("Editing stays open. New check-ins unlock after 70% of the month.")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
.background(Color(.systemBackground))
@@ -191,6 +220,27 @@ struct MonthlyCheckInView: View {
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
private var monthLabel: String {
Self.monthLabel(for: referenceDate, relativeTo: Date(), locale: .current)
}
private func unlockedAchievementKeys() -> Set<String> {
Set(
MonthlyCheckInStore
.achievementStatuses(referenceDate: referenceDate)
.filter(\.isUnlocked)
.map(\.id)
)
}
static func monthLabel(for date: Date, relativeTo referenceDate: Date, locale: Locale) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "LLLL yyyy"
formatter.locale = locale
let effectiveMonth = MonthlyCheckInStore.effectiveMonth(for: date, relativeTo: referenceDate)
return formatter.string(from: effectiveMonth)
}
private var reflectionCard: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
@@ -457,11 +507,7 @@ struct MonthlyCheckInView: View {
private func shareMonthlyCheckIn() {
let summary = viewModel.monthlySummary
let text = ShareService.buildMonthlyCheckInShareText(
summary: summary,
appName: appDisplayName
)
ShareService.shared.shareText(text)
ShareService.shared.shareMonthlyCheckIn(summary: summary, appName: appDisplayName)
}
private var appDisplayName: String {