github RushB-fr/freekiosk v1.2.20-beta.1
FreeKiosk v1.2.20 - Themes, TTS Voices & Stability Fixes

pre-release6 hours ago

Added

  • 🎨 Status bar light/dark theme (#118): A new "Status Bar Theme" toggle (Light / Dark) is now available in Settings → Display → Status Bar. In Dark mode (default), the status bar renders with white icons on a dark background — suitable for kiosks with dark web content. In Light mode, icons are black on a transparent/light background — suitable for kiosks with white or bright web content. All icons (battery, Wi-Fi, Bluetooth, volume, clock) are now rendered with MaterialCommunityIcons replacing the previous emoji characters, for consistent sizing, alignment, and color control across Android versions.

  • 🎙️ Voice selection for Web Speech API TTS polyfill (#169): The speechSynthesis.getVoices() polyfill now returns the actual list of installed Android TTS voices instead of an empty array. Web apps that select a specific voice via utterance.voice = voices.find(v => v.name === '...') will have that voice applied natively. REST API: POST /api/tts now accepts an optional voiceUri parameter to select a voice by URI for a single speak call. The available voice list is pre-cached at startup for performance (cacheTtsVoices()) and refreshed on demand.

Fixed

  • 🔐 SSL certificate dialog not shown for initial navigation and same-host redirects (#144): The custom SSL certificate acceptance dialog (Settings → General → Accept Self-Signed Certificates) only appeared when the failing URL exactly matched the currently loaded page URL. This excluded two common cases: (1) initial navigation — when the app launches and loads the first page, there is no currently loaded URL, so the dialog was never shown; (2) HTTP→HTTPS redirects — a redirect from http://host/ to https://host/ produces the same host but a different URL, which the string equality check rejected. Fixed by replacing the URL equality check with a isMainFrameRequest() helper that matches on same-host (regardless of scheme or path), and treating a null/empty current URL as a main-frame request. Sub-resource SSL errors (images, fonts, iframes from third-party domains) are still silently denied to avoid flooding the user with dialogs.

  • 📺 MQTT/REST screenOn / screenOff commands did not physically lock the screen (#155): POST /api/screen/on and POST /api/screen/off (and their MQTT equivalents) only activated or deactivated the screensaver overlay — they never called lockNow() to actually turn off the display. As a result, the MQTT screenOn status field stayed true even after sending screenOff. Fixed by delegating both commands to KioskModule.turnScreenOff() / KioskModule.turnScreenOn(), which call lockNow() and wakeUp() respectively. screenOn additionally calls setIsScreensaverActive(false) + resetTimer() to handle the case where only the overlay was active and no physical lock event fires.

  • 🏠 Dashboard mode returned to grid when tile page self-refreshes (#159): If the "Return to Start Page on Inactivity" feature was enabled, opening a dashboard tile armed the inactivity return timer. Pages that auto-refresh (e.g. Immich kiosk, Home Assistant dashboards) did not reset the timer because Reset on Navigation was off by default — so the timer fired after the configured delay and returned to the dashboard grid without any user interaction. Fixed: in dashboard mode, any page navigation (including self-refresh) always resets the inactivity timer, matching the user expectation that an actively-updating page should not be treated as "inactive."

  • 🌙 Overnight rules rejected by Scheduled URLs (#157): Recurring scheduled URL events with an end time before the start time (e.g. 22:00–07:00) were rejected with "End time must be after start time". Two fixes: (1) The validation in RecurringEventEditor now only rejects identical start/end times — crossing midnight is valid. (2) isEventActive() in planner.ts now detects overnight ranges (startTime > endTime) and handles the two sub-cases: before midnight (today is a scheduled day and currentTime >= startTime) and after midnight (yesterday was a scheduled day and currentTime < endTime). This correctly handles the case where an event starts Monday at 22:00 and is still active Tuesday at 06:30.

  • 📐 Multi-app grid tile widths cut off after device rotation (#160): In External App mode with multiple managed apps, the app grid used Dimensions.get('window').width to calculate tile widths. Dimensions returns stale values after device rotation until the component re-renders, causing tiles to overflow or be cut off. Fixed by replacing Dimensions with the useWindowDimensions() hook, which updates reactively on orientation change.

  • 🎙️ WebRTC microphone audio silent due to missing permission (#147): The MODIFY_AUDIO_SETTINGS permission was not declared in AndroidManifest.xml. This permission is required on Android for WebRTC to switch the audio mode to MODE_IN_COMMUNICATION (which activates the microphone path and echo cancellation). Without it, getUserMedia() succeeded but microphone audio was silent in WebRTC calls. Added as a normal protection level permission (auto-granted at install, no runtime prompt needed).

  • 🌐 REST API returns "Endpoint not found" for valid endpoints when called with POST (#146): Read-only endpoints (/api/status, /api/health, /api/info, /api/battery, etc.) only accepted GET requests. Automation tools that default to POST (Home Assistant REST integration, curl --request POST, Node-RED HTTP node, etc.) got a misleading 404 Endpoint not found response even though the endpoint exists — the wrong HTTP method was the only issue. Fixed by making all read-only status endpoints accept both GET and POST. The two endpoints that have both a read and write variant (/api/brightness, /api/volume) now use the HTTP method to disambiguate: GET reads the current value, POST with a body sets it. POST-only control endpoints that require a JSON body (/api/url, /api/tts, etc.) now return a proper 405 Method Not Allowed with a clear message when called with GET, instead of the generic 404.

  • 💾 Export backup fails with "Permission denied" on Android 10+ (#166): exportBackup() wrote directly to /storage/emulated/0/Download/ via RNFS.writeFile(). On Android 10+ (API 29+), WRITE_EXTERNAL_STORAGE is deprecated and silently denied, causing an EACCES crash. Fixed by switching the export flow to the Storage Access Framework: tapping Export now opens the system "Save As" dialog (ACTION_CREATE_DOCUMENT) where the user picks the save location. The file is then written via ContentResolver.openOutputStream() — no storage permission needed, works on all Android versions. The backup data collection logic is unchanged; only the write path changed. A new saveJsonFile(content, filename) method was added to FilePickerModule (Kotlin + TypeScript) to handle the SAF save dialog.

  • 🔇 Audio from previous scheduled URL continues playing after planner switches to next URL (#158): When the URL planner transitioned between scheduled events, it called setUrl() to navigate the WebView to the new URL — but the previous page's JavaScript (including Web Audio, HTML5 <audio>, timers) kept running in the background because the same WebView instance was reused. Fixed by incrementing webViewKey on each planner URL transition, which forces React to fully unmount and remount the WebViewComponent. The underlying Android WebView is destroyed, terminating all background sessions. The same remount is applied when the planner reverts to the base URL at the end of a scheduled period.

  • 📸 Screenshot key combination (Power + Volume Down) not disabled in kiosk mode (#172): On Android, pressing Power + Volume Down takes a screenshot even when Device Owner lock task mode is active. Fixed by calling DevicePolicyManager.setScreenCaptureDisabled(true) when entering kiosk mode (Device Owner only), and re-enabling it on exit. Prevents both the screenshot itself and the screenshot toolbar/preview from appearing over kiosk content.

  • 🔄 Auto-reload does not trigger on HTTP 5xx errors (e.g. 504 Gateway Timeout) (#173): The "Reload on Error" feature only handled network-level failures (no connectivity, DNS failure) via onError. HTTP error responses like 504 Gateway Timeout are delivered via onHttpError and were only logged — no reload was triggered. Fixed by extending handleHttpError to apply the same 5-second auto-reload for any HTTP 5xx status code when "Reload on Error" is enabled.

  • 💾 Custom User Agent not included in backup/restore (#174): The @kiosk_custom_user_agent storage key was missing from the backup key list in BackupService. Exporting and re-importing a configuration would silently drop the Custom User Agent setting. Added to the backup keys list.

  • ⌨️ Soft keyboard remains visible when screensaver activates in URL/Video/Image mode (#135): The existing fix (v1.2.19) dismissed the keyboard on ACTION_SCREEN_OFF, which only fires in Dim Only screensaver mode. In URL, Video, and Image screensaver modes the screen stays on — no SCREEN_OFF event fires — so the keyboard was never dismissed. Fixed by calling Keyboard.dismiss() at both screensaver activation paths in KioskScreen.

Don't miss a new freekiosk release

NewReleases is sending notifications on new releases.