Compare commits

..

No commits in common. "main" and "v1.1.0" have entirely different histories.
main ... v1.1.0

5 changed files with 6 additions and 69 deletions

View file

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

View file

@ -2,8 +2,6 @@
<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,13 +1,11 @@
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
@ -16,7 +14,6 @@ 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
@ -35,7 +32,6 @@ 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
@ -80,10 +76,6 @@ class MainActivity : AppCompatActivity() {
// Shared text ("Share to NikoGPT") waiting for the page to load.
private var pendingShare: String? = null
// Whether the chat's inner scroll container is at the top; pull-to-refresh
// only fires there (the WebView itself never scrolls).
@Volatile
private var webAtTop = true
// Where the camera writes the picture before it reaches the web app.
private var pendingCameraUri: Uri? = null
@ -99,38 +91,16 @@ class MainActivity : AppCompatActivity() {
private val legacySignIn: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val link = legacyLinkMode
val account = try {
GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
val account = GoogleSignIn.getSignedInAccountFromIntent(result.data)
.getResult(ApiException::class.java)
val token = account?.idToken
if (!token.isNullOrBlank()) deliverToken(token, link) else openGoogleInBrowser(link)
} catch (e: ApiException) {
// 12501/16 = the user closed the picker: stay in the app.
if (e.statusCode == 12501 || e.statusCode == 16) return@registerForActivityResult
Toast.makeText(this, getString(R.string.google_error_code, e.statusCode), Toast.LENGTH_LONG).show()
openGoogleInBrowser(link)
return@registerForActivityResult
} catch (e: Exception) {
openGoogleInBrowser(link)
return@registerForActivityResult
}
val token = account?.idToken
if (token.isNullOrBlank()) {
// microG accepted the account but could not mint an ID token
// (e.g. device registration is off): say so, then fall back.
Toast.makeText(this, getString(R.string.google_no_token), Toast.LENGTH_LONG).show()
openGoogleInBrowser(link)
return@registerForActivityResult
}
deliverToken(token, link)
}
// 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.
@ -225,22 +195,6 @@ 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
@ -280,10 +234,6 @@ class MainActivity : AppCompatActivity() {
})
refresh.setOnRefreshListener { webView.reload() }
// Pull-to-refresh only when the chat list is really at the top; a
// bigger trigger distance keeps normal scrolling from reloading.
refresh.setOnChildScrollUpCallback { _, _ -> !webAtTop }
refresh.setDistanceToTriggerSync((80 * resources.displayMetrics.density).toInt())
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
@ -360,13 +310,6 @@ class MainActivity : AppCompatActivity() {
fun linkGoogle(clientId: String) {
runOnUiThread { openGooglePicker(clientId, link = true) }
}
// The web app reports the message list's scroll position, so pulling
// down only refreshes when the list really is at the top.
@JavascriptInterface
fun setAtTop(atTop: Boolean) {
webAtTop = atTop
}
}
// openGooglePicker tries the native pickers in order: Credential Manager

View file

@ -7,8 +7,6 @@
<string name="download_started">Загрузка началась</string>
<string name="download_failed">Не удалось начать загрузку</string>
<string name="google_error">Не удалось войти через Google (%1$s)</string>
<string name="google_error_code">Не удалось войти через Google (код %1$d)</string>
<string name="google_no_token">Google не вернул ID-токен (проверьте Android OAuth-клиент: package и SHA-1)</string>
<string name="attach_file">Файл</string>
<string name="attach_camera">Камера</string>
</resources>

View file

@ -7,8 +7,6 @@
<string name="download_started">Download started</string>
<string name="download_failed">Could not start the download</string>
<string name="google_error">Google sign-in failed (%1$s)</string>
<string name="google_error_code">Google sign-in failed (code %1$d)</string>
<string name="google_no_token">Google did not return an ID token (check the Android OAuth client, package and SHA-1)</string>
<string name="attach_file">File</string>
<string name="attach_camera">Camera</string>
</resources>