All checks were successful
CI / Scraper / Lint (push) Successful in 10s
CI / Scraper / Test (push) Successful in 14s
Release / Scraper / Test (push) Successful in 18s
CI / Scraper / Lint (pull_request) Successful in 18s
Release / UI / Build (push) Successful in 23s
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 32s
Release / Scraper / Docker (push) Successful in 55s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
CI / Scraper / Docker Push (push) Successful in 1m5s
Release / UI / Docker (push) Successful in 1m12s
iOS CI / Build (push) Successful in 4m18s
iOS CI / Build (pull_request) Successful in 4m25s
iOS CI / Test (push) Successful in 8m11s
iOS CI / Test (pull_request) Successful in 8m21s
88 lines
5.0 KiB
Markdown
88 lines
5.0 KiB
Markdown
# LibNovel iOS App
|
|
|
|
SwiftUI app targeting iOS 17+. Consumes the Go scraper HTTP API for books, chapters, and audio. Uses MinIO presigned URLs for media playback and downloads.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
ios/LibNovel/LibNovel/
|
|
├── App/ # LibNovelApp.swift, ContentView.swift, RootTabView.swift
|
|
├── Models/ # Models.swift (all domain types)
|
|
├── Networking/ # APIClient.swift (URLSession-based HTTP client)
|
|
├── Services/ # AudioPlayerService, AudioDownloadService, AuthStore,
|
|
│ # BookVoicePreferences, NetworkMonitor
|
|
├── ViewModels/ # One per view/feature (HomeViewModel, BrowseViewModel, etc.)
|
|
├── Views/
|
|
│ ├── Auth/ # AuthView
|
|
│ ├── BookDetail/ # BookDetailView, CommentsView
|
|
│ ├── Browse/ # BrowseView (infinite scroll shelves)
|
|
│ ├── ChapterReader/ # ChapterReaderView, DownloadAudioButton
|
|
│ ├── Common/ # CommonViews (shared reusable components)
|
|
│ ├── Components/ # OfflineBanner
|
|
│ ├── Downloads/ # DownloadsView, DownloadQueueButton
|
|
│ ├── Home/ # HomeView
|
|
│ ├── Library/ # LibraryView (2-col grid, filters)
|
|
│ ├── Player/ # PlayerViews (floating FAB, compact, full-screen)
|
|
│ ├── Profile/ # ProfileView, VoiceSelectionView, UserProfileView, etc.
|
|
│ └── Search/ # SearchView
|
|
└── Extensions/ # NavDestination.swift, String+App.swift, Color+App.swift
|
|
```
|
|
|
|
## iOS / Swift Conventions
|
|
|
|
- **Deployment target**: iOS 17.0 — use iOS 17+ APIs freely.
|
|
- **Observable pattern**: The codebase currently uses `@StateObject` / `ObservableObject` / `@Published`. When adding new types, prefer the **`@Observable` macro** (iOS 17+) over `ObservableObject`. Do not refactor existing types unless explicitly asked.
|
|
- **Navigation**: Use `NavigationStack` (not `NavigationView`). Use `.navigationDestination(for:)` for type-safe routing.
|
|
- **Concurrency**: Use `async/await` and structured concurrency. Avoid callback-based APIs and `DispatchQueue.main.async` — prefer `@MainActor` or `await MainActor.run`.
|
|
- **State management**: Prefer `@State` + `@Binding` for local UI state. Use environment objects for app-wide services (authStore, audioPlayer, downloadService, networkMonitor).
|
|
- **SwiftData**: Not currently used. Do not introduce SwiftData without discussion.
|
|
- **SF Symbols**: Use `Image(systemName:)` for icons. No emoji in UI unless already present.
|
|
|
|
## Key Patterns
|
|
|
|
- **Download keys**: Use `::` as separator (e.g., `"slug::chapter-1::voice"`), never `-`. Slugs contain hyphens.
|
|
- **Voice fallback chain**: book override → global default → `"af_bella"`. See `BookVoicePreferences.voiceWithFallback()`.
|
|
- **Offline handling**: Wrap view bodies in `VStack` with `OfflineBanner` at top. Use `NetworkMonitor` (environment object) to gate network calls. Suppress network errors silently when offline via `ErrorAlertModifier`.
|
|
- **Audio playback priority**: local file → MinIO presigned URL → trigger TTS generation.
|
|
- **Progress display**: Show decimal % when < 10% (e.g., "3.4%"), rounded when >= 10% (e.g., "47%").
|
|
- **Cover images**: Always proxy via `/api/cover/{domain}/{slug}` — never link directly to source.
|
|
|
|
## Networking
|
|
|
|
`APIClient.swift` wraps all Go scraper API calls. When adding new endpoints:
|
|
|
|
1. Add a method to `APIClient`.
|
|
2. Keep error handling consistent — throw typed errors, let ViewModels catch and set `errorMessage`.
|
|
3. All requests are relative to `SCRAPER_API_URL` (configured at build time via xcconfig or environment).
|
|
|
|
## Using Documentation Tools
|
|
|
|
When writing or reviewing SwiftUI/Swift code:
|
|
|
|
- Use `context7` to look up current Apple SwiftUI/Swift documentation before implementing anything non-trivial. Apple's APIs evolve fast — do not rely on training data alone.
|
|
- Use `gh_grep` to find real-world Swift patterns when unsure how something is typically implemented.
|
|
|
|
Example prompts:
|
|
- "How does `.searchable` work in iOS 17? use context7"
|
|
- "Show me examples of `@Observable` with async tasks. use context7"
|
|
- "How do other apps implement background URLSession downloads in Swift? use gh_grep"
|
|
|
|
## UI/UX Skill
|
|
|
|
For any iOS view work, always load the `ios-ux` skill at the start of the task:
|
|
|
|
```
|
|
skill({ name: "ios-ux" })
|
|
```
|
|
|
|
This skill defines the full design system, animation rules, haptic feedback policy, accessibility checklist, performance guidelines, and offline handling requirements. It also governs how to handle screenshot-based reviews (analyze → suggest → confirm before applying).
|
|
|
|
## What to Avoid
|
|
|
|
- `NavigationView` — deprecated, use `NavigationStack`
|
|
- `ObservableObject` / `@Published` for new types — prefer `@Observable`
|
|
- `DispatchQueue.main.async` — prefer `@MainActor`
|
|
- Force unwrapping optionals
|
|
- Hardcoded color literals — use `Color+App.swift` extensions or semantic colors
|
|
- Adding new dependencies (SPM packages) without discussion
|