This commit is contained in:
@@ -615,5 +615,6 @@
|
||||
"File count: {{size}}": "عدد الملفات: {{size}}",
|
||||
"Background Read Aloud": "قراءة بصوت عالٍ في الخلفية",
|
||||
"Ready to read aloud": "جاهز للقراءة بصوت عالٍ",
|
||||
"Read Aloud": "القراءة بصوت عالٍ"
|
||||
"Read Aloud": "القراءة بصوت عالٍ",
|
||||
"Screen Brightness": "سطوع الشاشة"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "ফাইলের সংখ্যা: {{size}}",
|
||||
"Background Read Aloud": "পটভূমিতে উচ্চস্বরে পড়ুন",
|
||||
"Ready to read aloud": "উচ্চস্বরে পড়ার জন্য প্রস্তুত",
|
||||
"Read Aloud": "উচ্চস্বরে পড়ুন"
|
||||
"Read Aloud": "উচ্চস্বরে পড়ুন",
|
||||
"Screen Brightness": "স্ক্রিন উজ্জ্বলতা"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "དེབ་གནས་བཅས་པའི་ཨང་། {{size}}",
|
||||
"Background Read Aloud": "གནས་སྟངས་ཀྱིས་ཀློག་བྱེད།",
|
||||
"Ready to read aloud": "དེབ་ཀྱི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Read Aloud": "ཀློག་བྱེད།"
|
||||
"Read Aloud": "ཀློག་བྱེད།",
|
||||
"Screen Brightness": "རྒྱབ་སྐོར་གྱི་འོད་ཟེར།"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "Dateianzahl: {{size}}",
|
||||
"Background Read Aloud": "Hintergrund-Vorlesen",
|
||||
"Ready to read aloud": "Bereit zum Vorlesen",
|
||||
"Read Aloud": "Vorlesen"
|
||||
"Read Aloud": "Vorlesen",
|
||||
"Screen Brightness": "Bildschirmhelligkeit"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "Αριθμός αρχείων: {{size}}",
|
||||
"Background Read Aloud": "Ανάγνωση στο παρασκήνιο",
|
||||
"Ready to read aloud": "Έτοιμο για ανάγνωση",
|
||||
"Read Aloud": "Ανάγνωση"
|
||||
"Read Aloud": "Ανάγνωση",
|
||||
"Screen Brightness": "Φωτεινότητα οθόνης"
|
||||
}
|
||||
|
||||
@@ -603,5 +603,6 @@
|
||||
"File count: {{size}}": "Número de archivos: {{size}}",
|
||||
"Background Read Aloud": "Lectura en segundo plano",
|
||||
"Ready to read aloud": "Listo para leer en voz alta",
|
||||
"Read Aloud": "Leer en voz alta"
|
||||
"Read Aloud": "Leer en voz alta",
|
||||
"Screen Brightness": "Brillo de pantalla"
|
||||
}
|
||||
|
||||
@@ -603,5 +603,6 @@
|
||||
"File count: {{size}}": "Nombre de fichiers : {{size}}",
|
||||
"Background Read Aloud": "Lecture Vocale en Arrière-Plan",
|
||||
"Ready to read aloud": "Prêt à lire à haute voix",
|
||||
"Read Aloud": "Lire à haute voix"
|
||||
"Read Aloud": "Lire à haute voix",
|
||||
"Screen Brightness": "Luminosité de l'Écran"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "फ़ाइलों की संख्या: {{size}}",
|
||||
"Background Read Aloud": "पृष्ठभूमि में वाचन",
|
||||
"Ready to read aloud": "उच्च स्वर में पढ़ने के लिए तैयार",
|
||||
"Read Aloud": "उच्च स्वर में पढ़ें"
|
||||
"Read Aloud": "उच्च स्वर में पढ़ें",
|
||||
"Screen Brightness": "स्क्रीन ब्राइटनेस"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "Jumlah file: {{size}}",
|
||||
"Background Read Aloud": "Bacakan Latar Belakang",
|
||||
"Ready to read aloud": "Siap untuk dibacakan",
|
||||
"Read Aloud": "Bacakan"
|
||||
"Read Aloud": "Bacakan",
|
||||
"Screen Brightness": "Kecerahan Layar"
|
||||
}
|
||||
|
||||
@@ -603,5 +603,6 @@
|
||||
"File count: {{size}}": "Conteggio file: {{size}}",
|
||||
"Background Read Aloud": "Riproduzione in background",
|
||||
"Ready to read aloud": "Pronto per la lettura ad alta voce",
|
||||
"Read Aloud": "Leggi ad alta voce"
|
||||
"Read Aloud": "Leggi ad alta voce",
|
||||
"Screen Brightness": "Luminosità dello schermo"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "ファイル数: {{size}}",
|
||||
"Background Read Aloud": "バックグラウンド読み上げ",
|
||||
"Ready to read aloud": "読み上げの準備ができました",
|
||||
"Read Aloud": "読み上げ"
|
||||
"Read Aloud": "読み上げ",
|
||||
"Screen Brightness": "画面の明るさ"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "파일 수: {{size}}",
|
||||
"Background Read Aloud": "백그라운드 음성 읽기",
|
||||
"Ready to read aloud": "읽기 준비 완료",
|
||||
"Read Aloud": "음성 읽기"
|
||||
"Read Aloud": "음성 읽기",
|
||||
"Screen Brightness": "화면 밝기"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "Bestandsaantal: {{size}}",
|
||||
"Background Read Aloud": "Achtergrond Voorlezen",
|
||||
"Ready to read aloud": "Klaar om voor te lezen",
|
||||
"Read Aloud": "Voorlezen"
|
||||
"Read Aloud": "Voorlezen",
|
||||
"Screen Brightness": "Schermhelderheid"
|
||||
}
|
||||
|
||||
@@ -607,5 +607,6 @@
|
||||
"File count: {{size}}": "Liczba plików: {{size}}",
|
||||
"Background Read Aloud": "Czytanie na głos w tle",
|
||||
"Ready to read aloud": "Gotowy do przeczytania na głos",
|
||||
"Read Aloud": "Czytaj na głos"
|
||||
"Read Aloud": "Czytaj na głos",
|
||||
"Screen Brightness": "Jasność ekranu"
|
||||
}
|
||||
|
||||
@@ -603,5 +603,6 @@
|
||||
"File count: {{size}}": "Contagem de arquivos: {{size}}",
|
||||
"Background Read Aloud": "Leitura em Segundo Plano",
|
||||
"Ready to read aloud": "Pronto para leitura em voz alta",
|
||||
"Read Aloud": "Ler em Voz Alta"
|
||||
"Read Aloud": "Ler em Voz Alta",
|
||||
"Screen Brightness": "Brilho da Tela"
|
||||
}
|
||||
|
||||
@@ -607,5 +607,6 @@
|
||||
"File count: {{size}}": "Количество файлов: {{size}}",
|
||||
"Background Read Aloud": "Озвучивание в фоне",
|
||||
"Ready to read aloud": "Готов к чтению вслух",
|
||||
"Read Aloud": "Читать вслух"
|
||||
"Read Aloud": "Читать вслух",
|
||||
"Screen Brightness": "Яркость экрана"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "ගොනු ගණන: {{size}}",
|
||||
"Background Read Aloud": "පසුබිම් පවසා කියවීම",
|
||||
"Ready to read aloud": "වාදනය කිරීමට සූදානම්",
|
||||
"Read Aloud": "පවසා කියවීම"
|
||||
"Read Aloud": "පවසා කියවීම",
|
||||
"Screen Brightness": "තිරයේ දිදුලනුම"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"Sunset": "Solnedgång",
|
||||
"Reveal in Finder": "Visa i Finder",
|
||||
"Reveal in File Explorer": "Visa i Utforskaren",
|
||||
"Reveal in Folder": "Visa i mapp"
|
||||
"Reveal in Folder": "Visa i mapp",
|
||||
"Screen Brightness": "Skärmens ljusstyrka"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "கோப்பு எண்ணிக்கை: {{size}}",
|
||||
"Background Read Aloud": "பின்னணி உரை வாசிப்பு",
|
||||
"Ready to read aloud": "வாசிக்க தயாராக உள்ளது",
|
||||
"Read Aloud": "உரை வாசிப்பு"
|
||||
"Read Aloud": "உரை வாசிப்பு",
|
||||
"Screen Brightness": "திரை பிரகாசம்"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "จำนวนไฟล์: {{size}}",
|
||||
"Background Read Aloud": "อ่านออกเสียงเบื้องหลัง",
|
||||
"Ready to read aloud": "พร้อมที่จะอ่านออกเสียง",
|
||||
"Read Aloud": "อ่านออกเสียง"
|
||||
"Read Aloud": "อ่านออกเสียง",
|
||||
"Screen Brightness": "ความสว่างหน้าจอ"
|
||||
}
|
||||
|
||||
@@ -599,5 +599,6 @@
|
||||
"File count: {{size}}": "Dosya sayısı: {{size}}",
|
||||
"Background Read Aloud": "Arka Planda Sesli Okuma",
|
||||
"Ready to read aloud": "Sesli okumaya hazır",
|
||||
"Read Aloud": "Sesli Oku"
|
||||
"Read Aloud": "Sesli Oku",
|
||||
"Screen Brightness": "Ekran Parlaklığı"
|
||||
}
|
||||
|
||||
@@ -607,5 +607,6 @@
|
||||
"File count: {{size}}": "Кількість файлів: {{size}}",
|
||||
"Background Read Aloud": "Озвучування у фоновому режимі",
|
||||
"Ready to read aloud": "Готовий до читання вголос",
|
||||
"Read Aloud": "Читати вголос"
|
||||
"Read Aloud": "Читати вголос",
|
||||
"Screen Brightness": "Яскравість екрану"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "Số lượng tệp: {{size}}",
|
||||
"Background Read Aloud": "Đọc To Nền",
|
||||
"Ready to read aloud": "Sẵn sàng để đọc to",
|
||||
"Read Aloud": "Đọc To"
|
||||
"Read Aloud": "Đọc To",
|
||||
"Screen Brightness": "Độ sáng màn hình"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "文件数量: {{size}}",
|
||||
"Background Read Aloud": "后台语音朗读",
|
||||
"Ready to read aloud": "语音朗读已就绪",
|
||||
"Read Aloud": "语音朗读"
|
||||
"Read Aloud": "语音朗读",
|
||||
"Screen Brightness": "屏幕亮度"
|
||||
}
|
||||
|
||||
@@ -595,5 +595,6 @@
|
||||
"File count: {{size}}": "文件數量: {{size}}",
|
||||
"Background Read Aloud": "背景朗讀",
|
||||
"Ready to read aloud": "朗讀準備就緒",
|
||||
"Read Aloud": "朗讀"
|
||||
"Read Aloud": "朗讀",
|
||||
"Screen Brightness": "螢幕亮度"
|
||||
}
|
||||
|
||||
+44
-1
@@ -69,6 +69,11 @@ class LockScreenOrientationRequestArgs {
|
||||
var orientation: String? = null
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class SetScreenBrightnessRequestArgs {
|
||||
var brightness: Double? = null // 0.0 to 1.0
|
||||
}
|
||||
|
||||
interface KeyDownInterceptor {
|
||||
fun interceptVolumeKeys(enabled: Boolean)
|
||||
fun interceptBackKey(enabled: Boolean)
|
||||
@@ -76,7 +81,7 @@ interface KeyDownInterceptor {
|
||||
|
||||
@TauriPlugin(
|
||||
permissions = [
|
||||
Permission(strings = [Manifest.permission.MANAGE_EXTERNAL_STORAGE], alias = "manageStorage")
|
||||
Permission(strings = [Manifest.permission.MANAGE_EXTERNAL_STORAGE], alias = "manageStorage"),
|
||||
]
|
||||
)
|
||||
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
@@ -415,6 +420,44 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun get_screen_brightness(invoke: Invoke) {
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val brightness = Settings.System.getInt(
|
||||
activity.contentResolver,
|
||||
Settings.System.SCREEN_BRIGHTNESS
|
||||
)
|
||||
val normalizedBrightness = brightness / 255.0
|
||||
ret.put("brightness", normalizedBrightness)
|
||||
} catch (e: Exception) {
|
||||
ret.put("error", e.message)
|
||||
ret.put("brightness", -1.0)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun set_screen_brightness(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(SetScreenBrightnessRequestArgs::class.java)
|
||||
val ret = JSObject()
|
||||
try {
|
||||
val brightness = (args.brightness ?: 0.5).toFloat()
|
||||
if (brightness < 0.0 || brightness > 1.0) {
|
||||
invoke.reject("Brightness must be between 0.0 and 1.0")
|
||||
return
|
||||
}
|
||||
val layoutParams = activity.window.attributes
|
||||
layoutParams.screenBrightness = brightness
|
||||
activity.window.attributes = layoutParams
|
||||
ret.put("success", true)
|
||||
} catch (e: Exception) {
|
||||
ret.put("success", false)
|
||||
ret.put("error", e.message)
|
||||
}
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun request_manage_storage_permission(invoke: Invoke) {
|
||||
val ret = JSObject()
|
||||
|
||||
@@ -15,6 +15,8 @@ const COMMANDS: &[&str] = &[
|
||||
"iap_restore_purchases",
|
||||
"get_system_color_scheme",
|
||||
"get_safe_area_insets",
|
||||
"get_screen_brightness",
|
||||
"set_screen_brightness",
|
||||
"request_manage_storage_permission",
|
||||
"checkPermissions",
|
||||
"requestPermissions",
|
||||
|
||||
+26
@@ -49,6 +49,10 @@ class LockScreenOrientationRequestArgs: Decodable {
|
||||
let orientation: String?
|
||||
}
|
||||
|
||||
class SetScreenBrightnessRequestArgs: Decodable {
|
||||
let brightness: Float?
|
||||
}
|
||||
|
||||
struct InitializeRequest: Decodable {
|
||||
let publicKey: String?
|
||||
}
|
||||
@@ -539,6 +543,28 @@ class NativeBridgePlugin: Plugin {
|
||||
let colorScheme = (userInterfaceStyle == .dark) ? "dark" : "light"
|
||||
invoke.resolve(["colorScheme": colorScheme])
|
||||
}
|
||||
|
||||
@objc public func get_screen_brightness(_ invoke: Invoke) {
|
||||
let brightness = UIScreen.main.brightness
|
||||
invoke.resolve(["brightness": brightness])
|
||||
}
|
||||
|
||||
@objc public func set_screen_brightness(_ invoke: Invoke) {
|
||||
guard let args = try? invoke.parseArgs(SetScreenBrightnessRequestArgs.self) else {
|
||||
return invoke.reject("Failed to parse arguments")
|
||||
}
|
||||
|
||||
let brightness = args.brightness ?? 0.5
|
||||
|
||||
if brightness < 0.0 || brightness > 1.0 {
|
||||
return invoke.reject("Brightness must be between 0.0 and 1.0")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIScreen.main.brightness = CGFloat(brightness)
|
||||
}
|
||||
invoke.resolve(["success": true])
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_native_bridge")
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-get-screen-brightness"
|
||||
description = "Enables the get_screen_brightness command without any pre-configured scope."
|
||||
commands.allow = ["get_screen_brightness"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-get-screen-brightness"
|
||||
description = "Denies the get_screen_brightness command without any pre-configured scope."
|
||||
commands.deny = ["get_screen_brightness"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-set-screen-brightness"
|
||||
description = "Enables the set_screen_brightness command without any pre-configured scope."
|
||||
commands.allow = ["set_screen_brightness"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-set-screen-brightness"
|
||||
description = "Denies the set_screen_brightness command without any pre-configured scope."
|
||||
commands.deny = ["set_screen_brightness"]
|
||||
+54
@@ -20,6 +20,8 @@ Default permissions for the plugin
|
||||
- `allow-iap-restore-purchases`
|
||||
- `allow-get-system-color-scheme`
|
||||
- `allow-get-safe-area-insets`
|
||||
- `allow-get-screen-brightness`
|
||||
- `allow-set-screen-brightness`
|
||||
- `allow-request-manage-storage-permission`
|
||||
- `allow-checkPermissions`
|
||||
- `allow-requestPermissions`
|
||||
@@ -166,6 +168,32 @@ Denies the get_safe_area_insets command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-screen-brightness`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the get_screen_brightness command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-get-screen-brightness`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the get_screen_brightness command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-get-status-bar-height`
|
||||
|
||||
</td>
|
||||
@@ -478,6 +506,32 @@ Denies the request_manage_storage_permission command without any pre-configured
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-set-screen-brightness`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the set_screen_brightness command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:deny-set-screen-brightness`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the set_screen_brightness command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`native-bridge:allow-set-system-ui-visibility`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -17,6 +17,8 @@ permissions = [
|
||||
"allow-iap-restore-purchases",
|
||||
"allow-get-system-color-scheme",
|
||||
"allow-get-safe-area-insets",
|
||||
"allow-get-screen-brightness",
|
||||
"allow-set-screen-brightness",
|
||||
"allow-request-manage-storage-permission",
|
||||
"allow-checkPermissions",
|
||||
"allow-requestPermissions",
|
||||
|
||||
+26
-2
@@ -354,6 +354,18 @@
|
||||
"const": "deny-get-safe-area-insets",
|
||||
"markdownDescription": "Denies the get_safe_area_insets command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_screen_brightness command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-get-screen-brightness",
|
||||
"markdownDescription": "Enables the get_screen_brightness command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the get_screen_brightness command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-get-screen-brightness",
|
||||
"markdownDescription": "Denies the get_screen_brightness command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the get_status_bar_height command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -498,6 +510,18 @@
|
||||
"const": "deny-request-manage-storage-permission",
|
||||
"markdownDescription": "Denies the request_manage_storage_permission command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_screen_brightness command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-set-screen-brightness",
|
||||
"markdownDescription": "Enables the set_screen_brightness command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_screen_brightness command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-set-screen-brightness",
|
||||
"markdownDescription": "Denies the set_screen_brightness command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_system_ui_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -523,10 +547,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-request-manage-storage-permission`\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-request-manage-storage-permission`\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-request-manage-storage-permission`\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-request-manage-storage-permission`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -127,6 +127,21 @@ pub(crate) async fn get_safe_area_insets<R: Runtime>(
|
||||
app.native_bridge().get_safe_area_insets()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn get_screen_brightness<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
) -> Result<GetScreenBrightnessResponse> {
|
||||
app.native_bridge().get_screen_brightness()
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn set_screen_brightness<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
payload: SetScreenBrightnessRequest,
|
||||
) -> Result<SetScreenBrightnessResponse> {
|
||||
app.native_bridge().set_screen_brightness(payload)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn request_manage_storage_permission<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
|
||||
@@ -107,6 +107,17 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn get_screen_brightness(&self) -> crate::Result<GetScreenBrightnessResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn set_screen_brightness(
|
||||
&self,
|
||||
_payload: SetScreenBrightnessRequest,
|
||||
) -> crate::Result<SetScreenBrightnessResponse> {
|
||||
Err(crate::Error::UnsupportedPlatformError)
|
||||
}
|
||||
|
||||
pub fn request_manage_storage_permission(
|
||||
&self,
|
||||
) -> crate::Result<RequestManageStoragePermissionResponse> {
|
||||
|
||||
@@ -53,6 +53,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
commands::iap_restore_purchases,
|
||||
commands::get_system_color_scheme,
|
||||
commands::get_safe_area_insets,
|
||||
commands::get_screen_brightness,
|
||||
commands::set_screen_brightness,
|
||||
commands::request_manage_storage_permission,
|
||||
])
|
||||
.setup(|app, api| {
|
||||
|
||||
@@ -170,6 +170,25 @@ impl<R: Runtime> NativeBridge<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn get_screen_brightness(&self) -> crate::Result<GetScreenBrightnessResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("get_screen_brightness", ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn set_screen_brightness(
|
||||
&self,
|
||||
payload: SetScreenBrightnessRequest,
|
||||
) -> crate::Result<SetScreenBrightnessResponse> {
|
||||
self.0
|
||||
.run_mobile_plugin("set_screen_brightness", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> NativeBridge<R> {
|
||||
pub fn request_manage_storage_permission(
|
||||
&self,
|
||||
|
||||
@@ -168,6 +168,25 @@ pub struct GetSafeAreaInsetsResponse {
|
||||
pub right: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetScreenBrightnessResponse {
|
||||
pub brightness: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetScreenBrightnessRequest {
|
||||
pub brightness: f64, // 0.0 to 1.0
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetScreenBrightnessResponse {
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RequestManageStoragePermissionResponse {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { PiSun, PiMoon } from 'react-icons/pi';
|
||||
import { TbSunMoon } from 'react-icons/tb';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { themes } from '@/styles/themes';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import Slider from '@/components/Slider';
|
||||
|
||||
const SCREEN_BRIGHTNESS_LIMITS = {
|
||||
MIN: 0,
|
||||
MAX: 100,
|
||||
DEFAULT: 50,
|
||||
} as const;
|
||||
|
||||
interface ColorPanelProps {
|
||||
actionTab: string;
|
||||
bottomOffset: string;
|
||||
}
|
||||
|
||||
export const ColorPanel: React.FC<ColorPanelProps> = ({ actionTab, bottomOffset }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const { getScreenBrightness, setScreenBrightness } = useDeviceControlStore();
|
||||
const { themeMode, themeColor, isDarkMode, setThemeMode, setThemeColor } = useThemeStore();
|
||||
|
||||
const [screenBrightnessValue, setScreenBrightnessValue] = React.useState(
|
||||
settings.screenBrightness >= 0 ? settings.screenBrightness : SCREEN_BRIGHTNESS_LIMITS.DEFAULT,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appService?.isMobileApp) return;
|
||||
if (settings.screenBrightness >= 0) return;
|
||||
|
||||
getScreenBrightness().then((brightness) => {
|
||||
if (brightness >= 0.0 && brightness <= 1.0) {
|
||||
const screenBrightness = Math.round(brightness * 100);
|
||||
setScreenBrightnessValue(screenBrightness);
|
||||
}
|
||||
});
|
||||
}, [appService, settings, setSettings, getScreenBrightness]);
|
||||
|
||||
const debouncedSetScreenBrightness = useMemo(
|
||||
() =>
|
||||
debounce(async (value: number) => {
|
||||
settings.screenBrightness = value;
|
||||
setSettings(settings);
|
||||
saveSettings(envConfig, settings);
|
||||
await setScreenBrightness(value / 100);
|
||||
}, 100),
|
||||
[envConfig, settings, setSettings, saveSettings, setScreenBrightness],
|
||||
);
|
||||
|
||||
const handleScreenBrightnessChange = useCallback(
|
||||
async (value: number) => {
|
||||
if (!appService?.isMobileApp) return;
|
||||
|
||||
setScreenBrightnessValue(value);
|
||||
debouncedSetScreenBrightness(value);
|
||||
},
|
||||
[appService, debouncedSetScreenBrightness],
|
||||
);
|
||||
|
||||
const cycleThemeMode = () => {
|
||||
const nextMode = themeMode === 'auto' ? 'light' : themeMode === 'light' ? 'dark' : 'auto';
|
||||
setThemeMode(nextMode);
|
||||
};
|
||||
|
||||
const classes = clsx(
|
||||
'footerbar-color-mobile bg-base-200 absolute flex w-full flex-col items-center gap-y-8 px-4 transition-all sm:hidden',
|
||||
actionTab === 'color'
|
||||
? 'pointer-events-auto translate-y-0 pb-4 pt-8 ease-out'
|
||||
: 'pointer-events-none invisible translate-y-full overflow-hidden pb-0 pt-0 ease-in',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes} style={{ bottom: bottomOffset }}>
|
||||
{appService?.hasScreenBrightness && (
|
||||
<Slider
|
||||
label={_('Screen Brightness')}
|
||||
initialValue={screenBrightnessValue}
|
||||
bubbleLabel={`${screenBrightnessValue}`}
|
||||
minIcon={<PiSun size={16} />}
|
||||
maxIcon={<PiSun size={24} />}
|
||||
onChange={handleScreenBrightnessChange}
|
||||
min={SCREEN_BRIGHTNESS_LIMITS.MIN}
|
||||
max={SCREEN_BRIGHTNESS_LIMITS.MAX}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='w-full'>
|
||||
<div className='flex items-center justify-between p-2'>
|
||||
<span className='text-sm font-medium'>{_('Color')}</span>
|
||||
</div>
|
||||
<div
|
||||
className='flex gap-3 overflow-x-auto p-2'
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
<style>{`
|
||||
.theme-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
{themes.map(({ name, label, colors }) => (
|
||||
<button
|
||||
key={name}
|
||||
onClick={() => setThemeColor(name)}
|
||||
className={clsx(
|
||||
'flex flex-shrink-0 flex-col items-center justify-center rounded-lg p-3 transition-all',
|
||||
'h-[40px] min-w-[80px]',
|
||||
themeColor === name
|
||||
? 'ring-primary ring-offset-base-200 ring-2 ring-offset-2'
|
||||
: 'hover:opacity-80',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: isDarkMode ? colors.dark['base-100'] : colors.light['base-100'],
|
||||
color: isDarkMode ? colors.dark['base-content'] : colors.light['base-content'],
|
||||
}}
|
||||
>
|
||||
<span className='text-xs font-medium'>{_(label)}</span>
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={() => cycleThemeMode()}
|
||||
className={clsx(
|
||||
'flex flex-shrink-0 flex-col items-center justify-center rounded-lg p-3 transition-all',
|
||||
'h-[40px] min-w-[80px]',
|
||||
themeMode === 'dark'
|
||||
? 'ring-primary ring-offset-base-200 ring-2 ring-offset-2'
|
||||
: 'hover:opacity-80',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: (themes.find((t) => t.name === themeColor) || themes[0]!).colors
|
||||
.dark['base-100'],
|
||||
color: (themes.find((t) => t.name === themeColor) || themes[0]!).colors.dark[
|
||||
'base-content'
|
||||
],
|
||||
}}
|
||||
>
|
||||
{themeMode === 'light' ? (
|
||||
<PiSun size={20} />
|
||||
) : themeMode === 'dark' ? (
|
||||
<PiMoon size={20} />
|
||||
) : (
|
||||
<TbSunMoon size={20} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { FaHeadphones } from 'react-icons/fa6';
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||
import { RiArrowGoBackLine, RiArrowGoForwardLine } from 'react-icons/ri';
|
||||
@@ -22,6 +22,25 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
|
||||
const view = getView(bookKey);
|
||||
const viewState = getViewState(bookKey);
|
||||
const viewSettings = getViewSettings(bookKey);
|
||||
|
||||
const [progressValue, setProgressValue] = React.useState(
|
||||
progressValid ? progressFraction * 100 : 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (progressValid) {
|
||||
setProgressValue(progressFraction * 100);
|
||||
}
|
||||
}, [progressValid, progressFraction]);
|
||||
|
||||
const handleProgressChange = useCallback(
|
||||
(value: number) => {
|
||||
setProgressValue(value);
|
||||
navigationHandlers.onProgressChange(value);
|
||||
},
|
||||
[navigationHandlers],
|
||||
);
|
||||
|
||||
const isMobile = window.innerWidth < 640 || window.innerHeight < 640;
|
||||
|
||||
return (
|
||||
@@ -78,8 +97,8 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
|
||||
min={0}
|
||||
max={100}
|
||||
aria-label={_('Jump to Location')}
|
||||
value={progressValid ? progressFraction * 100 : 0}
|
||||
onChange={(e) => navigationHandlers.onProgressChange(parseInt(e.target.value, 10))}
|
||||
value={progressValue}
|
||||
onChange={(e) => handleProgressChange(parseInt(e.target.value, 10))}
|
||||
/>
|
||||
<Button
|
||||
icon={<FaHeadphones className={viewState?.ttsEnabled ? 'text-blue-500' : ''} />}
|
||||
|
||||
@@ -30,14 +30,14 @@ const MARGIN_CONSTANTS = {
|
||||
interface FontLayoutPanelProps {
|
||||
bookKey: string;
|
||||
actionTab: string;
|
||||
mobileBottomOffset: string;
|
||||
bottomOffset: string;
|
||||
marginIconSize: number;
|
||||
}
|
||||
|
||||
export const FontLayoutPanel: React.FC<FontLayoutPanelProps> = ({
|
||||
bookKey,
|
||||
actionTab,
|
||||
mobileBottomOffset,
|
||||
bottomOffset,
|
||||
marginIconSize,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
@@ -98,7 +98,7 @@ export const FontLayoutPanel: React.FC<FontLayoutPanelProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes} style={{ bottom: mobileBottomOffset }}>
|
||||
<div className={classes} style={{ bottom: bottomOffset }}>
|
||||
<Slider
|
||||
label={_('Font Size')}
|
||||
initialValue={viewSettings?.defaultFontSize ?? FONT_SIZE_LIMITS.DEFAULT}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { FooterBarProps, NavigationHandlers, FooterBarChildProps } from './types';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { viewPagination } from '../../hooks/usePagination';
|
||||
import MobileFooterBar from './MobileFooterBar';
|
||||
import DesktopFooterBar from './DesktopFooterBar';
|
||||
@@ -50,10 +51,11 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
return (progressInfo.current + 1) / progressInfo.total;
|
||||
}, [progressValid, progressInfo]);
|
||||
|
||||
const handleProgressChange = useCallback(
|
||||
(value: number) => {
|
||||
view?.goToFraction(value / 100.0);
|
||||
},
|
||||
const handleProgressChange = useMemo(
|
||||
() =>
|
||||
debounce((value: number) => {
|
||||
view?.goToFraction(value / 100.0);
|
||||
}, 100),
|
||||
[view],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { FooterBarChildProps } from './types';
|
||||
import { NavigationPanel } from './NavigationPanel';
|
||||
import { FontLayoutPanel } from './FontLayoutPanel';
|
||||
import { ColorPanel } from './ColorPanel';
|
||||
import { NavigationBar } from './NavigationBar';
|
||||
import { FooterBarChildProps } from './types';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
|
||||
const MobileFooterBar: React.FC<FooterBarChildProps> = ({
|
||||
bookKey,
|
||||
@@ -22,8 +23,9 @@ const MobileFooterBar: React.FC<FooterBarChildProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColorPanel actionTab={actionTab} bottomOffset={bottomOffset} />
|
||||
<NavigationPanel
|
||||
actionTab={actionTab!}
|
||||
actionTab={actionTab}
|
||||
progressFraction={progressFraction}
|
||||
progressValid={progressValid}
|
||||
navigationHandlers={navigationHandlers}
|
||||
@@ -32,13 +34,13 @@ const MobileFooterBar: React.FC<FooterBarChildProps> = ({
|
||||
/>
|
||||
<FontLayoutPanel
|
||||
bookKey={bookKey}
|
||||
actionTab={actionTab!}
|
||||
mobileBottomOffset={bottomOffset}
|
||||
actionTab={actionTab}
|
||||
bottomOffset={bottomOffset}
|
||||
marginIconSize={marginIconSize}
|
||||
/>
|
||||
<NavigationBar
|
||||
bookKey={bookKey}
|
||||
actionTab={actionTab!}
|
||||
actionTab={actionTab}
|
||||
navPadding={navPadding}
|
||||
onSetActionTab={onSetActionTab!}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { IoIosList as TOCIcon } from 'react-icons/io';
|
||||
import { PiNotePencil as NoteIcon } from 'react-icons/pi';
|
||||
import { RxSlider as SliderIcon } from 'react-icons/rx';
|
||||
import { RiFontFamily as FontIcon } from 'react-icons/ri';
|
||||
import { PiSun as ColorIcon } from 'react-icons/pi';
|
||||
import { MdOutlineHeadphones as TTSIcon } from 'react-icons/md';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
@@ -39,7 +39,11 @@ export const NavigationBar: React.FC<NavigationBarProps> = ({
|
||||
icon={<TOCIcon size={tocIconSize} />}
|
||||
onClick={() => onSetActionTab('toc')}
|
||||
/>
|
||||
<Button label={_('Notes')} icon={<NoteIcon />} onClick={() => onSetActionTab('note')} />
|
||||
<Button
|
||||
label={_('Color')}
|
||||
icon={<ColorIcon className={clsx(actionTab === 'color' && 'text-blue-500')} />}
|
||||
onClick={() => onSetActionTab('color')}
|
||||
/>
|
||||
<Button
|
||||
label={_('Reading Progress')}
|
||||
icon={<SliderIcon className={clsx(actionTab === 'progress' && 'text-blue-500')} />}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||
import { RiArrowGoBackLine, RiArrowGoForwardLine } from 'react-icons/ri';
|
||||
import { RiArrowLeftDoubleLine, RiArrowRightDoubleLine } from 'react-icons/ri';
|
||||
@@ -30,6 +30,25 @@ export const NavigationPanel: React.FC<NavigationPanelProps> = ({
|
||||
sliderHeight,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
|
||||
const [progressValue, setProgressValue] = React.useState(
|
||||
progressValid ? progressFraction * 100 : 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (progressValid) {
|
||||
setProgressValue(progressFraction * 100);
|
||||
}
|
||||
}, [progressValid, progressFraction]);
|
||||
|
||||
const handleProgressChange = useCallback(
|
||||
(value: number) => {
|
||||
setProgressValue(value);
|
||||
navigationHandlers.onProgressChange(value);
|
||||
},
|
||||
[navigationHandlers],
|
||||
);
|
||||
|
||||
const classes = clsx(
|
||||
'footerbar-progress-mobile bg-base-200 absolute flex w-full flex-col items-center gap-y-8 px-4 transition-all sm:hidden',
|
||||
actionTab === 'progress'
|
||||
@@ -43,9 +62,9 @@ export const NavigationPanel: React.FC<NavigationPanelProps> = ({
|
||||
<Slider
|
||||
label={_('Reading Progress')}
|
||||
heightPx={sliderHeight}
|
||||
bubbleLabel={`${Math.round(progressFraction * 100)}%`}
|
||||
initialValue={progressValid ? progressFraction * 100 : 0}
|
||||
onChange={navigationHandlers.onProgressChange}
|
||||
bubbleLabel={`${Math.round(progressValue)}%`}
|
||||
initialValue={progressValue}
|
||||
onChange={handleProgressChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex w-full items-center justify-between gap-x-6'>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CSPostHogProvider } from '@/context/PHContext';
|
||||
import { SyncProvider } from '@/context/SyncContext';
|
||||
import { initSystemThemeListener, loadDataTheme } from '@/store/themeStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
|
||||
import { useSafeAreaInsets } from '@/hooks/useSafeAreaInsets';
|
||||
import { getLocale } from '@/utils/misc';
|
||||
@@ -16,6 +17,7 @@ import { getLocale } from '@/utils/misc';
|
||||
const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
const { appService } = useEnv();
|
||||
const { applyUILanguage } = useSettingsStore();
|
||||
const { setScreenBrightness } = useDeviceControlStore();
|
||||
const iconSize = useDefaultIconSize();
|
||||
useSafeAreaInsets(); // Initialize safe area insets
|
||||
|
||||
@@ -38,9 +40,13 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
initSystemThemeListener(appService);
|
||||
appService.loadSettings().then((settings) => {
|
||||
applyUILanguage(settings.globalViewSettings?.uiLanguage);
|
||||
const brightness = settings.screenBrightness;
|
||||
if (appService.hasScreenBrightness && brightness >= 0) {
|
||||
setScreenBrightness(brightness / 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [appService, applyUILanguage]);
|
||||
}, [appService, applyUILanguage, setScreenBrightness]);
|
||||
|
||||
// Make sure appService is available in all children components
|
||||
if (!appService) return;
|
||||
|
||||
@@ -9,6 +9,8 @@ interface SliderProps {
|
||||
heightPx?: number;
|
||||
minLabel?: string;
|
||||
maxLabel?: string;
|
||||
minIcon?: React.ReactNode;
|
||||
maxIcon?: React.ReactNode;
|
||||
bubbleElement?: React.ReactNode;
|
||||
bubbleLabel?: string;
|
||||
className?: string;
|
||||
@@ -27,6 +29,8 @@ const Slider: React.FC<SliderProps> = ({
|
||||
heightPx = 40,
|
||||
minLabel = '',
|
||||
maxLabel = '',
|
||||
minIcon,
|
||||
maxIcon,
|
||||
bubbleElement,
|
||||
bubbleLabel = '',
|
||||
className = '',
|
||||
@@ -63,6 +67,7 @@ const Slider: React.FC<SliderProps> = ({
|
||||
}, [initialValue]);
|
||||
|
||||
const percentage = ((value - min) / (max - min)) * 100;
|
||||
const visualPercentage = (percentage / 100) * 95;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -79,22 +84,22 @@ const Slider: React.FC<SliderProps> = ({
|
||||
className='bg-base-300 absolute h-full rounded-full'
|
||||
style={{
|
||||
width:
|
||||
percentage > 0
|
||||
? `max(calc(${percentage}% + ${heightPx / 2}px), ${heightPx}px)`
|
||||
visualPercentage > 0
|
||||
? `max(calc(${visualPercentage}% + ${heightPx / 2}px), ${heightPx}px)`
|
||||
: '0px',
|
||||
[isRtl ? 'right' : 'left']: 0,
|
||||
}}
|
||||
></div>
|
||||
{/* Min/Max labels */}
|
||||
<div className='absolute inset-0 flex items-center justify-between px-4 text-sm'>
|
||||
<span className={`ml-2 ${minClassName}`}>{minLabel}</span>
|
||||
<span className={`mr-2 ${maxClassName}`}>{maxLabel}</span>
|
||||
{minIcon ? minIcon : <span className={`ml-2 ${minClassName}`}>{minLabel}</span>}
|
||||
{maxIcon ? maxIcon : <span className={`mr-2 ${maxClassName}`}>{maxLabel}</span>}
|
||||
</div>
|
||||
{/* Thumb bubble */}
|
||||
<div
|
||||
className='pointer-events-none absolute top-0 z-10'
|
||||
style={{
|
||||
[isRtl ? 'right' : 'left']: `max(${heightPx / 2}px, calc(${percentage}%))`,
|
||||
[isRtl ? 'right' : 'left']: `max(${heightPx / 2}px, calc(${visualPercentage}%))`,
|
||||
transform: isRtl ? 'translateX(calc(50%))' : 'translateX(calc(-50%))',
|
||||
height: '100%',
|
||||
}}
|
||||
|
||||
@@ -86,6 +86,7 @@ export abstract class BaseAppService implements AppService {
|
||||
hasHaptics = false;
|
||||
hasUpdater = false;
|
||||
hasOrientationLock = false;
|
||||
hasScreenBrightness = false;
|
||||
canCustomizeRootDir = false;
|
||||
distChannel = 'readest' as DistChannel;
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
|
||||
alwaysInForeground: false,
|
||||
autoCheckUpdates: true,
|
||||
screenWakeLock: false,
|
||||
screenBrightness: -1, // -1~100, -1 for system default
|
||||
openLastBooks: false,
|
||||
lastOpenBooks: [],
|
||||
autoImportBooksOnOpen: false,
|
||||
|
||||
@@ -348,6 +348,7 @@ export class NativeAppService extends BaseAppService {
|
||||
// orientation lock is not supported on iPad
|
||||
override hasOrientationLock =
|
||||
(OS_TYPE === 'ios' && getOSPlatform() === 'ios') || OS_TYPE === 'android';
|
||||
override hasScreenBrightness = OS_TYPE === 'ios' || OS_TYPE === 'android';
|
||||
// CustomizeRootDir has a blocker on macOS App Store builds due to Security Scoped Resource restrictions.
|
||||
// See: https://github.com/tauri-apps/tauri/issues/3716
|
||||
override canCustomizeRootDir = process.env['NEXT_PUBLIC_DIST_CHANNEL'] !== 'appstore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand';
|
||||
import { interceptKeys } from '@/utils/bridge';
|
||||
import { interceptKeys, getScreenBrightness, setScreenBrightness } from '@/utils/bridge';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
|
||||
declare global {
|
||||
@@ -23,6 +23,8 @@ type DeviceControlState = {
|
||||
backKeyIntercepted: boolean;
|
||||
volumeKeysInterceptionCount: number;
|
||||
backKeyInterceptionCount: number;
|
||||
getScreenBrightness: () => Promise<number>; // 0.0 to 1.0
|
||||
setScreenBrightness: (brightness: number) => Promise<void>; // brightness: 0.0 to 1.0
|
||||
acquireVolumeKeyInterception: () => void;
|
||||
releaseVolumeKeyInterception: () => void;
|
||||
acquireBackKeyInterception: () => void;
|
||||
@@ -74,4 +76,13 @@ export const useDeviceControlStore = create<DeviceControlState>((set, get) => ({
|
||||
set({ backKeyInterceptionCount: backKeyInterceptionCount - 1 });
|
||||
}
|
||||
},
|
||||
|
||||
getScreenBrightness: async () => {
|
||||
const res = await getScreenBrightness();
|
||||
return res.brightness;
|
||||
},
|
||||
|
||||
setScreenBrightness: async (brightness: number) => {
|
||||
await setScreenBrightness({ brightness });
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface SystemSettings {
|
||||
openBookInNewWindow: boolean;
|
||||
autoCheckUpdates: boolean;
|
||||
screenWakeLock: boolean;
|
||||
screenBrightness: number;
|
||||
alwaysShowStatusBar: boolean;
|
||||
alwaysInForeground: boolean;
|
||||
openLastBooks: boolean;
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface AppService {
|
||||
hasHaptics: boolean;
|
||||
hasUpdater: boolean;
|
||||
hasOrientationLock: boolean;
|
||||
hasScreenBrightness: boolean;
|
||||
isMobile: boolean;
|
||||
isAppDataSandbox: boolean;
|
||||
isMobileApp: boolean;
|
||||
|
||||
@@ -65,6 +65,20 @@ export interface GetSafeAreaInsetsResponse {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface GetScreenBrightnessResponse {
|
||||
brightness: number; // 0.0 to 1.0
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface SetScreenBrightnessRequest {
|
||||
brightness: number; // 0.0 to 1.0
|
||||
}
|
||||
|
||||
interface SetScreenBrightnessResponse {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function copyURIToPath(request: CopyURIRequest): Promise<CopyURIResponse> {
|
||||
const result = await invoke<CopyURIResponse>('plugin:native-bridge|copy_uri_to_path', {
|
||||
payload: request,
|
||||
@@ -145,3 +159,22 @@ export async function getSafeAreaInsets(): Promise<GetSafeAreaInsetsResponse> {
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getScreenBrightness(): Promise<GetScreenBrightnessResponse> {
|
||||
const result = await invoke<GetScreenBrightnessResponse>(
|
||||
'plugin:native-bridge|get_screen_brightness',
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function setScreenBrightness(
|
||||
request: SetScreenBrightnessRequest,
|
||||
): Promise<SetScreenBrightnessResponse> {
|
||||
const result = await invoke<SetScreenBrightnessResponse>(
|
||||
'plugin:native-bridge|set_screen_brightness',
|
||||
{
|
||||
payload: request,
|
||||
},
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user