227 lines
7.2 KiB
Swift
227 lines
7.2 KiB
Swift
//
|
|
// SearchWidget.swift
|
|
// SearchWidget
|
|
//
|
|
// Created by Johann Villegas on 9/04/25.
|
|
//
|
|
|
|
import WidgetKit
|
|
import SwiftUI
|
|
import ActivityKit
|
|
|
|
// Shared FlutterLiveActivities definition for use in both app and widget extension
|
|
struct FlutterLiveActivities: ActivityAttributes, Identifiable {
|
|
public typealias ContentState = FlutterLiveActivitiesContent
|
|
|
|
public struct FlutterLiveActivitiesContent: Codable, Hashable {
|
|
var title: String
|
|
var query: String
|
|
var resultsCount: String
|
|
var timestamp: String
|
|
}
|
|
|
|
var id = UUID()
|
|
}
|
|
|
|
// Define the lock screen/banner state of Live Activities
|
|
@available(iOS 16.2, *)
|
|
struct FlutterLiveActivitiesLiveActivity: Widget {
|
|
var body: some WidgetConfiguration {
|
|
ActivityConfiguration(for: FlutterLiveActivities.self) { context in
|
|
// Lock screen/banner UI goes here
|
|
LockScreenLiveActivityView(context: context)
|
|
} dynamicIsland: { context in
|
|
// Dynamic Island UI goes here
|
|
DynamicIslandLiveActivityView(context: context)
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 16.2, *)
|
|
struct LockScreenLiveActivityView: View {
|
|
let context: ActivityViewContext<FlutterLiveActivities>
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Text(context.state.title)
|
|
.font(.headline)
|
|
|
|
HStack {
|
|
Image(systemName: "magnifyingglass")
|
|
.foregroundColor(.green)
|
|
Text(context.state.query)
|
|
.font(.subheadline)
|
|
}
|
|
|
|
HStack {
|
|
Image(systemName: "doc.text")
|
|
.foregroundColor(.blue)
|
|
Text("Results: \(context.state.resultsCount)")
|
|
.font(.subheadline)
|
|
}
|
|
|
|
Text("Updated: \(formattedDate(from: context.state.timestamp))")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.padding()
|
|
}
|
|
|
|
func formattedDate(from isoString: String) -> String {
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
|
if let date = dateFormatter.date(from: isoString) {
|
|
dateFormatter.dateFormat = "HH:mm:ss"
|
|
return dateFormatter.string(from: date)
|
|
}
|
|
return isoString
|
|
}
|
|
}
|
|
|
|
@available(iOS 16.2, *)
|
|
struct DynamicIslandLiveActivityView: View {
|
|
let context: ActivityViewContext<FlutterLiveActivities>
|
|
|
|
var body: some View {
|
|
DynamicIsland {
|
|
// Expanded state
|
|
DynamicIslandExpandedRegion(.leading) {
|
|
HStack {
|
|
Image(systemName: "magnifyingglass")
|
|
.foregroundColor(.green)
|
|
Text(context.state.query)
|
|
.font(.headline)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
|
|
DynamicIslandExpandedRegion(.trailing) {
|
|
HStack {
|
|
Image(systemName: "doc.text")
|
|
.foregroundColor(.blue)
|
|
Text(context.state.resultsCount)
|
|
.font(.headline)
|
|
}
|
|
}
|
|
|
|
DynamicIslandExpandedRegion(.bottom) {
|
|
VStack(alignment: .leading) {
|
|
Text(context.state.title)
|
|
.font(.caption)
|
|
Text("Updated: \(formattedDate(from: context.state.timestamp))")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
} compactLeading: {
|
|
Image(systemName: "magnifyingglass")
|
|
.foregroundColor(.green)
|
|
} compactTrailing: {
|
|
Text(context.state.resultsCount)
|
|
.font(.headline)
|
|
} minimal: {
|
|
Image(systemName: "magnifyingglass")
|
|
.foregroundColor(.green)
|
|
}
|
|
}
|
|
|
|
func formattedDate(from isoString: String) -> String {
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
|
if let date = dateFormatter.date(from: isoString) {
|
|
dateFormatter.dateFormat = "HH:mm:ss"
|
|
return dateFormatter.string(from: date)
|
|
}
|
|
return isoString
|
|
}
|
|
}
|
|
|
|
struct SearchWidget: Widget {
|
|
let kind: String = "SearchWidget"
|
|
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: EmptyTimelineProvider()) { _ in
|
|
EmptyWidgetView()
|
|
}
|
|
.configurationDisplayName("Estudios Bíblicos")
|
|
.description("Search Widget for Bible Studies App")
|
|
.supportedFamilies([.systemSmall, .systemMedium])
|
|
}
|
|
}
|
|
|
|
// Empty widget for devices that don't support Live Activities
|
|
struct EmptyWidgetView: View {
|
|
var body: some View {
|
|
ZStack {
|
|
Color(red: 0.42, green: 0.55, blue: 0.14) // #6b8e23 color
|
|
VStack {
|
|
Image(systemName: "magnifyingglass")
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: 40, height: 40)
|
|
.foregroundColor(.white)
|
|
Text("Estudios Bíblicos")
|
|
.font(.headline)
|
|
.foregroundColor(.white)
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Empty provider for devices that don't support Live Activities
|
|
struct EmptyTimelineProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> EmptyTimelineEntry {
|
|
EmptyTimelineEntry()
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (EmptyTimelineEntry) -> ()) {
|
|
let entry = EmptyTimelineEntry()
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<EmptyTimelineEntry>) -> ()) {
|
|
let entry = EmptyTimelineEntry()
|
|
let timeline = Timeline(entries: [entry], policy: .never)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct EmptyTimelineEntry: TimelineEntry {
|
|
let date: Date = Date()
|
|
}
|
|
|
|
struct SearchWidget_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
SearchWidget()
|
|
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
|
}
|
|
}
|
|
|
|
// Preview for Live Activities
|
|
@available(iOS 16.2, *)
|
|
struct FlutterLiveActivitiesLiveActivity_Previews: PreviewProvider {
|
|
static let attributes = FlutterLiveActivities()
|
|
static let contentState = FlutterLiveActivities.ContentState(
|
|
title: "Searching Bible Studies",
|
|
query: "resurrection",
|
|
resultsCount: "42",
|
|
timestamp: ISO8601DateFormatter().string(from: Date())
|
|
)
|
|
|
|
static var previews: some View {
|
|
attributes
|
|
.previewContext(contentState, viewKind: .dynamicIsland(.compact))
|
|
.previewDisplayName("Island Compact")
|
|
attributes
|
|
.previewContext(contentState, viewKind: .dynamicIsland(.expanded))
|
|
.previewDisplayName("Island Expanded")
|
|
attributes
|
|
.previewContext(contentState, viewKind: .dynamicIsland(.minimal))
|
|
.previewDisplayName("Minimal")
|
|
attributes
|
|
.previewContext(contentState, viewKind: .content)
|
|
.previewDisplayName("Notification")
|
|
}
|
|
}
|