new homepage, history changes

This commit is contained in:
Öppen 2020-08-20 14:52:24 +01:00
parent 37d05f2c91
commit 11a4b6d8d2
20 changed files with 295 additions and 30 deletions

View File

@ -10,8 +10,8 @@ android {
applicationId "oppen.tva"
minSdkVersion 21
targetSdkVersion 30
versionCode 3
versionName "0.0.3 Alpha"
versionCode 5
versionName "0.2.0 Beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"properties": [],
"versionCode": 3,
"versionName": "0.0.3 Alpha",
"versionCode": 5,
"versionName": "0.2.0 Beta",
"enabled": true,
"outputFile": "app-release.apk"
}

View File

@ -17,6 +17,12 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="gemini" />
</intent-filter>
</activity>
</application>

View File

@ -1,7 +1,9 @@
package oppen
import android.os.CountDownTimer
import android.view.View
fun View.visible(visible: Boolean) = when {
visible -> this.visibility = View.VISIBLE
else -> this.visibility = View.GONE
@ -10,4 +12,15 @@ fun View.visible(visible: Boolean) = when {
fun View.visibleRetainingSpace(visible: Boolean) = when {
visible -> this.visibility = View.VISIBLE
else -> this.visibility = View.INVISIBLE
}
fun delay(ms: Long, action: () -> Unit){
object : CountDownTimer(ms, ms/2) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
action.invoke()
}
}.start()
}

View File

@ -15,4 +15,8 @@ object RuntimeCache {
fun get(uri: URI): Pair<GeminiResponse.Header, List<String>>? = lruCache[uri.toString()]
fun clear() = lruCache.evictAll()
fun remove(address: String) {
lruCache.remove(address)
}
}

View File

@ -1,4 +1,4 @@
package oppen.tva.io.history
package oppen.tva.io.history.tabs
import android.content.Context
import java.net.URI
@ -9,7 +9,7 @@ import kotlin.text.StringBuilder
* This is slow, unsafe, awful, and synchronous but I don't want get bogged down implementing Room just yet
*
*/
class BasicCache(context: Context): CacheInterface {
class BasicTabHistoryCache(context: Context): TabHistoryInterface {
private val DELIM = "||"
private val prefsKey = "oppen.tva.io.history.BasicCache.PREFS_KEY"

View File

@ -1,4 +1,4 @@
package oppen.tva.io.history
package oppen.tva.io.history.tabs
import java.net.URI

View File

@ -1,14 +1,14 @@
package oppen.tva.io.history
package oppen.tva.io.history.tabs
import android.content.Context
interface CacheInterface {
interface TabHistoryInterface {
fun getTabs(onTabs: (tabs: MutableList<Tab>, activeIndex: Int)-> Unit)
fun update(tabs: List<Tab>, activeIndex: Int)
companion object{
fun default(context: Context): CacheInterface{
return BasicCache(context)
fun default(context: Context): TabHistoryInterface {
return BasicTabHistoryCache(context)
}
}
}

View File

@ -0,0 +1,44 @@
package oppen.tva.io.history.uris
import android.content.Context
/**
*
* Another shared prefs implementation so I don't get slowed down by a Room implementation at this point
*
*/
class BasicURIHistory(context: Context): HistoryInterface {
private val DELIM = "||"
private val prefsKey = "oppen.tva.io.history.BasicURIHistory.prefsKey"
private val prefsHistoryKey = "oppen.tva.io.history.BasicURIHistory.prefsHistoryKey"
private val prefs = context.getSharedPreferences(prefsKey, Context.MODE_PRIVATE)
override fun add(address: String) {
val history = get()
when {
history.size >= 50 -> history.removeAt(0)
}
if(history.isNotEmpty() && history.size > 10){
if(history.subList(history.size - 10, history.size).contains(address)) return
}
history.add(address)
val raw = history.joinToString(DELIM)
prefs.edit().putString(prefsHistoryKey, raw).apply()
}
override fun clear(){
prefs.edit().clear().apply()
}
override fun get(): ArrayList<String> {
return when (val raw = prefs.getString(prefsHistoryKey, null)) {
null -> arrayListOf()
else -> ArrayList(raw.split(DELIM))
}
}
}

View File

@ -0,0 +1,15 @@
package oppen.tva.io.history.uris
import android.content.Context
interface HistoryInterface {
fun add(address: String)
fun get(): List<String>
fun clear()
companion object{
fun default(context: Context): HistoryInterface {
return BasicURIHistory(context)
}
}
}

View File

@ -18,8 +18,10 @@ import oppen.tva.databinding.ActivityTvaBinding
import oppen.tva.io.GeminiResponse
import oppen.tva.io.RuntimeCache
import oppen.tva.io.TvaState
import oppen.tva.io.history.CacheInterface
import oppen.tva.io.history.tabs.TabHistoryInterface
import oppen.tva.io.history.uris.HistoryInterface
import oppen.tva.ui.about.AboutDialog
import oppen.tva.ui.history.HistoryDialog
import oppen.tva.ui.overflow.OverflowPopup
import oppen.tva.ui.set_home.SetHomeDialog
import oppen.tva.ui.tabs.NewTabPopup
@ -31,6 +33,7 @@ class TvaActivity : AppCompatActivity() {
private val model by viewModels<TvaViewModel>()
private lateinit var binding: ActivityTvaBinding
private lateinit var history: HistoryInterface
private val adapter = GemtextAdapter { uri, longTap, view ->
if(longTap){
NewTabPopup.show(view){ menuId ->
@ -43,7 +46,6 @@ class TvaActivity : AppCompatActivity() {
}else{
model.request(uri)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -55,7 +57,9 @@ class TvaActivity : AppCompatActivity() {
binding.gemtextRecycler.layoutManager = LinearLayoutManager(this)
binding.gemtextRecycler.adapter = adapter
model.initialise(CacheInterface.default(this)){ state ->
history = HistoryInterface.default(this)
model.initialise(TabHistoryInterface.default(this)){ state ->
when(state){
is TvaState.AppQuery -> runOnUiThread{ showAlert("App backdoor/query not implemented yet") }
is TvaState.Requesting -> loadingView(true)
@ -92,9 +96,13 @@ class TvaActivity : AppCompatActivity() {
startActivity(Intent.createChooser(this, null))
}
}
R.id.overflow_menu_clear_cache -> {
RuntimeCache.clear()
showAlert("Runtime cache cleared")
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)
}
R.id.overflow_menu_about -> AboutDialog.show(this)
R.id.overflow_menu_set_home -> {
@ -128,6 +136,11 @@ class TvaActivity : AppCompatActivity() {
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data.toString().let{model.request(it)}
}
private fun showAlert(message: String) = runOnUiThread{
loadingView(false)
@ -150,6 +163,8 @@ class TvaActivity : AppCompatActivity() {
loadingView(false)
binding.addressEdit.setText(state.uri.toString())
adapter.render(state.lines)
history.add(state.uri.toString())
}
private fun loadingView(visible: Boolean) = runOnUiThread {

View File

@ -3,8 +3,8 @@ package oppen.tva.ui
import androidx.lifecycle.ViewModel
import oppen.tva.io.Datasource
import oppen.tva.io.TvaState
import oppen.tva.io.history.CacheInterface
import oppen.tva.io.history.Tab
import oppen.tva.io.history.tabs.TabHistoryInterface
import oppen.tva.io.history.tabs.Tab
import java.net.URI
class TvaViewModel: ViewModel() {
@ -13,10 +13,10 @@ class TvaViewModel: ViewModel() {
private var onState: (state: TvaState) -> Unit = {}
private var activeTab = 0
private lateinit var cache: CacheInterface
private lateinit var cache: TabHistoryInterface
var tabs = mutableListOf<Tab>()
fun initialise(cache: CacheInterface, onState: (state: TvaState) -> Unit){
fun initialise(cache: TabHistoryInterface, onState: (state: TvaState) -> Unit){
this.cache = cache
this.onState = onState
@ -25,7 +25,7 @@ class TvaViewModel: ViewModel() {
if(tabs.isEmpty()){
this.tabs.add(Tab(0))
activeTab = 0
request(URI.create("gemini://gemini.circumlunar.space/"))
request(URI.create("gemini://gemini.circumlunar.space/~oppen/tva/index.gmi"))
onState(TvaState.TabChange(1))
}else{
activeTab = activeIndex

View File

@ -0,0 +1,29 @@
package oppen.tva.ui.history
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row_history.view.*
import oppen.delay
import oppen.tva.R
class HistoryAdapter(val history: List<String>, val onClick:(address: String) -> Unit): RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
class ViewHolder(view: View): RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_history, parent, false))
}
override fun getItemCount(): Int = history.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.history_address.text = history[position]
holder.itemView.history_row.setOnClickListener {
delay(500){
onClick(history[holder.adapterPosition])
}
}
}
}

View File

@ -0,0 +1,60 @@
package oppen.tva.ui.history
import android.content.Context
import android.view.MenuInflater
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuCompat
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.dialog_about.view.close_tab_dialog
import kotlinx.android.synthetic.main.dialog_history.view.*
import oppen.tva.R
import oppen.tva.io.RuntimeCache
import oppen.tva.io.history.uris.HistoryInterface
object HistoryDialog {
fun show(context: Context, onHistoryItem: (address: String) -> Unit){
val historyCache = HistoryInterface.default(context)
val history = historyCache.get()
val dialog = AppCompatDialog(context, R.style.AppTheme)
val view = View.inflate(context, R.layout.dialog_history, null)
dialog.setContentView(view)
view.close_tab_dialog.setOnClickListener {
dialog.dismiss()
}
view.history_overflow.setOnClickListener {
val popup = PopupMenu(view.context, view.history_overflow)
val inflater: MenuInflater = popup.menuInflater
inflater.inflate(R.menu.history_overflow_menu, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
if(menuItem.itemId == R.id.history_overflow_clear_history){
historyCache.clear()
dialog.dismiss()
Toast.makeText(context, "History cleared", Toast.LENGTH_SHORT).show()
}else if(menuItem.itemId == R.id.history_overflow_clear_runtime_cache){
RuntimeCache.clear()
dialog.dismiss()
Toast.makeText(context, "Runtime cache cleared", Toast.LENGTH_SHORT).show()
}
true
}
MenuCompat.setGroupDividerEnabled(popup.menu, true)
popup.show()
}
view.history_recycler.layoutManager = LinearLayoutManager(context)
view.history_recycler.adapter = HistoryAdapter(history.asReversed()){ address ->
onHistoryItem(address)
dialog.dismiss()
}
dialog.show()
}
}

View File

@ -6,7 +6,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.grid_cell_tab.view.*
import oppen.tva.R
import oppen.tva.io.history.Tab
import oppen.tva.io.history.tabs.Tab
class TabsAdapter(
private val tabs: List<Tab>,

View File

@ -0,0 +1,40 @@
<?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="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/default_margin">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/close_tab_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_margin="@dimen/button_margin"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/vector_close" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/history_overflow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_margin="@dimen/button_margin"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/vector_overflow" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/history_recycler"
android:layout_below="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/history_row"
android:minHeight="60dp"
android:layout_height="60dp"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/history_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingStart="@dimen/default_margin" />
</RelativeLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/history_overflow_clear_history"
android:title="@string/clear_history" />
<item android:id="@+id/history_overflow_clear_runtime_cache"
android:title="@string/clear_cache" />
</menu>

View File

@ -1,17 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/app" >
<item android:id="@+id/overflow_menu_share"
<item
android:id="@+id/overflow_menu_share"
android:title="@string/share" />
<item android:id="@+id/overflow_menu_copy"
<item
android:id="@+id/overflow_menu_copy"
android:title="@string/copy_address" />
<item android:id="@+id/overflow_menu_set_home"
<item
android:id="@+id/overflow_menu_reload"
android:title="@string/reload"/>
<item
android:id="@+id/overflow_menu_set_home"
android:title="@string/set_home" />
</group>
<group android:id="@+id/cache" >
<item
android:id="@+id/overflow_menu_history"
android:title="@string/history" />
</group>
<group android:id="@+id/other" >
<item android:id="@+id/overflow_menu_clear_cache"
android:title="@string/clear_cache" />
<item android:id="@+id/overflow_menu_about"
<item
android:id="@+id/overflow_menu_about"
android:title="@string/about" />
</group>
</menu>

View File

@ -16,5 +16,8 @@
\nThis program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version.\n
\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
\nYou should have received a copy of the GNU General Public License along with this program. If not, see www.gnu.org/licenses</string>
<string name="clear_cache">Clear cache</string>
<string name="clear_cache">Clear runtime cache</string>
<string name="history">History</string>
<string name="clear_history">Clear history</string>
<string name="reload">Reload</string>
</resources>