150 lines
4.3 KiB
Swift
150 lines
4.3 KiB
Swift
import Foundation
|
|
import Combine
|
|
|
|
// MARK: - ViewModel
|
|
|
|
@MainActor
|
|
final class ShareViewModel: ObservableObject {
|
|
|
|
// MARK: Published state
|
|
|
|
@Published var shelves: [ShelfSummary] = []
|
|
@Published var books: [BookSummary] = []
|
|
@Published var chapters: [ChapterSummary] = []
|
|
|
|
@Published var selectedShelf: ShelfSummary?
|
|
@Published var selectedBook: BookSummary?
|
|
@Published var selectedChapter: ChapterSummary?
|
|
|
|
@Published var pageTitle: String = ""
|
|
@Published var isLoading: Bool = false
|
|
@Published var errorMessage: String?
|
|
@Published var isSaved: Bool = false
|
|
|
|
// MARK: Read-only
|
|
|
|
let sharedText: String
|
|
let isConfigured: Bool
|
|
let serverURL: String
|
|
|
|
// MARK: Private
|
|
|
|
private let apiService: ShareAPIServiceProtocol
|
|
private let defaults: UserDefaults?
|
|
|
|
private let lastShelfIDKey = "shareExtension.lastShelfID"
|
|
private let lastBookIDKey = "shareExtension.lastBookID"
|
|
|
|
// MARK: Computed
|
|
|
|
var isSaveDisabled: Bool {
|
|
pageTitle.trimmingCharacters(in: .whitespaces).isEmpty
|
|
|| selectedBook == nil
|
|
|| isLoading
|
|
}
|
|
|
|
// MARK: - Init
|
|
|
|
init(
|
|
sharedText: String,
|
|
apiService: ShareAPIServiceProtocol,
|
|
serverURL: String = "",
|
|
isConfigured: Bool = true,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
self.sharedText = sharedText
|
|
self.isConfigured = isConfigured
|
|
self.serverURL = serverURL
|
|
self.apiService = apiService
|
|
self.defaults = defaults
|
|
|
|
// Auto-populate title from the first non-empty line of the shared text.
|
|
let firstLine = sharedText
|
|
.components(separatedBy: .newlines)
|
|
.first { !$0.trimmingCharacters(in: .whitespaces).isEmpty } ?? ""
|
|
self.pageTitle = String(firstLine.prefix(100))
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
/// Loads all shelves and restores the last used shelf/book selection.
|
|
func loadShelves() async {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
shelves = try await apiService.fetchShelves()
|
|
|
|
// Restore last selected shelf
|
|
let lastShelfID = defaults?.integer(forKey: lastShelfIDKey) ?? 0
|
|
if lastShelfID != 0, let match = shelves.first(where: { $0.id == lastShelfID }) {
|
|
await selectShelf(match)
|
|
}
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func selectShelf(_ shelf: ShelfSummary) async {
|
|
selectedShelf = shelf
|
|
selectedBook = nil
|
|
selectedChapter = nil
|
|
books = []
|
|
chapters = []
|
|
defaults?.set(shelf.id, forKey: lastShelfIDKey)
|
|
|
|
isLoading = true
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
books = try await apiService.fetchBooks(shelfId: shelf.id)
|
|
|
|
// Restore last selected book
|
|
let lastBookID = defaults?.integer(forKey: lastBookIDKey) ?? 0
|
|
if lastBookID != 0, let match = books.first(where: { $0.id == lastBookID }) {
|
|
await selectBook(match)
|
|
}
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func selectBook(_ book: BookSummary) async {
|
|
selectedBook = book
|
|
selectedChapter = nil
|
|
chapters = []
|
|
defaults?.set(book.id, forKey: lastBookIDKey)
|
|
|
|
isLoading = true
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
chapters = try await apiService.fetchChapters(bookId: book.id)
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func savePage() async {
|
|
guard let book = selectedBook,
|
|
!pageTitle.trimmingCharacters(in: .whitespaces).isEmpty else { return }
|
|
|
|
isLoading = true
|
|
errorMessage = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
_ = try await apiService.createPage(
|
|
bookId: book.id,
|
|
chapterId: selectedChapter?.id,
|
|
title: pageTitle.trimmingCharacters(in: .whitespaces),
|
|
markdown: sharedText
|
|
)
|
|
isSaved = true
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
}
|