iOS/Sources/Extensions/AppIntents/Widget/Actions/WidgetActionsAppIntentTimel...

135 lines
4.5 KiB
Swift

import AppIntents
import RealmSwift
import Shared
import WidgetKit
@available(iOS 17, *)
struct WidgetActionsAppIntentTimelineProvider: AppIntentTimelineProvider {
typealias Entry = WidgetActionsEntry
typealias Intent = WidgetActionsAppIntent
func snapshot(for configuration: WidgetActionsAppIntent, in context: Context) async -> WidgetActionsEntry {
await withCheckedContinuation({ continuation in
Self.entry(for: configuration, in: context) { entries in
continuation.resume(returning: entries)
}
})
}
func timeline(for configuration: Intent, in context: Context) async -> Timeline<Entry> {
let entries = await withCheckedContinuation({ continuation in
Self.entry(for: configuration, in: context) { entries in
continuation.resume(returning: entries)
}
})
return .init(
entries: [entries],
policy: .after(
Current.date()
.addingTimeInterval(WidgetActionsDataSource.expiration.converted(to: .seconds).value)
)
)
}
func placeholder(in context: Context) -> WidgetActionsEntry {
let count = WidgetBasicContainerView.maximumCount(family: context.family)
let actions = stride(from: 0, to: count, by: 1).map { _ in
with(Action()) {
$0.Text = "Redacted Text"
$0.IconName = MaterialDesignIcons.bedEmptyIcon.name
}
}
return WidgetActionsEntry(actions: actions)
}
private static func entry(for configuration: Intent, in context: Context, completion: @escaping (Entry) -> Void) {
if !(configuration.actions?.isEmpty ?? true) {
var actions: [Action?] = []
var intentActionCheckCount = 0
configuration.actions?.forEach({ intentAction in
intentAction.asAction { action in
actions.append(action)
intentActionCheckCount += 1
if intentActionCheckCount == (configuration.actions?.count ?? 0) {
completion(.init(actions: actions.compactMap({ $0 })))
}
}
})
} else {
defaultActions(in: context) { actions in
completion(.init(actions: actions))
}
}
}
private static func defaultActions(in context: Context, completion: @escaping ([Action]) -> Void) {
WidgetActionsDataSource.actions { allActions in
let maxCount = WidgetBasicContainerView.maximumCount(family: context.family)
switch allActions.count {
case 0:
completion([])
case ...maxCount:
completion(Array(allActions))
default:
completion(Array(allActions[0 ..< maxCount]))
}
}
}
}
@available(iOS 17, *)
extension IntentActionAppEntity {
func asAction(completion: @escaping (Action?) -> Void) {
guard id.isEmpty == false else {
completion(nil)
return
}
func getAction() -> Action? {
Current.realm(objectTypes: [Action.self, RLMScene.self]).object(
ofType: Action.self,
forPrimaryKey: id
)
}
/*
Workaround for iOS 18,
'Realm accessed from incorrect thread.' if not called from main thread
while in iOS 17 it reports the same error if called in the main thread
*/
if #available(iOS 18, *) {
DispatchQueue.main.async {
completion(getAction())
}
} else {
completion(getAction())
}
}
}
enum WidgetActionsDataSource {
static var expiration: Measurement<UnitDuration> {
.init(value: 24, unit: .hours)
}
static func getActions() -> Results<Action> {
Current.realm(objectTypes: [Action.self, RLMScene.self]).objects(Action.self)
.sorted(byKeyPath: #keyPath(Action.Position))
}
static func actions(completion: @escaping (Results<Action>) -> Void) {
/*
Workaround for iOS 18,
'Realm accessed from incorrect thread.' if not called from main thread
while in iOS 17 it reports the same error if called in the main thread
*/
if #available(iOS 18, *) {
DispatchQueue.main.async {
completion(getActions())
}
} else {
completion(getActions())
}
}
}