diff --git a/app/build.gradle b/app/build.gradle index 1631f76..5066320 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ android { defaultConfig { applicationId "com.duckai.app" - minSdk 31 + minSdk 33 targetSdk 34 versionCode 1 versionName "1.0" @@ -35,5 +35,6 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.activity:activity-ktx:1.8.2' implementation 'com.google.android.material:material:1.11.0' } \ No newline at end of file diff --git a/app/src/main/java/com/duckai/app/web/MainActivity.kt b/app/src/main/java/com/duckai/app/web/MainActivity.kt index c7cd84f..3fc35a3 100644 --- a/app/src/main/java/com/duckai/app/web/MainActivity.kt +++ b/app/src/main/java/com/duckai/app/web/MainActivity.kt @@ -1,31 +1,67 @@ package com.duckai.app.web +import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri +import android.os.Build import android.os.Bundle +import android.view.View import android.view.inputmethod.InputMethodManager import android.webkit.CookieManager +import android.webkit.ValueCallback +import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebViewClient +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import com.duckai.app.R class MainActivity : AppCompatActivity() { private lateinit var webView: WebView + private lateinit var attachButton: View + private var filePathCallback: ValueCallback>? = null companion object { private const val BASE_URL = "https://duck.ai" } + private val filePickerLauncher = registerForActivityResult( + ActivityResultContracts.OpenMultipleDocuments() + ) { uris -> + if (uris.isNotEmpty()) { + filePathCallback?.onReceiveValue(uris.toTypedArray()) + Toast.makeText(this, getString(R.string.file_attached), Toast.LENGTH_SHORT).show() + } else { + filePathCallback?.onReceiveValue(null) + } + filePathCallback = null + } + + private val permissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + val allGranted = permissions.values.all { it } + if (allGranted) { + openFilePicker() + } else { + Toast.makeText(this, getString(R.string.permission_required), Toast.LENGTH_SHORT).show() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView = findViewById(R.id.webView) + attachButton = findViewById(R.id.attachButton) setupWebView() + setupAttachButton() loadUrlFromIntent() } @@ -44,13 +80,25 @@ class MainActivity : AppCompatActivity() { CookieManager.getInstance().setAcceptCookie(true) CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true) + + webView.webChromeClient = object : WebChromeClient() { + override fun onShowFileChooser( + webView: WebView?, + filePathCallback: ValueCallback>?, + fileChooserParams: FileChooserParams? + ): Boolean { + this@MainActivity.filePathCallback = filePathCallback + checkPermissionAndPickFile() + return true + } + } webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { view?.postDelayed({ view.evaluateJavascript( "setTimeout(() => {" + - " const input = document.querySelector('input[type=\"text\"], textarea[id*=\"message\"], [role=\"combobox\"]');" + + " const input = document.querySelector('input[type=\"text\"], textarea[id*=\"message\"], [role=\"combobox\"], input[type=\"file\"]');" + " if(input) { input.focus(); input.click(); }" + "}, 100);" ) { _ -> } @@ -59,6 +107,41 @@ class MainActivity : AppCompatActivity() { } } + private fun setupAttachButton() { + attachButton.setOnClickListener { + checkPermissionAndPickFile() + } + } + + private fun checkPermissionAndPickFile() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val permissions = arrayOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO + ) + val notGranted = permissions.filter { + ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED + } + if (notGranted.isEmpty()) { + openFilePicker() + } else { + permissionLauncher.launch(notGranted.toTypedArray()) + } + } else { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + openFilePicker() + } else { + permissionLauncher.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)) + } + } + } + + private fun openFilePicker() { + filePickerLauncher.launch(arrayOf("image/*", "video/*", "audio/*", "application/pdf")) + } + private fun loadUrlFromIntent() { val query = intent?.data?.getQueryParameter("q") val url = if (query != null) { diff --git a/app/src/main/res/drawable/ic_attach.xml b/app/src/main/res/drawable/ic_attach.xml new file mode 100644 index 0000000..7c9d932 --- /dev/null +++ b/app/src/main/res/drawable/ic_attach.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 22c3a4e..f160df8 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,4 +12,17 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aba6527..253b724 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,4 +9,8 @@ Yes No Chat history cleared + Attach file + File attached + No app found to open this file + Storage permission required \ No newline at end of file