mirror of https://git.sr.ht/~oppen/ariane
file download...
This commit is contained in:
parent
08842fd316
commit
3ff4758bbe
|
@ -39,6 +39,17 @@
|
||||||
android:label="@string/settings"
|
android:label="@string/settings"
|
||||||
android:theme="@style/SettingsTheme"/>
|
android:theme="@style/SettingsTheme"/>
|
||||||
|
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -14,6 +14,8 @@ sealed class GemState {
|
||||||
data class ResponseText(val uri: URI, val header: GeminiResponse.Header, val content: String) : GemState()
|
data class ResponseText(val uri: URI, val header: GeminiResponse.Header, val content: String) : GemState()
|
||||||
data class ResponseImage(val uri: URI, val header: GeminiResponse.Header, val cacheUri: Uri) : GemState()
|
data class ResponseImage(val uri: URI, val header: GeminiResponse.Header, val cacheUri: Uri) : GemState()
|
||||||
data class ResponseAudio(val uri: URI, val header: GeminiResponse.Header, val cacheUri: Uri) : GemState()
|
data class ResponseAudio(val uri: URI, val header: GeminiResponse.Header, val cacheUri: Uri) : GemState()
|
||||||
|
data class ResponseBinary(val uri: URI, val header: GeminiResponse.Header, val cacheUri: Uri) : GemState()
|
||||||
|
data class ResponseUnknownMime(val uri: URI, val header: GeminiResponse.Header) : GemState()
|
||||||
data class ResponseError(val header: GeminiResponse.Header): GemState()
|
data class ResponseError(val header: GeminiResponse.Header): GemState()
|
||||||
|
|
||||||
object Blank: GemState()
|
object Blank: GemState()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import oppen.ariane.io.GemState
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
interface Datasource {
|
interface Datasource {
|
||||||
|
fun request(uri: URI, forceDownload: Boolean, onUpdate: (state: GemState) -> Unit)
|
||||||
fun request(uri: URI, onUpdate: (state: GemState) -> Unit)
|
fun request(uri: URI, onUpdate: (state: GemState) -> Unit)
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
|
|
|
@ -16,12 +16,6 @@ import javax.net.ssl.*
|
||||||
|
|
||||||
const val GEMINI_SCHEME = "gemini"
|
const val GEMINI_SCHEME = "gemini"
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param protocol see: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class GeminiDatasource(
|
class GeminiDatasource(
|
||||||
private val context: Context): Datasource {
|
private val context: Context): Datasource {
|
||||||
|
|
||||||
|
@ -30,8 +24,12 @@ class GeminiDatasource(
|
||||||
|
|
||||||
private val addressBuilder = AddressBuilder()
|
private val addressBuilder = AddressBuilder()
|
||||||
|
|
||||||
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) {
|
private var forceDownload = false
|
||||||
|
|
||||||
|
override fun request(uri: URI, onUpdate: (state: GemState) -> Unit) = request(uri, false, onUpdate)
|
||||||
|
|
||||||
|
override fun request(uri: URI, forceDownload: Boolean, onUpdate: (state: GemState) -> Unit) {
|
||||||
|
this.forceDownload = forceDownload
|
||||||
//Any inputted uri starting with a colon is an app-specific command, eg. :prefs :settings
|
//Any inputted uri starting with a colon is an app-specific command, eg. :prefs :settings
|
||||||
if(uri.toString().startsWith(":")){
|
if(uri.toString().startsWith(":")){
|
||||||
onUpdate(GemState.AppQuery(uri))
|
onUpdate(GemState.AppQuery(uri))
|
||||||
|
@ -63,15 +61,9 @@ class GeminiDatasource(
|
||||||
val cached = RuntimeCache.get(parsedUri)
|
val cached = RuntimeCache.get(parsedUri)
|
||||||
if(cached != null){
|
if(cached != null){
|
||||||
last = parsedUri.toURI()
|
last = parsedUri.toURI()
|
||||||
onUpdate(
|
onUpdate(GemState.ResponseGemtext(parsedUri.toURI(), cached.first, cached.second))
|
||||||
GemState.ResponseGemtext(
|
|
||||||
parsedUri.toURI(),
|
|
||||||
cached.first,
|
|
||||||
cached.second
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}else{
|
}else{
|
||||||
request(parsedUri.toURI(), onUpdate)
|
request(parsedUri.toURI(), forceDownload, onUpdate)
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
onUpdate(GemState.NotGeminiRequest(uri))
|
onUpdate(GemState.NotGeminiRequest(uri))
|
||||||
|
@ -97,10 +89,9 @@ class GeminiDatasource(
|
||||||
println("REQ_PROTOCOL: $protocol")
|
println("REQ_PROTOCOL: $protocol")
|
||||||
|
|
||||||
//todo - extract and reuse this
|
//todo - extract and reuse this
|
||||||
val sslContext = if(protocol == "TLS_ALL"){
|
val sslContext = when (protocol) {
|
||||||
SSLContext.getInstance("TLS")
|
"TLS_ALL" -> SSLContext.getInstance("TLS")
|
||||||
}else{
|
else -> SSLContext.getInstance(protocol)
|
||||||
SSLContext.getInstance(protocol)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sslContext.init(null, DummyTrustManager.get(), null)
|
sslContext.init(null, DummyTrustManager.get(), null)
|
||||||
|
@ -119,25 +110,11 @@ class GeminiDatasource(
|
||||||
socket.startHandshake()
|
socket.startHandshake()
|
||||||
}catch (ce: ConnectException){
|
}catch (ce: ConnectException){
|
||||||
println("socket error: $ce")
|
println("socket error: $ce")
|
||||||
onUpdate(
|
onUpdate(GemState.ResponseError(GeminiResponse.Header(-1, ce.message ?: ce.toString())))
|
||||||
GemState.ResponseError(
|
|
||||||
GeminiResponse.Header(
|
|
||||||
-1,
|
|
||||||
ce.message ?: ce.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}catch (she: SSLHandshakeException){
|
}catch (she: SSLHandshakeException){
|
||||||
println("socket error: $she")
|
println("socket error: $she")
|
||||||
onUpdate(
|
onUpdate(GemState.ResponseError(GeminiResponse.Header(-2, she.message ?: she.toString())))
|
||||||
GemState.ResponseError(
|
|
||||||
GeminiResponse.Header(
|
|
||||||
-2,
|
|
||||||
she.message ?: she.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,14 +128,7 @@ class GeminiDatasource(
|
||||||
outWriter.flush()
|
outWriter.flush()
|
||||||
|
|
||||||
if (outWriter.checkError()) {
|
if (outWriter.checkError()) {
|
||||||
onUpdate(
|
onUpdate(GemState.ResponseError(GeminiResponse.Header(-1, "Print Writer Error")))
|
||||||
GemState.ResponseError(
|
|
||||||
GeminiResponse.Header(
|
|
||||||
-1,
|
|
||||||
"Print Writer Error"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
outWriter.close()
|
outWriter.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -176,18 +146,21 @@ class GeminiDatasource(
|
||||||
|
|
||||||
when {
|
when {
|
||||||
header.code == GeminiResponse.INPUT -> onUpdate(GemState.ResponseInput(uri, header))
|
header.code == GeminiResponse.INPUT -> onUpdate(GemState.ResponseInput(uri, header))
|
||||||
header.code == GeminiResponse.REDIRECT -> request(URI.create(header.meta), onUpdate)
|
header.code == GeminiResponse.REDIRECT -> request(URI.create(header.meta), forceDownload, onUpdate)
|
||||||
header.code != GeminiResponse.SUCCESS -> onUpdate(GemState.ResponseError(header))
|
header.code != GeminiResponse.SUCCESS -> onUpdate(GemState.ResponseError(header))
|
||||||
header.meta.startsWith("text/gemini") -> getGemtext(
|
header.meta.startsWith("text/gemini") -> getGemtext(bufferedReader, uri, header, onUpdate)
|
||||||
bufferedReader,
|
|
||||||
uri,
|
|
||||||
header,
|
|
||||||
onUpdate
|
|
||||||
)
|
|
||||||
header.meta.startsWith("text/") -> getString(socket, uri, header, onUpdate)
|
header.meta.startsWith("text/") -> getString(socket, uri, header, onUpdate)
|
||||||
header.meta.startsWith("image/") -> getBinary(socket, uri, header, onUpdate)
|
header.meta.startsWith("image/") -> getBinary(socket, uri, header, onUpdate)
|
||||||
header.meta.startsWith("audio/") -> getBinary(socket, uri, header, onUpdate)
|
header.meta.startsWith("audio/") -> getBinary(socket, uri, header, onUpdate)
|
||||||
else -> onUpdate(GemState.ResponseError(header))
|
else -> {
|
||||||
|
//File served over Gemini but not handled in-app, eg .pdf
|
||||||
|
if(forceDownload){
|
||||||
|
getBinary(socket, uri, header, onUpdate)
|
||||||
|
}else{
|
||||||
|
onUpdate(GemState.ResponseUnknownMime(uri, header))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close input
|
//Close input
|
||||||
|
@ -202,12 +175,7 @@ class GeminiDatasource(
|
||||||
socket.close()
|
socket.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGemtext(
|
private fun getGemtext(reader: BufferedReader, uri: URI, header: GeminiResponse.Header, onUpdate: (state: GemState) -> Unit){
|
||||||
reader: BufferedReader,
|
|
||||||
uri: URI,
|
|
||||||
header: GeminiResponse.Header,
|
|
||||||
onUpdate: (state: GemState) -> Unit
|
|
||||||
){
|
|
||||||
|
|
||||||
val lines = mutableListOf<String>()
|
val lines = mutableListOf<String>()
|
||||||
|
|
||||||
|
@ -222,28 +190,21 @@ class GeminiDatasource(
|
||||||
onUpdate(GemState.ResponseGemtext(uri, header, processed))
|
onUpdate(GemState.ResponseGemtext(uri, header, processed))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getString(
|
private fun getString(socket: SSLSocket?, uri: URI, header: GeminiResponse.Header, onUpdate: (state: GemState) -> Unit){
|
||||||
socket: SSLSocket?,
|
val content = socket?.inputStream?.bufferedReader().use {
|
||||||
uri: URI,
|
reader -> reader?.readText()
|
||||||
header: GeminiResponse.Header,
|
}
|
||||||
onUpdate: (state: GemState) -> Unit
|
|
||||||
){
|
|
||||||
val content = socket?.inputStream?.bufferedReader().use { reader -> reader?.readText() }
|
|
||||||
socket?.close()
|
socket?.close()
|
||||||
onUpdate(GemState.ResponseText(uri, header, content ?: "Error fetching content"))
|
onUpdate(GemState.ResponseText(uri, header, content ?: "Error fetching content"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBinary(
|
private fun getBinary(socket: SSLSocket?, uri: URI, header: GeminiResponse.Header, onUpdate: (state: GemState) -> Unit){
|
||||||
socket: SSLSocket?,
|
|
||||||
uri: URI,
|
|
||||||
header: GeminiResponse.Header,
|
|
||||||
onUpdate: (state: GemState) -> Unit
|
|
||||||
){
|
|
||||||
|
|
||||||
var filename: String? = null
|
var filename: String? = null
|
||||||
val fileSegmentIndex: Int = uri.path.lastIndexOf('/')
|
val fileSegmentIndex: Int = uri.path.lastIndexOf('/')
|
||||||
if (fileSegmentIndex != -1) {
|
|
||||||
filename = uri.path.substring(fileSegmentIndex + 1)
|
when {
|
||||||
|
fileSegmentIndex != -1 -> filename = uri.path.substring(fileSegmentIndex + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val host = uri.host.replace(".", "_")
|
val host = uri.host.replace(".", "_")
|
||||||
|
@ -255,22 +216,10 @@ class GeminiDatasource(
|
||||||
when {
|
when {
|
||||||
cacheFile.exists() -> {
|
cacheFile.exists() -> {
|
||||||
when {
|
when {
|
||||||
header.meta.startsWith("image/") -> onUpdate(
|
header.meta.startsWith("image/") -> onUpdate(GemState.ResponseImage(uri, header, cacheFile.toUri()))
|
||||||
GemState.ResponseImage(
|
header.meta.startsWith("audio/") -> onUpdate(GemState.ResponseAudio(uri, header, cacheFile.toUri()))
|
||||||
uri,
|
else -> onUpdate(GemState.ResponseBinary(uri, header, cacheFile.toUri()))
|
||||||
header,
|
|
||||||
cacheFile.toUri()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
header.meta.startsWith("audio/") -> onUpdate(
|
|
||||||
GemState.ResponseAudio(
|
|
||||||
uri,
|
|
||||||
header,
|
|
||||||
cacheFile.toUri()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
cacheFile.createNewFile()
|
cacheFile.createNewFile()
|
||||||
|
@ -280,20 +229,9 @@ class GeminiDatasource(
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
header.meta.startsWith("image/") -> onUpdate(
|
header.meta.startsWith("image/") -> onUpdate(GemState.ResponseImage(uri, header, cacheFile.toUri()))
|
||||||
GemState.ResponseImage(
|
header.meta.startsWith("audio/") -> onUpdate(GemState.ResponseAudio(uri, header, cacheFile.toUri()))
|
||||||
uri,
|
else -> onUpdate(GemState.ResponseBinary(uri, header, cacheFile.toUri()))
|
||||||
header,
|
|
||||||
cacheFile.toUri()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
header.meta.startsWith("audio/") -> onUpdate(
|
|
||||||
GemState.ResponseAudio(
|
|
||||||
uri,
|
|
||||||
header,
|
|
||||||
cacheFile.toUri()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package oppen.ariane.ui
|
package oppen.ariane.ui
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -34,6 +36,7 @@ import oppen.ariane.ui.modals_menus.input.InputDialog
|
||||||
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
|
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
|
||||||
import oppen.ariane.ui.settings.SettingsActivity
|
import oppen.ariane.ui.settings.SettingsActivity
|
||||||
import oppen.hideKeyboard
|
import oppen.hideKeyboard
|
||||||
|
import oppen.toURI
|
||||||
import oppen.visibleRetainingSpace
|
import oppen.visibleRetainingSpace
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
@ -42,6 +45,7 @@ import java.net.URLEncoder
|
||||||
|
|
||||||
const val CREATE_IMAGE_FILE_REQ = 628
|
const val CREATE_IMAGE_FILE_REQ = 628
|
||||||
const val CREATE_AUDIO_FILE_REQ = 629
|
const val CREATE_AUDIO_FILE_REQ = 629
|
||||||
|
const val CREATE_BINARY_FILE_REQ = 630
|
||||||
|
|
||||||
class GemActivity : AppCompatActivity() {
|
class GemActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -126,10 +130,25 @@ class GemActivity : AppCompatActivity() {
|
||||||
is GemState.ResponseText -> renderText(state)
|
is GemState.ResponseText -> renderText(state)
|
||||||
is GemState.ResponseImage -> renderImage(state)
|
is GemState.ResponseImage -> renderImage(state)
|
||||||
is GemState.ResponseAudio -> renderAudio(state)
|
is GemState.ResponseAudio -> renderAudio(state)
|
||||||
|
is GemState.ResponseBinary -> renderBinary(state)
|
||||||
is GemState.Blank -> {
|
is GemState.Blank -> {
|
||||||
binding.addressEdit.setText("")
|
binding.addressEdit.setText("")
|
||||||
adapter.render(arrayListOf())
|
adapter.render(arrayListOf())
|
||||||
}
|
}
|
||||||
|
is GemState.ResponseUnknownMime -> {
|
||||||
|
runOnUiThread {
|
||||||
|
loadingView(false)
|
||||||
|
AlertDialog.Builder(this, R.style.AppDialogTheme)
|
||||||
|
.setTitle(R.string.unknown_mime_dialog_title)
|
||||||
|
.setMessage("Address: ${state.uri}\nMeta: ${state.header.meta}")
|
||||||
|
.setPositiveButton("Download") { _, _ ->
|
||||||
|
loadingView(true)
|
||||||
|
model.requestBinaryDownload(state.uri)
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { _, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,17 +157,9 @@ class GemActivity : AppCompatActivity() {
|
||||||
EditorInfo.IME_ACTION_GO -> {
|
EditorInfo.IME_ACTION_GO -> {
|
||||||
val input = binding.addressEdit.text.toString()
|
val input = binding.addressEdit.text.toString()
|
||||||
|
|
||||||
if (input.startsWith("gemini://")) {
|
when {
|
||||||
model.request(input)
|
input.startsWith("gemini://") -> model.request(input)
|
||||||
} else {
|
else -> model.request("${Ariane.GEMINI_USER_SEARCH_BASE}${URLEncoder.encode(input, "UTF-8")}")
|
||||||
model.request(
|
|
||||||
"${Ariane.GEMINI_USER_SEARCH_BASE}${
|
|
||||||
URLEncoder.encode(
|
|
||||||
input,
|
|
||||||
"UTF-8"
|
|
||||||
)
|
|
||||||
}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.addressEdit.hideKeyboard()
|
binding.addressEdit.hideKeyboard()
|
||||||
|
@ -310,6 +321,7 @@ class GemActivity : AppCompatActivity() {
|
||||||
|
|
||||||
var imageState: GemState.ResponseImage? = null
|
var imageState: GemState.ResponseImage? = null
|
||||||
var audioState: GemState.ResponseAudio? = null
|
var audioState: GemState.ResponseAudio? = null
|
||||||
|
var binaryState: GemState.ResponseBinary? = null
|
||||||
|
|
||||||
private fun renderAudio(state: GemState.ResponseAudio) = runOnUiThread {
|
private fun renderAudio(state: GemState.ResponseAudio) = runOnUiThread {
|
||||||
loadingView(false)
|
loadingView(false)
|
||||||
|
@ -335,36 +347,57 @@ class GemActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderBinary(state: GemState.ResponseBinary) = runOnUiThread{
|
||||||
|
loadingView(false)
|
||||||
|
binaryState = state
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
intent.type = state.header.meta
|
||||||
|
intent.putExtra(Intent.EXTRA_TITLE, File(state.uri.path).name)
|
||||||
|
startActivityForResult(intent, CREATE_BINARY_FILE_REQ)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
if(resultCode == RESULT_OK && (requestCode == CREATE_IMAGE_FILE_REQ || requestCode == CREATE_AUDIO_FILE_REQ)){
|
if(resultCode == RESULT_OK && (requestCode == CREATE_IMAGE_FILE_REQ || requestCode == CREATE_AUDIO_FILE_REQ || requestCode == CREATE_BINARY_FILE_REQ)){
|
||||||
if(imageState == null && audioState == null) return
|
|
||||||
|
|
||||||
//todo - tidy this mess up...
|
//todo - tidy this mess up...
|
||||||
|
if(imageState == null && audioState == null && binaryState == null) return
|
||||||
data?.data?.let{ uri ->
|
data?.data?.let{ uri ->
|
||||||
val cachedFile = when {
|
val cachedFile = when {
|
||||||
imageState != null -> {
|
imageState != null -> File(imageState!!.cacheUri.path ?: "")
|
||||||
File(imageState!!.cacheUri.path ?: "")
|
audioState != null -> File(audioState!!.cacheUri.path ?: "")
|
||||||
}
|
binaryState != null -> File(binaryState!!.cacheUri.path ?: "")
|
||||||
else -> {
|
else -> {
|
||||||
File(audioState!!.cacheUri.path ?: "")
|
println("File download error - no state object exists")
|
||||||
|
showAlert("File download error - no state object exists")
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentResolver.openFileDescriptor(uri, "w")?.use { fileDescriptor ->
|
|
||||||
FileOutputStream(fileDescriptor.fileDescriptor).use { destOutput ->
|
cachedFile?.let{
|
||||||
val sourceChannel = FileInputStream(cachedFile).channel
|
contentResolver.openFileDescriptor(uri, "w")?.use { fileDescriptor ->
|
||||||
val destChannel = destOutput.channel
|
FileOutputStream(fileDescriptor.fileDescriptor).use { destOutput ->
|
||||||
sourceChannel.transferTo(0, sourceChannel.size(), destChannel)
|
val sourceChannel = FileInputStream(cachedFile).channel
|
||||||
sourceChannel.close()
|
val destChannel = destOutput.channel
|
||||||
destChannel.close()
|
sourceChannel.transferTo(0, sourceChannel.size(), destChannel)
|
||||||
|
sourceChannel.close()
|
||||||
|
destChannel.close()
|
||||||
|
|
||||||
|
cachedFile.deleteOnExit()
|
||||||
|
|
||||||
|
if(binaryState != null){
|
||||||
|
startActivity(Intent(DownloadManager.ACTION_VIEW_DOWNLOADS))
|
||||||
|
}else{
|
||||||
|
Snackbar.make(binding.root, "File saved to device", Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Snackbar.make(binding.root, "File saved to device", Snackbar.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
imageState = null
|
imageState = null
|
||||||
audioState = null
|
audioState = null
|
||||||
|
binaryState = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,13 @@ class GemViewModel: ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requestBinaryDownload(uri: URI) {
|
||||||
|
gemini.request(uri, true){ state ->
|
||||||
|
onState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo - same action as above... refactor
|
||||||
fun requestInlineImage(uri: URI, onImageReady: (cacheUri: Uri?) -> Unit){
|
fun requestInlineImage(uri: URI, onImageReady: (cacheUri: Uri?) -> Unit){
|
||||||
gemini.request(uri){ state ->
|
gemini.request(uri){ state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package oppen.ariane.ui.settings
|
package oppen.ariane.ui.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.*
|
import androidx.preference.*
|
||||||
import oppen.ariane.Ariane
|
import oppen.ariane.Ariane
|
||||||
|
@ -16,7 +14,6 @@ import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.SSLSocket
|
import javax.net.ssl.SSLSocket
|
||||||
import javax.net.ssl.SSLSocketFactory
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
|
||||||
|
|
||||||
class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
|
class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
|
||||||
|
|
||||||
lateinit var protocols: Array<String>
|
lateinit var protocols: Array<String>
|
||||||
|
|
|
@ -17,4 +17,6 @@
|
||||||
|
|
||||||
<style name="DayNightDialog" parent="Theme.AppCompat.DayNight.Dialog.Alert"/>
|
<style name="DayNightDialog" parent="Theme.AppCompat.DayNight.Dialog.Alert"/>
|
||||||
|
|
||||||
|
<style name="PrefsDialogTheme"></style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -36,4 +36,5 @@
|
||||||
<string name="delete">Delete</string>
|
<string name="delete">Delete</string>
|
||||||
<string name="move_down">Move down</string>
|
<string name="move_down">Move down</string>
|
||||||
<string name="move_up">Move up</string>
|
<string name="move_up">Move up</string>
|
||||||
|
<string name="unknown_mime_dialog_title">Unknown Mime Type</string>
|
||||||
</resources>
|
</resources>
|
|
@ -21,6 +21,13 @@
|
||||||
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
|
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="AppDialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
|
||||||
|
<item name="colorPrimary">@color/colorAccent</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorAccent</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
<item name="textAllCaps">false</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="FSDialog" parent="@style/AppTheme">
|
<style name="FSDialog" parent="@style/AppTheme">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<external-path name="external_files" path="."/>
|
||||||
|
</paths>
|
Loading…
Reference in New Issue