feat: select directory to save last book cover image (#2521)
This commit is contained in:
@@ -686,5 +686,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "تقليدي (هونغ كونغ) → مبسّط",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "تقليدي (تايوان • عبارات) → مبسّط",
|
||||
"Convert Simplified and Traditional Chinese": "تحويل المبسّط/التقليدي",
|
||||
"Convert Mode": "وضع التحويل"
|
||||
"Convert Mode": "وضع التحويل",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "فشل في الحفظ التلقائي لغلاف الكتاب لشاشة القفل: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "প্রথাগত (হংকং) → সরল",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "প্রথাগত (তাইওয়ান • বাক্য) → সরল",
|
||||
"Convert Simplified and Traditional Chinese": "সরল/প্রথাগত রূপান্তর",
|
||||
"Convert Mode": "রূপান্তর মোড"
|
||||
"Convert Mode": "রূপান্তর মোড",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "লক স্ক্রিনের জন্য বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করতে ব্যর্থ: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "སྲོལ་རྒྱུན (ཧོང་ཀོང) → སྟོང་བཟོས",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "སྲོལ་རྒྱུན (ཏའི་ཝན • ཚིག་ཕྲེང) → སྟོང་བཟོས",
|
||||
"Convert Simplified and Traditional Chinese": "རྒྱ་ཡིག སྟོང་བཟོས/སྲོལ་རྒྱུན བསྒྱུར་བ",
|
||||
"Convert Mode": "བསྒྱུར་བའི་རྣམ་པ"
|
||||
"Convert Mode": "བསྒྱུར་བའི་རྣམ་པ",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "སྒྲོམ་སྒོར་དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Traditionell (Hongkong) → Vereinfacht",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Traditionell (Taiwan • Phrasen) → Vereinfacht",
|
||||
"Convert Simplified and Traditional Chinese": "Vereinfach./Traditionell konvertieren",
|
||||
"Convert Mode": "Konvertierungsmodus"
|
||||
"Convert Mode": "Konvertierungsmodus",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Automatisches Speichern des Buchcovers für den Sperrbildschirm fehlgeschlagen: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Παραδ. (Χονγκ Κονγκ) → Απλοπ.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Παραδ. (Ταϊβάν • φράσεις) → Απλοπ.",
|
||||
"Convert Simplified and Traditional Chinese": "Μετατροπή Απλοπ./Παραδ. Κινέζικων",
|
||||
"Convert Mode": "Λειτουργία μετατροπής"
|
||||
"Convert Mode": "Λειτουργία μετατροπής",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Αποτυχία αυτόματης αποθήκευσης εξωφύλλου βιβλίου για την οθόνη κλειδώματος: {{error}}"
|
||||
}
|
||||
|
||||
@@ -667,12 +667,13 @@
|
||||
"No Conversion": "Sin conversión",
|
||||
"Simplified to Traditional": "Simpl. → Trad.",
|
||||
"Traditional to Simplified": "Trad. → Simpl.",
|
||||
"Simplified to Traditional (Taiwán)": "Simpl. → Trad. (Taiwán)",
|
||||
"Simplified to Traditional (Taiwan)": "Simpl. → Trad. (Taiwan)",
|
||||
"Simplified to Traditional (Hong Kong)": "Simpl. → Trad. (Hong Kong)",
|
||||
"Simplified to Traditional (Taiwán), with phrases": "Simpl. → Trad. (Taiwán • frases)",
|
||||
"Traditional (Taiwán) to Simplified": "Trad. (Taiwán) → Simpl.",
|
||||
"Simplified to Traditional (Taiwan), with phrases": "Simpl. → Trad. (Taiwan • frases)",
|
||||
"Traditional (Taiwan) to Simplified": "Trad. (Taiwan) → Simpl.",
|
||||
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simpl.",
|
||||
"Traditional (Taiwán) to Simplified, with phrases": "Trad. (Taiwán • frases) → Simpl.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taiwan • frases) → Simpl.",
|
||||
"Convert Simplified and Traditional Chinese": "Convertir chino Simpl./Trad.",
|
||||
"Convert Mode": "Modo de conversión"
|
||||
"Convert Mode": "Modo de conversión",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Error al guardar automáticamente la portada del libro para la pantalla de bloqueo: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "سنتی (هنگکنگ) → ساده",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "سنتی (تایوان • عبارات) → ساده",
|
||||
"Convert Simplified and Traditional Chinese": "تبدیل چینی ساده/سنتی",
|
||||
"Convert Mode": "حالت تبدیل"
|
||||
"Convert Mode": "حالت تبدیل",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "ذخیرهی خودکار جلد کتاب برای صفحهی قفل ناموفق بود: {{error}}"
|
||||
}
|
||||
|
||||
@@ -674,5 +674,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Trad. (Hong Kong) → Simp.",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Trad. (Taïwan • phrases) → Simp.",
|
||||
"Convert Simplified and Traditional Chinese": "Convertir chinois simp./trad.",
|
||||
"Convert Mode": "Mode de conversion"
|
||||
"Convert Mode": "Mode de conversion",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Échec de l'enregistrement automatique de la couverture du livre pour l'écran de verrouillage : {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "पारंपरिक (हांगकांग) → सरलीकृत",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "पारंपरिक (ताइवान • वाक्यांश) → सरलीकृत",
|
||||
"Convert Simplified and Traditional Chinese": "सरलीकृत/पारंपरिक चीनी रूपांतरण",
|
||||
"Convert Mode": "रूपांतरण मोड"
|
||||
"Convert Mode": "रूपांतरण मोड",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "लॉक स्क्रीन के लिए पुस्तक कवर स्वचालित रूप से सहेजने में विफल: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Tradisional (Hong Kong) → Sederhana",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Tradisional (Taiwan • frasa) → Sederhana",
|
||||
"Convert Simplified and Traditional Chinese": "Konversi Tionghoa Sederhana/Tradisional",
|
||||
"Convert Mode": "Mode Konversi"
|
||||
"Convert Mode": "Mode Konversi",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Gagal menyimpan otomatis sampul buku untuk layar kunci: {{error}}"
|
||||
}
|
||||
|
||||
@@ -674,5 +674,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Tradizionale (Hong Kong) → Semplificato",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Tradizionale (Taiwan • frasi) → Semplificato",
|
||||
"Convert Simplified and Traditional Chinese": "Converti Cinese Semplificato/Tradizionale",
|
||||
"Convert Mode": "Modalità conversione"
|
||||
"Convert Mode": "Modalità conversione",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Impossibile salvare automaticamente la copertina del libro per la schermata di blocco: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "繁体字(香港) → 簡体字",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "繁体字(台湾) → 簡体字 • フレーズ",
|
||||
"Convert Simplified and Traditional Chinese": "簡体字/繁体字変換",
|
||||
"Convert Mode": "変換モード"
|
||||
"Convert Mode": "変換モード",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "ロック画面の本のカバーの自動保存に失敗しました:{{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "번체 (홍콩) → 간체",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "번체 (대만) → 간체 • 문구",
|
||||
"Convert Simplified and Traditional Chinese": "간체/번체 변환",
|
||||
"Convert Mode": "변환 모드"
|
||||
"Convert Mode": "변환 모드",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "잠금 화면용 책 표지 자동 저장 실패: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Traditioneel (Hong Kong) → Vereenvoudigd",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Traditioneel (Taiwan • zinnen) → Vereenvoudigd",
|
||||
"Convert Simplified and Traditional Chinese": "Converteer Vereenvoudigd/Traditioneel Chinees",
|
||||
"Convert Mode": "Conversiemodus"
|
||||
"Convert Mode": "Conversiemodus",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Automatisch opslaan van boekomslag voor vergrendelscherm mislukt: {{error}}"
|
||||
}
|
||||
|
||||
@@ -678,5 +678,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Tradycyjny (Hongkong) → Uproszczony",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Tradycyjny (Tajwan • frazy) → Uproszczony",
|
||||
"Convert Simplified and Traditional Chinese": "Konwertuj chiński uproszczony/tradycyjny",
|
||||
"Convert Mode": "Tryb konwersji"
|
||||
"Convert Mode": "Tryb konwersji",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Nie udało się automatycznie zapisać okładki książki dla ekranu blokady: {{error}}"
|
||||
}
|
||||
|
||||
@@ -674,5 +674,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Tradicional (Hong Kong) → Simplificado",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Tradicional (Taiwan • frases) → Simplificado",
|
||||
"Convert Simplified and Traditional Chinese": "Converter Chinês Simpl./Trad.",
|
||||
"Convert Mode": "Modo de conversão"
|
||||
"Convert Mode": "Modo de conversão",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Falha ao salvar automaticamente a capa do livro para a tela de bloqueio: {{error}}"
|
||||
}
|
||||
|
||||
@@ -678,5 +678,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Традиционный (Гонконг) → Упрощённый",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Традиционный (Тайвань • фразы) → Упрощённый",
|
||||
"Convert Simplified and Traditional Chinese": "Преобразовать упрощённый/традиционный китайский",
|
||||
"Convert Mode": "Режим преобразования"
|
||||
"Convert Mode": "Режим преобразования",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Не удалось автоматически сохранить обложку книги для экрана блокировки: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "සාම්ප්රදායික (හොං කොං) → සරල",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "සාම්ප්රදායික (තායිවාන • වාක්ය) → සරල",
|
||||
"Convert Simplified and Traditional Chinese": "සරල/සාම්ප්රදායික චීන පරිවර්තනය",
|
||||
"Convert Mode": "පරිවර්තන මෝඩියුලය"
|
||||
"Convert Mode": "පරිවර්තන මෝඩියුලය",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "අගුළු තිරය සඳහා පොත් ආවරණය ස්වයංක්රීයව සුරකිීමට අසමත් විය: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Traditionell (Hongkong) → Förenklad",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Traditionell (Taiwan • fraser) → Förenklad",
|
||||
"Convert Simplified and Traditional Chinese": "Konvertera förenklad/traditionell kinesiska",
|
||||
"Convert Mode": "Konverteringsläge"
|
||||
"Convert Mode": "Konverteringsläge",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Kunde inte autospara bokomslag för låsskärm: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "பாரம்பரிய (ஹாங்காங்) → எளிய",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "பாரம்பரிய (தைவான் • வசனங்கள்) → எளிய",
|
||||
"Convert Simplified and Traditional Chinese": "எளிய/பாரம்பரிய சீன மொழி மாற்றம்",
|
||||
"Convert Mode": "மாற்ற முறை"
|
||||
"Convert Mode": "மாற்ற முறை",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "பூட்டு திரைக்கான புத்தகக் கோப்பை தானாகச் சேமிக்க முடியவில்லை: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "ตัวเต็ม (ฮ่องกง) → ตัวย่อ",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "ตัวเต็ม (ไต้หวัน • วลี) → ตัวย่อ",
|
||||
"Convert Simplified and Traditional Chinese": "แปลงจีนตัวย่อ/ตัวเต็ม",
|
||||
"Convert Mode": "โหมดแปลงค่า"
|
||||
"Convert Mode": "โหมดแปลงค่า",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "ไม่สามารถบันทึกปกหนังสือสำหรับหน้าจอล็อกโดยอัตโนมัติ: {{error}}"
|
||||
}
|
||||
|
||||
@@ -670,5 +670,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Geleneksel (Hong Kong) → Basitleştirilmiş",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Geleneksel (Tayvan • ifadeler) → Basitleştirilmiş",
|
||||
"Convert Simplified and Traditional Chinese": "Basitleştirilmiş/Geleneksel Çince Dönüştür",
|
||||
"Convert Mode": "Dönüştürme Modu"
|
||||
"Convert Mode": "Dönüştürme Modu",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Kitap kapağı kilit ekranı için otomatik kaydedilemedi: {{error}}"
|
||||
}
|
||||
|
||||
@@ -678,5 +678,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Традиційне (Гонконг) → Спрощене",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Традиційне (Тайвань • фрази) → Спрощене",
|
||||
"Convert Simplified and Traditional Chinese": "Конвертувати спрощену/традиційну китайську",
|
||||
"Convert Mode": "Режим конвертації"
|
||||
"Convert Mode": "Режим конвертації",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Не вдалося автоматично зберегти обкладинку книги для екрана блокування: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "Phồn thể (Hồng Kông) → Giản thể",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "Phồn thể (Đài Loan • cụm từ) → Giản thể",
|
||||
"Convert Simplified and Traditional Chinese": "Chuyển đổi Trung giản/Phồn thể",
|
||||
"Convert Mode": "Chế độ chuyển đổi"
|
||||
"Convert Mode": "Chế độ chuyển đổi",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "Không thể tự động lưu bìa sách cho màn hình khóa: {{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "繁体(香港) → 简体",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "繁体(台湾) → 简体习惯用语",
|
||||
"Convert Simplified and Traditional Chinese": "简繁中文转换",
|
||||
"Convert Mode": "转换模式"
|
||||
"Convert Mode": "转换模式",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "自动保存锁屏书籍封面失败:{{error}}"
|
||||
}
|
||||
|
||||
@@ -666,5 +666,6 @@
|
||||
"Traditional (Hong Kong) to Simplified": "繁體(香港) → 簡體",
|
||||
"Traditional (Taiwan) to Simplified, with phrases": "繁體(台灣) → 簡體慣用語",
|
||||
"Convert Simplified and Traditional Chinese": "簡繁中文轉換",
|
||||
"Convert Mode": "轉換模式"
|
||||
"Convert Mode": "轉換模式",
|
||||
"Failed to auto-save book cover for lock screen: {{error}}": "自動保存鎖定螢幕書籍封面失敗:{{error}}"
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
},
|
||||
{
|
||||
"path": "**/Readest/**/*"
|
||||
},
|
||||
{
|
||||
"path": "**/last-book-cover.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+8
@@ -6,6 +6,7 @@ import androidx.activity.enableEdgeToEdge
|
||||
import android.view.KeyEvent
|
||||
import android.webkit.WebView
|
||||
import android.util.Log
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.app.ActivityManager
|
||||
import android.content.res.Configuration
|
||||
@@ -13,6 +14,7 @@ import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import com.readest.native_bridge.KeyDownInterceptor
|
||||
import com.readest.native_bridge.NativeBridgePlugin
|
||||
|
||||
class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
private lateinit var wv: WebView
|
||||
@@ -92,4 +94,10 @@ class MainActivity : TauriActivity(), KeyDownInterceptor {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
NativeBridgePlugin.getInstance()?.handleActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
+92
-1
@@ -8,6 +8,7 @@ import android.util.Log
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.provider.DocumentsContract
|
||||
import android.view.View
|
||||
import android.view.KeyEvent
|
||||
import android.view.WindowInsets
|
||||
@@ -25,6 +26,8 @@ import androidx.core.content.FileProvider
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.InvokeArg
|
||||
@@ -128,11 +131,16 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
var pendingInvoke: Invoke? = null
|
||||
private const val REQUEST_MANAGE_STORAGE = 1001
|
||||
private const val FOLDER_PICKER_REQUEST_CODE = 1002
|
||||
var pendingInvoke: Invoke? = null
|
||||
var pendingFolderPickerInvoke: Invoke? = null
|
||||
private var instance: NativeBridgePlugin? = null
|
||||
fun getInstance(): NativeBridgePlugin? = instance
|
||||
}
|
||||
|
||||
override fun load(webView: WebView) {
|
||||
instance = this
|
||||
super.load(webView)
|
||||
handleIntent(activity.intent)
|
||||
}
|
||||
@@ -682,4 +690,87 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
invoke.reject("Failed to open URL: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
fun select_directory(invoke: Invoke) {
|
||||
pendingFolderPickerInvoke = invoke
|
||||
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
activity.startActivityForResult(intent, FOLDER_PICKER_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
val result = JSObject()
|
||||
result.put("cancelled", true)
|
||||
result.put("uri", null)
|
||||
result.put("path", null)
|
||||
result.put("error", e.message)
|
||||
invoke.resolve(result)
|
||||
pendingFolderPickerInvoke = null
|
||||
}
|
||||
}
|
||||
|
||||
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == FOLDER_PICKER_REQUEST_CODE) {
|
||||
val invoke = pendingFolderPickerInvoke
|
||||
if (invoke != null) {
|
||||
handleDirectorySelected(data?.data, invoke)
|
||||
pendingFolderPickerInvoke = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDirectorySelected(uri: Uri?, invoke: Invoke) {
|
||||
val result = JSObject()
|
||||
if (uri == null) {
|
||||
result.put("cancelled", true)
|
||||
result.put("uri", null)
|
||||
result.put("path", null)
|
||||
} else {
|
||||
try {
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
result.put("cancelled", false)
|
||||
result.put("uri", uri.toString())
|
||||
result.put("path", extractPathFromUri(uri))
|
||||
} catch (e: SecurityException) {
|
||||
result.put("cancelled", true)
|
||||
result.put("uri", uri.toString())
|
||||
result.put("path", extractPathFromUri(uri))
|
||||
result.put("error", "Permission error: ${e.message}")
|
||||
} catch (e: Exception) {
|
||||
result.put("cancelled", true)
|
||||
result.put("uri", null)
|
||||
result.put("path", null)
|
||||
result.put("error", "Error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
invoke.resolve(result)
|
||||
pendingInvoke = null
|
||||
}
|
||||
|
||||
private fun extractPathFromUri(uri: Uri): String? {
|
||||
val path = uri.path ?: return null
|
||||
return try {
|
||||
when {
|
||||
DocumentsContract.isTreeUri(uri) -> {
|
||||
val treeDocId = DocumentsContract.getTreeDocumentId(uri)
|
||||
val split = treeDocId.split(":")
|
||||
if (split[0].equals("primary", ignoreCase = true)) {
|
||||
if (split.size > 1) {
|
||||
Environment.getExternalStorageDirectory().path + "/" + split[1]
|
||||
} else {
|
||||
Environment.getExternalStorageDirectory().path
|
||||
}
|
||||
} else {
|
||||
"/storage/${split[0]}/" + (if (split.size > 1) split[1] else "")
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const COMMANDS: &[&str] = &[
|
||||
"set_screen_brightness",
|
||||
"get_external_sdcard_path",
|
||||
"open_external_url",
|
||||
"select_directory",
|
||||
"request_manage_storage_permission",
|
||||
"check_permissions",
|
||||
"request_permissions",
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-select-directory"
|
||||
description = "Enables the select_directory command without any pre-configured scope."
|
||||
commands.allow = ["select_directory"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-select-directory"
|
||||
description = "Denies the select_directory command without any pre-configured scope."
|
||||
commands.deny = ["select_directory"]
|
||||
+27
@@ -24,6 +24,7 @@ Default permissions for the plugin
|
||||
- `allow-set-screen-brightness`
|
||||
- `allow-get-external-sdcard-path`
|
||||
- `allow-open-external-url`
|
||||
- `allow-select-directory`
|
||||
- `allow-request-manage-storage-permission`
|
||||
- `allow-check-permissions`
|
||||
- `allow-request-permissions`
|
||||
@@ -666,6 +667,32 @@ Denies the request_permissions command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-select-directory`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the select_directory command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-select-directory`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the select_directory command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-set-screen-brightness`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -21,6 +21,7 @@ permissions = [
|
||||
"allow-set-screen-brightness",
|
||||
"allow-get-external-sdcard-path",
|
||||
"allow-open-external-url",
|
||||
"allow-select-directory",
|
||||
"allow-request-manage-storage-permission",
|
||||
"allow-check-permissions",
|
||||
"allow-request-permissions",
|
||||
|
||||
+14
-2
@@ -582,6 +582,18 @@
|
||||
"const": "deny-request-permissions",
|
||||
"markdownDescription": "Denies the request_permissions command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the select_directory command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-select-directory",
|
||||
"markdownDescription": "Enables the select_directory command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the select_directory command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-select-directory",
|
||||
"markdownDescription": "Denies the select_directory command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_screen_brightness command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -619,10 +631,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`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
|
||||
"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`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
|
||||
"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`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
|
||||
"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`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-get-screen-brightness`\n- `allow-set-screen-brightness`\n- `allow-get-external-sdcard-path`\n- `allow-open-external-url`\n- `allow-select-directory`\n- `allow-request-manage-storage-permission`\n- `allow-check-permissions`\n- `allow-request-permissions`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -157,6 +157,13 @@ pub(crate) async fn open_external_url<R: Runtime>(
|
||||
app.native_bridge().open_external_url(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn select_directory<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<SelectDirectoryResponse> {
|
||||
app.native_bridge().select_directory()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn request_manage_storage_permission<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
|
||||
@@ -129,6 +129,10 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn select_directory(&self) -> crate::Result<SelectDirectoryResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn request_manage_storage_permission(
|
||||
&self,
|
||||
) -> crate::Result<RequestManageStoragePermissionResponse> {
|
||||
|
||||
@@ -57,6 +57,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::set_screen_brightness,
|
||||
commands::get_external_sdcard_path,
|
||||
commands::open_external_url,
|
||||
commands::select_directory,
|
||||
commands::request_manage_storage_permission,
|
||||
])
|
||||
.setup(|app, api| {
|
||||
|
||||
@@ -208,6 +208,14 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn select_directory(&self) -> crate::Result<SelectDirectoryResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("select_directory", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn request_manage_storage_permission(
|
||||
&self,
|
||||
|
||||
@@ -215,3 +215,12 @@ pub struct OpenExternalUrlResponse {
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SelectDirectoryResponse {
|
||||
pub cancelled: Option<bool>,
|
||||
pub uri: Option<String>,
|
||||
pub path: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { optInTelemetry, optOutTelemetry } from '@/utils/telemetry';
|
||||
import { setAboutDialogVisible } from '@/components/AboutWindow';
|
||||
import { setMigrateDataDirDialogVisible } from '@/app/library/components/MigrateDataWindow';
|
||||
import { saveSysSettings } from '@/helpers/settings';
|
||||
import { selectDirectory } from '@/utils/bridge';
|
||||
import UserAvatar from '@/components/UserAvatar';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
import Quota from '@/components/Quota';
|
||||
@@ -32,6 +33,7 @@ interface SettingsMenuProps {
|
||||
|
||||
interface Permissions {
|
||||
postNotification: PermissionState;
|
||||
manageStorage: PermissionState;
|
||||
}
|
||||
|
||||
const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
@@ -172,8 +174,22 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
setSettingsDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleSetSavedBookCoverForLockScreen = () => {
|
||||
const handleSetSavedBookCoverForLockScreen = async () => {
|
||||
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
|
||||
if (permission.manageStorage !== 'granted') {
|
||||
permission = await invoke<Permissions>(
|
||||
'plugin:native-bridge|request_manage_storage_permission',
|
||||
);
|
||||
}
|
||||
if (permission.manageStorage !== 'granted' && appService?.distChannel === 'readest') return;
|
||||
|
||||
const newValue = settings.savedBookCoverForLockScreen ? '' : 'default';
|
||||
if (newValue) {
|
||||
const response = await selectDirectory();
|
||||
if (response.path) {
|
||||
saveSysSettings(envConfig, 'savedBookCoverForLockScreenPath', response.path);
|
||||
}
|
||||
}
|
||||
saveSysSettings(envConfig, 'savedBookCoverForLockScreen', newValue);
|
||||
setSavedBookCoverForLockScreen(newValue);
|
||||
};
|
||||
@@ -205,6 +221,10 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
? _('Light Mode')
|
||||
: _('Auto Mode');
|
||||
|
||||
const savedBookCoverPath = settings.savedBookCoverForLockScreenPath;
|
||||
const coverDir = savedBookCoverPath ? savedBookCoverPath.split('/').pop() : 'Images';
|
||||
const savedBookCoverDescription = `💾 ${coverDir}/last-book-cover.png`;
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={clsx(
|
||||
@@ -317,7 +337,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
<MenuItem
|
||||
label={_('Save Book Cover')}
|
||||
tooltip={_('Auto-save last book cover')}
|
||||
description={savedBookCoverForLockScreen ? '💾 Images/last-book-cover.png' : ''}
|
||||
description={savedBookCoverForLockScreen ? savedBookCoverDescription : ''}
|
||||
toggled={!!savedBookCoverForLockScreen}
|
||||
onClick={handleSetSavedBookCoverForLockScreen}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { throttle } from '@/utils/throttle';
|
||||
import { getCoverFilename } from '@/utils/book';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
|
||||
export const useBookCoverAutoSave = (bookKey: string) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig, appService } = useEnv();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -17,15 +20,31 @@ export const useBookCoverAutoSave = (bookKey: string) => {
|
||||
const bookData = useBookDataStore.getState().getBookData(bookKey);
|
||||
const book = bookData?.book;
|
||||
const savedBookHash = settings.savedBookCoverForLockScreen;
|
||||
const savedCoverPath = settings.savedBookCoverForLockScreenPath;
|
||||
if (appService && book && savedBookHash && savedBookHash !== book?.hash) {
|
||||
const coverPath = await appService.resolveFilePath(getCoverFilename(book), 'Books');
|
||||
try {
|
||||
await appService.copyFile(coverPath, 'last-book-cover.png', 'Images');
|
||||
const lastCoverFilename = 'last-book-cover.png';
|
||||
const builtinImagesPath = await appService.resolveFilePath('', 'Images');
|
||||
if (!savedCoverPath || savedCoverPath === builtinImagesPath) {
|
||||
await appService.copyFile(coverPath, lastCoverFilename, 'Images');
|
||||
} else {
|
||||
await appService.copyFile(
|
||||
coverPath,
|
||||
`${savedCoverPath}/${lastCoverFilename}`,
|
||||
'None',
|
||||
);
|
||||
}
|
||||
settings.savedBookCoverForLockScreen = book.hash;
|
||||
useSettingsStore.getState().setSettings(settings);
|
||||
useSettingsStore.getState().saveSettings(envConfig, settings);
|
||||
} catch (error) {
|
||||
console.error('Failed to auto-save book cover for lock screen:', error);
|
||||
eventDispatcher.dispatch('toast', {
|
||||
type: 'error',
|
||||
message: _('Failed to auto-save book cover for lock screen: {{error}}', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
@@ -224,8 +224,12 @@ export const nativeFileSystem: FileSystem = {
|
||||
}
|
||||
},
|
||||
async copyFile(srcPath: string, dstPath: string, base: BaseDir) {
|
||||
if (!(await this.exists(getDirPath(dstPath), base))) {
|
||||
await this.createDir(getDirPath(dstPath), base, true);
|
||||
try {
|
||||
if (!(await this.exists(getDirPath(dstPath), base))) {
|
||||
await this.createDir(getDirPath(dstPath), base, true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Failed to create directory for copying file:', error);
|
||||
}
|
||||
if (isContentURI(srcPath)) {
|
||||
const prefix = await this.getPrefix(base);
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface SystemSettings {
|
||||
lastOpenBooks: string[];
|
||||
autoImportBooksOnOpen: boolean;
|
||||
savedBookCoverForLockScreen: string;
|
||||
savedBookCoverForLockScreenPath: string;
|
||||
telemetryEnabled: boolean;
|
||||
libraryViewMode: LibraryViewModeType;
|
||||
librarySortBy: LibrarySortByType;
|
||||
|
||||
@@ -84,6 +84,13 @@ interface GetExternalSDCardPathResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface SelectDirectoryResponse {
|
||||
cancelled?: boolean;
|
||||
uri?: string;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function copyURIToPath(request: CopyURIRequest): Promise<CopyURIResponse> {
|
||||
const result = await invoke<CopyURIResponse>('plugin:native-bridge|copy_uri_to_path', {
|
||||
payload: request,
|
||||
@@ -190,3 +197,8 @@ export async function getExternalSDCardPath(): Promise<GetExternalSDCardPathResp
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function selectDirectory(): Promise<SelectDirectoryResponse> {
|
||||
const result = await invoke<SelectDirectoryResponse>('plugin:native-bridge|select_directory');
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user