From 4275508ccd197ff5ac2e545be1425aa75184c026 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 28 Apr 2025 18:36:24 +0800 Subject: [PATCH] feat: add volume keys for page turning, closes #471 (#982) --- .../public/locales/ar/translation.json | 7 +- .../public/locales/de/translation.json | 7 +- .../public/locales/el/translation.json | 7 +- .../public/locales/es/translation.json | 7 +- .../public/locales/fr/translation.json | 7 +- .../public/locales/hi/translation.json | 7 +- .../public/locales/id/translation.json | 7 +- .../public/locales/it/translation.json | 7 +- .../public/locales/ja/translation.json | 7 +- .../public/locales/ko/translation.json | 7 +- .../public/locales/pl/translation.json | 7 +- .../public/locales/pt/translation.json | 7 +- .../public/locales/ru/translation.json | 7 +- .../public/locales/tr/translation.json | 7 +- .../public/locales/uk/translation.json | 7 +- .../public/locales/vi/translation.json | 7 +- .../public/locales/zh-CN/translation.json | 7 +- .../public/locales/zh-TW/translation.json | 7 +- .../com/bilingify/readest/MainActivity.kt | 72 +++++++- .../src/main/java/NativeBridgePlugin.kt | 32 ++++ .../tauri-plugin-native-bridge/build.rs | 1 + .../ios/Sources/NativeBridgePlugin.swift | 165 ++++++++++++++++++ .../commands/intercept_keys.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 | 4 + .../tauri-plugin-native-bridge/src/lib.rs | 1 + .../tauri-plugin-native-bridge/src/mobile.rs | 8 + .../tauri-plugin-native-bridge/src/models.rs | 7 + .../app/reader/components/FoliateViewer.tsx | 6 +- .../src/app/reader/components/FooterBar.tsx | 8 +- .../app/reader/components/NotebookToggler.tsx | 7 + .../reader/components/settings/MiscPanel.tsx | 32 +++- .../src/app/reader/hooks/useIframeEvents.ts | 114 +----------- .../src/app/reader/hooks/usePageFlip.ts | 139 +++++++++++++++ apps/readest-app/src/context/EnvContext.tsx | 8 + apps/readest-app/src/services/appService.ts | 1 + apps/readest-app/src/services/constants.ts | 1 + .../src/services/nativeAppService.ts | 1 + apps/readest-app/src/store/deviceStore.ts | 55 ++++++ apps/readest-app/src/types/book.ts | 1 + apps/readest-app/src/types/system.ts | 1 + apps/readest-app/src/utils/bridge.ts | 11 ++ 45 files changed, 692 insertions(+), 174 deletions(-) create mode 100644 apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/intercept_keys.toml create mode 100644 apps/readest-app/src/app/reader/hooks/usePageFlip.ts create mode 100644 apps/readest-app/src/store/deviceStore.ts diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index f3eb78bb..55481451 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -27,7 +27,6 @@ "Delete": "حذف", "Delete Highlight": "حذف التمييز", "Dictionary": "القاموس", - "Disable Click-to-Flip": "تعطيل التقليب بالنقر", "Download Readest": "تحميل ريديست", "Edit": "تحرير", "Enter your custom CSS here...": "أدخل الـ CSS المخصص هنا...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "استيراد تلقائي عند فتح الملف", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "تبديل منطقة التقليب بالنقر", "No chapters detected.": "لم يتم التعرف على فصول.", "Failed to parse the EPUB file.": "فشل في تحليل ملف EPUB.", "This book format is not supported.": "هذا تنسيق الكتاب غير مدعوم.", @@ -298,5 +296,8 @@ "List": "قائمة", "Grid": "شبكة", "(from 'As You Like It', Act II)": "(من 'كما تحب'، الفصل الثاني)", - "Link Color": "لون الرابط" + "Link Color": "لون الرابط", + "Volume Keys for Page Flip": "مفاتيح الصوت لتقليب الصفحات", + "Clicks for Page Flip": "نقرات لتقليب الصفحات", + "Swap Clicks Area": "تبديل منطقة النقرات" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 7d066e6a..8f628284 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -27,7 +27,6 @@ "Delete": "Löschen", "Delete Highlight": "Hervorhebung löschen", "Dictionary": "Wörterbuch", - "Disable Click-to-Flip": "Klick zum Blättern deaktivieren", "Download Readest": "Readest herunterladen", "Edit": "Bearbeiten", "Enter your custom CSS here...": "Geben Sie hier Ihr benutzerdefiniertes CSS ein...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Automatischer Import beim Öffnen einer Datei", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Klick-zu-Blättern-Bereich tauschen", "No chapters detected.": "Keine Kapitel erkannt.", "Failed to parse the EPUB file.": "Fehler beim Parsen der EPUB-Datei.", "This book format is not supported.": "Dieses Buchformat wird nicht unterstützt.", @@ -298,5 +296,8 @@ "List": "Liste", "Grid": "Raster", "(from 'As You Like It', Act II)": "(aus 'Wie es euch gefällt', Akt II)", - "Link Color": "Linkfarbe" + "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" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index f8ca92e4..992aa705 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -27,7 +27,6 @@ "Delete": "Διαγραφή", "Delete Highlight": "Διαγραφή επισήμανσης", "Dictionary": "Λεξικό", - "Disable Click-to-Flip": "Απενεργοποίηση κλικ για γύρισμα σελίδας", "Download Readest": "Λήψη Readest", "Edit": "Επεξεργασία", "Enter your custom CSS here...": "Εισάγετε το προσαρμοσμένο CSS εδώ...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Αυτόματη εισαγωγή κατά το άνοιγμα αρχείου", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Ανταλλαγή περιοχής κλικ για γύρισμα σελίδας", "No chapters detected.": "Δεν εντοπίστηκαν κεφάλαια.", "Failed to parse the EPUB file.": "Αποτυχία ανάλυσης του αρχείου EPUB.", "This book format is not supported.": "Αυτή η μορφή βιβλίου δεν υποστηρίζεται.", @@ -298,5 +296,8 @@ "List": "Λίστα", "Grid": "Πλέγμα", "(from 'As You Like It', Act II)": "('Όπως σας αρέσει', Πράξη II)", - "Link Color": "Χρώμα συνδέσμου" + "Link Color": "Χρώμα συνδέσμου", + "Volume Keys for Page Flip": "Πλήκτρα έντασης για ανατροπή σελίδας", + "Clicks for Page Flip": "Κλικ για ανατροπή σελίδας", + "Swap Clicks Area": "Ανταλλαγή περιοχής κλικ" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index a4a2b4e9..9494e7c8 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -27,7 +27,6 @@ "Delete": "Eliminar", "Delete Highlight": "Eliminar resaltado", "Dictionary": "Diccionario", - "Disable Click-to-Flip": "Desactivar clic para voltear", "Download Readest": "Descargar Readest", "Edit": "Editar", "Enter your custom CSS here...": "Introduce tu CSS personalizado aquí...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Importación automática al abrir archivo", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Intercambiar área de clic para voltear", "No chapters detected.": "No se detectaron capítulos.", "Failed to parse the EPUB file.": "Error al analizar el archivo EPUB.", "This book format is not supported.": "Este formato de libro no es compatible.", @@ -298,5 +296,8 @@ "List": "Lista", "Grid": "Cuadrícula", "(from 'As You Like It', Act II)": "(de 'Como gustéis', Acto II)", - "Link Color": "Color del enlace" + "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" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index a2ab3330..7f7c055e 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -27,7 +27,6 @@ "Delete": "Supprimer", "Delete Highlight": "Supprimer le surlignage", "Dictionary": "Dictionnaire", - "Disable Click-to-Flip": "Désactiver le clic pour tourner", "Download Readest": "Télécharger Readest", "Edit": "Modifier", "Enter your custom CSS here...": "Saisissez votre CSS personnalisé ici...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Importation automatique à l'ouverture du fichier", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Échanger la zone de clic pour tourner", "No chapters detected.": "Aucun chapitre détecté.", "Failed to parse the EPUB file.": "Échec de l'analyse du fichier EPUB.", "This book format is not supported.": "Ce format de livre n'est pas pris en charge.", @@ -298,5 +296,8 @@ "List": "Liste", "Grid": "Grille", "(from 'As You Like It', Act II)": "(de 'Comme il vous plaira', Acte II)", - "Link Color": "Couleur du lien" + "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" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 2188771c..f62a0cee 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -27,7 +27,6 @@ "Delete": "हटाएं", "Delete Highlight": "हाइलाइट हटाएं", "Dictionary": "शब्दकोश", - "Disable Click-to-Flip": "क्लिक-टू-फ्लिप अक्षम करें", "Download Readest": "Readest डाउनलोड करें", "Edit": "संपादित करें", "Enter your custom CSS here...": "अपना कस्टम CSS यहां दर्ज करें...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "फ़ाइल खोलने पर स्वचालित आयात", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "क्लिक-टू-फ्लिप क्षेत्र स्वैप करें", "No chapters detected.": "कोई अध्याय नहीं मिला।", "Failed to parse the EPUB file.": "EPUB फ़ाइल को पार्स करने में विफल।", "This book format is not supported.": "यह पुस्तक प्रारूप समर्थित नहीं है।", @@ -298,5 +296,8 @@ "List": "सूची", "Grid": "ग्रिड", "(from 'As You Like It', Act II)": "('जैसा आप इसे पसंद करते हैं', अधिनियम II)", - "Link Color": "लिंक रंग" + "Link Color": "लिंक रंग", + "Volume Keys for Page Flip": "पृष्ठ फ़्लिप के लिए वॉल्यूम कुंजियाँ", + "Clicks for Page Flip": "पृष्ठ फ़्लिप के लिए क्लिक करें", + "Swap Clicks Area": "क्लिक क्षेत्र स्वैप करें" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 729d96ab..b2192324 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -27,7 +27,6 @@ "Delete": "Hapus", "Delete Highlight": "Hapus Sorotan", "Dictionary": "Kamus", - "Disable Click-to-Flip": "Nonaktifkan Klik untuk Membalik", "Download Readest": "Unduh Readest", "Edit": "Edit", "Enter your custom CSS here...": "Masukkan CSS kustom Anda di sini...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Impor Otomatis saat File Dibuka", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Tukar Area Klik untuk Membalik", "No chapters detected.": "Tidak ada bab yang terdeteksi.", "Failed to parse the EPUB file.": "Gagal mengurai file EPUB.", "This book format is not supported.": "Format buku ini tidak didukung.", @@ -298,5 +296,8 @@ "List": "Daftar", "Grid": "Jala", "(from 'As You Like It', Act II)": "(dari 'Seperti yang Anda Inginkan', Bab II)", - "Link Color": "Warna Tautan" + "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" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index ebcb4c86..3b6e4bc0 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -27,7 +27,6 @@ "Delete": "Elimina", "Delete Highlight": "Elimina evidenziazione", "Dictionary": "Dizionario", - "Disable Click-to-Flip": "Disattiva click per voltare pagina", "Download Readest": "Scarica Readest", "Edit": "Modifica", "Enter your custom CSS here...": "Inserisci qui il tuo CSS personalizzato...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Importazione automatica all'apertura del file", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Scambia area click per voltare pagina", "No chapters detected.": "Nessun capitolo rilevato.", "Failed to parse the EPUB file.": "Impossibile analizzare il file EPUB.", "This book format is not supported.": "Questo formato di libro non è supportato.", @@ -298,5 +296,8 @@ "List": "Elenco", "Grid": "Griglia", "(from 'As You Like It', Act II)": "(da 'Come vi piace', Atto II)", - "Link Color": "Colore link" + "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" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index eb3cca69..b81641eb 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -27,7 +27,6 @@ "Delete": "削除", "Delete Highlight": "ハイライトを削除", "Dictionary": "辞書", - "Disable Click-to-Flip": "クリックめくりを無効化", "Download Readest": "Readestをダウンロード", "Edit": "編集", "Enter your custom CSS here...": "ここにカスタムCSSを入力...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "ファイルを開くと自動インポート", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "クリックめくりエリアを入れ替え", "No chapters detected.": "章が検出されません。", "Failed to parse the EPUB file.": "EPUBファイルの解析に失敗しました。", "This book format is not supported.": "この書籍形式はサポートされていません。", @@ -298,5 +296,8 @@ "List": "リスト", "Grid": "グリッド", "(from 'As You Like It', Act II)": "(「お気に召すまま」第2幕)", - "Link Color": "リンクカラー" + "Link Color": "リンクカラー", + "Volume Keys for Page Flip": "音量キーでページめくり", + "Clicks for Page Flip": "タップでページめくり", + "Swap Clicks Area": "タップ領域を入れ替え" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 7d5436e8..d7b1df42 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -27,7 +27,6 @@ "Delete": "삭제", "Delete Highlight": "하이라이트 삭제", "Dictionary": "사전", - "Disable Click-to-Flip": "클릭 넘기기 비활성화", "Download Readest": "Readest 다운로드", "Edit": "편집", "Enter your custom CSS here...": "여기에 사용자 정의 CSS를 입력하세요...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "파일 열 때 자동 가져오기", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "클릭-뒤집기 영역 교환", "No chapters detected.": "장 발견되지 않음.", "Failed to parse the EPUB file.": "EPUB 파일 구문 분석 실패.", "This book format is not supported.": "이 책 형식은 지원되지 않습니다.", @@ -298,5 +296,8 @@ "List": "목록", "Grid": "격자", "(from 'As You Like It', Act II)": "(당신이 좋아하는 대로, 2막)", - "Link Color": "링크 색상" + "Link Color": "링크 색상", + "Volume Keys for Page Flip": "페이지 넘김을 위한 볼륨 키", + "Clicks for Page Flip": "페이지 넘김을 위한 클릭", + "Swap Clicks Area": "클릭 영역 교환" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index d444aa03..564ced4e 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -27,7 +27,6 @@ "Delete": "Usuń", "Delete Highlight": "Usuń zaznaczenie", "Dictionary": "Słownik", - "Disable Click-to-Flip": "Wyłącz przewracanie kliknięciem", "Download Readest": "Pobierz Readest", "Edit": "Edytuj", "Enter your custom CSS here...": "Wprowadź własny CSS tutaj...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Automatyczne importowanie przy otwieraniu pliku", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Zamień obszar kliknięcia na przewracanie", "No chapters detected.": "Nie wykryto rozdziałów.", "Failed to parse the EPUB file.": "Nie udało się przeanalizować pliku EPUB.", "This book format is not supported.": "Ten format książki nie jest obsługiwany.", @@ -298,5 +296,8 @@ "List": "Lista", "Grid": "Siatka", "(from 'As You Like It', Act II)": "(z „Jak wam się podoba”, Akt II)", - "Link Color": "Kolor linku" + "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" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index f76db371..b079fe6d 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -27,7 +27,6 @@ "Delete": "Excluir", "Delete Highlight": "Excluir Destaque", "Dictionary": "Dicionário", - "Disable Click-to-Flip": "Desativar Clique para Virar", "Download Readest": "Baixar Readest", "Edit": "Editar", "Enter your custom CSS here...": "Insira seu CSS personalizado aqui...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Importação Automática ao Abrir Arquivo", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Trocar Área de Clique para Virar", "No chapters detected.": "Nenhum capítulo detectado.", "Failed to parse the EPUB file.": "Falha ao analisar o arquivo EPUB.", "This book format is not supported.": "Este formato de livro não é suportado.", @@ -298,5 +296,8 @@ "List": "Lista", "Grid": "Grade", "(from 'As You Like It', Act II)": "(de 'Como Gostais', Ato II)", - "Link Color": "Cor do Link" + "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" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index ed4f8b70..e36b0b88 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -27,7 +27,6 @@ "Delete": "Удалить", "Delete Highlight": "Удалить выделение", "Dictionary": "Словарь", - "Disable Click-to-Flip": "Отключить переворот по клику", "Download Readest": "Скачать Readest", "Edit": "Редактировать", "Enter your custom CSS here...": "Введите ваш пользовательский CSS здесь...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Автоматический импорт при открытии файла", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Переключить область клика для переворота", "No chapters detected.": "Не обнаружено глав.", "Failed to parse the EPUB file.": "Не удалось разобрать файл EPUB.", "This book format is not supported.": "Этот формат книги не поддерживается.", @@ -298,5 +296,8 @@ "List": "Список", "Grid": "Сетка", "(from 'As You Like It', Act II)": "(из 'Как вам это понравится', Акт II)", - "Link Color": "Цвет ссылки" + "Link Color": "Цвет ссылки", + "Volume Keys for Page Flip": "Клавиши громкости для перелистывания страниц", + "Clicks for Page Flip": "Клики для перелистывания страниц", + "Swap Clicks Area": "Переключить область кликов" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 37bd3f9e..d0d72bae 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -27,7 +27,6 @@ "Delete": "Sil", "Delete Highlight": "Vurguyu Sil", "Dictionary": "Sözlük", - "Disable Click-to-Flip": "Tıkla-Çevir'i Devre Dışı Bırak", "Download Readest": "Readest'i İndir", "Edit": "Düzenle", "Enter your custom CSS here...": "Özel CSS'nizi buraya girin...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Dosya Açıldığında Otomatik İçe Aktar", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Tıkla-Çevir Alanını Değiştir", "No chapters detected.": "Bölüm bulunamadı.", "Failed to parse the EPUB file.": "EPUB dosyası ayrıştırılamadı.", "This book format is not supported.": "Bu kitap formatı desteklenmiyor.", @@ -298,5 +296,8 @@ "List": "Liste", "Grid": "Izgara", "(from 'As You Like It', Act II)": "(As You Like It, 2. Perde)", - "Link Color": "Bağlantı Rengi" + "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" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 8a8b4743..e3cf54f7 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -27,7 +27,6 @@ "Delete": "Видалити", "Delete Highlight": "Видалити виділення", "Dictionary": "Словник", - "Disable Click-to-Flip": "Вимкнути гортання кліком", "Download Readest": "Завантажити Readest", "Edit": "Редагувати", "Enter your custom CSS here...": "Введіть ваш користувацький CSS тут...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Автоматичний імпорт при відкритті файлу", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Поміняти місцями область кліку для перевороту", "No chapters detected.": "Не виявлено жодного розділу.", "Failed to parse the EPUB file.": "Не вдалося розібрати файл EPUB.", "This book format is not supported.": "Цей формат книги не підтримується.", @@ -298,5 +296,8 @@ "List": "Список", "Grid": "Сітка", "(from 'As You Like It', Act II)": "(з 'Як вам це подобається', Акт II)", - "Link Color": "Колір посилання" + "Link Color": "Колір посилання", + "Volume Keys for Page Flip": "Клавіші гучності для перевороту сторінки", + "Clicks for Page Flip": "Клацання для перевороту сторінки", + "Swap Clicks Area": "Змінити область клацання" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 7a5e1c86..fe7c2ad5 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -27,7 +27,6 @@ "Delete": "Xóa", "Delete Highlight": "Xóa văn bản được đánh dấu", "Dictionary": "Từ điển", - "Disable Click-to-Flip": "Tắt tính năng nhấp để lật trang", "Download Readest": "Tải Readest", "Edit": "Chỉnh sửa", "Enter your custom CSS here...": "Nhập CSS tùy chỉnh của bạn vào đây...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "Tự động nhập khi mở tệp", "LXGW WenKai GB Screen": "LXGW WenKai SC", "LXGW WenKai TC": "LXGW WenKai TC", - "Swap Click-to-Flip Area": "Hoán đổi khu vực nhấp để lật trang", "No chapters detected.": "Không phát hiện thấy chương nào.", "Failed to parse the EPUB file.": "Không thể phân tích tệp EPUB.", "This book format is not supported.": "Định dạng sách này không được hỗ trợ.", @@ -298,5 +296,8 @@ "List": "Danh sách", "Grid": "Lưới", "(from 'As You Like It', Act II)": "(từ 'Như bạn thích', Hành động II)", - "Link Color": "Màu liên kết" + "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" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 7855fd66..2cba2fb5 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -27,7 +27,6 @@ "Delete": "删除", "Delete Highlight": "删除划线", "Dictionary": "词典", - "Disable Click-to-Flip": "禁用点击翻页", "Download Readest": "下载 Readest", "Edit": "编辑", "Enter your custom CSS here...": "在此处输入您的自定义 CSS...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "打开文件时自动导入", "LXGW WenKai GB Screen": "霞鹜文楷", "LXGW WenKai TC": "霞鶩文楷", - "Swap Click-to-Flip Area": "交换点击翻页区域", "No chapters detected.": "未检测到章节。", "Failed to parse the EPUB file.": "无法解析 EPUB 文件。", "This book format is not supported.": "不支持此书籍格式。", @@ -298,5 +296,8 @@ "List": "列表", "Grid": "网格", "(from 'As You Like It', Act II)": "出自《皆大欢喜》,第二幕", - "Link Color": "链接颜色" + "Link Color": "链接颜色", + "Volume Keys for Page Flip": "音量键翻页", + "Clicks for Page Flip": "点击翻页", + "Swap Clicks Area": "交换点击区域" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 20683cb9..22ba5f52 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -27,7 +27,6 @@ "Delete": "刪除", "Delete Highlight": "刪除劃線", "Dictionary": "詞典", - "Disable Click-to-Flip": "禁用點擊翻頁", "Download Readest": "下載 Readest", "Edit": "編輯", "Enter your custom CSS here...": "在此處輸入您的自定義 CSS...", @@ -259,7 +258,6 @@ "Auto Import on File Open": "打開文件時自動導入", "LXGW WenKai GB Screen": "霞鶩文楷 SC", "LXGW WenKai TC": "霞鶩文楷 TC", - "Swap Click-to-Flip Area": "交換點擊翻頁區域", "No chapters detected.": "未檢測到章節。", "Failed to parse the EPUB file.": "無法解析 EPUB 文件。", "This book format is not supported.": "不支援此書籍格式。", @@ -298,5 +296,8 @@ "List": "列表", "Grid": "網格", "(from 'As You Like It', Act II)": "(出自《皆大歡喜》,第二幕)", - "Link Color": "連結顏色" + "Link Color": "連結顏色", + "Volume Keys for Page Flip": "音量鍵翻頁", + "Clicks for Page Flip": "點擊翻頁", + "Swap Clicks Area": "交換點擊區域" } diff --git a/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt b/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt index a10754ae..e6291a6a 100644 --- a/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt +++ b/apps/readest-app/src-tauri/gen/android/app/src/main/java/com/bilingify/readest/MainActivity.kt @@ -2,8 +2,78 @@ package com.bilingify.readest import android.os.Bundle import androidx.activity.enableEdgeToEdge +import android.view.KeyEvent +import android.webkit.WebView +import android.util.Log +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import com.readest.native_bridge.KeyDownInterceptor + +class MainActivity : TauriActivity(), KeyDownInterceptor { + private lateinit var wv: WebView + private var interceptVolumeKeysEnabled = false + private var interceptBackKeyEnabled = false + + override fun onWebViewCreate(webView: WebView) { + wv = webView + } + + private val keyEventMap = mapOf( + KeyEvent.KEYCODE_BACK to "Back", + KeyEvent.KEYCODE_VOLUME_DOWN to "VolumeDown", + KeyEvent.KEYCODE_VOLUME_UP to "VolumeUp" + ) + + override fun interceptVolumeKeys(enabled: Boolean) { + Log.d("MainActivity", "Intercept volume keys: $enabled") + interceptVolumeKeysEnabled = enabled + } + + override fun interceptBackKey(enabled: Boolean) { + Log.d("MainActivity", "Intercept back key: $enabled") + interceptBackKeyEnabled = enabled + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + val keyName = keyEventMap[keyCode] + if (keyName != null) { + wv.evaluateJavascript( + """ + try { + window.onNativeKeyDown("$keyName", $keyCode) + } catch (err) { + false + } + """.trimIndent() + ) { result -> + run { + if (result.equals("true", ignoreCase = true)) { + Log.d("Key Event", "Key event $keyName intercepted") + } + } + } + return when (keyCode) { + KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> { + if (interceptVolumeKeysEnabled) { + true + } else { + super.onKeyDown(keyCode, event) + } + } + KeyEvent.KEYCODE_BACK -> { + if (interceptBackKeyEnabled) { + true + } else { + super.onKeyDown(keyCode, event) + } + } + else -> super.onKeyDown(keyCode, event) + } + } + return super.onKeyDown(keyCode, event) + } -class MainActivity : TauriActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) 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 9330572e..0ce22ba9 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 @@ -6,6 +6,7 @@ import android.net.Uri import android.util.Log import android.os.Build import android.view.View +import android.view.KeyEvent import android.view.WindowInsets import android.view.WindowManager import android.view.WindowInsetsController @@ -49,6 +50,17 @@ class SetSystemUIVisibilityRequestArgs { var darkMode: Boolean? = false } +@InvokeArg +class InterceptKeysRequestArgs { + var volumeKeys: Boolean? = null + var backKey: Boolean? = null +} + +interface KeyDownInterceptor { + fun interceptVolumeKeys(enabled: Boolean) + fun interceptBackKey(enabled: Boolean) +} + @TauriPlugin class NativeBridgePlugin(private val activity: Activity): Plugin(activity) { private val implementation = NativeBridge() @@ -289,4 +301,24 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) { } invoke.resolve(ret) } + + @Command + fun intercept_keys(invoke: Invoke) { + val args = invoke.parseArgs(InterceptKeysRequestArgs::class.java) + if (activity is KeyDownInterceptor) { + when (args.backKey) { + true -> (activity as KeyDownInterceptor).interceptBackKey(true) + false -> (activity as KeyDownInterceptor).interceptBackKey(false) + else -> {} + } + when (args.volumeKeys) { + true -> (activity as KeyDownInterceptor).interceptVolumeKeys(true) + false -> (activity as KeyDownInterceptor).interceptVolumeKeys(false) + else -> {} + } + } else { + Log.e("NativeBridgePlugin", "Activity does not implement KeyDownInterceptor") + } + 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 5de5e897..02502b11 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 @@ -7,6 +7,7 @@ const COMMANDS: &[&str] = &[ "set_system_ui_visibility", "get_status_bar_height", "get_sys_fonts_list", + "intercept_keys", ]; 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 5c4fa93f..b8640bca 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 @@ -2,6 +2,7 @@ import AVFoundation import AuthenticationServices import CoreText import MediaPlayer +import ObjectiveC import SwiftRs import Tauri import UIKit @@ -35,8 +36,140 @@ class SetSystemUIVisibilityRequestArgs: Decodable { let darkMode: Bool } +class InterceptKeysRequestArgs: Decodable { + let backKey: Bool? + let volumeKeys: Bool? +} + +class VolumeKeyHandler: NSObject { + private var audioSession: AVAudioSession? + private var originalVolume: Float = 0.0 + private var volumeView: MPVolumeView? + private var isIntercepting = false + private var webView: WKWebView? + private var volumeSlider: UISlider? + + func startInterception(webView: WKWebView) { + if isIntercepting { + return + } + + self.webView = webView + isIntercepting = true + + audioSession = AVAudioSession.sharedInstance() + do { + try audioSession?.setActive(true) + } catch { + print("Failed to activate audio session: \(error)") + } + + originalVolume = audioSession?.outputVolume ?? 0.1 + + DispatchQueue.main.async { [weak self] in + self?.setupHiddenVolumeView() + } + + audioSession?.addObserver(self, forKeyPath: "outputVolume", options: [.new], context: nil) + } + + func stopInterception() { + if !isIntercepting { + return + } + + isIntercepting = false + audioSession?.removeObserver(self, forKeyPath: "outputVolume") + DispatchQueue.main.async { [weak self] in + self?.setSystemVolume(self?.originalVolume ?? 0.1) + self?.volumeView?.removeFromSuperview() + self?.volumeView = nil + self?.volumeSlider = nil + } + + do { + try audioSession?.setActive(false) + } catch { + print("Failed to deactivate audio session: \(error)") + } + } + + private func setSystemVolume(_ volume: Float) { + DispatchQueue.main.async { [weak self] in + self?.volumeSlider?.value = volume + } + } + + private func setupHiddenVolumeView() { + assert(Thread.isMainThread, "setupHiddenVolumeView must be called on main thread") + + let frame = CGRect(x: -1000, y: -1000, width: 1, height: 1) + volumeView = MPVolumeView(frame: frame) + volumeSlider = volumeView?.subviews.first(where: { $0 is UISlider }) as? UISlider + if let window = UIApplication.shared.windows.first { + window.addSubview(volumeView!) + } + setSystemVolume(originalVolume) + } + + override func observeValue( + forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, + context: UnsafeMutableRawPointer? + ) { + if keyPath == "outputVolume", let audioSession = self.audioSession, isIntercepting { + let currentVolume = audioSession.outputVolume + + if currentVolume > originalVolume { + DispatchQueue.main.async { [weak self] in + self?.webView?.evaluateJavaScript( + "window.onNativeKeyDown('VolumeUp');", completionHandler: nil) + self?.setSystemVolume(self?.originalVolume ?? 0.1) + } + } else if currentVolume < originalVolume { + DispatchQueue.main.async { [weak self] in + self?.webView?.evaluateJavaScript( + "window.onNativeKeyDown('VolumeDown');", completionHandler: nil) + self?.setSystemVolume(self?.originalVolume ?? 0.1) + } + } + } + } +} + class NativeBridgePlugin: Plugin { private var authSession: ASWebAuthenticationSession? + private var webView: WKWebView? + + @objc public override func load(webview: WKWebView) { + self.webView = webview + print("NativeBridgePlugin loaded") + } + + private struct AssociatedKeys { + static var volumeKeyHandler = "volumeKeyHandler" + static var interceptingVolumeKeys = "interceptingVolumeKeys" + } + + private var volumeKeyHandler: VolumeKeyHandler? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.volumeKeyHandler) as? VolumeKeyHandler + } + set { + objc_setAssociatedObject( + self, &AssociatedKeys.volumeKeyHandler, newValue, .OBJC_ASSOCIATION_RETAIN) + } + } + + private var interceptingVolumeKeys: Bool { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.interceptingVolumeKeys) as? Bool + ?? false + } + set { + objc_setAssociatedObject( + self, &AssociatedKeys.interceptingVolumeKeys, newValue, .OBJC_ASSOCIATION_RETAIN) + } + } @objc public func use_background_audio(_ invoke: Invoke) { do { @@ -130,6 +263,38 @@ class NativeBridgePlugin: Plugin { invoke.resolve(["fonts": fontList]) } + + private func interceptVolumeKeys(_ intercept: Bool) { + interceptingVolumeKeys = intercept + + if intercept { + if volumeKeyHandler == nil { + volumeKeyHandler = VolumeKeyHandler() + } + + if let webView = self.webView { + volumeKeyHandler?.startInterception(webView: webView) + } + } else { + volumeKeyHandler?.stopInterception() + } + } + + @objc public func intercept_keys(_ invoke: Invoke) { + do { + let args = try invoke.parseArgs(InterceptKeysRequestArgs.self) + + if let volumeKeys = args.volumeKeys { + DispatchQueue.main.async { [weak self] in + self?.interceptVolumeKeys(volumeKeys) + } + } + + invoke.resolve() + } catch { + invoke.reject(error.localizedDescription) + } + } } @_cdecl("init_plugin_native_bridge") diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/intercept_keys.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/intercept_keys.toml new file mode 100644 index 00000000..250a702a --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/intercept_keys.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-intercept-keys" +description = "Enables the intercept_keys command without any pre-configured scope." +commands.allow = ["intercept_keys"] + +[[permission]] +identifier = "deny-intercept-keys" +description = "Denies the intercept_keys command without any pre-configured scope." +commands.deny = ["intercept_keys"] 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 54ab55f0..389f441d 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 @@ -12,6 +12,7 @@ Default permissions for the plugin - `allow-set-system-ui-visibility` - `allow-get-status-bar-height` - `allow-get-sys-fonts-list` +- `allow-intercept-keys` ## Permission Table @@ -181,6 +182,32 @@ Denies the install_package command without any pre-configured scope. +`native-bridge:allow-intercept-keys` + + + + +Enables the intercept_keys command without any pre-configured scope. + + + + + + + +`native-bridge:deny-intercept-keys` + + + + +Denies the intercept_keys 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 3fad039f..fff8cefd 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 @@ -9,4 +9,5 @@ permissions = [ "allow-set-system-ui-visibility", "allow-get-status-bar-height", "allow-get-sys-fonts-list", + "allow-intercept-keys", ] 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 249a2e77..5fe38855 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 @@ -366,6 +366,18 @@ "const": "deny-install-package", "markdownDescription": "Denies the install_package command without any pre-configured scope." }, + { + "description": "Enables the intercept_keys command without any pre-configured scope.", + "type": "string", + "const": "allow-intercept-keys", + "markdownDescription": "Enables the intercept_keys command without any pre-configured scope." + }, + { + "description": "Denies the intercept_keys command without any pre-configured scope.", + "type": "string", + "const": "deny-intercept-keys", + "markdownDescription": "Denies the intercept_keys command without any pre-configured scope." + }, { "description": "Enables the set_system_ui_visibility command without any pre-configured scope.", "type": "string", @@ -391,10 +403,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`", + "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`", "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`" + "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`" } ] } 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 666cde2b..586841a6 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 @@ -65,3 +65,11 @@ pub(crate) async fn get_sys_fonts_list( ) -> Result { app.native_bridge().get_sys_fonts_list() } + +#[command] +pub(crate) async fn intercept_keys( + app: AppHandle, + payload: InterceptKeysRequest, +) -> Result<()> { + app.native_bridge().intercept_keys(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 33f4e781..de854d57 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 @@ -56,4 +56,8 @@ impl NativeBridge { } Ok(GetSysFontsListResponse { fonts, error: None }) } + + pub fn intercept_keys(&self, _payload: InterceptKeysRequest) -> 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 559fcd58..4a8796f0 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 @@ -45,6 +45,7 @@ pub fn init() -> TauriPlugin { commands::set_system_ui_visibility, commands::get_status_bar_height, commands::get_sys_fonts_list, + commands::intercept_keys, ]) .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 3a8cc0bc..76182959 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 @@ -93,3 +93,11 @@ impl NativeBridge { .map_err(Into::into) } } + +impl NativeBridge { + pub fn intercept_keys(&self, payload: InterceptKeysRequest) -> crate::Result<()> { + self.0 + .run_mobile_plugin("intercept_keys", 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 64b004ce..00046c93 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 @@ -72,3 +72,10 @@ pub struct GetSysFontsListResponse { pub fonts: Vec, pub error: Option, } + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InterceptKeysRequest { + pub volume_keys: Option, + pub back_key: Option, +} diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 46ce02f8..60534b6c 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -6,6 +6,7 @@ import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; import { useClickEvent, useTouchEvent } from '../hooks/useIframeEvents'; +import { usePageFlip } from '../hooks/usePageFlip'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; import { useProgressSync } from '../hooks/useProgressSync'; import { useProgressAutoSave } from '../hooks/useProgressAutoSave'; @@ -138,8 +139,9 @@ const FoliateViewer: React.FC<{ } }; + const { handlePageFlip } = usePageFlip(bookKey, viewRef, containerRef); useTouchEvent(bookKey, viewRef); - const { handleTurnPage } = useClickEvent(bookKey, viewRef, containerRef); + useClickEvent(bookKey, handlePageFlip); useFoliateEvents(viewRef.current, { onLoad: docLoadHandler, @@ -223,7 +225,7 @@ const FoliateViewer: React.FC<{ <>
handleTurnPage(event)} + onClick={(event) => handlePageFlip(event)} ref={containerRef} /> diff --git a/apps/readest-app/src/app/reader/components/FooterBar.tsx b/apps/readest-app/src/app/reader/components/FooterBar.tsx index dfea21c8..602756f6 100644 --- a/apps/readest-app/src/app/reader/components/FooterBar.tsx +++ b/apps/readest-app/src/app/reader/components/FooterBar.tsx @@ -103,7 +103,6 @@ const FooterBar: React.FC = ({ }; const handleSetActionTab = (tab: string) => { - console.log('handleSetActionTab', tab); setActionTab(actionTab === tab ? '' : tab); if (tab === 'tts') { setHoveredBookKey(''); @@ -130,9 +129,10 @@ const FooterBar: React.FC = ({ const isVisible = hoveredBookKey === bookKey; const progressInfo = bookFormat === 'PDF' ? section : pageinfo; const progressValid = !!progressInfo; - const progressFraction = progressValid - ? ((progressInfo!.next ?? progressInfo!.current) + 1) / progressInfo!.total - : 0; + const progressFraction = + progressValid && progressInfo?.total > 0 + ? ((progressInfo!.next ?? progressInfo!.current) + 1) / progressInfo!.total || 0 + : 0; return ( <> diff --git a/apps/readest-app/src/app/reader/components/NotebookToggler.tsx b/apps/readest-app/src/app/reader/components/NotebookToggler.tsx index b2dde4b0..85772c9a 100644 --- a/apps/readest-app/src/app/reader/components/NotebookToggler.tsx +++ b/apps/readest-app/src/app/reader/components/NotebookToggler.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { LuNotebookPen } from 'react-icons/lu'; +import { useEnv } from '@/context/EnvContext'; +import { useReaderStore } from '@/store/readerStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { useNotebookStore } from '@/store/notebookStore'; import { useTranslation } from '@/hooks/useTranslation'; @@ -13,11 +15,16 @@ interface NotebookTogglerProps { const NotebookToggler: React.FC = ({ bookKey }) => { const _ = useTranslation(); + const { appService } = useEnv(); + const { setHoveredBookKey } = useReaderStore(); const { sideBarBookKey, setSideBarBookKey } = useSidebarStore(); const { isNotebookVisible, toggleNotebook } = useNotebookStore(); const iconSize16 = useResponsiveSize(16); const handleToggleSidebar = () => { + if (appService?.isMobile) { + setHoveredBookKey(''); + } if (sideBarBookKey === bookKey) { toggleNotebook(); } 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 aff3e044..7fcb0ce1 100644 --- a/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx +++ b/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx @@ -5,6 +5,7 @@ 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 { saveViewSettings } from '../../utils/viewSettingsHelper'; import { TRANSLATED_LANGS } from '@/services/constants'; @@ -16,12 +17,14 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { const _ = useTranslation(); const { envConfig, appService } = useEnv(); const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore(); + const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore(); const { getView, getViewSettings, setViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey)!; const [animated, setAnimated] = useState(viewSettings.animated!); const [isDisableClick, setIsDisableClick] = useState(viewSettings.disableClick!); const [swapClickArea, setSwapClickArea] = useState(viewSettings.swapClickArea!); + const [volumeKeysToFlip, setVolumeKeysToFlip] = useState(viewSettings.volumeKeysToFlip!); const [isContinuousScroll, setIsContinuousScroll] = useState(viewSettings.continuousScroll!); const [draftStylesheet, setDraftStylesheet] = useState(viewSettings.userStylesheet!); const [draftStylesheetSaved, setDraftStylesheetSaved] = useState(true); @@ -140,6 +143,18 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [swapClickArea]); + useEffect(() => { + saveViewSettings(envConfig, bookKey, 'volumeKeysToFlip', volumeKeysToFlip, false, false); + if (appService?.isMobileApp) { + if (volumeKeysToFlip) { + acquireVolumeKeyInterception(); + } else { + releaseVolumeKeyInterception(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [volumeKeysToFlip]); + useEffect(() => { saveViewSettings(envConfig, bookKey, 'continuousScroll', isContinuousScroll, false, false); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -198,17 +213,28 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => { onChange={() => setIsContinuousScroll(!isContinuousScroll)} />
+ {appService?.isMobileApp && ( +
+ {_('Volume Keys for Page Flip')} + setVolumeKeysToFlip(!volumeKeysToFlip)} + /> +
+ )}
- {_('Disable Click-to-Flip')} + {_('Clicks for Page Flip')} setIsDisableClick(!isDisableClick)} />
- {_('Swap Click-to-Flip Area')} + {_('Swap Clicks Area')} , - containerRef: React.RefObject, -) => { - const { appService } = useEnv(); - const { getViewSettings } = useReaderStore(); - const { hoveredBookKey, setHoveredBookKey } = useReaderStore(); - const handleTurnPage = async ( - msg: MessageEvent | React.MouseEvent, - ) => { - if (msg instanceof MessageEvent) { - if (msg.data && msg.data.bookKey === bookKey) { - const viewSettings = getViewSettings(bookKey)!; - if (msg.data.type === 'iframe-single-click') { - const viewElement = containerRef.current; - if (viewElement) { - const { screenX } = msg.data; - const viewRect = viewElement.getBoundingClientRect(); - let windowStartX; - // Currently for tauri APP the window.screenX is always 0 - if (isTauriAppPlatform()) { - if (appService?.isMobile) { - windowStartX = 0; - } else { - const windowPosition = (await tauriGetWindowLogicalPosition()) as { - x: number; - y: number; - }; - windowStartX = windowPosition.x; - } - } else { - windowStartX = window.screenX; - } - const viewStartX = windowStartX + viewRect.left; - const viewCenterX = viewStartX + viewRect.width / 2; - const consumed = eventDispatcher.dispatchSync('iframe-single-click'); - if (!consumed) { - const centerStartX = viewStartX + viewRect.width * 0.375; - const centerEndX = viewStartX + viewRect.width * 0.625; - if ( - viewSettings.disableClick! || - (screenX >= centerStartX && screenX <= centerEndX) - ) { - // toggle visibility of the header bar and the footer bar - setHoveredBookKey(hoveredBookKey ? null : bookKey); - } else { - if (hoveredBookKey) { - setHoveredBookKey(null); - return; - } - if (!viewSettings.disableClick! && screenX >= viewCenterX) { - if (viewSettings.swapClickArea) { - viewRef.current?.goLeft(); - } else { - viewRef.current?.goRight(); - } - } else if (!viewSettings.disableClick! && screenX < viewCenterX) { - if (viewSettings.swapClickArea) { - viewRef.current?.goRight(); - } else { - viewRef.current?.goLeft(); - } - } - } - } - } - } else if (msg.data.type === 'iframe-wheel' && !viewSettings.scrolled) { - // The wheel event is handled by the iframe itself in scrolled mode. - const { deltaY } = msg.data; - if (deltaY > 0) { - viewRef.current?.next(1); - } else if (deltaY < 0) { - viewRef.current?.prev(1); - } - } else if (msg.data.type === 'iframe-mouseup') { - if (msg.data.button === 3) { - viewRef.current?.history.back(); - } else if (msg.data.button === 4) { - viewRef.current?.history.forward(); - } - } - } - } else { - const { clientX } = msg; - const width = window.innerWidth; - const leftThreshold = width * 0.5; - const rightThreshold = width * 0.5; - if (clientX < leftThreshold) { - viewRef.current?.goLeft(); - } else if (clientX > rightThreshold) { - viewRef.current?.goRight(); - } - } - }; +export const useClickEvent = (bookKey: string, handlePageFlip: (msg: MessageEvent) => void) => { + const { hoveredBookKey } = useReaderStore(); useEffect(() => { - window.addEventListener('message', handleTurnPage); + window.addEventListener('message', handlePageFlip); return () => { - window.removeEventListener('message', handleTurnPage); + window.removeEventListener('message', handlePageFlip); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hoveredBookKey, viewRef]); - - return { - handleTurnPage, - }; + }, [bookKey, hoveredBookKey, handlePageFlip]); }; interface IframeTouch { @@ -213,5 +111,5 @@ export const useTouchEvent = ( window.removeEventListener('message', handleTouch); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hoveredBookKey, viewRef]); + }, [hoveredBookKey, viewRef.current]); }; diff --git a/apps/readest-app/src/app/reader/hooks/usePageFlip.ts b/apps/readest-app/src/app/reader/hooks/usePageFlip.ts new file mode 100644 index 00000000..b8b3fff5 --- /dev/null +++ b/apps/readest-app/src/app/reader/hooks/usePageFlip.ts @@ -0,0 +1,139 @@ +import { useEffect } from 'react'; +import { useEnv } from '@/context/EnvContext'; +import { FoliateView } from '@/types/view'; +import { useReaderStore } from '@/store/readerStore'; +import { useDeviceControlStore } from '@/store/deviceStore'; +import { eventDispatcher } from '@/utils/event'; +import { isTauriAppPlatform } from '@/services/environment'; +import { tauriGetWindowLogicalPosition } from '@/utils/window'; + +export const usePageFlip = ( + bookKey: string, + viewRef: React.MutableRefObject, + containerRef: React.RefObject, +) => { + const { appService } = useEnv(); + const { getViewSettings } = useReaderStore(); + const { hoveredBookKey, setHoveredBookKey } = useReaderStore(); + const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore(); + const handlePageFlip = async ( + msg: MessageEvent | CustomEvent | React.MouseEvent, + ) => { + if (msg instanceof MessageEvent) { + if (msg.data && msg.data.bookKey === bookKey) { + const viewSettings = getViewSettings(bookKey)!; + if (msg.data.type === 'iframe-single-click') { + const viewElement = containerRef.current; + if (viewElement) { + const { screenX } = msg.data; + const viewRect = viewElement.getBoundingClientRect(); + let windowStartX; + // Currently for tauri APP the window.screenX is always 0 + if (isTauriAppPlatform()) { + if (appService?.isMobile) { + windowStartX = 0; + } else { + const windowPosition = (await tauriGetWindowLogicalPosition()) as { + x: number; + y: number; + }; + windowStartX = windowPosition.x; + } + } else { + windowStartX = window.screenX; + } + const viewStartX = windowStartX + viewRect.left; + const viewCenterX = viewStartX + viewRect.width / 2; + const consumed = eventDispatcher.dispatchSync('iframe-single-click'); + if (!consumed) { + const centerStartX = viewStartX + viewRect.width * 0.375; + const centerEndX = viewStartX + viewRect.width * 0.625; + if ( + viewSettings.disableClick! || + (screenX >= centerStartX && screenX <= centerEndX) + ) { + // toggle visibility of the header bar and the footer bar + setHoveredBookKey(hoveredBookKey ? null : bookKey); + } else { + if (hoveredBookKey) { + setHoveredBookKey(null); + return; + } + if (!viewSettings.disableClick! && screenX >= viewCenterX) { + if (viewSettings.swapClickArea) { + viewRef.current?.goLeft(); + } else { + viewRef.current?.goRight(); + } + } else if (!viewSettings.disableClick! && screenX < viewCenterX) { + if (viewSettings.swapClickArea) { + viewRef.current?.goRight(); + } else { + viewRef.current?.goLeft(); + } + } + } + } + } + } else if (msg.data.type === 'iframe-wheel' && !viewSettings.scrolled) { + // The wheel event is handled by the iframe itself in scrolled mode. + const { deltaY } = msg.data; + if (deltaY > 0) { + viewRef.current?.next(1); + } else if (deltaY < 0) { + viewRef.current?.prev(1); + } + } else if (msg.data.type === 'iframe-mouseup') { + if (msg.data.button === 3) { + viewRef.current?.history.back(); + } else if (msg.data.button === 4) { + viewRef.current?.history.forward(); + } + } + } + } else if (msg instanceof CustomEvent) { + const { keyName } = msg.detail; + const viewSettings = getViewSettings(bookKey); + if (viewSettings?.volumeKeysToFlip) { + setHoveredBookKey(''); + if (keyName === 'VolumeUp') { + viewRef.current?.goLeft(); + } else if (keyName === 'VolumeDown') { + viewRef.current?.goRight(); + } + } + } else { + const { clientX } = msg; + const width = window.innerWidth; + const leftThreshold = width * 0.5; + const rightThreshold = width * 0.5; + if (clientX < leftThreshold) { + viewRef.current?.goLeft(); + } else if (clientX > rightThreshold) { + viewRef.current?.goRight(); + } + } + }; + + useEffect(() => { + if (!appService?.isMobileApp) return; + + const viewSettings = getViewSettings(bookKey); + if (viewSettings?.volumeKeysToFlip) { + acquireVolumeKeyInterception(); + } else { + releaseVolumeKeyInterception(); + } + + eventDispatcher.on('native-key-down', handlePageFlip); + return () => { + releaseVolumeKeyInterception(); + eventDispatcher.off('native-key-down', handlePageFlip); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + handlePageFlip, + }; +}; diff --git a/apps/readest-app/src/context/EnvContext.tsx b/apps/readest-app/src/context/EnvContext.tsx index 16663775..aeaa4ae1 100644 --- a/apps/readest-app/src/context/EnvContext.tsx +++ b/apps/readest-app/src/context/EnvContext.tsx @@ -18,6 +18,14 @@ export const EnvProvider = ({ children }: { children: ReactNode }) => { React.useEffect(() => { envConfig.getAppService().then((service) => setAppService(service)); + window.addEventListener('error', (e) => { + if (e.message === 'ResizeObserver loop limit exceeded') { + e.stopImmediatePropagation(); + e.preventDefault(); + return true; + } + return false; + }); }, [envConfig]); return {children}; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 6c77712f..7c00c974 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -51,6 +51,7 @@ export abstract class BaseAppService implements AppService { isAppDataSandbox = false; isAndroidApp = false; isIOSApp = false; + isMobileApp = false; hasTrafficLight = false; hasWindow = false; hasWindowBar = false; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 5aa6fb62..d53077cb 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -86,6 +86,7 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = { scrolled: false, disableClick: false, swapClickArea: false, + volumeKeysToFlip: false, continuousScroll: false, maxColumnCount: 2, maxInlineSize: getDefaultMaxInlineSize(), diff --git a/apps/readest-app/src/services/nativeAppService.ts b/apps/readest-app/src/services/nativeAppService.ts index f6dd39ea..6529f58b 100644 --- a/apps/readest-app/src/services/nativeAppService.ts +++ b/apps/readest-app/src/services/nativeAppService.ts @@ -213,6 +213,7 @@ export class NativeAppService extends BaseAppService { override isAndroidApp = OS_TYPE === 'android'; override isIOSApp = OS_TYPE === 'ios'; override isMacOSApp = OS_TYPE === 'macos'; + override isMobileApp = ['android', 'ios'].includes(OS_TYPE); override hasTrafficLight = OS_TYPE === 'macos'; override hasWindow = !(OS_TYPE === 'ios' || OS_TYPE === 'android'); override hasWindowBar = !(OS_TYPE === 'ios' || OS_TYPE === 'android'); diff --git a/apps/readest-app/src/store/deviceStore.ts b/apps/readest-app/src/store/deviceStore.ts new file mode 100644 index 00000000..3ed5bd18 --- /dev/null +++ b/apps/readest-app/src/store/deviceStore.ts @@ -0,0 +1,55 @@ +import { create } from 'zustand'; +import { interceptKeys } from '@/utils/bridge'; +import { eventDispatcher } from '@/utils/event'; + +declare global { + interface Window { + onNativeKeyDown?: (keyName: string) => void; + } +} + +const handleNativeKeyDown = (keyName: string) => { + if (keyName === 'VolumeUp' || keyName === 'VolumeDown') { + return eventDispatcher.dispatch('native-key-down', { keyName }); + } + if (keyName === 'Back') { + return eventDispatcher.dispatch('native-key-down', { keyName }); + } + return false; +}; + +type DeviceControlState = { + volumeKeysIntercepted: boolean; + backKeyIntercepted: boolean; + acquireVolumeKeyInterception: () => void; + releaseVolumeKeyInterception: () => void; + acquireBackKeyInterception: () => void; + releaseBackKeyInterception: () => void; +}; + +export const useDeviceControlStore = create((set) => ({ + volumeKeysIntercepted: false, + backKeyIntercepted: false, + + acquireVolumeKeyInterception: () => { + window.onNativeKeyDown = handleNativeKeyDown; + interceptKeys({ volumeKeys: true }); + set({ volumeKeysIntercepted: true }); + }, + + releaseVolumeKeyInterception: () => { + interceptKeys({ volumeKeys: false }); + set({ volumeKeysIntercepted: false }); + }, + + acquireBackKeyInterception: () => { + window.onNativeKeyDown = handleNativeKeyDown; + interceptKeys({ backKey: true }); + set({ backKeyIntercepted: true }); + }, + + releaseBackKeyInterception: () => { + interceptKeys({ backKey: false }); + set({ backKeyIntercepted: false }); + }, +})); diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index d616fbd8..33124ad7 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -71,6 +71,7 @@ export interface BookLayout { scrolled: boolean; disableClick: boolean; swapClickArea: boolean; + volumeKeysToFlip: boolean; continuousScroll: boolean; maxColumnCount: number; maxInlineSize: number; diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index ace4c7d9..ebf80a84 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -36,6 +36,7 @@ export interface AppService { hasUpdater: boolean; isMobile: boolean; isAppDataSandbox: boolean; + isMobileApp: boolean; isAndroidApp: boolean; isIOSApp: boolean; isMacOSApp: boolean; diff --git a/apps/readest-app/src/utils/bridge.ts b/apps/readest-app/src/utils/bridge.ts index 1892d702..1a9b49e3 100644 --- a/apps/readest-app/src/utils/bridge.ts +++ b/apps/readest-app/src/utils/bridge.ts @@ -43,6 +43,11 @@ export interface GetSystemFontsListResponse { error?: string; } +export interface InterceptKeysRequest { + volumeKeys?: boolean; + backKey?: boolean; +} + export async function copyURIToPath(request: CopyURIRequest): Promise { const result = await invoke('plugin:native-bridge|copy_uri_to_path', { payload: request, @@ -97,3 +102,9 @@ export async function getSysFontsList(): Promise { cachedSysFontsResult = result; return result; } + +export async function interceptKeys(request: InterceptKeysRequest): Promise { + await invoke('plugin:native-bridge|intercept_keys', { + payload: request, + }); +}