1.2.0: voice input in the web chat
Some checks failed
build / apk (push) Has been cancelled

- RECORD_AUDIO permission granted to the WebView (onPermissionRequest)
- the chat's single composer button becomes a microphone when the input is
  empty and voice input is available; recording stops into server-side ASR
  (/api/transcribe) when the browser has no Web Speech API
- version 1.2.0
This commit is contained in:
Niko Marmeladkov 2026-10-05 20:33:26 +03:00
parent 898bcb792b
commit 07383624fc
3 changed files with 34 additions and 2 deletions

View file

@ -22,8 +22,8 @@ android {
applicationId = "dev.n1ko.nikogpt"
minSdk = 29 // Android 10
targetSdk = 35
versionCode = 5
versionName = "1.1.0"
versionCode = 6
versionName = "1.2.0"
}
signingConfigs {

View file

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- Voice input in the web chat (MediaRecorder + server-side recognition). -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="false"

View file

@ -1,11 +1,13 @@
package dev.n1ko.nikogpt
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.net.http.SslError
import android.os.Bundle
@ -14,6 +16,7 @@ import android.view.View
import android.webkit.CookieManager
import android.webkit.DownloadListener
import android.webkit.JavascriptInterface
import android.webkit.PermissionRequest
import android.webkit.SslErrorHandler
import android.webkit.URLUtil
import android.webkit.ValueCallback
@ -32,6 +35,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@ -103,6 +107,16 @@ class MainActivity : AppCompatActivity() {
}
}
// Runtime microphone permission, bridged to the WebView.
private var pendingWebPermission: PermissionRequest? = null
private val audioPermission: ActivityResultLauncher<String> =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
val request = pendingWebPermission
pendingWebPermission = null
if (request == null) return@registerForActivityResult
if (granted) request.grant(request.resources) else request.deny()
}
// Camera capture for the attach button.
private val takePicture: ActivityResultLauncher<Uri> =
registerForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
@ -195,6 +209,22 @@ class MainActivity : AppCompatActivity() {
}
webView.webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
if (request.resources.none { it == PermissionRequest.RESOURCE_AUDIO_CAPTURE }) {
request.deny()
return
}
if (ContextCompat.checkSelfPermission(
this@MainActivity, Manifest.permission.RECORD_AUDIO,
) == PackageManager.PERMISSION_GRANTED
) {
request.grant(request.resources)
return
}
pendingWebPermission = request
audioPermission.launch(Manifest.permission.RECORD_AUDIO)
}
override fun onProgressChanged(view: WebView, newProgress: Int) {
progress.visibility = if (newProgress in 1..99) View.VISIBLE else View.GONE
progress.progress = newProgress