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