Release 1.3.0: onboarding UX, charts paywall banner, re-engagement notifications, Crashlytics

- Crashlytics: add FirebaseCrashlytics framework + dSYM upload build phase
- Onboarding: useSampleData off by default, Add First Investment as primary CTA
- AddSourceView: contextual placeholder and footer explaining what a source is
- Charts: CompactPaywallBanner visible to free users on every visit
- Notifications: re-engagement (7d) and monthly check-in local notifications
- Paywall: decorative chart preview replacing static crown icon
- Localization: all new strings in en + es-ES
This commit is contained in:
alexandrev-tibco
2026-05-01 09:26:37 +02:00
parent 10f6d0ca20
commit 94ed4d17eb
11 changed files with 340 additions and 131 deletions
@@ -176,6 +176,62 @@ extension Notification.Name {
static let didResetData = Notification.Name("didResetData")
}
// MARK: - Re-engagement Notifications
extension NotificationService {
/// Schedules a re-engagement notification 7 days from now.
/// Call this every time the app becomes active to reset the timer.
func scheduleReEngagementNotification() {
guard isAuthorized else { return }
center.removePendingNotificationRequests(withIdentifiers: ["re_engagement"])
let content = UNMutableNotificationContent()
content.title = String(localized: "reengagement_title")
content.body = String(localized: "reengagement_body")
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 7 * 24 * 3600, repeats: false)
let request = UNNotificationRequest(identifier: "re_engagement", content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print("Re-engagement notification error: \(error)")
}
}
}
/// Schedules a monthly check-in notification on the 1st of each month at 9am.
/// Only schedules if not already pending.
func scheduleMonthlyCheckIn() {
guard isAuthorized else { return }
let identifier = "monthly_checkin"
center.getPendingNotificationRequests { [weak self] requests in
guard let self, !requests.contains(where: { $0.identifier == identifier }) else { return }
let content = UNMutableNotificationContent()
content.title = String(localized: "monthly_checkin_notification_title")
content.body = String(localized: "monthly_checkin_notification_body")
content.sound = .default
var components = DateComponents()
components.day = 1
components.hour = 9
components.minute = 0
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
self.center.add(request) { error in
if let error = error {
print("Monthly check-in notification error: \(error)")
}
}
}
}
}
// MARK: - Background Refresh
extension NotificationService {