numerous fixes

This commit is contained in:
Öppen 2020-08-18 17:17:04 +01:00
parent 4ea369610a
commit e58deb28c3
23 changed files with 293 additions and 87 deletions

View File

@ -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

View File

@ -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)))
} }
} }

View File

@ -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)
}

View File

@ -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()]
}

View File

@ -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()
} }

View File

@ -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 {

View File

@ -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))
} }
} }

View File

@ -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()
} }
} }

View File

@ -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()
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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" />

View File

@ -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" />

View File

@ -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" />

View File

@ -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" />

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>