untested tab management

This commit is contained in:
Öppen 2020-08-17 21:25:39 +01:00
parent c5fceff48f
commit 0765d85e52
10 changed files with 229 additions and 10 deletions

View File

@ -7,5 +7,7 @@ sealed class TvaState {
data class Requesting(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 TabChange(val count: Int) : TvaState()
object Blank: TvaState()
object GeminiPrintWriterError : TvaState()
}

View File

@ -9,11 +9,15 @@ import java.net.URI
object NewTabPopup {
fun show(uri: URI, view: View?, onMenuOption: (menuId: Int) -> Unit){
fun show(view: View?, onMenuOption: (menuId: Int) -> Unit){
if(view != null) {
val popup = PopupMenu(view.context, view)
val inflater: MenuInflater = popup.menuInflater
inflater.inflate(R.menu.link_menu, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
onMenuOption(menuItem.itemId)
true
}
popup.show()
}
}

View File

@ -3,18 +3,17 @@ package oppen.tva.ui
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.badge.BadgeDrawable
import oppen.alert
import oppen.tva.R
import oppen.tva.databinding.ActivityTvaBinding
import oppen.tva.io.TvaState
import oppen.tva.io.history.CacheInterface
import oppen.tva.ui.tabs.TabsDialog
import oppen.visibleRetainingSpace
@ -24,8 +23,13 @@ class TvaActivity : AppCompatActivity() {
private lateinit var binding: ActivityTvaBinding
private val adapter = GemtextAdapter { uri, longTap, view ->
if(longTap){
NewTabPopup.show(uri, view){ menuId ->
NewTabPopup.show(view){ menuId ->
when (menuId) {
R.id.link_menu_open_in_new_tab -> {
model.newTab(uri)
}
R.id.link_menu_copy -> "Not implemented yet".alert(this)
}
}
}else{
model.request(uri)
@ -48,7 +52,12 @@ class TvaActivity : AppCompatActivity() {
is TvaState.Requesting -> loadingView(true)
is TvaState.NotGeminiRequest -> externalProtocol(state)
is TvaState.GeminiResponse -> renderGemtext(state)
TvaState.GeminiPrintWriterError -> TODO()
is TvaState.TabChange -> binding.tabCount.text = "${state.count}"
is TvaState.Blank -> {
binding.addressEdit.setText("")
adapter.render(arrayListOf())
}
TvaState.GeminiPrintWriterError -> "Error with socket writer".alert(this)
}
}
@ -64,7 +73,9 @@ class TvaActivity : AppCompatActivity() {
binding.more.setOnClickListener { "Not implemented yet".alert(this) }
binding.home.setOnClickListener { "Not implemented yet".alert(this) }
binding.tabs.setOnClickListener { "Not implemented yet".alert(this) }
binding.tabs.setOnClickListener {
TabsDialog().show(this, model)
}
}
private fun externalProtocol(state: TvaState.NotGeminiRequest) {

View File

@ -14,26 +14,35 @@ class TvaViewModel: ViewModel() {
private var activeTab = 0
private lateinit var cache: CacheInterface
private var tabs = mutableListOf<Tab>()
var tabs = mutableListOf<Tab>()
fun initialise(cache: CacheInterface, onState: (state: TvaState) -> Unit){
this.cache = cache
this.onState = onState
cache.getTabs { tabs, activeIndex ->
this.tabs.addAll(tabs)
if(tabs.isEmpty()){
this.tabs.add(Tab(0))
activeTab = 0
request(URI.create("gemini://gemini.circumlunar.space/"))
onState(TvaState.TabChange(1))
}else{
activeTab = activeIndex
request(tabs[activeTab].history.last())
onState(TvaState.TabChange(tabs.size))
}
}
}
fun newTab(uri: URI) {
val newTab = Tab(tabs.size)
tabs.add(newTab)
activeTab = newTab.index
onState(TvaState.TabChange(tabs.size))
request(uri)
}
fun request(address: String) {
request(URI.create(address))
}
@ -56,7 +65,6 @@ class TvaViewModel: ViewModel() {
}
onState(state)
}
fun canGoBack(): Boolean {
@ -82,4 +90,35 @@ class TvaViewModel: ViewModel() {
fun persistTabState() = cache.update(tabs, activeTab)
fun changeTab(changeIndex: Int) {
activeTab = changeIndex
request(tabs[activeTab].history.last())
}
fun deleteTab(deleteIndex: Int) {
if(deleteIndex > activeTab){
tabs.removeAt(deleteIndex)
onState(TvaState.TabChange(tabs.size))
}else if(deleteIndex < activeTab){
tabs.removeAt(deleteIndex)
activeTab--
onState(TvaState.TabChange(tabs.size))
}else if(deleteIndex == activeTab){
if(tabs.size > 1){
tabs.removeAt(deleteIndex)
if(activeTab < tabs.size - 1){
activeTab--
}else{
activeTab = tabs.size -1
}
request(tabs[activeTab].history.last())
}else{
//Only one tab - we want a fresh state
tabs.first().history.clear()
onState(TvaState.Blank)
}
}
}
}

View File

@ -0,0 +1,35 @@
package oppen.tva.ui.tabs
import android.view.LayoutInflater
import android.view.View
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
class TabsAdapter(
private val tabs: List<Tab>,
val onTabDelete: (index: Int) -> Unit,
val onTabChange: (index: Int) -> Unit): RecyclerView.Adapter<TabsAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.grid_cell_tab, parent, false))
}
override fun getItemCount(): Int = tabs.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tab = tabs[position]
holder.itemView.tab_last_uri.text = "${tab.history.last()}"
holder.itemView.remove_tab.setOnClickListener {
onTabDelete(holder.adapterPosition)
}
holder.itemView.tab_cell.setOnClickListener {
onTabChange(holder.adapterPosition)
}
}
}

View File

@ -0,0 +1,34 @@
package oppen.tva.ui.tabs
import android.content.Context
import android.view.View
import androidx.appcompat.app.AppCompatDialog
import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.android.synthetic.main.dialog_tabs.view.*
import oppen.tva.R
import oppen.tva.ui.TvaViewModel
class TabsDialog {
fun show(context: Context, model: TvaViewModel){
val dialog = AppCompatDialog(context, R.style.AppTheme)
val view = View.inflate(context, R.layout.dialog_tabs, null)
dialog.setContentView(view)
view.close_tab_dialog.setOnClickListener {
dialog.dismiss()
}
view.tab_dialog_recycler.layoutManager = GridLayoutManager(context, 2)
view.tab_dialog_recycler.adapter = TabsAdapter(model.tabs, { deleteIndex ->
model.deleteTab(deleteIndex)
dialog.dismiss()
}){ changeIndex ->
model.changeTab(changeIndex)
dialog.dismiss()
}
dialog.show()
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -0,0 +1,43 @@
<?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">
<RelativeLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/default_margin"
android:paddingBottom="@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/tab_dialog_overflow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:visibility="gone"
android:layout_margin="@dimen/button_margin"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/vector_overflow" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tab_dialog_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/header" />
</RelativeLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tab_cell"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_margin="@dimen/button_margin"
android:background="@drawable/drawable_rounded_rect"
android:padding="@dimen/default_margin"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackgroundBorderless">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/remove_tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/vector_cancel" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tab_last_uri"
android:layout_centerInParent="true"
android:textAlignment="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="gemini://oppenlab.net" />
</RelativeLayout>