Some checks failed
CI / UI / Build (pull_request) Failing after 6s
CI / Scraper / Lint (pull_request) Failing after 9s
CI / Scraper / Test (pull_request) Failing after 8s
CI / Scraper / Build (pull_request) Has been skipped
iOS CI / Build (push) Has been cancelled
iOS CI / Test (push) Has been cancelled
iOS CI / Build (pull_request) Failing after 2s
iOS CI / Test (pull_request) Has been skipped
- Add safeAreaInset on ChapterReaderView ScrollView so Prev/Next buttons clear the mini player bar when audio is active - Fix mini player height reservation (66pt constant via AppLayout) - Add AsyncCoverImage(isBackground:) to suppress rounded-rect placeholder showing through blurred hero background (empty square bug) - Reduce chapter page size 100→50 on iOS - Fix ChapterRow date/chevron layout: minLength spacer + fixedSize date - Add String.strippingTrailingDate() extension; apply in MiniPlayer, FullPlayer, and ChapterReader header to clean up stale concatenated titles - Make Prev/Next buttons equal-width (maxWidth: .infinity) for easier tapping
83 lines
2.2 KiB
Swift
83 lines
2.2 KiB
Swift
import SwiftUI
|
|
import Kingfisher
|
|
|
|
// MARK: - Empty state placeholder used across all screens
|
|
|
|
struct EmptyStateView: View {
|
|
let icon: String
|
|
let title: String
|
|
let message: String
|
|
|
|
var body: some View {
|
|
VStack(spacing: 14) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 48))
|
|
.foregroundStyle(.tertiary)
|
|
Text(title)
|
|
.font(.headline)
|
|
Text(message)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 40)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Cover image card reused across screens
|
|
|
|
struct BookCard: View {
|
|
let book: Book
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
AsyncCoverImage(url: book.cover)
|
|
.frame(height: 200)
|
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
Text(book.title)
|
|
.font(.caption.bold())
|
|
.lineLimit(2)
|
|
Text(book.author)
|
|
.font(.caption2)
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Async cover image with disk/memory caching via Kingfisher
|
|
|
|
struct AsyncCoverImage: View {
|
|
let url: String
|
|
/// When true the placeholder is a plain colour fill — used for blurred hero backgrounds
|
|
/// so the rounded-rect loading indicator doesn't bleed through.
|
|
var isBackground: Bool = false
|
|
|
|
var body: some View {
|
|
KFImage(URL(string: url))
|
|
.resizable()
|
|
.placeholder {
|
|
if isBackground {
|
|
Color(.systemGray6)
|
|
} else {
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.fill(Color(.systemGray5))
|
|
.overlay(Image(systemName: "book.closed").foregroundStyle(.secondary))
|
|
}
|
|
}
|
|
.scaledToFill()
|
|
}
|
|
}
|
|
|
|
// MARK: - Tag chip
|
|
|
|
struct TagChip: View {
|
|
let label: String
|
|
var body: some View {
|
|
Text(label)
|
|
.font(.caption2.bold())
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 4)
|
|
.background(Color(.systemGray5), in: Capsule())
|
|
}
|
|
}
|