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"
 | |
|     }
 | |
| }
 |