WatchThemeStore.swift 3.05 KB
import Foundation

/// Shared Watch theme persistence.
/// The Watch app and Widget extension read this exact JSON data from App Group.
final class WatchThemeStore {
  static let shared = WatchThemeStore()

  private init() {}

  @discardableResult
  func saveThemeJSONString(_ json: String) -> Bool {
    guard let data = json.data(using: .utf8) else { return false }
    AppGroupConstants.defaults?.set(data, forKey: AppGroupConstants.Key.myWatchTheme)
    return true
  }

  @discardableResult
  func saveTheme(_ theme: WatchTransformTheme) -> Bool {
    guard let defaults = AppGroupConstants.defaults else { return false }
    let imageStore = SyncedWatchThemeImageStore(themeId: theme.themeId)
    let model = SyncedWatchThemeModel(
      id: Int(theme.themeId),
      themeName: theme.themeName,
      isOfficial: 0,
      energeticTitle: theme.energeticDescription,
      energeticImageURL: imageStore.save(
        theme.energeticImage?.data,
        status: "energetic",
        defaults: defaults
      ),
      normalTitle: theme.normalDescription,
      normalImageURL: imageStore.save(
        theme.normalImage?.data,
        status: "normal",
        defaults: defaults
      ),
      slightStressTitle: theme.slightStressfulDescription,
      slightStressImageURL: imageStore.save(
        theme.slightStressfulImage?.data,
        status: "slight_stressful",
        defaults: defaults
      ),
      stressfulTitle: theme.stressfulDescription,
      stressfulImageURL: imageStore.save(
        theme.stressfulImage?.data,
        status: "stressful",
        defaults: defaults
      )
    )

    do {
      let data = try JSONEncoder().encode(model)
      defaults.set(data, forKey: AppGroupConstants.Key.myWatchTheme)
      defaults.synchronize()
      return true
    } catch {
      return false
    }
  }

  func clearTheme() {
    AppGroupConstants.defaults?.removeObject(forKey: AppGroupConstants.Key.myWatchTheme)
  }
}

private struct SyncedWatchThemeImageStore {
  let themeId: Int64

  func save(_ data: Data?, status: String, defaults: UserDefaults) -> String? {
    guard let data else { return nil }
    let key = "watch_theme_\(themeId)_\(status)"
    defaults.set(data, forKey: key)
    return key
  }
}

private struct SyncedWatchThemeModel: Codable {
  let id: Int?
  let themeName: String?
  let isOfficial: Int?
  let energeticTitle: String?
  let energeticImageURL: String?
  let normalTitle: String?
  let normalImageURL: String?
  let slightStressTitle: String?
  let slightStressImageURL: String?
  let stressfulTitle: String?
  let stressfulImageURL: String?

  enum CodingKeys: String, CodingKey {
    case id
    case themeName = "theme_name"
    case isOfficial = "is_official"
    case energeticTitle = "energetic_description"
    case energeticImageURL = "energetic_image"
    case normalTitle = "normal_description"
    case normalImageURL = "normal_image"
    case slightStressTitle = "slight_stressful_description"
    case slightStressImageURL = "slight_stressful_image"
    case stressfulTitle = "stressful_description"
    case stressfulImageURL = "stressful_image"
  }
}