2020-08-15 14:52:27 +00:00
|
|
|
package oppen.tva.ui
|
|
|
|
|
2020-08-18 16:17:04 +00:00
|
|
|
import android.content.Context
|
2020-08-15 20:33:44 +00:00
|
|
|
import android.content.Intent
|
2020-08-25 11:57:35 +00:00
|
|
|
import android.media.MediaPlayer
|
2020-08-15 20:33:44 +00:00
|
|
|
import android.net.Uri
|
2020-08-15 14:52:27 +00:00
|
|
|
import android.os.Bundle
|
2020-08-25 11:57:35 +00:00
|
|
|
import android.text.SpannableString
|
|
|
|
import android.text.style.ForegroundColorSpan
|
2020-08-15 14:52:27 +00:00
|
|
|
import android.view.inputmethod.EditorInfo
|
|
|
|
import androidx.activity.viewModels
|
2020-08-18 19:02:16 +00:00
|
|
|
import androidx.appcompat.app.AlertDialog
|
2020-08-15 20:33:44 +00:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2020-08-25 11:57:35 +00:00
|
|
|
import androidx.core.text.set
|
2020-08-15 14:52:27 +00:00
|
|
|
import androidx.databinding.DataBindingUtil
|
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
2020-08-18 10:40:10 +00:00
|
|
|
import com.google.android.material.snackbar.Snackbar
|
2020-08-20 17:24:23 +00:00
|
|
|
import oppen.hideKeyboard
|
2020-08-15 14:52:27 +00:00
|
|
|
import oppen.tva.R
|
2020-08-20 13:55:59 +00:00
|
|
|
import oppen.tva.Tva
|
2020-08-15 14:52:27 +00:00
|
|
|
import oppen.tva.databinding.ActivityTvaBinding
|
|
|
|
import oppen.tva.io.TvaState
|
2020-08-21 15:12:00 +00:00
|
|
|
import oppen.tva.io.gemini.Datasource
|
2020-08-21 15:46:08 +00:00
|
|
|
import oppen.tva.io.gemini.GeminiResponse
|
|
|
|
import oppen.tva.io.gemini.RuntimeCache
|
2020-08-20 13:52:24 +00:00
|
|
|
import oppen.tva.io.history.tabs.TabHistoryInterface
|
|
|
|
import oppen.tva.io.history.uris.HistoryInterface
|
2020-08-25 12:07:20 +00:00
|
|
|
import oppen.tva.ui.audio_player.AudioPlayer
|
2020-08-21 15:12:00 +00:00
|
|
|
import oppen.tva.ui.content_image.ImageDialog
|
2020-08-20 17:40:54 +00:00
|
|
|
import oppen.tva.ui.content_text.TextDialog
|
2020-08-20 14:16:22 +00:00
|
|
|
import oppen.tva.ui.modals_menus.about.AboutDialog
|
|
|
|
import oppen.tva.ui.modals_menus.history.HistoryDialog
|
|
|
|
import oppen.tva.ui.modals_menus.input.InputDialog
|
|
|
|
import oppen.tva.ui.modals_menus.overflow.OverflowPopup
|
|
|
|
import oppen.tva.ui.modals_menus.set_home.SetHomeDialog
|
|
|
|
import oppen.tva.ui.modals_menus.tabs.NewTabPopup
|
|
|
|
import oppen.tva.ui.modals_menus.tabs.TabsDialog
|
2020-08-16 21:30:24 +00:00
|
|
|
import oppen.visibleRetainingSpace
|
2020-08-21 15:46:08 +00:00
|
|
|
import java.io.File
|
|
|
|
import java.io.FileInputStream
|
|
|
|
import java.io.FileOutputStream
|
2020-08-20 17:24:23 +00:00
|
|
|
import java.net.URLEncoder
|
2020-08-15 14:52:27 +00:00
|
|
|
|
2020-08-21 15:46:08 +00:00
|
|
|
const val CREATE_IMAGE_FILE_REQ = 628
|
2020-08-21 15:59:07 +00:00
|
|
|
const val CREATE_AUDIO_FILE_REQ = 629
|
2020-08-15 20:33:44 +00:00
|
|
|
|
2020-08-15 14:52:27 +00:00
|
|
|
class TvaActivity : AppCompatActivity() {
|
|
|
|
|
2020-08-20 14:57:38 +00:00
|
|
|
private var inSearch = false
|
|
|
|
|
2020-08-25 11:57:35 +00:00
|
|
|
private val mediaPlayer = MediaPlayer()
|
|
|
|
|
2020-08-15 14:52:27 +00:00
|
|
|
private val model by viewModels<TvaViewModel>()
|
|
|
|
private lateinit var binding: ActivityTvaBinding
|
2020-08-20 13:52:24 +00:00
|
|
|
private lateinit var history: HistoryInterface
|
2020-08-16 22:21:25 +00:00
|
|
|
private val adapter = GemtextAdapter { uri, longTap, view ->
|
|
|
|
if(longTap){
|
2020-08-17 20:25:39 +00:00
|
|
|
NewTabPopup.show(view){ menuId ->
|
|
|
|
when (menuId) {
|
|
|
|
R.id.link_menu_open_in_new_tab -> {
|
|
|
|
model.newTab(uri)
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 22:21:25 +00:00
|
|
|
}
|
|
|
|
}else{
|
2020-08-20 14:57:38 +00:00
|
|
|
//Reset input text hint after user has been searching
|
|
|
|
if(inSearch) {
|
|
|
|
binding.addressEdit.hint = getString(R.string.main_input_hint)
|
|
|
|
inSearch = false
|
|
|
|
}
|
2020-08-16 22:21:25 +00:00
|
|
|
model.request(uri)
|
|
|
|
}
|
2020-08-15 14:52:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
binding = DataBindingUtil.setContentView(this, R.layout.activity_tva)
|
|
|
|
binding.viewmodel = model
|
|
|
|
binding.lifecycleOwner = this
|
|
|
|
|
|
|
|
binding.gemtextRecycler.layoutManager = LinearLayoutManager(this)
|
|
|
|
binding.gemtextRecycler.adapter = adapter
|
|
|
|
|
2020-08-20 13:52:24 +00:00
|
|
|
history = HistoryInterface.default(this)
|
|
|
|
|
2020-08-21 15:12:00 +00:00
|
|
|
model.initialise(TabHistoryInterface.default(this), Datasource.factory(this)){ state ->
|
2020-08-15 14:52:27 +00:00
|
|
|
when(state){
|
2020-08-18 16:17:04 +00:00
|
|
|
is TvaState.AppQuery -> runOnUiThread{ showAlert("App backdoor/query not implemented yet") }
|
2020-08-20 14:57:38 +00:00
|
|
|
is TvaState.ResponseInput -> runOnUiThread {
|
|
|
|
loadingView(false)
|
|
|
|
InputDialog.show(this, state){ queryAddress ->
|
2020-08-20 14:16:22 +00:00
|
|
|
model.request(queryAddress)
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 21:30:24 +00:00
|
|
|
is TvaState.Requesting -> loadingView(true)
|
2020-08-15 20:33:44 +00:00
|
|
|
is TvaState.NotGeminiRequest -> externalProtocol(state)
|
2020-08-18 19:02:16 +00:00
|
|
|
is TvaState.ResponseError -> showAlert("${GeminiResponse.getCodeString(state.header.code)}: ${state.header.meta}")
|
2020-08-18 16:17:04 +00:00
|
|
|
is TvaState.ResponseGemtext -> renderGemtext(state)
|
2020-08-20 17:40:54 +00:00
|
|
|
is TvaState.ResponseText -> renderText(state)
|
2020-08-21 15:12:00 +00:00
|
|
|
is TvaState.ResponseImage -> renderImage(state)
|
2020-08-21 15:59:07 +00:00
|
|
|
is TvaState.ResponseAudio -> renderAudio(state)
|
2020-08-17 20:25:39 +00:00
|
|
|
is TvaState.TabChange -> binding.tabCount.text = "${state.count}"
|
|
|
|
is TvaState.Blank -> {
|
|
|
|
binding.addressEdit.setText("")
|
|
|
|
adapter.render(arrayListOf())
|
|
|
|
}
|
2020-08-15 14:52:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
binding.addressEdit.setOnEditorActionListener { _, actionId, _ ->
|
|
|
|
when (actionId) {
|
|
|
|
EditorInfo.IME_ACTION_GO -> {
|
2020-08-20 14:57:38 +00:00
|
|
|
val input = binding.addressEdit.text.toString()
|
|
|
|
|
|
|
|
if(input.startsWith("gemini://")){
|
|
|
|
model.request(input)
|
|
|
|
}else{
|
2020-08-20 17:24:23 +00:00
|
|
|
model.request("${Tva.GEMINI_USER_SEARCH_BASE}${URLEncoder.encode(input, "UTF-8")}")
|
2020-08-20 14:57:38 +00:00
|
|
|
}
|
|
|
|
|
2020-08-20 17:24:23 +00:00
|
|
|
binding.addressEdit.hideKeyboard()
|
|
|
|
|
2020-08-15 14:52:27 +00:00
|
|
|
return@setOnEditorActionListener true
|
|
|
|
}
|
|
|
|
else -> return@setOnEditorActionListener false
|
|
|
|
}
|
|
|
|
}
|
2020-08-15 21:12:17 +00:00
|
|
|
|
2020-08-18 16:17:04 +00:00
|
|
|
binding.more.setOnClickListener {
|
|
|
|
OverflowPopup.show(binding.more){menuId ->
|
|
|
|
when (menuId) {
|
2020-08-20 14:57:38 +00:00
|
|
|
R.id.overflow_menu_search -> {
|
|
|
|
binding.addressEdit.hint = getString(R.string.main_input_search_hint)
|
|
|
|
binding.addressEdit.text?.clear()
|
|
|
|
binding.addressEdit.requestFocus()
|
|
|
|
inSearch = true
|
|
|
|
}
|
2020-08-18 16:17:04 +00:00
|
|
|
R.id.overflow_menu_share -> {
|
|
|
|
Intent().apply {
|
|
|
|
action = Intent.ACTION_SEND
|
|
|
|
putExtra(Intent.EXTRA_TEXT, binding.addressEdit.text.toString())
|
|
|
|
type = "text/plain"
|
|
|
|
startActivity(Intent.createChooser(this, null))
|
|
|
|
}
|
|
|
|
}
|
2020-08-20 13:52:24 +00:00
|
|
|
R.id.overflow_menu_reload -> {
|
|
|
|
val address = binding.addressEdit.text.toString()
|
|
|
|
RuntimeCache.remove(address)
|
|
|
|
model.request(address)
|
|
|
|
}
|
|
|
|
R.id.overflow_menu_history -> HistoryDialog.show(this){ historyAddress ->
|
|
|
|
model.request(historyAddress)
|
2020-08-19 11:33:16 +00:00
|
|
|
}
|
2020-08-18 20:21:43 +00:00
|
|
|
R.id.overflow_menu_about -> AboutDialog.show(this)
|
2020-08-18 16:17:04 +00:00
|
|
|
R.id.overflow_menu_set_home -> {
|
2020-08-18 20:21:43 +00:00
|
|
|
SetHomeDialog.show(this, binding.addressEdit.text.toString()){
|
2020-08-18 16:17:04 +00:00
|
|
|
showAlert("Home capsule updated")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
binding.home.setOnClickListener {
|
2020-08-20 14:16:22 +00:00
|
|
|
val prefs = getSharedPreferences("oppen.tva.ui.dialogs.set_home", Context.MODE_PRIVATE)
|
2020-08-20 13:55:59 +00:00
|
|
|
val home = prefs.getString("home", Tva.DEFAULT_HOME_CAPSULE)
|
2020-08-18 16:17:04 +00:00
|
|
|
model.request(home!!)
|
|
|
|
}
|
2020-08-17 20:25:39 +00:00
|
|
|
binding.tabs.setOnClickListener {
|
|
|
|
TabsDialog().show(this, model)
|
|
|
|
}
|
2020-08-15 14:52:27 +00:00
|
|
|
}
|
|
|
|
|
2020-08-20 13:52:24 +00:00
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
|
|
super.onNewIntent(intent)
|
|
|
|
intent?.data.toString().let{model.request(it)}
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:17:04 +00:00
|
|
|
private fun showAlert(message: String) = runOnUiThread{
|
|
|
|
loadingView(false)
|
2020-08-18 19:02:16 +00:00
|
|
|
|
|
|
|
if(message.length > 40){
|
|
|
|
AlertDialog.Builder(this)
|
|
|
|
.setMessage(message)
|
|
|
|
.show()
|
|
|
|
}else {
|
|
|
|
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
|
|
|
|
}
|
2020-08-18 16:17:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun externalProtocol(state: TvaState.NotGeminiRequest) = runOnUiThread {
|
2020-08-16 21:30:24 +00:00
|
|
|
loadingView(false)
|
2020-08-15 20:33:44 +00:00
|
|
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(state.uri.toString()))
|
|
|
|
startActivity(browserIntent)
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:17:04 +00:00
|
|
|
private fun renderGemtext(state: TvaState.ResponseGemtext) = runOnUiThread {
|
2020-08-16 21:30:24 +00:00
|
|
|
loadingView(false)
|
2020-08-25 11:57:35 +00:00
|
|
|
val addressSpan = SpannableString(state.uri.toString())
|
|
|
|
addressSpan.set(0, 9, ForegroundColorSpan(resources.getColor(R.color.protocol_address)))
|
|
|
|
binding.addressEdit.setText(addressSpan)
|
2020-08-18 16:17:04 +00:00
|
|
|
adapter.render(state.lines)
|
2020-08-20 13:52:24 +00:00
|
|
|
|
|
|
|
history.add(state.uri.toString())
|
2020-08-15 14:52:27 +00:00
|
|
|
}
|
2020-08-15 20:20:15 +00:00
|
|
|
|
2020-08-20 17:40:54 +00:00
|
|
|
private fun renderText(state: TvaState.ResponseText) = runOnUiThread {
|
|
|
|
loadingView(false)
|
|
|
|
TextDialog.show(this, state)
|
|
|
|
}
|
|
|
|
|
2020-08-21 15:46:08 +00:00
|
|
|
var imageState: TvaState.ResponseImage? = null
|
2020-08-26 16:13:26 +00:00
|
|
|
var audioState: TvaState.ResponseAudio? = null
|
2020-08-21 15:46:08 +00:00
|
|
|
|
2020-08-21 15:59:07 +00:00
|
|
|
private fun renderAudio(state: TvaState.ResponseAudio) = runOnUiThread {
|
2020-08-25 11:57:35 +00:00
|
|
|
loadingView(false)
|
2020-08-26 16:13:26 +00:00
|
|
|
AudioPlayer.play(this, binding, mediaPlayer, state){ state ->
|
|
|
|
audioState = state
|
|
|
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
|
|
intent.type = "audio/mpeg"
|
|
|
|
intent.putExtra(Intent.EXTRA_TITLE, File(state.uri.path).name)
|
|
|
|
startActivityForResult(intent, CREATE_AUDIO_FILE_REQ)
|
|
|
|
}
|
2020-08-21 15:59:07 +00:00
|
|
|
}
|
|
|
|
|
2020-08-21 15:12:00 +00:00
|
|
|
private fun renderImage(state: TvaState.ResponseImage) = runOnUiThread{
|
|
|
|
loadingView(false)
|
2020-08-21 15:46:08 +00:00
|
|
|
ImageDialog.show(this, state){ state ->
|
|
|
|
imageState = state
|
|
|
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
|
|
intent.type = "image/*"
|
|
|
|
intent.putExtra(Intent.EXTRA_TITLE, File(state.uri.path).name)
|
|
|
|
startActivityForResult(intent, CREATE_IMAGE_FILE_REQ)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
2020-08-26 16:13:26 +00:00
|
|
|
if(resultCode == RESULT_OK && (requestCode == CREATE_IMAGE_FILE_REQ || requestCode == CREATE_AUDIO_FILE_REQ)){
|
|
|
|
if(imageState == null && audioState == null) return
|
|
|
|
|
|
|
|
//todo - tidy this mess up...
|
|
|
|
data?.data?.let{ uri ->
|
|
|
|
val cachedFile = when {
|
|
|
|
imageState != null -> {
|
|
|
|
File(imageState!!.cacheUri.path ?: "")
|
|
|
|
}
|
|
|
|
else -> {
|
|
|
|
File(audioState!!.cacheUri.path ?: "")
|
|
|
|
}
|
|
|
|
}
|
2020-08-21 15:46:08 +00:00
|
|
|
contentResolver.openFileDescriptor(uri, "w")?.use { fileDescriptor ->
|
|
|
|
FileOutputStream(fileDescriptor.fileDescriptor).use { destOutput ->
|
|
|
|
val sourceChannel = FileInputStream(cachedFile).channel
|
|
|
|
val destChannel = destOutput.channel
|
|
|
|
sourceChannel.transferTo(0, sourceChannel.size(), destChannel)
|
|
|
|
sourceChannel.close()
|
|
|
|
destChannel.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-26 16:13:26 +00:00
|
|
|
|
|
|
|
Snackbar.make(binding.root, "File saved to device", Snackbar.LENGTH_SHORT).show()
|
|
|
|
|
|
|
|
imageState = null
|
|
|
|
audioState = null
|
2020-08-21 15:46:08 +00:00
|
|
|
}
|
2020-08-21 15:12:00 +00:00
|
|
|
}
|
|
|
|
|
2020-08-16 21:30:24 +00:00
|
|
|
private fun loadingView(visible: Boolean) = runOnUiThread {
|
|
|
|
binding.progressBar.visibleRetainingSpace(visible)
|
|
|
|
if(visible) binding.appBar.setExpanded(true)
|
|
|
|
}
|
|
|
|
|
2020-08-15 20:20:15 +00:00
|
|
|
@ExperimentalStdlibApi
|
|
|
|
override fun onBackPressed() {
|
|
|
|
if(model.canGoBack()){
|
|
|
|
model.goBack()
|
|
|
|
}else{
|
|
|
|
super.onBackPressed()
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 20:06:11 +00:00
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
super.onDestroy()
|
|
|
|
model.persistTabState()
|
|
|
|
}
|
2020-08-15 14:52:27 +00:00
|
|
|
}
|