From 5e04f6ae039d47cee56608fcdda00dc23f242e0f Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 5 May 2025 16:55:50 +0800 Subject: [PATCH] feat: support locking screen orientation, closes #860 (#1034) --- .../public/locales/ar/translation.json | 6 +- .../public/locales/de/translation.json | 6 +- .../public/locales/el/translation.json | 6 +- .../public/locales/es/translation.json | 6 +- .../public/locales/fr/translation.json | 6 +- .../public/locales/hi/translation.json | 6 +- .../public/locales/id/translation.json | 6 +- .../public/locales/it/translation.json | 6 +- .../public/locales/ja/translation.json | 6 +- .../public/locales/ko/translation.json | 6 +- .../public/locales/pl/translation.json | 6 +- .../public/locales/pt/translation.json | 6 +- .../public/locales/ru/translation.json | 6 +- .../public/locales/tr/translation.json | 6 +- .../public/locales/uk/translation.json | 6 +- .../public/locales/vi/translation.json | 6 +- .../public/locales/zh-CN/translation.json | 6 +- .../public/locales/zh-TW/translation.json | 6 +- .../src/main/java/NativeBridgePlugin.kt | 22 +++++ .../tauri-plugin-native-bridge/build.rs | 1 + .../ios/Sources/NativeBridgePlugin.swift | 80 +++++++++++++++++++ .../commands/lock_screen_orientation.toml | 13 +++ .../permissions/autogenerated/reference.md | 27 +++++++ .../permissions/default.toml | 1 + .../permissions/schemas/schema.json | 16 +++- .../src/commands.rs | 8 ++ .../tauri-plugin-native-bridge/src/desktop.rs | 7 ++ .../tauri-plugin-native-bridge/src/lib.rs | 1 + .../tauri-plugin-native-bridge/src/mobile.rs | 11 +++ .../tauri-plugin-native-bridge/src/models.rs | 6 ++ apps/readest-app/src/app/library/page.tsx | 7 ++ .../app/reader/components/FoliateViewer.tsx | 7 ++ .../reader/components/settings/MiscPanel.tsx | 51 ++++++++++++ apps/readest-app/src/services/appService.ts | 3 + apps/readest-app/src/services/constants.ts | 5 ++ apps/readest-app/src/types/book.ts | 12 ++- apps/readest-app/src/utils/bridge.ts | 10 +++ 37 files changed, 375 insertions(+), 21 deletions(-) create mode 100644 apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/lock_screen_orientation.toml diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 55481451..a5711837 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -299,5 +299,9 @@ "Link Color": "لون الرابط", "Volume Keys for Page Flip": "مفاتيح الصوت لتقليب الصفحات", "Clicks for Page Flip": "نقرات لتقليب الصفحات", - "Swap Clicks Area": "تبديل منطقة النقرات" + "Swap Clicks Area": "تبديل منطقة النقرات", + "Screen": "الشاشة", + "Orientation": "الاتجاه", + "Portrait": "العمودي", + "Landscape": "الأفقي" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 8f628284..44fb19d9 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -299,5 +299,9 @@ "Link Color": "Linkfarbe", "Volume Keys for Page Flip": "Volumen-Tasten für Seitenwechsel", "Clicks for Page Flip": "Tippen zum Blättern", - "Swap Clicks Area": "Tippbereiche tauschen" + "Swap Clicks Area": "Tippbereiche tauschen", + "Screen": "Bildschirm", + "Orientation": "Orientierung", + "Portrait": "Hochformat", + "Landscape": "Querformat" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 992aa705..97bed9ab 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -299,5 +299,9 @@ "Link Color": "Χρώμα συνδέσμου", "Volume Keys for Page Flip": "Πλήκτρα έντασης για ανατροπή σελίδας", "Clicks for Page Flip": "Κλικ για ανατροπή σελίδας", - "Swap Clicks Area": "Ανταλλαγή περιοχής κλικ" + "Swap Clicks Area": "Ανταλλαγή περιοχής κλικ", + "Screen": "Οθόνη", + "Orientation": "Προσανατολισμός", + "Portrait": "Πορτραίτο", + "Landscape": "Τοπίο" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 9494e7c8..63a918cf 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -299,5 +299,9 @@ "Link Color": "Color del enlace", "Volume Keys for Page Flip": "Botones de volumen para pasar página", "Clicks for Page Flip": "Toques para pasar página", - "Swap Clicks Area": "Invertir zonas de toque" + "Swap Clicks Area": "Invertir zonas de toque", + "Screen": "Pantalla", + "Orientation": "Orientación", + "Portrait": "Vertical", + "Landscape": "Horizontal" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 7f7c055e..7df07499 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -299,5 +299,9 @@ "Link Color": "Couleur du lien", "Volume Keys for Page Flip": "Boutons volume pour tourner la page", "Clicks for Page Flip": "Taps pour tourner la page", - "Swap Clicks Area": "Inverser zones de tap" + "Swap Clicks Area": "Inverser zones de tap", + "Screen": "Écran", + "Orientation": "Orientation", + "Portrait": "Portrait", + "Landscape": "Paysage" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index f62a0cee..7a34a275 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -299,5 +299,9 @@ "Link Color": "लिंक रंग", "Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ", "Clicks for Page Flip": "पृष्ठ फ़्लिप के लिए क्लिक करें", - "Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें" + "Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें", + "Screen": "स्क्रीन", + "Orientation": "अवयव", + "Portrait": "पोर्ट्रेट", + "Landscape": "लैंडस्केप" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index b2192324..46f6db5d 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -299,5 +299,9 @@ "Link Color": "Warna Tautan", "Volume Keys for Page Flip": "Volume Keys untuk Pembalikan Halaman", "Clicks for Page Flip": "Klik untuk Pembalikan Halaman", - "Swap Clicks Area": "Area Klik Tukar" + "Swap Clicks Area": "Area Klik Tukar", + "Screen": "Screen", + "Orientation": "Orientasi", + "Portrait": "Potret", + "Landscape": "Lanskap" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 3b6e4bc0..fddd0049 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -299,5 +299,9 @@ "Link Color": "Colore link", "Volume Keys for Page Flip": "Tasti volume per girare pagina", "Clicks for Page Flip": "Clic per girare pagina", - "Swap Clicks Area": "Inverti area dei clic" + "Swap Clicks Area": "Inverti area dei clic", + "Screen": "Schermo", + "Orientation": "Orientamento", + "Portrait": "Ritratto", + "Landscape": "Paesaggio" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index b81641eb..5e2c4fdd 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -299,5 +299,9 @@ "Link Color": "リンクカラー", "Volume Keys for Page Flip": "音量キーでページめくり", "Clicks for Page Flip": "タップでページめくり", - "Swap Clicks Area": "タップ領域を入れ替え" + "Swap Clicks Area": "タップ領域を入れ替え", + "Screen": "画面", + "Orientation": "向き", + "Portrait": "縦", + "Landscape": "横" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index d7b1df42..e8d01765 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -299,5 +299,9 @@ "Link Color": "링크 색상", "Volume Keys for Page Flip": "페이지 넘김을 위한 볼륨 키", "Clicks for Page Flip": "페이지 넘김을 위한 클릭", - "Swap Clicks Area": "클릭 영역 교환" + "Swap Clicks Area": "클릭 영역 교환", + "Screen": "화면", + "Orientation": "방향", + "Portrait": "세로", + "Landscape": "가로" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 564ced4e..611fcd69 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -299,5 +299,9 @@ "Link Color": "Kolor linku", "Volume Keys for Page Flip": "Klucze głośności do przewracania stron", "Clicks for Page Flip": "Kliknięcia do przewracania stron", - "Swap Clicks Area": "Zamień obszar kliknięcia" + "Swap Clicks Area": "Zamień obszar kliknięcia", + "Screen": "Ekran", + "Orientation": "Orientacja", + "Portrait": "Portret", + "Landscape": "Krajobraz" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index b079fe6d..e0cfdd45 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -299,5 +299,9 @@ "Link Color": "Cor do Link", "Volume Keys for Page Flip": "Teclas de Volume para Virar Página", "Clicks for Page Flip": "Cliques para Virar Página", - "Swap Clicks Area": "Trocar Área de Cliques" + "Swap Clicks Area": "Trocar Área de Cliques", + "Screen": "Tela", + "Orientation": "Orientação", + "Portrait": "Retrato", + "Landscape": "Paisagem" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index e36b0b88..1d28a37d 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -299,5 +299,9 @@ "Link Color": "Цвет ссылки", "Volume Keys for Page Flip": "Клавиши громкости для перелистывания страниц", "Clicks for Page Flip": "Клики для перелистывания страниц", - "Swap Clicks Area": "Переключить область кликов" + "Swap Clicks Area": "Переключить область кликов", + "Screen": "Экран", + "Orientation": "Ориентация", + "Portrait": "Портрет", + "Landscape": "Ландшафт" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index d0d72bae..703874f2 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -299,5 +299,9 @@ "Link Color": "Bağlantı Rengi", "Volume Keys for Page Flip": "Sayfa Çevirme için Ses Seviyesi Tuşları", "Clicks for Page Flip": "Sayfa Çevirme için Tıklamalar", - "Swap Clicks Area": "Tıklama Alanını Değiştir" + "Swap Clicks Area": "Tıklama Alanını Değiştir", + "Screen": "Ekran", + "Orientation": "Yönlendirme", + "Portrait": "Portre", + "Landscape": "Yatay" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index e3cf54f7..29a82a91 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -299,5 +299,9 @@ "Link Color": "Колір посилання", "Volume Keys for Page Flip": "Клавіші гучності для перевороту сторінки", "Clicks for Page Flip": "Клацання для перевороту сторінки", - "Swap Clicks Area": "Змінити область клацання" + "Swap Clicks Area": "Змінити область клацання", + "Screen": "Екран", + "Orientation": "Орієнтація", + "Portrait": "Портрет", + "Landscape": "Ландшафт" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index fe7c2ad5..0e8d6b46 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -299,5 +299,9 @@ "Link Color": "Màu liên kết", "Volume Keys for Page Flip": "Phím âm lượng để lật trang", "Clicks for Page Flip": "Nhấp để lật trang", - "Swap Clicks Area": "Đổi khu vực nhấp" + "Swap Clicks Area": "Đổi khu vực nhấp", + "Screen": "Màn hình", + "Orientation": "Định hướng", + "Portrait": "Chân dung", + "Landscape": "Phong cảnh" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 2cba2fb5..26066b24 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -299,5 +299,9 @@ "Link Color": "链接颜色", "Volume Keys for Page Flip": "音量键翻页", "Clicks for Page Flip": "点击翻页", - "Swap Clicks Area": "交换点击区域" + "Swap Clicks Area": "交换点击区域", + "Screen": "屏幕", + "Orientation": "屏幕方向", + "Portrait": "竖屏", + "Landscape": "横屏" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 22ba5f52..3c6eec6c 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -299,5 +299,9 @@ "Link Color": "連結顏色", "Volume Keys for Page Flip": "音量鍵翻頁", "Clicks for Page Flip": "點擊翻頁", - "Swap Clicks Area": "交換點擊區域" + "Swap Clicks Area": "交換點擊區域", + "Screen": "螢幕", + "Orientation": "螢幕方向", + "Portrait": "豎屏", + "Landscape": "橫屏" } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt index 48e5a288..f9d5469d 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt @@ -12,6 +12,7 @@ import android.view.WindowManager import android.view.WindowInsetsController import android.graphics.Color import android.webkit.WebView +import android.content.pm.ActivityInfo import android.graphics.fonts.SystemFonts import android.graphics.fonts.Font import androidx.core.view.WindowCompat @@ -56,6 +57,11 @@ class InterceptKeysRequestArgs { var backKey: Boolean? = null } +@InvokeArg +class LockScreenOrientationRequestArgs { + var orientation: String? = null +} + interface KeyDownInterceptor { fun interceptVolumeKeys(enabled: Boolean) fun interceptBackKey(enabled: Boolean) @@ -326,4 +332,20 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) { } invoke.resolve() } + + @Command + fun lock_screen_orientation(invoke: Invoke) { + val args = invoke.parseArgs(LockScreenOrientationRequestArgs::class.java) + val orientation = args.orientation ?: "auto" + when (orientation) { + "portrait" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + "landscape" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + "auto" -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + else -> { + invoke.reject("Invalid orientation mode") + return + } + } + invoke.resolve() + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs index 02502b11..590f0be2 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs @@ -8,6 +8,7 @@ const COMMANDS: &[&str] = &[ "get_status_bar_height", "get_sys_fonts_list", "intercept_keys", + "lock_screen_orientation", ]; fn main() { diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/ios/Sources/NativeBridgePlugin.swift b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/ios/Sources/NativeBridgePlugin.swift index 13a13ea1..5f92c4e4 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/ios/Sources/NativeBridgePlugin.swift +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/ios/Sources/NativeBridgePlugin.swift @@ -44,6 +44,10 @@ class InterceptKeysRequestArgs: Decodable { let volumeKeys: Bool? } +class LockScreenOrientationRequestArgs: Decodable { + let orientation: String? +} + class VolumeKeyHandler: NSObject { private var audioSession: AVAudioSession? private var originalVolume: Float = 0.0 @@ -150,6 +154,9 @@ class VolumeKeyHandler: NSObject { class NativeBridgePlugin: Plugin { private var webView: WKWebView? private var authSession: ASWebAuthenticationSession? + private var isOrientationLocked = false + private var currentOrientationMask: UIInterfaceOrientationMask = .all + private var orientationObserver: NSObjectProtocol? @objc public override func load(webview: WKWebView) { self.webView = webview @@ -174,6 +181,10 @@ class NativeBridgePlugin: Plugin { if volumeKeyHandler != nil { activateVolumeKeyInterception() } + + if orientationObserver != nil { + self.setupOrientationObserver() + } } @objc func appDidEnterBackground() { @@ -338,6 +349,75 @@ class NativeBridgePlugin: Plugin { invoke.reject(error.localizedDescription) } } + + @objc public func lock_screen_orientation(_ invoke: Invoke) throws { + guard let args = try? invoke.parseArgs(LockScreenOrientationRequestArgs.self) else { + return invoke.reject("Invalid arguments") + } + + DispatchQueue.main.async { + UIDevice.current.beginGeneratingDeviceOrientationNotifications() + let orientation = args.orientation ?? "auto" + switch orientation.lowercased() { + case "portrait": + self.isOrientationLocked = true + self.currentOrientationMask = .portrait + self.forceOrientation(.portrait) + self.setupOrientationObserver() + case "landscape": + self.isOrientationLocked = true + self.currentOrientationMask = .landscape + self.forceOrientation(.landscapeRight) + self.setupOrientationObserver() + case "auto": + self.isOrientationLocked = false + self.currentOrientationMask = .all + default: + invoke.reject("Invalid orientation mode") + return + } + + invoke.resolve() + } + } + + private func forceOrientation(_ orientation: UIInterfaceOrientation) { + if #available(iOS 16.0, *) { + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + let orientationMask: UIInterfaceOrientationMask = + orientation.isPortrait ? .portrait : .landscape + + for window in windowScene.windows { + if let rootVC = window.rootViewController { + rootVC.setNeedsUpdateOfSupportedInterfaceOrientations() + } + } + + windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientationMask)) { error in + print("Orientation update error: \(error.localizedDescription)") + } + } + } else { + UIDevice.current.setValue(orientation.rawValue, forKey: "orientation") + UIViewController.attemptRotationToDeviceOrientation() + } + } + + private func setupOrientationObserver() { + orientationObserver = NotificationCenter.default.addObserver( + forName: UIDevice.orientationDidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + guard let self = self, self.isOrientationLocked else { return } + + if self.currentOrientationMask == .portrait { + self.forceOrientation(.portrait) + } else if self.currentOrientationMask == .landscape { + self.forceOrientation(.landscapeRight) + } + } + } } @_cdecl("init_plugin_native_bridge") diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/lock_screen_orientation.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/lock_screen_orientation.toml new file mode 100644 index 00000000..ddf675ff --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/lock_screen_orientation.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-lock-screen-orientation" +description = "Enables the lock_screen_orientation command without any pre-configured scope." +commands.allow = ["lock_screen_orientation"] + +[[permission]] +identifier = "deny-lock-screen-orientation" +description = "Denies the lock_screen_orientation command without any pre-configured scope." +commands.deny = ["lock_screen_orientation"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md index 389f441d..1ce8c04b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md @@ -13,6 +13,7 @@ Default permissions for the plugin - `allow-get-status-bar-height` - `allow-get-sys-fonts-list` - `allow-intercept-keys` +- `allow-lock-screen-orientation` ## Permission Table @@ -208,6 +209,32 @@ Denies the intercept_keys command without any pre-configured scope. +`native-bridge:allow-lock-screen-orientation` + + + + +Enables the lock_screen_orientation command without any pre-configured scope. + + + + + + + +`native-bridge:deny-lock-screen-orientation` + + + + +Denies the lock_screen_orientation command without any pre-configured scope. + + + + + + + `native-bridge:allow-set-system-ui-visibility` diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml index fff8cefd..225774e2 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml @@ -10,4 +10,5 @@ permissions = [ "allow-get-status-bar-height", "allow-get-sys-fonts-list", "allow-intercept-keys", + "allow-lock-screen-orientation", ] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json index 5fe38855..02eac9d9 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json @@ -378,6 +378,18 @@ "const": "deny-intercept-keys", "markdownDescription": "Denies the intercept_keys command without any pre-configured scope." }, + { + "description": "Enables the lock_screen_orientation command without any pre-configured scope.", + "type": "string", + "const": "allow-lock-screen-orientation", + "markdownDescription": "Enables the lock_screen_orientation command without any pre-configured scope." + }, + { + "description": "Denies the lock_screen_orientation command without any pre-configured scope.", + "type": "string", + "const": "deny-lock-screen-orientation", + "markdownDescription": "Denies the lock_screen_orientation command without any pre-configured scope." + }, { "description": "Enables the set_system_ui_visibility command without any pre-configured scope.", "type": "string", @@ -403,10 +415,10 @@ "markdownDescription": "Denies the use_background_audio command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`" } ] } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs index 586841a6..49409031 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs @@ -73,3 +73,11 @@ pub(crate) async fn intercept_keys( ) -> Result<()> { app.native_bridge().intercept_keys(payload) } + +#[command] +pub(crate) async fn lock_screen_orientation( + app: AppHandle, + payload: LockScreenOrientationRequest, +) -> Result<()> { + app.native_bridge().lock_screen_orientation(payload) +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs index 80fd1f57..bb7f2fae 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs @@ -61,4 +61,11 @@ impl NativeBridge { pub fn intercept_keys(&self, _payload: InterceptKeysRequest) -> crate::Result<()> { Err(crate::Error::UnsupportedPlatformError) } + + pub fn lock_screen_orientation( + &self, + _payload: LockScreenOrientationRequest, + ) -> crate::Result<()> { + Err(crate::Error::UnsupportedPlatformError) + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs index 4a8796f0..97264f7d 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs @@ -46,6 +46,7 @@ pub fn init() -> TauriPlugin { commands::get_status_bar_height, commands::get_sys_fonts_list, commands::intercept_keys, + commands::lock_screen_orientation, ]) .setup(|app, api| { #[cfg(mobile)] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs index 76182959..eaa6ecfa 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs @@ -101,3 +101,14 @@ impl NativeBridge { .map_err(Into::into) } } + +impl NativeBridge { + pub fn lock_screen_orientation( + &self, + payload: LockScreenOrientationRequest, + ) -> crate::Result<()> { + self.0 + .run_mobile_plugin("lock_screen_orientation", payload) + .map_err(Into::into) + } +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs index 23e0eb2c..030aa65b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs @@ -80,3 +80,9 @@ pub struct InterceptKeysRequest { pub volume_keys: Option, pub back_key: Option, } + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LockScreenOrientationRequest { + pub orientation: String, +} diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 47b41331..cbca5554 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -31,6 +31,7 @@ import { useBooksSync } from './hooks/useBooksSync'; import { useThemeStore } from '@/store/themeStore'; import { useScreenWakeLock } from '@/hooks/useScreenWakeLock'; import { useOpenWithBooks } from '@/hooks/useOpenWithBooks'; +import { lockScreenOrientation } from '@/utils/bridge'; import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen, @@ -116,6 +117,12 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP // eslint-disable-next-line react-hooks/exhaustive-deps }, [settings]); + useEffect(() => { + if (appService?.isMobileApp) { + lockScreenOrientation({ orientation: 'portrait' }); + } + }, [appService]); + const handleDropedFiles = async (files: File[] | string[]) => { if (files.length === 0) return; const supportedFiles = files.filter((file) => { diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 1b847ee0..250aafa9 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { BookDoc, getDirection } from '@/libs/document'; import { BookConfig } from '@/types/book'; import { FoliateView, wrappedFoliateView } from '@/types/view'; +import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; @@ -26,12 +27,14 @@ import { import { getMaxInlineSize } from '@/utils/config'; import { getDirFromUILanguage } from '@/utils/rtl'; import { transformContent } from '@/services/transformService'; +import { lockScreenOrientation } from '@/utils/bridge'; const FoliateViewer: React.FC<{ bookKey: string; bookDoc: BookDoc; config: BookConfig; }> = ({ bookKey, bookDoc, config }) => { + const { appService } = useEnv(); const containerRef = useRef(null); const viewRef = useRef(null); const isViewCreated = useRef(false); @@ -188,6 +191,10 @@ const FoliateViewer: React.FC<{ const maxColumnCount = viewSettings.maxColumnCount!; const maxInlineSize = getMaxInlineSize(viewSettings); const maxBlockSize = viewSettings.maxBlockSize!; + const screenOrientation = viewSettings.screenOrientation!; + if (appService?.isMobileApp) { + await lockScreenOrientation({ orientation: screenOrientation }); + } if (animated) { view.renderer.setAttribute('animated', ''); } else { diff --git a/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx b/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx index 7fcb0ce1..5729a4d2 100644 --- a/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx @@ -1,12 +1,15 @@ import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; import i18n from 'i18next'; +import { IoPhoneLandscapeOutline, IoPhonePortraitOutline } from 'react-icons/io5'; +import { MdOutlineScreenRotation } from 'react-icons/md'; import { useEnv } from '@/context/EnvContext'; import { useReaderStore } from '@/store/readerStore'; import { useSettingsStore } from '@/store/settingsStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useDeviceControlStore } from '@/store/deviceStore'; import { getStyles } from '@/utils/style'; +import { lockScreenOrientation } from '@/utils/bridge'; import { saveViewSettings } from '../../utils/viewSettingsHelper'; import { TRANSLATED_LANGS } from '@/services/constants'; import cssbeautify from 'cssbeautify'; @@ -28,6 +31,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const [isContinuousScroll, setIsContinuousScroll] = useState(viewSettings.continuousScroll!); const [draftStylesheet, setDraftStylesheet] = useState(viewSettings.userStylesheet!); const [draftStylesheetSaved, setDraftStylesheetSaved] = useState(true); + const [screenOrientation, setScreenOrientation] = useState(viewSettings.screenOrientation!); const [error, setError] = useState(null); const [inputFocusInAndroid, setInputFocusInAndroid] = useState(false); @@ -160,6 +164,14 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isContinuousScroll]); + useEffect(() => { + saveViewSettings(envConfig, bookKey, 'screenOrientation', screenOrientation, false, false); + if (appService?.isMobileApp) { + lockScreenOrientation({ orientation: screenOrientation }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [screenOrientation]); + return (
= ({ bookKey }) => {
+
+

{_('Screen')}

+
+
+
+ {_('Orientation')} +
+
+ +
+ +
+ +
+ +
+ +
+
+
+
+
+
+

{_('Behavior')}

diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 129c5f90..6d583695 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -33,6 +33,7 @@ import { DEFAULT_SYSTEM_SETTINGS, DEFAULT_CJK_VIEW_SETTINGS, DEFAULT_MOBILE_READSETTINGS, + DEFAULT_SCREEN_CONFIG, } from './constants'; import { getOSPlatform, isCJKEnv, isContentURI, isValidURL } from '@/utils/misc'; import { deserializeConfig, serializeConfig } from '@/utils/serializer'; @@ -95,6 +96,7 @@ export abstract class BaseAppService implements AppService { ...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}), ...DEFAULT_VIEW_CONFIG, ...DEFAULT_TTS_CONFIG, + ...DEFAULT_SCREEN_CONFIG, ...settings.globalViewSettings, }; } catch { @@ -114,6 +116,7 @@ export abstract class BaseAppService implements AppService { ...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}), ...DEFAULT_VIEW_CONFIG, ...DEFAULT_TTS_CONFIG, + ...DEFAULT_SCREEN_CONFIG, }, } as SystemSettings; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 1f04a9c8..3548b551 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -4,6 +4,7 @@ import { BookSearchConfig, BookStyle, HighlightColor, + ScreenConfig, TTSConfig, ViewConfig, ViewSettings, @@ -139,6 +140,10 @@ export const DEFAULT_TTS_CONFIG: TTSConfig = { ttsVoice: '', }; +export const DEFAULT_SCREEN_CONFIG: ScreenConfig = { + screenOrientation: 'auto', +}; + export const DEFAULT_BOOK_SEARCH_CONFIG: BookSearchConfig = { scope: 'book', matchCase: false, diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 3fc697f1..c0a6fd28 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -124,7 +124,17 @@ export interface TTSConfig { ttsVoice: string; } -export interface ViewSettings extends BookLayout, BookStyle, BookFont, ViewConfig, TTSConfig {} +export interface ScreenConfig { + screenOrientation: 'auto' | 'portrait' | 'landscape'; +} + +export interface ViewSettings + extends BookLayout, + BookStyle, + BookFont, + ViewConfig, + TTSConfig, + ScreenConfig {} export interface BookProgress { location: string; diff --git a/apps/readest-app/src/utils/bridge.ts b/apps/readest-app/src/utils/bridge.ts index 64da07ec..12bd0758 100644 --- a/apps/readest-app/src/utils/bridge.ts +++ b/apps/readest-app/src/utils/bridge.ts @@ -48,6 +48,10 @@ export interface InterceptKeysRequest { backKey?: boolean; } +export interface LockScreenRequest { + orientation: 'portrait' | 'landscape' | 'auto'; +} + export async function copyURIToPath(request: CopyURIRequest): Promise { const result = await invoke('plugin:native-bridge|copy_uri_to_path', { payload: request, @@ -108,3 +112,9 @@ export async function interceptKeys(request: InterceptKeysRequest): Promise { + await invoke('plugin:native-bridge|lock_screen_orientation', { + payload: request, + }); +}