mirror of https://git.sr.ht/~oppen/ariane
numerous fixes
This commit is contained in:
parent
4ea369610a
commit
e58deb28c3
|
@ -1,16 +1,7 @@
|
||||||
package oppen
|
package oppen
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
||||||
fun String.alert(context: Context){
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setMessage(this)
|
|
||||||
.setPositiveButton("OK"){_, _ ->}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun View.visible(visible: Boolean) = when {
|
fun View.visible(visible: Boolean) = when {
|
||||||
visible -> this.visibility = View.VISIBLE
|
visible -> this.visibility = View.VISIBLE
|
||||||
else -> this.visibility = View.GONE
|
else -> this.visibility = View.GONE
|
||||||
|
|
|
@ -22,39 +22,50 @@ class GeminiDatasource: Datasource{
|
||||||
|
|
||||||
when (uri.scheme) {
|
when (uri.scheme) {
|
||||||
GEMINI_SCHEME -> {
|
GEMINI_SCHEME -> {
|
||||||
//Indicate app should show progress indicator
|
|
||||||
onUpdate(TvaState.Requesting(uri))
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
val cached = RuntimeCache.get(uri)
|
||||||
geminiRequest(uri, onUpdate)
|
if(cached != null){
|
||||||
|
last = uri
|
||||||
|
onUpdate(TvaState.ResponseGemtext(uri, cached.first, cached.second))
|
||||||
|
return
|
||||||
|
}else{
|
||||||
|
onUpdate(TvaState.Requesting(uri))
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
geminiRequest(uri, onUpdate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val address = uri.toString()
|
val address = uri.toString()
|
||||||
when {
|
val parsedUri = when {
|
||||||
address.startsWith("//") -> {
|
address.startsWith("//") -> {
|
||||||
//just missing protocol
|
//just missing protocol
|
||||||
onUpdate(TvaState.Requesting(uri))
|
URI.create("gemini:$address")
|
||||||
request(URI.create("gemini:$address"), onUpdate)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
address.startsWith("/") -> {
|
address.startsWith("/") -> {
|
||||||
//internal navigation
|
//internal navigation
|
||||||
val internalNav = "gemini://${last?.host}$address"
|
val internalNav = "gemini://${last?.host}$address"
|
||||||
onUpdate(TvaState.Requesting(uri))
|
URI.create(internalNav)
|
||||||
request(URI.create(internalNav), onUpdate)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
!address.contains("://") -> {
|
!address.contains("://") -> {
|
||||||
//looks like a relative link
|
//looks like a relative link
|
||||||
val lastAddress = last.toString()
|
val lastAddress = last.toString()
|
||||||
val relAddress = "${lastAddress.substring(0, lastAddress.lastIndexOf("/") + 1)}$address"
|
val relAddress = "${lastAddress.substring(0, lastAddress.lastIndexOf("/") + 1)}$address"
|
||||||
onUpdate(TvaState.Requesting(uri))
|
URI.create(relAddress)
|
||||||
request(URI.create(relAddress), onUpdate)
|
}
|
||||||
|
else -> {
|
||||||
|
onUpdate(TvaState.NotGeminiRequest(uri))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
else -> onUpdate(TvaState.NotGeminiRequest(uri))
|
}
|
||||||
|
|
||||||
|
val cached = RuntimeCache.get(parsedUri)
|
||||||
|
if(cached != null){
|
||||||
|
last = parsedUri
|
||||||
|
onUpdate(TvaState.ResponseGemtext(parsedUri, cached.first, cached.second))
|
||||||
|
}else{
|
||||||
|
request(parsedUri, onUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,31 +98,56 @@ class GeminiDatasource: Datasource{
|
||||||
outWriter.flush()
|
outWriter.flush()
|
||||||
|
|
||||||
if (outWriter.checkError()) {
|
if (outWriter.checkError()) {
|
||||||
onUpdate(TvaState.GeminiPrintWriterError)
|
onUpdate(TvaState.ResponseError(GeminiResponse.Header(-1, "Print Writer Error")))
|
||||||
outWriter.close()
|
outWriter.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outputStreamWriter.close()
|
||||||
|
bufferedWriter.close()
|
||||||
|
outWriter.close()
|
||||||
|
|
||||||
// IN <<<<<<<<<<<<<<<<<<<<<<<<<<<
|
// IN <<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||||
|
|
||||||
|
var headerLine = ""
|
||||||
|
InputStreamReader(socket.inputStream).use{ streamReader ->
|
||||||
|
BufferedReader(streamReader).use{ bufferedReader ->
|
||||||
|
headerLine = bufferedReader.readLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Tva: header: $headerLine")
|
||||||
|
|
||||||
|
val header = GeminiResponse.parseHeader(headerLine)
|
||||||
|
|
||||||
|
when {
|
||||||
|
header.code != GeminiResponse.SUCCESS -> onUpdate(TvaState.ResponseError(header))
|
||||||
|
header.meta == "text/gemini" -> getGemtext(socket, uri, header, onUpdate)
|
||||||
|
header.meta.startsWith("text/") -> getString(socket, uri, header, onUpdate)
|
||||||
|
else -> onUpdate(TvaState.ResponseError(header))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getGemtext(socket: SSLSocket, uri: URI, header: GeminiResponse.Header, onUpdate: (state: TvaState) -> Unit){
|
||||||
|
|
||||||
val lines = mutableListOf<String>()
|
val lines = mutableListOf<String>()
|
||||||
|
|
||||||
socket.inputStream.reader().use { inputStreamReader ->
|
socket.inputStream.reader().use { inputStreamReader ->
|
||||||
BufferedReader(inputStreamReader).use { reader ->
|
BufferedReader(inputStreamReader).use { reader ->
|
||||||
lines.addAll(reader.readLines())
|
lines.addAll(reader.readLines())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val header = lines.firstOrNull() ?: ""
|
socket.close()
|
||||||
lines.removeAt(0)
|
|
||||||
|
|
||||||
println("Tva: header: $header")
|
val processed = GemtextHelper.findCodeBlocks(lines)
|
||||||
|
RuntimeCache.put(uri, header, processed)
|
||||||
|
onUpdate(TvaState.ResponseGemtext(uri, header, processed))
|
||||||
|
}
|
||||||
|
|
||||||
outputStreamWriter.close()
|
private fun getString(socket: SSLSocket, uri: URI, header: GeminiResponse.Header, onUpdate: (state: TvaState) -> Unit){
|
||||||
bufferedWriter.close()
|
val content = socket.inputStream.bufferedReader().use { reader -> reader.readText() }
|
||||||
outWriter.close()
|
|
||||||
|
|
||||||
|
socket.close()
|
||||||
|
|
||||||
|
|
||||||
onUpdate(TvaState.GeminiResponse(uri, header, GemtextHelper.findCodeBlocks(lines)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package oppen.tva.io
|
||||||
|
|
||||||
|
object GeminiResponse {
|
||||||
|
|
||||||
|
const val INPUT = 1
|
||||||
|
const val SUCCESS = 2
|
||||||
|
const val REDIRECT = 3
|
||||||
|
const val TEMPORARY_FAILURE = 4
|
||||||
|
const val PERMANENT_FAILURE = 5
|
||||||
|
const val CLIENT_CERTIFICATE_REQUIRED = 6
|
||||||
|
const val UNKNOWN = -1
|
||||||
|
|
||||||
|
fun parseHeader(header: String): Header{
|
||||||
|
val segments = header.trim().split(" ")
|
||||||
|
val meta = if(segments.size > 1){
|
||||||
|
segments[1]
|
||||||
|
}else{
|
||||||
|
"No meta/mime type"
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
segments.first().startsWith("1") -> Header(INPUT, meta)
|
||||||
|
segments.first().startsWith("2") -> Header(SUCCESS, meta)
|
||||||
|
segments.first().startsWith("3") -> Header(REDIRECT, meta)
|
||||||
|
segments.first().startsWith("4") -> Header(TEMPORARY_FAILURE, meta)
|
||||||
|
segments.first().startsWith("5") -> Header(PERMANENT_FAILURE, meta)
|
||||||
|
segments.first().startsWith("6") -> Header(CLIENT_CERTIFICATE_REQUIRED, meta)
|
||||||
|
else -> Header(UNKNOWN, meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Header(val code: Int, val meta: String)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package oppen.tva.io
|
||||||
|
|
||||||
|
import androidx.collection.LruCache
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object RuntimeCache {
|
||||||
|
|
||||||
|
private const val CACHE_SIZE = 4 * 1024 * 1024
|
||||||
|
private val lruCache = LruCache<String, Pair<GeminiResponse.Header, List<String>>>(CACHE_SIZE)
|
||||||
|
|
||||||
|
fun put(uri: URI, header: GeminiResponse.Header, lines: List<String>){
|
||||||
|
lruCache.put(uri.toString(), Pair(header, lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(uri: URI): Pair<GeminiResponse.Header, List<String>>? = lruCache[uri.toString()]
|
||||||
|
}
|
|
@ -6,8 +6,11 @@ sealed class TvaState {
|
||||||
data class AppQuery(val uri: URI): TvaState()
|
data class AppQuery(val uri: URI): TvaState()
|
||||||
data class Requesting(val uri: URI): TvaState()
|
data class Requesting(val uri: URI): TvaState()
|
||||||
data class NotGeminiRequest(val uri: URI) : TvaState()
|
data class NotGeminiRequest(val uri: URI) : TvaState()
|
||||||
data class GeminiResponse(val uri: URI, val header: String, val lines: List<String>) : TvaState()
|
|
||||||
|
data class ResponseGemtext(val uri: URI, val header: GeminiResponse.Header, val lines: List<String>) : TvaState()
|
||||||
|
data class ResponseText(val uri: URI, val header: GeminiResponse.Header, val content: String) : TvaState()
|
||||||
|
data class ResponseError(val header: GeminiResponse.Header): TvaState()
|
||||||
|
|
||||||
data class TabChange(val count: Int) : TvaState()
|
data class TabChange(val count: Int) : TvaState()
|
||||||
object Blank: TvaState()
|
object Blank: TvaState()
|
||||||
object GeminiPrintWriterError : TvaState()
|
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
package oppen.tva.ui
|
package oppen.tva.ui
|
||||||
|
|
||||||
import android.R.attr.label
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -12,11 +12,12 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import oppen.alert
|
|
||||||
import oppen.tva.R
|
import oppen.tva.R
|
||||||
import oppen.tva.databinding.ActivityTvaBinding
|
import oppen.tva.databinding.ActivityTvaBinding
|
||||||
import oppen.tva.io.TvaState
|
import oppen.tva.io.TvaState
|
||||||
import oppen.tva.io.history.CacheInterface
|
import oppen.tva.io.history.CacheInterface
|
||||||
|
import oppen.tva.ui.overflow.OverflowPopup
|
||||||
|
import oppen.tva.ui.set_home.SetHome
|
||||||
import oppen.tva.ui.tabs.NewTabPopup
|
import oppen.tva.ui.tabs.NewTabPopup
|
||||||
import oppen.tva.ui.tabs.TabsDialog
|
import oppen.tva.ui.tabs.TabsDialog
|
||||||
import oppen.visibleRetainingSpace
|
import oppen.visibleRetainingSpace
|
||||||
|
@ -33,19 +34,6 @@ class TvaActivity : AppCompatActivity() {
|
||||||
R.id.link_menu_open_in_new_tab -> {
|
R.id.link_menu_open_in_new_tab -> {
|
||||||
model.newTab(uri)
|
model.newTab(uri)
|
||||||
}
|
}
|
||||||
R.id.link_menu_copy -> {
|
|
||||||
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
val clip = ClipData.newPlainText(getString(R.string.gemini_address), uri.toString())
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
Snackbar.make(binding.root, getString(R.string.address_copied_to_clipboard), Snackbar.LENGTH_SHORT).setAction(R.string.share) {
|
|
||||||
Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
putExtra(Intent.EXTRA_TEXT, uri.toString())
|
|
||||||
type = "text/plain"
|
|
||||||
startActivity(Intent.createChooser(this, null))
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
@ -65,16 +53,17 @@ class TvaActivity : AppCompatActivity() {
|
||||||
|
|
||||||
model.initialise(CacheInterface.default(this)){ state ->
|
model.initialise(CacheInterface.default(this)){ state ->
|
||||||
when(state){
|
when(state){
|
||||||
is TvaState.AppQuery -> TODO()
|
is TvaState.AppQuery -> runOnUiThread{ showAlert("App backdoor/query not implemented yet") }
|
||||||
is TvaState.Requesting -> loadingView(true)
|
is TvaState.Requesting -> loadingView(true)
|
||||||
is TvaState.NotGeminiRequest -> externalProtocol(state)
|
is TvaState.NotGeminiRequest -> externalProtocol(state)
|
||||||
is TvaState.GeminiResponse -> renderGemtext(state)
|
is TvaState.ResponseError -> showAlert("${state.header.code}: ${state.header.meta}")
|
||||||
|
is TvaState.ResponseGemtext -> renderGemtext(state)
|
||||||
|
is TvaState.ResponseText -> runOnUiThread{ showAlert("Plain text display not implemented") }
|
||||||
is TvaState.TabChange -> binding.tabCount.text = "${state.count}"
|
is TvaState.TabChange -> binding.tabCount.text = "${state.count}"
|
||||||
is TvaState.Blank -> {
|
is TvaState.Blank -> {
|
||||||
binding.addressEdit.setText("")
|
binding.addressEdit.setText("")
|
||||||
adapter.render(arrayListOf())
|
adapter.render(arrayListOf())
|
||||||
}
|
}
|
||||||
TvaState.GeminiPrintWriterError -> "Error with socket writer".alert(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,27 +77,66 @@ class TvaActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.more.setOnClickListener { "Not implemented yet".alert(this) }
|
binding.more.setOnClickListener {
|
||||||
binding.home.setOnClickListener { "Not implemented yet".alert(this) }
|
OverflowPopup.show(binding.more){menuId ->
|
||||||
|
when (menuId) {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.overflow_menu_about -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
R.id.overflow_menu_set_home -> {
|
||||||
|
SetHome.show(this, binding.addressEdit.text.toString()){
|
||||||
|
showAlert("Home capsule updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.overflow_menu_copy -> {
|
||||||
|
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText(getString(R.string.gemini_address), binding.addressEdit.text.toString())
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Snackbar.make(binding.root, getString(R.string.address_copied_to_clipboard), Snackbar.LENGTH_SHORT).setAction(R.string.share) {
|
||||||
|
Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_TEXT, binding.addressEdit.text.toString())
|
||||||
|
type = "text/plain"
|
||||||
|
startActivity(Intent.createChooser(this, null))
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.home.setOnClickListener {
|
||||||
|
val prefs = getSharedPreferences("oppen.tva.ui.set_home", Context.MODE_PRIVATE)
|
||||||
|
val home = prefs.getString("home", "gemini://gemini.circumlunar.space/")
|
||||||
|
model.request(home!!)
|
||||||
|
}
|
||||||
binding.tabs.setOnClickListener {
|
binding.tabs.setOnClickListener {
|
||||||
TabsDialog().show(this, model)
|
TabsDialog().show(this, model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun externalProtocol(state: TvaState.NotGeminiRequest) {
|
private fun showAlert(message: String) = runOnUiThread{
|
||||||
|
loadingView(false)
|
||||||
|
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun externalProtocol(state: TvaState.NotGeminiRequest) = runOnUiThread {
|
||||||
loadingView(false)
|
loadingView(false)
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(state.uri.toString()))
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(state.uri.toString()))
|
||||||
startActivity(browserIntent)
|
startActivity(browserIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderGemtext(state: TvaState.GeminiResponse) = runOnUiThread {
|
private fun renderGemtext(state: TvaState.ResponseGemtext) = runOnUiThread {
|
||||||
loadingView(false)
|
loadingView(false)
|
||||||
if(state.header.startsWith("2") && state.header.contains("text/gemini")) {
|
binding.addressEdit.setText(state.uri.toString())
|
||||||
binding.addressEdit.setText(state.uri.toString())
|
adapter.render(state.lines)
|
||||||
adapter.render(state.lines)
|
|
||||||
}else{
|
|
||||||
"Server returned an error - or non gemtext mimetype not implemented yet: ${state.header}".alert(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadingView(visible: Boolean) = runOnUiThread {
|
private fun loadingView(visible: Boolean) = runOnUiThread {
|
||||||
|
|
|
@ -50,16 +50,17 @@ class TvaViewModel: ViewModel() {
|
||||||
fun request(uri: URI){
|
fun request(uri: URI){
|
||||||
gemini.request(uri){ state ->
|
gemini.request(uri){ state ->
|
||||||
when(state){
|
when(state){
|
||||||
is TvaState.Requesting -> onState(state)
|
|
||||||
is TvaState.AppQuery -> {}
|
is TvaState.AppQuery -> {}
|
||||||
|
is TvaState.ResponseGemtext -> renderGemini(state)
|
||||||
|
is TvaState.Requesting -> onState(state)
|
||||||
|
is TvaState.ResponseError -> onState(state)
|
||||||
is TvaState.NotGeminiRequest -> onState(state)
|
is TvaState.NotGeminiRequest -> onState(state)
|
||||||
is TvaState.GeminiResponse -> renderGemini(state)
|
is TvaState.ResponseText -> onState(state)
|
||||||
TvaState.GeminiPrintWriterError -> onState(state)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderGemini(state: TvaState.GeminiResponse) {
|
private fun renderGemini(state: TvaState.ResponseGemtext) {
|
||||||
if(tabs[activeTab].history.isEmpty() || tabs[activeTab].history.last() != state.uri){
|
if(tabs[activeTab].history.isEmpty() || tabs[activeTab].history.last() != state.uri){
|
||||||
tabs[activeTab].add(state.uri)
|
tabs[activeTab].add(state.uri)
|
||||||
}
|
}
|
||||||
|
@ -100,11 +101,9 @@ class TvaViewModel: ViewModel() {
|
||||||
|
|
||||||
if(deleteIndex > activeTab){
|
if(deleteIndex > activeTab){
|
||||||
tabs.removeAt(deleteIndex)
|
tabs.removeAt(deleteIndex)
|
||||||
onState(TvaState.TabChange(tabs.size))
|
|
||||||
}else if(deleteIndex < activeTab){
|
}else if(deleteIndex < activeTab){
|
||||||
tabs.removeAt(deleteIndex)
|
tabs.removeAt(deleteIndex)
|
||||||
activeTab--
|
activeTab--
|
||||||
onState(TvaState.TabChange(tabs.size))
|
|
||||||
}else if(deleteIndex == activeTab){
|
}else if(deleteIndex == activeTab){
|
||||||
if(tabs.size > 1){
|
if(tabs.size > 1){
|
||||||
tabs.removeAt(deleteIndex)
|
tabs.removeAt(deleteIndex)
|
||||||
|
@ -120,5 +119,6 @@ class TvaViewModel: ViewModel() {
|
||||||
onState(TvaState.Blank)
|
onState(TvaState.Blank)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onState(TvaState.TabChange(tabs.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,9 +3,10 @@ package oppen.tva.ui.overflow
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.core.view.MenuCompat
|
||||||
import oppen.tva.R
|
import oppen.tva.R
|
||||||
|
|
||||||
class OverflowPopup {
|
object OverflowPopup {
|
||||||
|
|
||||||
fun show(view: View?, onMenuOption: (menuId: Int) -> Unit){
|
fun show(view: View?, onMenuOption: (menuId: Int) -> Unit){
|
||||||
if(view != null) {
|
if(view != null) {
|
||||||
|
@ -16,6 +17,7 @@ class OverflowPopup {
|
||||||
onMenuOption(menuItem.itemId)
|
onMenuOption(menuItem.itemId)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
MenuCompat.setGroupDividerEnabled(popup.menu, true)
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package oppen.tva.ui.set_home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AppCompatDialog
|
||||||
|
import kotlinx.android.synthetic.main.dialog_set_home.view.*
|
||||||
|
import oppen.tva.R
|
||||||
|
|
||||||
|
object SetHome {
|
||||||
|
|
||||||
|
fun show(context: Context, currentAddress: String, onUpdate: () -> Unit){
|
||||||
|
val prefs = context.getSharedPreferences("oppen.tva.ui.set_home", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
val home = prefs.getString("home", "")
|
||||||
|
|
||||||
|
val dialog = AppCompatDialog(context, R.style.DayNightDialog)
|
||||||
|
|
||||||
|
val view = View.inflate(context, R.layout.dialog_set_home, null)
|
||||||
|
|
||||||
|
view.home_edit_text.setText(home)
|
||||||
|
|
||||||
|
view.set_home_button.setOnClickListener {
|
||||||
|
prefs.edit().putString("home", view.home_edit_text.text.toString()).apply()
|
||||||
|
onUpdate()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.use_current_button.setOnClickListener {
|
||||||
|
view.home_edit_text.setText(currentAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setTitle("Set home capsule")
|
||||||
|
|
||||||
|
dialog.setContentView(view)
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,25 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24.5dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="48"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="49"
|
||||||
android:tint="?attr/colorControlNormal">
|
android:tint="?attr/colorControlNormal">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:pathData="M47.723,19.3306L24.481,0.1701C24.3393,0.0567 24.1692,0 24.0086,0C23.8385,0 23.6685,0.0567 23.5362,0.1701L11.5845,10.0054V0.7464C11.5845,0.3307 11.2444,0 10.8381,0C10.4318,0 10.0917,0.3401 10.0917,0.7464V11.2431L0.2753,19.3306C-0.046,19.5951 -0.0932,20.0675 0.1713,20.3887C0.4359,20.71 0.9083,20.7572 1.2295,20.4927L4.2056,18.0456V45.0007C4.2056,46.6541 5.5472,48.0052 7.2101,48.0052H40.8071C42.4605,48.0052 43.8115,46.6635 43.8115,45.0007V18.0362L46.7877,20.4832C46.9294,20.5966 47.0994,20.6533 47.2601,20.6533C47.4774,20.6533 47.6947,20.5588 47.8364,20.3793C48.0915,20.0675 48.0442,19.5951 47.723,19.3306ZM35.2422,46.4935H25.2652V34.0977H35.2422V46.4935ZM42.2999,45.0007C42.2999,45.8321 41.6291,46.5029 40.7976,46.5029H36.7445V33.3419C36.7445,32.9262 36.4043,32.5955 35.9981,32.5955H24.5188C24.1031,32.5955 23.7724,32.9356 23.7724,33.3419V46.4935H7.2006C6.3692,46.4935 5.6984,45.8227 5.6984,44.9913V16.7985L23.9991,1.7195L42.2999,16.7985V45.0007Z"
|
||||||
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.8381,34.6646C10.4224,34.6646 10.0917,35.0047 10.0917,35.411V36.6581C10.0917,37.0738 10.4319,37.4045 10.8381,37.4045C11.2444,37.4045 11.5845,37.0644 11.5845,36.6581V35.411C11.5845,34.9953 11.2538,34.6646 10.8381,34.6646Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.8381,38.8029C10.4224,38.8029 10.0917,39.143 10.0917,39.5492V40.7964C10.0917,41.2121 10.4319,41.5428 10.8381,41.5428C11.2444,41.5428 11.5845,41.2026 11.5845,40.7964V39.5492C11.5845,39.143 11.2538,38.8029 10.8381,38.8029Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.2249,34.6646C16.8092,34.6646 16.4785,35.0047 16.4785,35.411V36.6581C16.4785,37.0738 16.8187,37.4045 17.2249,37.4045C17.6312,37.4045 17.9713,37.0644 17.9713,36.6581V35.411C17.9808,34.9953 17.6406,34.6646 17.2249,34.6646Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.2249,38.8029C16.8092,38.8029 16.4785,39.143 16.4785,39.5492V40.7964C16.4785,41.2121 16.8187,41.5428 17.2249,41.5428C17.6312,41.5428 17.9713,41.2026 17.9713,40.7964V39.5492C17.9808,39.143 17.6406,38.8029 17.2249,38.8029Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M17.2722,16.3072C17.2722,20.0203 20.2861,23.0342 23.9991,23.0342C27.7122,23.0342 30.7261,20.0203 30.7261,16.3072C30.7261,12.5942 27.7122,9.5803 23.9991,9.5803C20.2861,9.5803 17.2722,12.5942 17.2722,16.3072ZM29.2239,16.3072C29.2239,19.1889 26.8808,21.5319 23.9991,21.5319C21.1175,21.5319 18.7744,19.1889 18.7744,16.3072C18.7744,13.4256 21.1175,11.0825 23.9991,11.0825C26.8808,11.0825 29.2239,13.4256 29.2239,16.3072Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/default_margin">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
|
android:id="@+id/home_edit_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/set_home_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:text="Update"
|
||||||
|
android:layout_below="@+id/home_edit_text"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/use_current_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toLeftOf="@+id/set_home_button"
|
||||||
|
android:text="Use current"
|
||||||
|
android:layout_below="@+id/home_edit_text"/>
|
||||||
|
</RelativeLayout>
|
|
@ -11,5 +11,6 @@
|
||||||
android:paddingBottom="@dimen/default_margin"
|
android:paddingBottom="@dimen/default_margin"
|
||||||
android:fontFamily="monospace"
|
android:fontFamily="monospace"
|
||||||
android:background="@color/code_background"
|
android:background="@color/code_background"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
|
@ -5,4 +5,5 @@
|
||||||
android:textSize="@dimen/h1_text_size"
|
android:textSize="@dimen/h1_text_size"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:layout_margin="@dimen/default_margin" />
|
android:layout_margin="@dimen/default_margin" />
|
|
@ -5,4 +5,5 @@
|
||||||
android:textSize="@dimen/h2_text_size"
|
android:textSize="@dimen/h2_text_size"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:layout_margin="@dimen/default_margin" />
|
android:layout_margin="@dimen/default_margin" />
|
|
@ -5,4 +5,5 @@
|
||||||
android:textSize="@dimen/h3_text_size"
|
android:textSize="@dimen/h3_text_size"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:layout_margin="@dimen/default_margin" />
|
android:layout_margin="@dimen/default_margin" />
|
|
@ -6,7 +6,7 @@
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:paddingLeft="@dimen/default_margin"
|
android:layout_marginLeft="@dimen/default_margin"
|
||||||
android:paddingRight="@dimen/default_margin"
|
android:layout_marginRight="@dimen/default_margin"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
|
@ -5,5 +5,6 @@
|
||||||
android:textSize="@dimen/default_text_size"
|
android:textSize="@dimen/default_text_size"
|
||||||
android:paddingLeft="@dimen/default_margin"
|
android:paddingLeft="@dimen/default_margin"
|
||||||
android:paddingRight="@dimen/default_margin"
|
android:paddingRight="@dimen/default_margin"
|
||||||
|
android:textIsSelectable="true"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
|
@ -2,6 +2,4 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@+id/link_menu_open_in_new_tab"
|
<item android:id="@+id/link_menu_open_in_new_tab"
|
||||||
android:title="@string/open_in_new_tab" />
|
android:title="@string/open_in_new_tab" />
|
||||||
<item android:id="@+id/link_menu_copy"
|
|
||||||
android:title="@string/copy_address" />
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@+id/overflow_menu_about"
|
<group android:id="@+id/app" >
|
||||||
android:title="@string/about" />
|
<item android:id="@+id/overflow_menu_share"
|
||||||
|
android:title="@string/share" />
|
||||||
|
<item android:id="@+id/overflow_menu_copy"
|
||||||
|
android:title="@string/copy_address" />
|
||||||
|
<item android:id="@+id/overflow_menu_set_home"
|
||||||
|
android:title="@string/set_home" />
|
||||||
|
</group>
|
||||||
|
<group android:id="@+id/other" >
|
||||||
|
<item android:id="@+id/overflow_menu_about"
|
||||||
|
android:title="@string/about" />
|
||||||
|
</group>
|
||||||
</menu>
|
</menu>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#FFFFFF</color>
|
<color name="colorPrimary">#ffffff</color>
|
||||||
<color name="colorPrimaryDark">#B8B8B8</color>
|
<color name="colorPrimaryDark">#B8B8B8</color>
|
||||||
<color name="colorAccent">#03DAC5</color>
|
<color name="colorAccent">#03DAC5</color>
|
||||||
<color name="code_background">#000000</color>
|
<color name="code_background">#000000</color>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#FFFFFF</color>
|
<color name="colorPrimary">#ffffff</color>
|
||||||
<color name="colorPrimaryDark">#B8B8B8</color>
|
<color name="colorPrimaryDark">#1d1d1d</color>
|
||||||
<color name="colorAccent">#03DAC5</color>
|
<color name="colorAccent">#03DAC5</color>
|
||||||
<color name="code_background">#efefef</color>
|
<color name="code_background">#efefef</color>
|
||||||
</resources>
|
</resources>
|
|
@ -4,7 +4,8 @@
|
||||||
<string name="open_in_new_tab">Open in new tab</string>
|
<string name="open_in_new_tab">Open in new tab</string>
|
||||||
<string name="copy_address">Copy address</string>
|
<string name="copy_address">Copy address</string>
|
||||||
<string name="about">About</string>
|
<string name="about">About</string>
|
||||||
<string name="address_copied_to_clipboard">Link copied to clipboard</string>
|
<string name="address_copied_to_clipboard">Address copied to clipboard</string>
|
||||||
<string name="gemini_address">Gemini address</string>
|
<string name="gemini_address">Gemini address</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
|
<string name="set_home">Set home</string>
|
||||||
</resources>
|
</resources>
|
|
@ -14,4 +14,6 @@
|
||||||
|
|
||||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
<style name="DayNightDialog" parent="Theme.AppCompat.DayNight.Dialog.Alert"/>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue