146 lines
4.9 KiB
Swift
146 lines
4.9 KiB
Swift
import AppIntents
|
|
import HAKit
|
|
import RealmSwift
|
|
import Shared
|
|
import WidgetKit
|
|
|
|
@available(iOS 17, *)
|
|
struct WidgetDetailsAppIntentTimelineProvider: AppIntentTimelineProvider {
|
|
typealias Entry = WidgetDetailsEntry
|
|
typealias Intent = WidgetDetailsAppIntent
|
|
|
|
func snapshot(for configuration: WidgetDetailsAppIntent, in context: Context) async -> WidgetDetailsEntry {
|
|
do {
|
|
return try await entry(for: configuration, in: context)
|
|
} catch {
|
|
Current.Log.debug("Using placeholder for gauge widget snapshot")
|
|
return placeholder(in: context)
|
|
}
|
|
}
|
|
|
|
func timeline(for configuration: WidgetDetailsAppIntent, in context: Context) async -> Timeline<Entry> {
|
|
do {
|
|
let snapshot = try await entry(for: configuration, in: context)
|
|
return .init(
|
|
entries: [snapshot],
|
|
policy: .after(
|
|
Current.date()
|
|
.addingTimeInterval(WidgetDetailsDataSource.expiration.converted(to: .seconds).value)
|
|
)
|
|
)
|
|
} catch {
|
|
Current.Log.debug("Using placeholder for gauge widget")
|
|
return .init(
|
|
entries: [placeholder(in: context)],
|
|
policy: .after(
|
|
Current.date()
|
|
.addingTimeInterval(WidgetDetailsDataSource.expiration.converted(to: .seconds).value)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
func placeholder(in context: Context) -> WidgetDetailsEntry {
|
|
.init(
|
|
upperText: nil, lowerText: nil, detailsText: nil,
|
|
runAction: false, action: nil
|
|
)
|
|
}
|
|
|
|
private func entry(for configuration: WidgetDetailsAppIntent, in context: Context) async throws -> Entry {
|
|
guard let server = configuration.server.getServer() ?? Current.servers.all.first,
|
|
let connection = Current.api(for: server)?.connection else {
|
|
Current.Log.error("Failed to fetch data for details widget: No servers exist")
|
|
throw WidgetDetailsDataError.noServers
|
|
}
|
|
|
|
let upperTemplate = !configuration.upperTemplate.isEmpty ? configuration.upperTemplate : "?"
|
|
let lowerTemplate = !configuration.lowerTemplate.isEmpty ? configuration.lowerTemplate : "?"
|
|
let detailsTemplate = !configuration.detailsTemplate.isEmpty ? configuration.detailsTemplate : "?"
|
|
let template = "\(upperTemplate)|\(lowerTemplate)|\(detailsTemplate)"
|
|
|
|
let result = await withCheckedContinuation { continuation in
|
|
connection.send(.init(
|
|
type: .rest(.post, "template"),
|
|
data: ["template": template],
|
|
shouldRetry: true
|
|
)) { result in
|
|
continuation.resume(returning: result)
|
|
}
|
|
}
|
|
|
|
var data: HAData?
|
|
switch result {
|
|
case let .success(resultData):
|
|
data = resultData
|
|
case let .failure(error):
|
|
Current.Log.error("Failed to render template for details widget: \(error)")
|
|
throw WidgetDetailsDataError.apiError
|
|
}
|
|
guard let data else {
|
|
throw WidgetDetailsDataError.apiError
|
|
}
|
|
var renderedTemplate: String?
|
|
switch data {
|
|
case let .primitive(response):
|
|
renderedTemplate = response as? String
|
|
default:
|
|
Current.Log.error("Failed to render template for details widget: Bad response data")
|
|
throw WidgetDetailsDataError.badResponse
|
|
}
|
|
|
|
let params = renderedTemplate?.split(separator: "|") ?? []
|
|
guard params.count == 3 else {
|
|
Current.Log.error("Failed to render template for details widget: Wrong length response")
|
|
throw WidgetDetailsDataError.badResponse
|
|
}
|
|
|
|
let upperText = String(params[0])
|
|
let lowerText = String(params[1])
|
|
let detailsText = String(params[2])
|
|
|
|
let action = await withCheckedContinuation { continuation in
|
|
if let action = configuration.action {
|
|
action.asAction { action in
|
|
continuation.resume(returning: action)
|
|
}
|
|
} else {
|
|
continuation.resume(returning: nil)
|
|
}
|
|
}
|
|
|
|
return .init(
|
|
upperText: upperText != "?" ? upperText : nil,
|
|
lowerText: lowerText != "?" ? lowerText : nil,
|
|
detailsText: detailsText != "?" ? detailsText : nil,
|
|
|
|
runAction: configuration.runAction,
|
|
action: action
|
|
)
|
|
}
|
|
}
|
|
|
|
enum WidgetDetailsDataSource {
|
|
static var expiration: Measurement<UnitDuration> {
|
|
.init(value: 15, unit: .minutes)
|
|
}
|
|
}
|
|
|
|
@available(iOS 17, *)
|
|
struct WidgetDetailsEntry: TimelineEntry {
|
|
var date = Date()
|
|
|
|
var upperText: String?
|
|
var lowerText: String?
|
|
var detailsText: String?
|
|
|
|
var runAction: Bool
|
|
var action: Action?
|
|
}
|
|
|
|
enum WidgetDetailsDataError: Error {
|
|
case noServers
|
|
case apiError
|
|
case badResponse
|
|
}
|