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,5 +1,7 @@
import Foundation
import UIKit
import SwiftUI
import LinkPresentation
class ShareService {
static let shared = ShareService()
@@ -18,6 +20,81 @@ class ShareService {
"""
}
static func buildPortfolioValueShareText(
totalValue: String,
changeText: String,
changeLabel: String,
yearChange: String?,
sinceInceptionChange: String?,
appName: String
) -> String {
var lines = [
"Total Portfolio Value",
totalValue,
"\(changeText) \(changeLabel)"
]
if let yearChange {
lines.append("YoY: \(yearChange)")
}
if let sinceInceptionChange {
lines.append("Since inception: \(sinceInceptionChange)")
}
lines.append("")
lines.append("Shared from \(appName)")
return lines.joined(separator: "\n")
}
@MainActor
func shareMonthlyCheckIn(summary: MonthlySummary, appName: String) {
let text = Self.buildMonthlyCheckInShareText(summary: summary, appName: appName)
shareCard(
cardTitle: "\(summary.periodLabel) Check-in",
fallbackText: text
) {
MonthlyCheckInShareCardView(
summary: summary,
appName: appName,
qrCodeImage: GoalShareService.generateQRCode(for: GoalShareService.appStoreURL, size: 200)
)
}
}
@MainActor
func sharePortfolioValue(
totalValue: String,
changeText: String,
changeLabel: String,
yearChange: String?,
sinceInceptionChange: String?
) {
let appName = Self.appDisplayName
let text = Self.buildPortfolioValueShareText(
totalValue: totalValue,
changeText: changeText,
changeLabel: changeLabel,
yearChange: yearChange,
sinceInceptionChange: sinceInceptionChange,
appName: appName
)
shareCard(
cardTitle: "Portfolio Snapshot",
fallbackText: text
) {
PortfolioValueShareCardView(
totalValue: totalValue,
changeText: changeText,
changeLabel: changeLabel,
yearChange: yearChange,
sinceInceptionChange: sinceInceptionChange,
appName: appName,
qrCodeImage: GoalShareService.generateQRCode(for: GoalShareService.appStoreURL, size: 200)
)
}
}
func shareText(_ content: String) {
guard let viewController = ShareService.topViewController() else { return }
@@ -41,6 +118,36 @@ class ShareService {
}
}
@MainActor
private func shareCard<Content: View>(
cardTitle: String,
fallbackText: String,
@ViewBuilder card: () -> Content
) {
guard let viewController = ShareService.topViewController() else { return }
let shareURL = GoalShareService.appStoreURL
if #available(iOS 16.0, *) {
let renderer = ImageRenderer(content: card())
let scale = viewController.view.window?.windowScene?.screen.scale
?? viewController.traitCollection.displayScale
renderer.scale = scale
if let image = renderer.uiImage {
let item = CardShareItem(
image: image,
title: cardTitle,
text: fallbackText,
url: shareURL
)
presentShareSheet(items: [item], from: viewController)
return
}
}
presentShareSheet(items: [fallbackText], from: viewController)
}
func shareTextFile(content: String, fileName: String) {
guard let viewController = ShareService.topViewController() else { return }
@@ -144,4 +251,67 @@ class ShareService {
.replacingOccurrences(of: ";", with: "\\;")
.replacingOccurrences(of: ",", with: "\\,")
}
private func presentShareSheet(items: [Any], from viewController: UIViewController) {
let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
if let popover = activityVC.popoverPresentationController {
popover.sourceView = viewController.view
popover.sourceRect = CGRect(
x: viewController.view.bounds.midX,
y: viewController.view.bounds.midY,
width: 0,
height: 0
)
popover.permittedArrowDirections = []
}
viewController.present(activityVC, animated: true)
}
private static var appDisplayName: String {
if let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
return name
}
if let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
return name
}
return "Portfolio Journal"
}
}
private class CardShareItem: NSObject, UIActivityItemSource {
let image: UIImage
let title: String
let text: String
let url: URL
init(image: UIImage, title: String, text: String, url: URL) {
self.image = image
self.title = title
self.text = text
self.url = url
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
image
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
image
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
title
}
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
let metadata = LPLinkMetadata()
metadata.title = title
metadata.originalURL = url
metadata.url = url
metadata.imageProvider = NSItemProvider(object: image)
if let appIcon = UIImage(named: "BrandMark") {
metadata.iconProvider = NSItemProvider(object: appIcon)
}
return metadata
}
}