Skip to content

Commit de0e9b8

Browse files
authored
feat: add reader keyboard support on iOS and tvOS (#731)
## Problem Reader keyboard support was limited to macOS even though iPad, iPhone, and Apple TV can all be used with external keyboards. The help overlay also shared settings across reader types, and the initial UIKit responder handling introduced two iOS regressions: sheet presentation could trigger AttributeGraph cycle warnings, and EPUB keyboard shortcuts could stop responding after toggling controls. ## Approach This PR introduces shared reader keyboard command models and routes iOS and tvOS key events through a UIKit bridge while keeping the existing macOS command integration. The keyboard help overlay is available across supported reader platforms, auto-shows only when appropriate, and uses per-reader configuration where needed. On iOS, responder updates are deferred off the SwiftUI update path and then refreshed asynchronously so the reader can recover keyboard focus without reintroducing the cycle warning. ## Scope - Add iOS and tvOS keyboard shortcut support for DIVINA reader navigation and controls - Extend the keyboard help overlay and auto-show behavior across DIVINA, EPUB, and PDF readers - Add independent EPUB and PDF keyboard help preferences while preserving DIVINA settings - Refactor reader command state and handlers into platform-neutral types - Avoid iOS AttributeGraph cycles by deferring UIKit first responder changes outside SwiftUI updates - Restore EPUB keyboard shortcut responsiveness after toggling controls on iOS - Bump build version to 399
1 parent b6fd537 commit de0e9b8

48 files changed

Lines changed: 1475 additions & 788 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

KMReader.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@
438438
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "KMReader/KMReader-macOS.entitlements";
439439
CODE_SIGN_IDENTITY = "Apple Development";
440440
CODE_SIGN_STYLE = Automatic;
441-
CURRENT_PROJECT_VERSION = 398;
441+
CURRENT_PROJECT_VERSION = 399;
442442
DEVELOPMENT_TEAM = M777UHWZA4;
443443
ENABLE_APP_SANDBOX = YES;
444444
ENABLE_HARDENED_RUNTIME = YES;
@@ -496,7 +496,7 @@
496496
"CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = "KMReader/KMReader-macOS.entitlements";
497497
CODE_SIGN_IDENTITY = "Apple Distribution";
498498
CODE_SIGN_STYLE = Manual;
499-
CURRENT_PROJECT_VERSION = 398;
499+
CURRENT_PROJECT_VERSION = 399;
500500
DEVELOPMENT_TEAM = "";
501501
"DEVELOPMENT_TEAM[sdk=appletvos*]" = M777UHWZA4;
502502
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = M777UHWZA4;
@@ -556,7 +556,7 @@
556556
CODE_SIGN_ENTITLEMENTS = KMReaderWidgets/KMReaderWidgets.entitlements;
557557
CODE_SIGN_IDENTITY = "Apple Development";
558558
CODE_SIGN_STYLE = Automatic;
559-
CURRENT_PROJECT_VERSION = 398;
559+
CURRENT_PROJECT_VERSION = 399;
560560
DEVELOPMENT_TEAM = M777UHWZA4;
561561
GENERATE_INFOPLIST_FILE = YES;
562562
INFOPLIST_FILE = KMReaderWidgets/Info.plist;
@@ -590,7 +590,7 @@
590590
CODE_SIGN_IDENTITY = "Apple Distribution";
591591
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
592592
CODE_SIGN_STYLE = Manual;
593-
CURRENT_PROJECT_VERSION = 398;
593+
CURRENT_PROJECT_VERSION = 399;
594594
DEVELOPMENT_TEAM = "";
595595
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = M777UHWZA4;
596596
GENERATE_INFOPLIST_FILE = YES;

KMReader/Core/Storage/AppConfig.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,18 @@ enum AppConfig {
545545
}
546546
}
547547

548+
static nonisolated var epubShowKeyboardHelpOverlay: Bool {
549+
get {
550+
if UserDefaults.standard.object(forKey: "epubShowKeyboardHelpOverlay") != nil {
551+
return UserDefaults.standard.bool(forKey: "epubShowKeyboardHelpOverlay")
552+
}
553+
return true
554+
}
555+
set {
556+
UserDefaults.standard.set(newValue, forKey: "epubShowKeyboardHelpOverlay")
557+
}
558+
}
559+
548560
static nonisolated var enableReaderLiveActivity: Bool {
549561
get {
550562
if UserDefaults.standard.object(forKey: "enableReaderLiveActivity") != nil {

KMReader/Core/Storage/LiveTextManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
import Foundation
77

8-
#if !os(tvOS)
8+
#if os(iOS) || os(macOS)
99
import VisionKit
1010
#endif
1111

1212
@MainActor
1313
class LiveTextManager {
1414
static let shared = LiveTextManager()
1515

16-
#if !os(tvOS)
16+
#if os(iOS) || os(macOS)
1717
let analyzer = ImageAnalyzer()
1818
#endif
1919

KMReader/Features/Auth/ViewModels/AuthViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class AuthViewModel {
297297
await SSEService.shared.connect()
298298

299299
WidgetDataService.refreshWidgetData()
300-
#if !os(tvOS)
300+
#if os(iOS) || os(macOS)
301301
SpotlightIndexService.removeAllItems()
302302
SpotlightIndexService.indexAllDownloadedBooks(instanceId: finalInstanceId)
303303
#endif

KMReader/Features/Auth/Views/LoginView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ struct LoginView: View {
209209
.frame(maxWidth: .infinity)
210210
.padding(.vertical, 10)
211211
}
212-
#if !os(tvOS)
212+
#if os(iOS) || os(macOS)
213213
.frame(maxWidth: 360)
214214
#endif
215215
.frame(maxWidth: .infinity, alignment: .center)

KMReader/Features/Book/Views/BookDetailView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ struct BookDetailView: View {
8888
url: KomgaWebLinkBuilder.book(serverURL: current.serverURL, bookId: bookId),
8989
scope: .browse
9090
)
91-
#if !os(tvOS)
91+
#if os(iOS) || os(macOS)
9292
.toolbar {
9393
ToolbarItem(placement: .automatic) {
9494
bookToolbarContent
@@ -239,7 +239,7 @@ struct BookDetailView: View {
239239
@ViewBuilder
240240
private var bookToolbarContent: some View {
241241
HStack {
242-
#if !os(tvOS)
242+
#if os(iOS) || os(macOS)
243243
if let shareURL {
244244
ShareLink(item: shareURL, subject: Text(navigationTitle)) {
245245
Image(systemName: "square.and.arrow.up")

KMReader/Features/Browse/Views/BrowseView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ struct BrowseView: View {
148148
.inlineNavigationBarTitle(title)
149149
.animation(.default, value: librarySelection)
150150
.searchable(text: $searchQuery)
151-
#if !os(tvOS)
151+
#if os(iOS) || os(macOS)
152152
.toolbar {
153153
if librarySelection == nil {
154154
#if os(macOS)

KMReader/Features/Browse/Views/SavedFiltersView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ struct SavedFiltersView: View {
8383
}
8484
.adaptiveButtonStyle(.plain)
8585
}
86-
#if !os(tvOS)
86+
#if os(iOS) || os(macOS)
8787
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
8888
Button(role: .destructive) {
8989
deleteFilter(filter)

KMReader/Features/Collection/Views/CollectionDetailView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ struct CollectionDetailView: View {
9090
} message: {
9191
Text("This will permanently delete \(collection?.name ?? "this collection") from Komga.")
9292
}
93-
#if !os(tvOS)
93+
#if os(iOS) || os(macOS)
9494
.toolbar {
9595
ToolbarItem(placement: .automatic) {
9696
collectionToolbarContent

KMReader/Features/Dashboard/Views/DashboardView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ struct DashboardView: View {
321321
}
322322
}
323323
}
324-
#if !os(tvOS)
324+
#if os(iOS) || os(macOS)
325325
.toolbar {
326326
#if os(macOS)
327327
ToolbarItem(placement: .navigation) {

0 commit comments

Comments
 (0)