InvestmentTrackerApp/InvestmentTracker/Views/Settings/SettingsView.swift

389 lines
12 KiB
Swift

import SwiftUI
struct SettingsView: View {
@EnvironmentObject var iapService: IAPService
@StateObject private var viewModel: SettingsViewModel
init() {
_viewModel = StateObject(wrappedValue: SettingsViewModel(iapService: IAPService()))
}
var body: some View {
NavigationStack {
List {
// Premium Section
premiumSection
// Notifications Section
notificationsSection
// Data Section
dataSection
// About Section
aboutSection
// Danger Zone
dangerZoneSection
}
.navigationTitle("Settings")
.sheet(isPresented: $viewModel.showingPaywall) {
PaywallView()
}
.sheet(isPresented: $viewModel.showingExportOptions) {
ExportOptionsSheet(viewModel: viewModel)
}
.confirmationDialog(
"Reset All Data",
isPresented: $viewModel.showingResetConfirmation,
titleVisibility: .visible
) {
Button("Reset Everything", role: .destructive) {
viewModel.resetAllData()
}
} message: {
Text("This will permanently delete all your investment data. This action cannot be undone.")
}
.alert("Success", isPresented: .constant(viewModel.successMessage != nil)) {
Button("OK") {
viewModel.successMessage = nil
}
} message: {
Text(viewModel.successMessage ?? "")
}
.alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK") {
viewModel.errorMessage = nil
}
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}
// MARK: - Premium Section
private var premiumSection: some View {
Section {
if viewModel.isPremium {
HStack {
ZStack {
Circle()
.fill(Color.yellow.opacity(0.2))
.frame(width: 44, height: 44)
Image(systemName: "crown.fill")
.foregroundStyle(
LinearGradient(
colors: [.yellow, .orange],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
VStack(alignment: .leading, spacing: 2) {
Text("Premium Active")
.font(.headline)
if viewModel.isFamilyShared {
Text("Family Sharing")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.positiveGreen)
}
} else {
Button {
viewModel.upgradeToPremium()
} label: {
HStack {
ZStack {
Circle()
.fill(Color.appPrimary.opacity(0.1))
.frame(width: 44, height: 44)
Image(systemName: "crown.fill")
.foregroundColor(.appPrimary)
}
VStack(alignment: .leading, spacing: 2) {
Text("Upgrade to Premium")
.font(.headline)
.foregroundColor(.primary)
Text("Unlock all features for €4.69")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
}
}
Button {
Task {
await viewModel.restorePurchases()
}
} label: {
Text("Restore Purchases")
}
}
} header: {
Text("Subscription")
} footer: {
if !viewModel.isPremium {
Text("Free: \(viewModel.sourceLimitText)\(viewModel.historyLimitText)")
}
}
}
// MARK: - Notifications Section
private var notificationsSection: some View {
Section {
HStack {
Text("Notifications")
Spacer()
Text(viewModel.notificationsEnabled ? "Enabled" : "Disabled")
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
.onTapGesture {
if !viewModel.notificationsEnabled {
Task {
await viewModel.requestNotificationPermission()
}
} else {
viewModel.openSystemSettings()
}
}
if viewModel.notificationsEnabled {
DatePicker(
"Default Reminder Time",
selection: $viewModel.defaultNotificationTime,
displayedComponents: .hourAndMinute
)
.onChange(of: viewModel.defaultNotificationTime) { _, newTime in
viewModel.updateNotificationTime(newTime)
}
}
} header: {
Text("Notifications")
} footer: {
Text("Set when you'd like to receive investment update reminders.")
}
}
// MARK: - Data Section
private var dataSection: some View {
Section {
Button {
if viewModel.canExport {
viewModel.showingExportOptions = true
} else {
viewModel.showingPaywall = true
}
} label: {
HStack {
Label("Export Data", systemImage: "square.and.arrow.up")
Spacer()
if !viewModel.canExport {
Image(systemName: "lock.fill")
.font(.caption)
.foregroundColor(.appWarning)
}
}
}
HStack {
Text("Total Sources")
Spacer()
Text("\(viewModel.totalSources)")
.foregroundColor(.secondary)
}
HStack {
Text("Total Snapshots")
Spacer()
Text("\(viewModel.totalSnapshots)")
.foregroundColor(.secondary)
}
HStack {
Text("Storage Used")
Spacer()
Text(viewModel.storageUsedText)
.foregroundColor(.secondary)
}
} header: {
Text("Data")
}
}
// MARK: - About Section
private var aboutSection: some View {
Section {
HStack {
Text("Version")
Spacer()
Text(viewModel.appVersion)
.foregroundColor(.secondary)
}
Link(destination: URL(string: AppConstants.URLs.privacyPolicy)!) {
HStack {
Text("Privacy Policy")
Spacer()
Image(systemName: "arrow.up.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
Link(destination: URL(string: AppConstants.URLs.termsOfService)!) {
HStack {
Text("Terms of Service")
Spacer()
Image(systemName: "arrow.up.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
Link(destination: URL(string: AppConstants.URLs.support)!) {
HStack {
Text("Support")
Spacer()
Image(systemName: "arrow.up.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
Button {
requestAppReview()
} label: {
HStack {
Text("Rate App")
Spacer()
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
} header: {
Text("About")
}
}
// MARK: - Danger Zone Section
private var dangerZoneSection: some View {
Section {
Button(role: .destructive) {
viewModel.showingResetConfirmation = true
} label: {
HStack {
Image(systemName: "trash")
Text("Reset All Data")
}
}
} header: {
Text("Danger Zone")
} footer: {
Text("This will permanently delete all your investment sources, snapshots, and settings.")
}
}
// MARK: - Helpers
private func requestAppReview() {
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
import StoreKit
SKStoreReviewController.requestReview(in: scene)
}
}
// MARK: - Export Options Sheet
struct ExportOptionsSheet: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var viewModel: SettingsViewModel
var body: some View {
NavigationStack {
List {
Section {
Button {
viewModel.exportData(format: .csv)
dismiss()
} label: {
HStack {
Image(systemName: "tablecells")
.foregroundColor(.positiveGreen)
.frame(width: 30)
VStack(alignment: .leading) {
Text("CSV")
.font(.headline)
Text("Compatible with Excel, Google Sheets")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
Button {
viewModel.exportData(format: .json)
dismiss()
} label: {
HStack {
Image(systemName: "doc.text")
.foregroundColor(.appPrimary)
.frame(width: 30)
VStack(alignment: .leading) {
Text("JSON")
.font(.headline)
Text("Full data structure for backup")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
} header: {
Text("Select Format")
}
}
.navigationTitle("Export Data")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Cancel") {
dismiss()
}
}
}
}
.presentationDetents([.medium])
}
}
#Preview {
SettingsView()
.environmentObject(IAPService())
}