366 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//  Models.swift
 | 
						|
//  WatchRunner Watch App
 | 
						|
//
 | 
						|
//  Created by LittleSheep on 2025/10/29.
 | 
						|
//
 | 
						|
 | 
						|
import Foundation
 | 
						|
 | 
						|
// MARK: - Models
 | 
						|
 | 
						|
struct AppToken: Codable {
 | 
						|
    let token: String
 | 
						|
}
 | 
						|
 | 
						|
struct SnActivity: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let type: String
 | 
						|
    let data: ActivityData?
 | 
						|
    let createdAt: Date
 | 
						|
}
 | 
						|
 | 
						|
enum ActivityData: Codable {
 | 
						|
    case post(SnPost)
 | 
						|
    case discovery(DiscoveryData)
 | 
						|
    case unknown
 | 
						|
 | 
						|
    init(from decoder: Decoder) throws {
 | 
						|
        let container = try decoder.singleValueContainer()
 | 
						|
        if let post = try? container.decode(SnPost.self) {
 | 
						|
            self = .post(post)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        if let discoveryData = try? container.decode(DiscoveryData.self) {
 | 
						|
            self = .discovery(discoveryData)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        self = .unknown
 | 
						|
    }
 | 
						|
 | 
						|
    func encode(to encoder: Encoder) throws {
 | 
						|
        // Not needed for decoding
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct SnPost: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let title: String?
 | 
						|
    let content: String?
 | 
						|
    let publisher: SnPublisher
 | 
						|
    let attachments: [SnCloudFile]
 | 
						|
    let tags: [SnPostTag]
 | 
						|
}
 | 
						|
 | 
						|
struct DiscoveryData: Codable {
 | 
						|
    let items: [DiscoveryItem]
 | 
						|
}
 | 
						|
 | 
						|
struct DiscoveryItem: Codable, Identifiable {
 | 
						|
    var id = UUID()
 | 
						|
    let type: String
 | 
						|
    let data: DiscoveryItemData
 | 
						|
 | 
						|
    enum CodingKeys: String, CodingKey {
 | 
						|
        case type, data
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
enum DiscoveryItemData: Codable {
 | 
						|
    case realm(SnRealm)
 | 
						|
    case publisher(SnPublisher)
 | 
						|
    case article(SnWebArticle)
 | 
						|
    case unknown
 | 
						|
 | 
						|
    init(from decoder: Decoder) throws {
 | 
						|
        let container = try decoder.singleValueContainer()
 | 
						|
        if let realm = try? container.decode(SnRealm.self) {
 | 
						|
            self = .realm(realm)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        if let publisher = try? container.decode(SnPublisher.self) {
 | 
						|
            self = .publisher(publisher)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        if let article = try? container.decode(SnWebArticle.self) {
 | 
						|
            self = .article(article)
 | 
						|
            return
 | 
						|
        }
 | 
						|
        self = .unknown
 | 
						|
    }
 | 
						|
 | 
						|
    func encode(to encoder: Encoder) throws {
 | 
						|
        // Not needed for decoding
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct SnRealm: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let name: String
 | 
						|
    let description: String?
 | 
						|
}
 | 
						|
 | 
						|
struct SnPublisher: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let name: String
 | 
						|
    let nick: String?
 | 
						|
    let description: String?
 | 
						|
    let picture: SnCloudFile?
 | 
						|
}
 | 
						|
 | 
						|
struct SnCloudFile: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let mimeType: String?
 | 
						|
}
 | 
						|
 | 
						|
struct SnPostTag: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let slug: String
 | 
						|
    let name: String?
 | 
						|
}
 | 
						|
 | 
						|
struct SnWebArticle: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let title: String
 | 
						|
    let url: String
 | 
						|
}
 | 
						|
 | 
						|
struct SnNotification: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let topic: String
 | 
						|
    let title: String
 | 
						|
    let subtitle: String
 | 
						|
    let content: String
 | 
						|
    let meta: [String: AnyCodable]?
 | 
						|
    let priority: Int
 | 
						|
    let viewedAt: Date?
 | 
						|
    let accountId: String
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
 | 
						|
    enum CodingKeys: String, CodingKey {
 | 
						|
        case id
 | 
						|
        case topic
 | 
						|
        case title
 | 
						|
        case subtitle
 | 
						|
        case content
 | 
						|
        case meta
 | 
						|
        case priority
 | 
						|
        case viewedAt = "viewedAt"
 | 
						|
        case accountId = "accountId"
 | 
						|
        case createdAt = "createdAt"
 | 
						|
        case updatedAt = "updatedAt"
 | 
						|
        case deletedAt = "deletedAt"
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct AnyCodable: Codable {
 | 
						|
    let value: Any
 | 
						|
 | 
						|
    init(_ value: Any) {
 | 
						|
        self.value = value
 | 
						|
    }
 | 
						|
 | 
						|
    init(from decoder: Decoder) throws {
 | 
						|
        let container = try decoder.singleValueContainer()
 | 
						|
        if let intValue = try? container.decode(Int.self) {
 | 
						|
            value = intValue
 | 
						|
        } else if let doubleValue = try? container.decode(Double.self) {
 | 
						|
            value = doubleValue
 | 
						|
        } else if let boolValue = try? container.decode(Bool.self) {
 | 
						|
            value = boolValue
 | 
						|
        } else if let stringValue = try? container.decode(String.self) {
 | 
						|
            value = stringValue
 | 
						|
        } else if let arrayValue = try? container.decode([AnyCodable].self) {
 | 
						|
            value = arrayValue
 | 
						|
        } else if let dictValue = try? container.decode([String: AnyCodable].self) {
 | 
						|
            value = dictValue
 | 
						|
        } else {
 | 
						|
            value = NSNull()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    func encode(to encoder: Encoder) throws {
 | 
						|
        var container = encoder.singleValueContainer()
 | 
						|
        switch value {
 | 
						|
        case let intValue as Int:
 | 
						|
            try container.encode(intValue)
 | 
						|
        case let doubleValue as Double:
 | 
						|
            try container.encode(doubleValue)
 | 
						|
        case let boolValue as Bool:
 | 
						|
            try container.encode(boolValue)
 | 
						|
        case let stringValue as String:
 | 
						|
            try container.encode(stringValue)
 | 
						|
        case let arrayValue as [AnyCodable]:
 | 
						|
            try container.encode(arrayValue)
 | 
						|
        case let dictValue as [String: AnyCodable]:
 | 
						|
            try container.encode(dictValue)
 | 
						|
        default:
 | 
						|
            try container.encodeNil()
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct NotificationResponse {
 | 
						|
    let notifications: [SnNotification]
 | 
						|
    let total: Int
 | 
						|
    let hasMore: Bool
 | 
						|
}
 | 
						|
 | 
						|
struct ActivityResponse {
 | 
						|
    let activities: [SnActivity]
 | 
						|
    let hasMore: Bool
 | 
						|
    let nextCursor: String?
 | 
						|
}
 | 
						|
 | 
						|
struct SnAccount: Codable {
 | 
						|
    let id: String
 | 
						|
    let name: String
 | 
						|
    let nick: String
 | 
						|
    let profile: SnUserProfile
 | 
						|
    let createdAt: Date
 | 
						|
}
 | 
						|
 | 
						|
struct SnUserProfile: Codable {
 | 
						|
    let bio: String?
 | 
						|
    let picture: SnCloudFile?
 | 
						|
    let background: SnCloudFile?
 | 
						|
    let level: Int
 | 
						|
    let experience: Int
 | 
						|
    let levelingProgress: Double
 | 
						|
}
 | 
						|
 | 
						|
struct SnAccountStatus: Codable {
 | 
						|
    let id: String
 | 
						|
    let attitude: Int
 | 
						|
    let isOnline: Bool
 | 
						|
    let isInvisible: Bool
 | 
						|
    let isNotDisturb: Bool
 | 
						|
    let isCustomized: Bool
 | 
						|
    let label: String
 | 
						|
    let meta: [String: AnyCodable]?
 | 
						|
    let clearedAt: Date?
 | 
						|
    let accountId: String
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
}
 | 
						|
 | 
						|
// MARK: - Chat Models
 | 
						|
 | 
						|
struct SnChatRoom: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let name: String?
 | 
						|
    let description: String?
 | 
						|
    let type: Int
 | 
						|
    let isPublic: Bool
 | 
						|
    let isCommunity: Bool
 | 
						|
    let picture: SnCloudFile?
 | 
						|
    let background: SnCloudFile?
 | 
						|
    let realmId: String?
 | 
						|
    let realm: SnRealm?
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
    let members: [SnChatMember]?
 | 
						|
}
 | 
						|
 | 
						|
struct SnChatMessage: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let type: String
 | 
						|
    let content: String?
 | 
						|
    let nonce: String?
 | 
						|
    let meta: [String: AnyCodable]
 | 
						|
    let membersMentioned: [String]?
 | 
						|
    let editedAt: Date?
 | 
						|
    let attachments: [SnCloudFile]
 | 
						|
    let reactions: [SnChatReaction]
 | 
						|
    let repliedMessageId: String?
 | 
						|
    let forwardedMessageId: String?
 | 
						|
    let senderId: String
 | 
						|
    let sender: SnChatMember
 | 
						|
    let chatRoomId: String
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
 | 
						|
    enum CodingKeys: String, CodingKey {
 | 
						|
        case id, type, content, nonce, meta, membersMentioned, editedAt, attachments, reactions, repliedMessageId, forwardedMessageId, senderId, sender, chatRoomId, createdAt, updatedAt, deletedAt
 | 
						|
    }
 | 
						|
 | 
						|
    init(from decoder: Decoder) throws {
 | 
						|
        let container = try decoder.container(keyedBy: CodingKeys.self)
 | 
						|
        id = try container.decode(String.self, forKey: .id)
 | 
						|
        type = try container.decode(String.self, forKey: .type)
 | 
						|
        content = try container.decodeIfPresent(String.self, forKey: .content)
 | 
						|
        nonce = try container.decodeIfPresent(String.self, forKey: .nonce)
 | 
						|
        meta = try container.decode([String: AnyCodable].self, forKey: .meta)
 | 
						|
        membersMentioned = try container.decodeIfPresent([String].self, forKey: .membersMentioned) ?? []
 | 
						|
        editedAt = try container.decodeIfPresent(Date.self, forKey: .editedAt)
 | 
						|
        attachments = try container.decode([SnCloudFile].self, forKey: .attachments)
 | 
						|
        reactions = try container.decode([SnChatReaction].self, forKey: .reactions)
 | 
						|
        repliedMessageId = try container.decodeIfPresent(String.self, forKey: .repliedMessageId)
 | 
						|
        forwardedMessageId = try container.decodeIfPresent(String.self, forKey: .forwardedMessageId)
 | 
						|
        senderId = try container.decode(String.self, forKey: .senderId)
 | 
						|
        sender = try container.decode(SnChatMember.self, forKey: .sender)
 | 
						|
        chatRoomId = try container.decode(String.self, forKey: .chatRoomId)
 | 
						|
        createdAt = try container.decode(Date.self, forKey: .createdAt)
 | 
						|
        updatedAt = try container.decode(Date.self, forKey: .updatedAt)
 | 
						|
        deletedAt = try container.decodeIfPresent(Date.self, forKey: .deletedAt)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
struct SnChatReaction: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let messageId: String
 | 
						|
    let senderId: String
 | 
						|
    let sender: SnChatMember
 | 
						|
    let symbol: String
 | 
						|
    let attitude: Int
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
}
 | 
						|
 | 
						|
struct SnChatMember: Codable, Identifiable {
 | 
						|
    let id: String
 | 
						|
    let chatRoomId: String
 | 
						|
    let chatRoom: SnChatRoom?
 | 
						|
    let accountId: String
 | 
						|
    let account: SnAccount
 | 
						|
    let nick: String?
 | 
						|
    let role: Int
 | 
						|
    let notify: Int
 | 
						|
    let joinedAt: Date?
 | 
						|
    let breakUntil: Date?
 | 
						|
    let timeoutUntil: Date?
 | 
						|
    let isBot: Bool
 | 
						|
    let status: SnAccountStatus?
 | 
						|
    let createdAt: Date
 | 
						|
    let updatedAt: Date
 | 
						|
    let deletedAt: Date?
 | 
						|
}
 | 
						|
 | 
						|
struct SnChatSummary: Codable {
 | 
						|
    let unreadCount: Int
 | 
						|
    let lastMessage: SnChatMessage?
 | 
						|
}
 | 
						|
 | 
						|
struct ChatRoomsResponse {
 | 
						|
    let rooms: [SnChatRoom]
 | 
						|
}
 | 
						|
 | 
						|
struct ChatInvitesResponse {
 | 
						|
    let invites: [SnChatMember]
 | 
						|
}
 | 
						|
 | 
						|
struct MessageSyncResponse: Codable {
 | 
						|
    let messages: [SnChatMessage]
 | 
						|
    let currentTimestamp: Date
 | 
						|
 | 
						|
    enum CodingKeys: String, CodingKey {
 | 
						|
        case messages
 | 
						|
        case currentTimestamp = "current_timestamp"
 | 
						|
    }
 | 
						|
}
 |