1.6.0 for Android and 1.1.0 for iOS - Finalizing Export and Import

formats :)
This commit is contained in:
2025-09-28 02:37:03 -06:00
parent cf2e2f7c57
commit c3f847e1e6
48 changed files with 6944 additions and 1107 deletions

View File

@@ -12,6 +12,9 @@ struct SettingsView: View {
var body: some View {
List {
SyncSection()
.environmentObject(dataManager.syncService)
DataManagementSection(
activeSheet: $activeSheet
)
@@ -303,6 +306,361 @@ struct ExportDataView: View {
}
}
struct SyncSection: View {
@EnvironmentObject var syncService: SyncService
@EnvironmentObject var dataManager: ClimbingDataManager
@State private var showingSyncSettings = false
@State private var showingDisconnectAlert = false
var body: some View {
Section("Sync") {
// Sync Status
HStack {
Image(
systemName: syncService.isConnected
? "checkmark.circle.fill"
: syncService.isConfigured
? "exclamationmark.triangle.fill"
: "exclamationmark.circle.fill"
)
.foregroundColor(
syncService.isConnected
? .green
: syncService.isConfigured
? .orange
: .red
)
VStack(alignment: .leading) {
Text("Sync Server")
.font(.headline)
Text(
syncService.isConnected
? "Connected"
: syncService.isConfigured
? "Configured - Not tested"
: "Not configured"
)
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
}
// Configure Server
Button(action: {
showingSyncSettings = true
}) {
HStack {
Image(systemName: "gear")
.foregroundColor(.blue)
Text("Configure Server")
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
.foregroundColor(.primary)
if syncService.isConfigured {
// Sync Now - only show if connected
if syncService.isConnected {
Button(action: {
performSync()
}) {
HStack {
if syncService.isSyncing {
ProgressView()
.scaleEffect(0.8)
Text("Syncing...")
.foregroundColor(.secondary)
} else {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundColor(.green)
Text("Sync Now")
Spacer()
if let lastSync = syncService.lastSyncTime {
Text(
RelativeDateTimeFormatter().localizedString(
for: lastSync, relativeTo: Date())
)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
.disabled(syncService.isSyncing)
.foregroundColor(.primary)
}
// Auto-sync configuration - always visible for testing
HStack {
VStack(alignment: .leading) {
Text("Auto-sync")
Text("Sync automatically on app launch and data changes")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Toggle(
"",
isOn: Binding(
get: { syncService.isAutoSyncEnabled },
set: { syncService.isAutoSyncEnabled = $0 }
)
)
.disabled(!syncService.isConnected)
}
.foregroundColor(.primary)
// Disconnect option - only show if connected
if syncService.isConnected {
Button(action: {
showingDisconnectAlert = true
}) {
HStack {
Image(systemName: "power")
.foregroundColor(.orange)
Text("Disconnect")
Spacer()
}
}
.foregroundColor(.primary)
}
if let error = syncService.syncError {
Text(error)
.font(.caption)
.foregroundColor(.red)
.padding(.leading, 24)
}
}
}
.sheet(isPresented: $showingSyncSettings) {
SyncSettingsView()
.environmentObject(syncService)
}
.alert("Disconnect from Server", isPresented: $showingDisconnectAlert) {
Button("Cancel", role: .cancel) {}
Button("Disconnect", role: .destructive) {
syncService.disconnect()
}
} message: {
Text(
"This will sign you out but keep your server settings. You'll need to test the connection again to sync."
)
}
}
private func performSync() {
Task {
do {
try await syncService.syncWithServer(dataManager: dataManager)
} catch {
print("Sync failed: \(error)")
}
}
}
}
struct SyncSettingsView: View {
@EnvironmentObject var syncService: SyncService
@Environment(\.dismiss) private var dismiss
@State private var serverURL: String = ""
@State private var authToken: String = ""
@State private var showingDisconnectAlert = false
@State private var isTesting = false
@State private var showingTestResult = false
@State private var testResultMessage = ""
var body: some View {
NavigationView {
Form {
Section {
TextField("Server URL", text: $serverURL)
.textFieldStyle(.roundedBorder)
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
.placeholder(when: serverURL.isEmpty) {
Text("http://your-server:8080")
.foregroundColor(.secondary)
}
TextField("Auth Token", text: $authToken)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
.disableAutocorrection(true)
.placeholder(when: authToken.isEmpty) {
Text("your-secret-token")
.foregroundColor(.secondary)
}
} header: {
Text("Server Configuration")
} footer: {
Text(
"Enter your sync server URL and authentication token. You must test the connection before syncing is available."
)
}
Section {
Button(action: {
testConnection()
}) {
HStack {
if isTesting {
ProgressView()
.scaleEffect(0.8)
Text("Testing...")
.foregroundColor(.secondary)
} else {
Image(systemName: "network")
.foregroundColor(.blue)
Text("Test Connection")
Spacer()
if syncService.isConnected {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
}
}
}
}
.disabled(
isTesting
|| serverURL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|| authToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
)
.foregroundColor(.primary)
} header: {
Text("Connection")
} footer: {
Text("Test the connection to verify your server settings before saving.")
}
Section {
Button("Disconnect from Server") {
showingDisconnectAlert = true
}
.foregroundColor(.orange)
Button("Clear Configuration") {
syncService.clearConfiguration()
serverURL = ""
authToken = ""
}
.foregroundColor(.red)
} footer: {
Text(
"Disconnect will sign you out but keep settings. Clear Configuration removes all sync settings."
)
}
}
.navigationTitle("Sync Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
let newURL = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
let newToken = authToken.trimmingCharacters(in: .whitespacesAndNewlines)
// Mark as disconnected if settings changed
if newURL != syncService.serverURL || newToken != syncService.authToken {
syncService.isConnected = false
UserDefaults.standard.set(false, forKey: "sync_is_connected")
}
syncService.serverURL = newURL
syncService.authToken = newToken
dismiss()
}
.fontWeight(.semibold)
}
}
}
.onAppear {
serverURL = syncService.serverURL
authToken = syncService.authToken
}
.alert("Disconnect from Server", isPresented: $showingDisconnectAlert) {
Button("Cancel", role: .cancel) {}
Button("Disconnect", role: .destructive) {
syncService.disconnect()
dismiss()
}
} message: {
Text(
"This will sign you out but keep your server settings. You'll need to test the connection again to sync."
)
}
.alert("Connection Test", isPresented: $showingTestResult) {
Button("OK") {}
} message: {
Text(testResultMessage)
}
}
private func testConnection() {
isTesting = true
let testURL = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
let testToken = authToken.trimmingCharacters(in: .whitespacesAndNewlines)
// Store original values in case test fails
let originalURL = syncService.serverURL
let originalToken = syncService.authToken
Task {
do {
// Temporarily set the values for testing
syncService.serverURL = testURL
syncService.authToken = testToken
try await syncService.testConnection()
await MainActor.run {
isTesting = false
testResultMessage =
"Connection successful! You can now save and sync your data."
showingTestResult = true
}
} catch {
// Restore original values if test failed
syncService.serverURL = originalURL
syncService.authToken = originalToken
await MainActor.run {
isTesting = false
testResultMessage = "Connection failed: \(error.localizedDescription)"
showingTestResult = true
}
}
}
}
}
// Removed AutoSyncSettingsView - now using simple toggle in main settings
extension View {
func placeholder<Content: View>(
when shouldShow: Bool,
alignment: Alignment = .leading,
@ViewBuilder placeholder: () -> Content
) -> some View {
ZStack(alignment: alignment) {
placeholder().opacity(shouldShow ? 1 : 0)
self
}
}
}
struct ImportDataView: View {
@EnvironmentObject var dataManager: ClimbingDataManager
@Environment(\.dismiss) private var dismiss