587 lines
20 KiB
Swift
587 lines
20 KiB
Swift
import WidgetKit
|
|
import SwiftUI
|
|
|
|
struct DailySentenceEntry: TimelineEntry {
|
|
let date: Date
|
|
let sentence: String
|
|
let author: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct ReadlaterEntry: TimelineEntry {
|
|
let date: Date
|
|
let count: Int
|
|
let previewText: String
|
|
let previewAuthor: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct DailyCardEntry: TimelineEntry {
|
|
let date: Date
|
|
let sentence: String
|
|
let author: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct FortuneEntry: TimelineEntry {
|
|
let date: Date
|
|
let text: String
|
|
let keyword: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct CountdownEntry: TimelineEntry {
|
|
let date: Date
|
|
let title: String
|
|
let daysLeft: Int
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct PomodoroEntry: TimelineEntry {
|
|
let date: Date
|
|
let remaining: Int
|
|
let state: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct SolarTermEntry: TimelineEntry {
|
|
let date: Date
|
|
let name: String
|
|
let poem: String
|
|
let isDark: Bool
|
|
}
|
|
|
|
struct CheckinEntry: TimelineEntry {
|
|
let date: Date
|
|
let days: Int
|
|
let todayDone: Bool
|
|
let isDark: Bool
|
|
}
|
|
|
|
private func isDarkMode() -> Bool {
|
|
return UserDefaults(suiteName: "group.apps.xy.xianyan.share")?.string(forKey: "widget_theme_mode") == "dark"
|
|
}
|
|
|
|
struct DailySentenceProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> DailySentenceEntry {
|
|
DailySentenceEntry(date: Date(), sentence: "生活不止眼前的苟且", author: "高晓松", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (DailySentenceEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = DailySentenceEntry(
|
|
date: Date(),
|
|
sentence: defaults?.string(forKey: "daily_sentence") ?? "生活不止眼前的苟且",
|
|
author: defaults?.string(forKey: "daily_sentence_author") ?? "高晓松",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<DailySentenceEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = DailySentenceEntry(
|
|
date: Date(),
|
|
sentence: defaults?.string(forKey: "daily_sentence") ?? "暂无句子",
|
|
author: defaults?.string(forKey: "daily_sentence_author") ?? "",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct ReadlaterProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> ReadlaterEntry {
|
|
ReadlaterEntry(date: Date(), count: 0, previewText: "", previewAuthor: "", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (ReadlaterEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = ReadlaterEntry(
|
|
date: Date(),
|
|
count: defaults?.integer(forKey: "readlater_count") ?? 0,
|
|
previewText: defaults?.string(forKey: "readlater_preview_text") ?? "",
|
|
previewAuthor: defaults?.string(forKey: "readlater_preview_author") ?? "",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<ReadlaterEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = ReadlaterEntry(
|
|
date: Date(),
|
|
count: defaults?.integer(forKey: "readlater_count") ?? 0,
|
|
previewText: defaults?.string(forKey: "readlater_preview_text") ?? "",
|
|
previewAuthor: defaults?.string(forKey: "readlater_preview_author") ?? "",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct DailyCardProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> DailyCardEntry {
|
|
DailyCardEntry(date: Date(), sentence: "生活不止眼前的苟且", author: "高晓松", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (DailyCardEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = DailyCardEntry(
|
|
date: Date(),
|
|
sentence: defaults?.string(forKey: "daily_sentence") ?? "暂无句子",
|
|
author: defaults?.string(forKey: "daily_sentence_author") ?? "",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<DailyCardEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = DailyCardEntry(
|
|
date: Date(),
|
|
sentence: defaults?.string(forKey: "daily_sentence") ?? "暂无句子",
|
|
author: defaults?.string(forKey: "daily_sentence_author") ?? "",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct FortuneProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> FortuneEntry {
|
|
FortuneEntry(date: Date(), text: "今日运势不错", keyword: "✨", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (FortuneEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = FortuneEntry(
|
|
date: Date(),
|
|
text: defaults?.string(forKey: "fortune_text") ?? "今日运势不错",
|
|
keyword: defaults?.string(forKey: "fortune_keyword") ?? "✨",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<FortuneEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = FortuneEntry(
|
|
date: Date(),
|
|
text: defaults?.string(forKey: "fortune_text") ?? "今日运势不错",
|
|
keyword: defaults?.string(forKey: "fortune_keyword") ?? "✨",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct CountdownProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> CountdownEntry {
|
|
CountdownEntry(date: Date(), title: "倒计时", daysLeft: 0, isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (CountdownEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let title = defaults?.string(forKey: "countdown_title") ?? "倒计时"
|
|
let targetStr = defaults?.string(forKey: "countdown_target") ?? ""
|
|
let days = Self.calculateDays(from: targetStr)
|
|
completion(CountdownEntry(date: Date(), title: title, daysLeft: days, isDark: isDarkMode()))
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<CountdownEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let title = defaults?.string(forKey: "countdown_title") ?? "倒计时"
|
|
let targetStr = defaults?.string(forKey: "countdown_target") ?? ""
|
|
let days = Self.calculateDays(from: targetStr)
|
|
let entry = CountdownEntry(date: Date(), title: title, daysLeft: days, isDark: isDarkMode())
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
|
|
private static func calculateDays(from isoString: String) -> Int {
|
|
guard !isoString.isEmpty else { return 0 }
|
|
let formatter = ISO8601DateFormatter()
|
|
formatter.formatOptions = [.withFullDate]
|
|
guard let target = formatter.date(from: String(isoString.prefix(10))) else { return 0 }
|
|
return Calendar.current.dateComponents([.day], from: Date(), to: target).day ?? 0
|
|
}
|
|
}
|
|
|
|
struct PomodoroProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> PomodoroEntry {
|
|
PomodoroEntry(date: Date(), remaining: 1500, state: "idle", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (PomodoroEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = PomodoroEntry(
|
|
date: Date(),
|
|
remaining: defaults?.integer(forKey: "pomodoro_remaining") ?? 1500,
|
|
state: defaults?.string(forKey: "pomodoro_state") ?? "idle",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<PomodoroEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = PomodoroEntry(
|
|
date: Date(),
|
|
remaining: defaults?.integer(forKey: "pomodoro_remaining") ?? 1500,
|
|
state: defaults?.string(forKey: "pomodoro_state") ?? "idle",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct SolarTermProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> SolarTermEntry {
|
|
SolarTermEntry(date: Date(), name: "立春", poem: "春到人间草木知", isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (SolarTermEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = SolarTermEntry(
|
|
date: Date(),
|
|
name: defaults?.string(forKey: "solar_term_name") ?? "立春",
|
|
poem: defaults?.string(forKey: "solar_term_poem") ?? "春到人间草木知",
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<SolarTermEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = SolarTermEntry(
|
|
date: Date(),
|
|
name: defaults?.string(forKey: "solar_term_name") ?? "立春",
|
|
poem: defaults?.string(forKey: "solar_term_poem") ?? "春到人间草木知",
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct CheckinProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> CheckinEntry {
|
|
CheckinEntry(date: Date(), days: 0, todayDone: false, isDark: false)
|
|
}
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (CheckinEntry) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = CheckinEntry(
|
|
date: Date(),
|
|
days: defaults?.integer(forKey: "checkin_days") ?? 0,
|
|
todayDone: defaults?.bool(forKey: "checkin_today") ?? false,
|
|
isDark: isDarkMode()
|
|
)
|
|
completion(entry)
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<CheckinEntry>) -> Void) {
|
|
let defaults = UserDefaults(suiteName: "group.apps.xy.xianyan.share")
|
|
let entry = CheckinEntry(
|
|
date: Date(),
|
|
days: defaults?.integer(forKey: "checkin_days") ?? 0,
|
|
todayDone: defaults?.bool(forKey: "checkin_today") ?? false,
|
|
isDark: isDarkMode()
|
|
)
|
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
|
completion(timeline)
|
|
}
|
|
}
|
|
|
|
struct WidgetColors {
|
|
let bg: Color
|
|
let primary: Color
|
|
let secondary: Color
|
|
|
|
init(isDark: Bool) {
|
|
if isDark {
|
|
bg = Color(red: 28/255, green: 28/255, blue: 30/255)
|
|
primary = Color(red: 224/255, green: 224/255, blue: 224/255)
|
|
secondary = Color(red: 170/255, green: 170/255, blue: 170/255)
|
|
} else {
|
|
bg = Color.white
|
|
primary = Color(red: 51/255, green: 51/255, blue: 51/255)
|
|
secondary = Color(red: 136/255, green: 136/255, blue: 136/255)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DailySentenceWidgetEntryView: View {
|
|
var entry: DailySentenceEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text(entry.sentence)
|
|
.font(.body)
|
|
.foregroundColor(colors.primary)
|
|
.lineLimit(3)
|
|
.multilineTextAlignment(.leading)
|
|
Spacer()
|
|
Text("— \(entry.author)")
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct ReadlaterWidgetEntryView: View {
|
|
var entry: ReadlaterEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
HStack {
|
|
Text("\(entry.count)条未读")
|
|
.font(.headline)
|
|
.foregroundColor(colors.primary)
|
|
Spacer()
|
|
}
|
|
if !entry.previewText.isEmpty {
|
|
Text(entry.previewText)
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
.lineLimit(2)
|
|
}
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct DailyCardWidgetEntryView: View {
|
|
var entry: DailyCardEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(spacing: 8) {
|
|
Spacer()
|
|
Text(entry.sentence)
|
|
.font(.body)
|
|
.foregroundColor(colors.primary)
|
|
.lineLimit(4)
|
|
.multilineTextAlignment(.center)
|
|
Text("— \(entry.author)")
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct FortuneWidgetEntryView: View {
|
|
var entry: FortuneEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(entry.keyword)
|
|
.font(.title2)
|
|
Text(entry.text)
|
|
.font(.caption)
|
|
.foregroundColor(colors.primary)
|
|
.lineLimit(2)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct CountdownWidgetEntryView: View {
|
|
var entry: CountdownEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(spacing: 4) {
|
|
Text(entry.title)
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
Text("\(entry.daysLeft)")
|
|
.font(.system(size: 36, weight: .bold, design: .rounded))
|
|
.foregroundColor(colors.primary)
|
|
Text("天")
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct PomodoroWidgetEntryView: View {
|
|
var entry: PomodoroEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
let min = entry.remaining / 60
|
|
let sec = entry.remaining % 60
|
|
VStack(spacing: 4) {
|
|
Text(String(format: "%02d:%02d", min, sec))
|
|
.font(.system(size: 32, weight: .bold, design: .monospaced))
|
|
.foregroundColor(colors.primary)
|
|
Text(entry.state == "running" ? "专注中" : entry.state == "paused" ? "已暂停" : "准备开始")
|
|
.font(.caption2)
|
|
.foregroundColor(colors.secondary)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct SolarTermWidgetEntryView: View {
|
|
var entry: SolarTermEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(entry.name)
|
|
.font(.title3)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(colors.primary)
|
|
Text(entry.poem)
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
.lineLimit(2)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct CheckinWidgetEntryView: View {
|
|
var entry: CheckinEntry
|
|
var body: some View {
|
|
let colors = WidgetColors(isDark: entry.isDark)
|
|
VStack(spacing: 4) {
|
|
Text("连续\(entry.days)天")
|
|
.font(.title3)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(colors.primary)
|
|
Text(entry.todayDone ? "✅ 今日已签" : "📝 点击签到")
|
|
.font(.caption)
|
|
.foregroundColor(colors.secondary)
|
|
}
|
|
.padding()
|
|
.containerBackground(for: .widget) { colors.bg }
|
|
}
|
|
}
|
|
|
|
struct DailySentenceWidget: Widget {
|
|
let kind: String = "DailySentenceWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: DailySentenceProvider()) { entry in
|
|
DailySentenceWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("每日一句")
|
|
.description("每日推送精选句子")
|
|
.supportedFamilies([.systemSmall, .systemMedium])
|
|
}
|
|
}
|
|
|
|
struct ReadlaterWidget: Widget {
|
|
let kind: String = "ReadlaterWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: ReadlaterProvider()) { entry in
|
|
ReadlaterWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("稍后读")
|
|
.description("未读数量和最新预览")
|
|
.supportedFamilies([.systemSmall, .systemMedium])
|
|
}
|
|
}
|
|
|
|
struct DailyCardWidget: Widget {
|
|
let kind: String = "DailyCardWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: DailyCardProvider()) { entry in
|
|
DailyCardWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("日签卡片")
|
|
.description("精美日签含日期和句子")
|
|
.supportedFamilies([.systemMedium, .systemLarge])
|
|
}
|
|
}
|
|
|
|
struct FortuneWidget: Widget {
|
|
let kind: String = "FortuneWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: FortuneProvider()) { entry in
|
|
FortuneWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("每日运势")
|
|
.description("今日运势和幸运关键词")
|
|
.supportedFamilies([.systemSmall])
|
|
}
|
|
}
|
|
|
|
struct CountdownWidget: Widget {
|
|
let kind: String = "CountdownWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: CountdownProvider()) { entry in
|
|
CountdownWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("倒计时")
|
|
.description("自定义倒计时事件")
|
|
.supportedFamilies([.systemSmall, .systemMedium])
|
|
}
|
|
}
|
|
|
|
struct PomodoroWidget: Widget {
|
|
let kind: String = "PomodoroWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: PomodoroProvider()) { entry in
|
|
PomodoroWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("番茄钟")
|
|
.description("桌面快捷专注计时")
|
|
.supportedFamilies([.systemSmall])
|
|
}
|
|
}
|
|
|
|
struct SolarTermWidget: Widget {
|
|
let kind: String = "SolarTermWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: SolarTermProvider()) { entry in
|
|
SolarTermWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("节气诗词")
|
|
.description("当前节气与对应诗词")
|
|
.supportedFamilies([.systemSmall, .systemMedium])
|
|
}
|
|
}
|
|
|
|
struct CheckinWidget: Widget {
|
|
let kind: String = "CheckinWidget"
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(kind: kind, provider: CheckinProvider()) { entry in
|
|
CheckinWidgetEntryView(entry: entry)
|
|
}
|
|
.configurationDisplayName("每日签到")
|
|
.description("连续签到天数和快捷签到")
|
|
.supportedFamilies([.systemSmall])
|
|
}
|
|
}
|
|
|
|
@main
|
|
struct XianyanWidgetBundle: WidgetBundle {
|
|
var body: some Widget {
|
|
DailySentenceWidget()
|
|
ReadlaterWidget()
|
|
DailyCardWidget()
|
|
FortuneWidget()
|
|
CountdownWidget()
|
|
PomodoroWidget()
|
|
SolarTermWidget()
|
|
CheckinWidget()
|
|
}
|
|
}
|