forked from akai/readest
7da41a65ad
Add a resizable home-screen widget on iOS and Android showing recent
in-progress books with cover, reading progress, and tap-to-open.
- One responsive widget: Android resizable 1x1 to 4x3 (one book per
column, up to 3); iOS Small/Medium/Large families. Covers are cropped,
rounded, with a percent badge and a progress bar (baked into the bitmap
on Android, SwiftUI overlays on iOS).
- TTS controls (previous, play-pause, next) appear in 2+ row sizes when
TTS is active, wired to the existing media session. Reading progress
stays live during background TTS via a fraction computed from the baked
offline locations.
- Publishes a snapshot plus downsized cover thumbnails to the iOS App
Group and Android SharedPreferences through a new update_reading_widget
native-bridge command; refresh is debounced and driven by library and
progress changes, TTS, and app backgrounding.
- Tapping a cover opens readest://book/{hash}, switching the reader in
place when one is already open.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
41 lines
1.1 KiB
Swift
41 lines
1.1 KiB
Swift
import Foundation
|
|
import UIKit
|
|
|
|
struct WidgetSnapshotBook: Codable, Identifiable {
|
|
let hash: String
|
|
let title: String
|
|
let author: String
|
|
let percent: Int
|
|
var id: String { hash }
|
|
}
|
|
|
|
struct WidgetSnapshot: Codable {
|
|
let books: [WidgetSnapshotBook]
|
|
let sectionTitle: String
|
|
let emptyTitle: String
|
|
}
|
|
|
|
enum WidgetSnapshotStore {
|
|
static let suiteName = "group.com.bilingify.readest"
|
|
static let snapshotKey = "readingWidgetSnapshot"
|
|
|
|
static func load() -> WidgetSnapshot {
|
|
guard
|
|
let data = UserDefaults(suiteName: suiteName)?.data(forKey: snapshotKey),
|
|
let snapshot = try? JSONDecoder().decode(WidgetSnapshot.self, from: data)
|
|
else {
|
|
return WidgetSnapshot(books: [], sectionTitle: "Continue reading", emptyTitle: "")
|
|
}
|
|
return snapshot
|
|
}
|
|
|
|
static func coverImage(for hash: String) -> UIImage? {
|
|
guard
|
|
let container = FileManager.default
|
|
.containerURL(forSecurityApplicationGroupIdentifier: suiteName)
|
|
else { return nil }
|
|
let url = container.appendingPathComponent("widget/covers/\(hash).jpg")
|
|
return UIImage(contentsOfFile: url.path)
|
|
}
|
|
}
|