✨ watchOS able to set status
This commit is contained in:
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct AccountView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State private var user: SnAccount?
|
||||
@State private var status: SnAccountStatus?
|
||||
@State private var isLoading = false
|
||||
@State private var error: Error?
|
||||
|
||||
@@ -102,20 +103,64 @@ struct AccountView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Bio
|
||||
if let bio = user.profile.bio, !bio.isEmpty {
|
||||
Text(bio)
|
||||
.font(.body)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Text("No bio available")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
// Status
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("Status")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
NavigationLink(
|
||||
destination: StatusCreationView(initialStatus: status)
|
||||
.environmentObject(appState)
|
||||
) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.blue.opacity(0.1))
|
||||
.frame(width: 28, height: 28)
|
||||
Image(systemName: "pencil")
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: 28, height: 28)
|
||||
}
|
||||
|
||||
if let status = status {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(status.isOnline ? Color.green : Color.gray)
|
||||
.frame(width: 8, height: 8)
|
||||
Text(status.label.isEmpty ? "No status" : status.label)
|
||||
.font(.body)
|
||||
}
|
||||
|
||||
if status.isInvisible {
|
||||
Text("Invisible")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
if status.isNotDisturb {
|
||||
Text("Do Not Disturb")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
if let clearedAt = status.clearedAt {
|
||||
Text("Clears: \(clearedAt.formatted(date: .abbreviated, time: .shortened))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No status set")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Level and Progress
|
||||
VStack(spacing: 8) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Level \(user.profile.level)")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
@@ -127,10 +172,25 @@ struct AccountView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
// Bio
|
||||
if let bio = user.profile.bio, !bio.isEmpty {
|
||||
Text(bio)
|
||||
.font(.body)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(alignment: .leading)
|
||||
} else {
|
||||
Text("No bio available")
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(alignment: .leading)
|
||||
}
|
||||
|
||||
// Member since
|
||||
Text("Member since: \(user.createdAt.formatted(.dateTime.month(.abbreviated).year()))")
|
||||
Text("Joined at \(user.createdAt.formatted(.dateTime.month(.abbreviated).year()))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(alignment: .leading)
|
||||
}
|
||||
.padding()
|
||||
// Load images when user data is available
|
||||
@@ -168,6 +228,7 @@ struct AccountView: View {
|
||||
|
||||
do {
|
||||
user = try await networkService.fetchUserProfile(token: token, serverUrl: serverUrl)
|
||||
status = try await networkService.fetchAccountStatus(token: token, serverUrl: serverUrl)
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
@@ -145,7 +145,6 @@ struct PostDetailView: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(width: .infinity)
|
||||
}
|
||||
.navigationTitle("Post")
|
||||
}
|
||||
|
||||
132
ios/WatchRunner Watch App/Views/StatusCreationView.swift
Normal file
132
ios/WatchRunner Watch App/Views/StatusCreationView.swift
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// StatusCreationView.swift
|
||||
// WatchRunner Watch App
|
||||
//
|
||||
// Created by LittleSheep on 2025/10/30.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct StatusCreationView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
let initialStatus: SnAccountStatus?
|
||||
|
||||
@State private var attitude: Int
|
||||
@State private var isInvisible: Bool
|
||||
@State private var isNotDisturb: Bool
|
||||
@State private var label: String
|
||||
@State private var isSubmitting: Bool = false
|
||||
@State private var error: Error? = nil
|
||||
|
||||
private let networkService = NetworkService()
|
||||
|
||||
init(initialStatus: SnAccountStatus? = nil) {
|
||||
self.initialStatus = initialStatus
|
||||
_attitude = State(initialValue: initialStatus?.attitude ?? 1)
|
||||
_isInvisible = State(initialValue: initialStatus?.isInvisible ?? false)
|
||||
_isNotDisturb = State(initialValue: initialStatus?.isNotDisturb ?? false)
|
||||
_label = State(initialValue: initialStatus?.label ?? "")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
// Title
|
||||
Text("Set Status")
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
|
||||
// Label TextField
|
||||
TextField("Status label", text: $label)
|
||||
.textFieldStyle(.automatic)
|
||||
.padding(.horizontal)
|
||||
|
||||
// Attitude Picker
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Mood")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Picker("Attitude", selection: $attitude) {
|
||||
Text("😊 Positive").tag(0)
|
||||
Text("😐 Neutral").tag(1)
|
||||
Text("😢 Negative").tag(2)
|
||||
}
|
||||
.pickerStyle(.wheel)
|
||||
.frame(height: 80)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
// Toggles
|
||||
VStack(spacing: 12) {
|
||||
Toggle("Invisible", isOn: $isInvisible)
|
||||
.padding(.horizontal)
|
||||
|
||||
Toggle("Do Not Disturb", isOn: $isNotDisturb)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
// Error message
|
||||
if let error = error {
|
||||
Text("Error: \(error.localizedDescription)")
|
||||
.foregroundColor(.red)
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
// Buttons
|
||||
HStack(spacing: 12) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.buttonStyle(.glass)
|
||||
|
||||
Button(isSubmitting ? "Saving..." : "Save") {
|
||||
Task {
|
||||
await submitStatus()
|
||||
}
|
||||
}
|
||||
.buttonStyle(.glassProminent)
|
||||
.disabled(isSubmitting)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Status")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
private func submitStatus() async {
|
||||
guard let token = appState.token, let serverUrl = appState.serverUrl else {
|
||||
error = NSError(domain: "StatusCreationView", code: 1, userInfo: [NSLocalizedDescriptionKey: "Authentication not available"])
|
||||
return
|
||||
}
|
||||
|
||||
isSubmitting = true
|
||||
error = nil
|
||||
|
||||
do {
|
||||
_ = try await networkService.createOrUpdateStatus(
|
||||
attitude: attitude,
|
||||
isInvisible: isInvisible,
|
||||
isNotDisturb: isNotDisturb,
|
||||
label: label.isEmpty ? nil : label,
|
||||
token: token,
|
||||
serverUrl: serverUrl
|
||||
)
|
||||
dismiss()
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
StatusCreationView()
|
||||
.environmentObject(AppState())
|
||||
}
|
||||
Reference in New Issue
Block a user