Section 8/141 menit

8. App Intents: Widget yang Dapat Dikonfigurasi

8. App Intents: Widget yang Dapat Dikonfigurasi

App Intents (iOS 17+) menggantikan SiriKit Intent Definition Files yang lama untuk konfigurasi widget.

Definisikan Intent Konfigurasi

swift
import AppIntents
import WidgetKit

// Intent yang mendefinisikan parameter konfigurasi widget
struct HabitSelectionIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Pilih Habit"
    static var description = IntentDescription("Pilih habit yang ingin ditampilkan di widget")

    @Parameter(title: "Habit", default: nil)
    var habit: HabitEntity?
}

// Entity yang merepresentasikan Habit (untuk picker di widget config)
struct HabitEntity: AppEntity {
    let id: String
    let name: String

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Habit"
    static var defaultQuery = HabitEntityQuery()

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }
}

struct HabitEntityQuery: EntityQuery {
    func entities(for identifiers: [HabitEntity.ID]) async throws -> [HabitEntity] {
        // Ambil habits berdasarkan ID dari shared storage
        let sharedDefaults = UserDefaults(suiteName: "group.com.perusahaan.namaapp")
        let allHabits = sharedDefaults?.stringArray(forKey: "allHabits") ?? []
        return allHabits
            .filter { identifiers.contains($0) }
            .map { HabitEntity(id: $0, name: $0) }
    }

    func suggestedEntities() async throws -> [HabitEntity] {
        // Tampilkan semua habit yang tersedia di picker
        let sharedDefaults = UserDefaults(suiteName: "group.com.perusahaan.namaapp")
        let allHabits = sharedDefaults?.stringArray(forKey: "allHabits") ?? []
        return allHabits.map { HabitEntity(id: $0, name: $0) }
    }
}

AppIntentTimelineProvider

swift
// Provider yang menggunakan intent untuk konfigurasi
struct ConfigurableHabitProvider: AppIntentTimelineProvider {
    // Intent konfigurasi yang dipilih user di widget gallery
    typealias Intent = HabitSelectionIntent

    func placeholder(in context: Context) -> HabitEntry {
        HabitEntry(date: Date(), habitName: "Olahraga", isCompleted: false, streak: 7, habitID: "placeholder")
    }

    func snapshot(for configuration: HabitSelectionIntent, in context: Context) async -> HabitEntry {
        let name = configuration.habit?.name ?? "Olahraga"
        return HabitEntry(date: Date(), habitName: name, isCompleted: true, streak: 14, habitID: configuration.habit?.id ?? "")
    }

    func timeline(for configuration: HabitSelectionIntent, in context: Context) async -> Timeline<HabitEntry> {
        let sharedDefaults = UserDefaults(suiteName: "group.com.perusahaan.namaapp")
        let selectedHabitID = configuration.habit?.id ?? ""
        let isCompleted = sharedDefaults?.bool(forKey: "habitCompleted_\(selectedHabitID)") ?? false
        let streak = sharedDefaults?.integer(forKey: "streak_\(selectedHabitID)") ?? 0
        let name = configuration.habit?.name ?? "Pilih Habit"

        let entry = HabitEntry(date: Date(), habitName: name, isCompleted: isCompleted, streak: streak, habitID: selectedHabitID)
        let midnight = Calendar.current.startOfDay(for: Date().addingTimeInterval(86400))
        return Timeline(entries: [entry], policy: .after(midnight))
    }
}

// Widget menggunakan AppIntentConfiguration
struct ConfigurableHabitWidget: Widget {
    var body: some WidgetConfiguration {
        AppIntentConfiguration(
            kind: "ConfigurableHabitWidget",
            intent: HabitSelectionIntent.self,
            provider: ConfigurableHabitProvider()
        ) { entry in
            InteractiveHabitView(entry: entry)
        }
        .configurationDisplayName("Habit Tracker")
        .description("Pilih dan pantau satu habit spesifik.")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}