|
| 1 | +// UserAccount.swift |
| 2 | +// Kaset |
| 3 | +// |
| 4 | +// Created for account switcher feature. |
| 5 | + |
| 6 | +import Foundation |
| 7 | + |
| 8 | +/// Represents a YouTube Music user account (primary or brand account). |
| 9 | +/// |
| 10 | +/// YouTube Music allows users to have multiple accounts: |
| 11 | +/// - **Primary account**: The main Google account (no brandId) |
| 12 | +/// - **Brand accounts**: Managed channel accounts associated with the primary account |
| 13 | +/// |
| 14 | +/// ## API Response Mapping |
| 15 | +/// - `name`: From `accountName.runs[0].text` |
| 16 | +/// - `handle`: From `channelHandle.runs[0].text` (optional) |
| 17 | +/// - `brandId`: From `serviceEndpoint.selectActiveIdentityEndpoint.supportedTokens[].pageIdToken.pageId` |
| 18 | +/// - `thumbnailURL`: From `accountPhoto.thumbnails[0].url` |
| 19 | +/// - `isSelected`: From `isSelected` |
| 20 | +public struct UserAccount: Identifiable, Equatable, Sendable, Hashable { |
| 21 | + // MARK: - Properties |
| 22 | + |
| 23 | + /// Unique identifier for the account. |
| 24 | + /// Uses `brandId` for brand accounts, "primary" for the main account. |
| 25 | + public let id: String |
| 26 | + |
| 27 | + /// Display name of the account. |
| 28 | + public let name: String |
| 29 | + |
| 30 | + /// Channel handle (e.g., "@username"), if available. |
| 31 | + public let handle: String? |
| 32 | + |
| 33 | + /// Brand account identifier, nil for primary accounts. |
| 34 | + public let brandId: String? |
| 35 | + |
| 36 | + /// URL for the account's profile photo thumbnail. |
| 37 | + public let thumbnailURL: URL? |
| 38 | + |
| 39 | + /// Whether this account is currently selected/active. |
| 40 | + public let isSelected: Bool |
| 41 | + |
| 42 | + // MARK: - Computed Properties |
| 43 | + |
| 44 | + /// Returns `true` if this is the primary Google account (not a brand account). |
| 45 | + public var isPrimary: Bool { |
| 46 | + self.brandId == nil |
| 47 | + } |
| 48 | + |
| 49 | + /// Human-readable label for the account type. |
| 50 | + /// Returns "Personal" for primary accounts, "Brand" for brand accounts. |
| 51 | + public var typeLabel: String { |
| 52 | + self.isPrimary ? "Personal" : "Brand" |
| 53 | + } |
| 54 | + |
| 55 | + // MARK: - Initialization |
| 56 | + |
| 57 | + /// Creates a new UserAccount instance. |
| 58 | + /// |
| 59 | + /// - Parameters: |
| 60 | + /// - id: Unique identifier for the account. |
| 61 | + /// - name: Display name of the account. |
| 62 | + /// - handle: Optional channel handle. |
| 63 | + /// - brandId: Brand account identifier (nil for primary). |
| 64 | + /// - thumbnailURL: URL for the profile photo. |
| 65 | + /// - isSelected: Whether the account is currently active. |
| 66 | + public init( |
| 67 | + id: String, |
| 68 | + name: String, |
| 69 | + handle: String?, |
| 70 | + brandId: String?, |
| 71 | + thumbnailURL: URL?, |
| 72 | + isSelected: Bool |
| 73 | + ) { |
| 74 | + self.id = id |
| 75 | + self.name = name |
| 76 | + self.handle = handle |
| 77 | + self.brandId = brandId |
| 78 | + self.thumbnailURL = thumbnailURL |
| 79 | + self.isSelected = isSelected |
| 80 | + } |
| 81 | + |
| 82 | + // MARK: - Factory Methods |
| 83 | + |
| 84 | + /// Creates a UserAccount from API response fields. |
| 85 | + /// |
| 86 | + /// This factory method automatically determines the account ID based on whether |
| 87 | + /// a brandId is provided. |
| 88 | + /// |
| 89 | + /// - Parameters: |
| 90 | + /// - name: Display name from `accountName.runs[0].text`. |
| 91 | + /// - handle: Optional handle from `channelHandle.runs[0].text`. |
| 92 | + /// - brandId: Brand ID from `pageIdToken.pageId`, nil for primary account. |
| 93 | + /// - thumbnailURL: Thumbnail URL from `accountPhoto.thumbnails[0].url`. |
| 94 | + /// - isSelected: Selection state from `isSelected`. |
| 95 | + /// - Returns: A configured UserAccount instance. |
| 96 | + public static func from( |
| 97 | + name: String, |
| 98 | + handle: String?, |
| 99 | + brandId: String?, |
| 100 | + thumbnailURL: URL?, |
| 101 | + isSelected: Bool |
| 102 | + ) -> UserAccount { |
| 103 | + let accountId = brandId ?? "primary" |
| 104 | + return UserAccount( |
| 105 | + id: accountId, |
| 106 | + name: name, |
| 107 | + handle: handle, |
| 108 | + brandId: brandId, |
| 109 | + thumbnailURL: thumbnailURL, |
| 110 | + isSelected: isSelected |
| 111 | + ) |
| 112 | + } |
| 113 | +} |
0 commit comments