iOS: codebase cleanup — remove dead code, unify design, deduplicate patterns
Some checks failed
CI / UI / Build (pull_request) Failing after 7s
CI / Scraper / Test (pull_request) Failing after 10s
CI / Scraper / Lint (pull_request) Failing after 10s
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

- Remove swift-markdown-ui dependency (project.yml, pbxproj, Package.resolved)
- Remove all debug print() statements (APIClient, BrowseViewModel, ChapterReaderViewModel, ChapterReaderView)
- Remove dead UI stubs: isFavorited/isLiked state, heart/star buttons, empty Share/Add to Library menu items in FullPlayerView
- Remove unused audioToolbarButton toolbar builder in ChapterReaderView
- Remove unused model types: NovelListing, BookDetailData, ChapterContent
- Remove amberLight color (never referenced)
- Remove mini player interactive seek (horizontal drag/tap to seek) — progress bar is now display-only
- Fix AccentColor asset to amber #f59e0b (was orange-pink mismatch)
- Fix Discover tab icon: globe → globe.americas.fill
- Fix speed slider max: 3.0 → 2.0 in ProfileView
- Fix yearText: use static DateFormatter instead of allocating on every access
- Fix ChangePasswordView.save(): DispatchQueue.main.asyncAfter → Task.sleep
- Deduplicate navigationDestination blocks via .appNavigationDestination() View extension
- Deduplicate .alert(Error) pattern via .errorAlert() View extension
This commit is contained in:
Admin
2026-03-08 15:30:43 +05:00
parent e43699747d
commit 2793ad8cfa
19 changed files with 183 additions and 402 deletions

View File

@@ -7,6 +7,9 @@ struct ChapterReaderView: View {
let slug: String
let chapterNumber: Int
/// Tracks the currently displayed chapter updated in-place by skip/auto-next
/// so we never accumulate stale listeners on the navigation stack.
@State private var currentChapter: Int
@StateObject private var vm: ChapterReaderViewModel
@EnvironmentObject var audioPlayer: AudioPlayerService
@EnvironmentObject var authStore: AuthStore
@@ -14,20 +17,17 @@ struct ChapterReaderView: View {
init(slug: String, chapterNumber: Int) {
self.slug = slug
self.chapterNumber = chapterNumber
_currentChapter = State(initialValue: chapterNumber)
_vm = StateObject(wrappedValue: ChapterReaderViewModel(slug: slug, chapter: chapterNumber))
}
var body: some View {
let _ = print("[ChapterReaderView] body eval — slug=\(slug) ch=\(chapterNumber) isLoading=\(vm.isLoading) hasContent=\(vm.content != nil) error=\(vm.error ?? "nil")")
Group {
if vm.isLoading {
let _ = print("[ChapterReaderView] branch: LOADING")
ProgressView().frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let content = vm.content {
let _ = print("[ChapterReaderView] branch: CONTENT html=\(content.html.count)chars title='\(content.chapter.title)'")
readerContent(content)
} else if let errMsg = vm.error {
let _ = print("[ChapterReaderView] branch: ERROR '\(errMsg)'")
VStack(spacing: 16) {
Image(systemName: "exclamationmark.triangle")
.font(.largeTitle)
@@ -42,7 +42,6 @@ struct ChapterReaderView: View {
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
let _ = print("[ChapterReaderView] branch: BLANK (no loading, no content, no error)")
Color.clear
}
}
@@ -54,43 +53,42 @@ struct ChapterReaderView: View {
floatingAudioButton
}
}
.task {
print("[ChapterReaderView] .task fired — calling vm.load()")
.task(id: currentChapter) {
await vm.load()
print("[ChapterReaderView] .task completed")
}
.onReceive(NotificationCenter.default.publisher(for: .audioDidFinishChapter)) { note in
guard let next = note.userInfo?["next"] as? Int else { return }
let shouldAutoNavigate = note.userInfo?["autoNext"] as? Bool ?? false
if shouldAutoNavigate {
vm.navigateTo = next
}
// Only handle if this is the top-most (currently active) chapter view
guard shouldAutoNavigate, currentChapter == audioPlayer.chapter else { return }
navigateToChapter(next)
}
.onReceive(NotificationCenter.default.publisher(for: .skipToNextChapter)) { note in
guard let next = note.userInfo?["next"] as? Int else { return }
vm.navigateTo = next
// Only the view whose chapter matches the currently playing chapter should handle this
guard currentChapter == audioPlayer.chapter else { return }
navigateToChapter(next)
}
.onReceive(NotificationCenter.default.publisher(for: .skipToPrevChapter)) { note in
guard let prev = note.userInfo?["prev"] as? Int else { return }
vm.navigateTo = prev
}
.navigationDestination(isPresented: Binding(
get: { vm.navigateTo != nil },
set: { if !$0 { vm.navigateTo = nil } }
)) {
if let next = vm.navigateTo {
ChapterReaderView(slug: slug, chapterNumber: next)
}
guard currentChapter == audioPlayer.chapter else { return }
navigateToChapter(prev)
}
}
/// Navigate to a chapter in-place: reloads content without pushing to the navigation stack.
/// Back button always returns to BookDetailView regardless of how many chapters were visited.
private func navigateToChapter(_ chapter: Int) {
vm.switchChapter(to: chapter)
currentChapter = chapter
}
// MARK: - Content
@State private var webHeight: CGFloat = 800
@ViewBuilder
private func readerContent(_ content: ChapterResponse) -> some View {
let _ = print("[ChapterReaderView] readerContent — html=\(content.html.count)chars webHeight=\(webHeight)")
ScrollView {
VStack(alignment: .leading, spacing: 16) {
// Header
@@ -114,17 +112,21 @@ struct ChapterReaderView: View {
Divider()
// Prev / Next navigation
// Prev / Next navigation in-place swap so back button always returns to book
HStack(spacing: 12) {
if let prev = content.prev {
NavigationLink(value: NavDestination.chapter(slug, prev)) {
Button {
navigateToChapter(prev)
} label: {
Label("Ch.\(prev)", systemImage: "chevron.left")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
}
if let next = content.next {
NavigationLink(value: NavDestination.chapter(slug, next)) {
Button {
navigateToChapter(next)
} label: {
Label("Ch.\(next)", systemImage: "chevron.right")
.labelStyle(ReverseLabelStyle())
.frame(maxWidth: .infinity)
@@ -171,23 +173,6 @@ struct ChapterReaderView: View {
.padding(.trailing, 20)
.padding(.bottom, 20)
}
// MARK: - Audio toolbar button (kept for when player is active)
@ToolbarContentBuilder
private var audioToolbarButton: some ToolbarContent {
ToolbarItem(placement: .topBarTrailing) {
Button {
vm.toggleAudio(audioPlayer: audioPlayer, settings: authStore.settings)
} label: {
Image(systemName: audioPlayer.isActive &&
audioPlayer.slug == slug &&
audioPlayer.chapter == chapterNumber
? "speaker.wave.2.fill" : "speaker.wave.2")
.foregroundStyle(.amber)
}
}
}
}
// MARK: - HTML content renderer using WKWebView
@@ -224,7 +209,6 @@ struct HTMLContentView: UIViewRepresentable {
p { margin: 0 0 1em 0; }
"""
let wrapped = "<html><head><style>\(css)</style><meta name='viewport' content='width=device-width, initial-scale=1'></head><body>\(html)</body></html>"
print("[HTMLContentView] updateUIView — html=\(html.count)chars loading into WKWebView")
uiView.loadHTMLString(wrapped, baseURL: nil)
}
@@ -233,18 +217,12 @@ struct HTMLContentView: UIViewRepresentable {
init(_ parent: HTMLContentView) { self.parent = parent }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("[HTMLContentView] didFinish — evaluating scrollHeight")
webView.evaluateJavaScript("document.body.scrollHeight") { result, error in
DispatchQueue.main.async {
print("[HTMLContentView] scrollHeight result=\(String(describing: result)) error=\(String(describing: error))")
if let h = result as? CGFloat, h > 0 {
print("[HTMLContentView] height set to CGFloat \(h)")
self.parent.height = h
} else if let h = result as? Double, h > 0 {
print("[HTMLContentView] height set to Double \(h)")
self.parent.height = CGFloat(h)
} else {
print("[HTMLContentView] ⚠️ could not read height — keeping \(self.parent.height)")
}
}
}