389 lines
12 KiB
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())
|
|
}
|