HistoryFragment: basic history browsable list
Have a card for each entry, rendered from last visited first to oldest. Will probably start choking once the history has too many entries.
This commit is contained in:
parent
473b1e012c
commit
8231a7080a
11
README.md
11
README.md
|
@ -5,6 +5,17 @@ Comet is a Gemini browser for Android, compatible back to Android 7.0.
|
|||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Developed using standard Android SDK practices for an intuitive experience.
|
||||
- Uses the TLS capabilities of your device.
|
||||
- Basic browsing capabilities, like click on links to visit them, wow!
|
||||
- Streaming for all content types.
|
||||
- History.
|
||||
|
||||
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
|
|
|
@ -56,8 +56,10 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.0'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
|
|
@ -38,5 +38,7 @@ object History {
|
|||
dao.update(entry.also { it.title = title; it.lastVisit = now })
|
||||
}
|
||||
|
||||
suspend fun getAll(): List<HistoryEntry> = Database.INSTANCE.historyEntryDao().getAll()
|
||||
|
||||
suspend fun getLast(): HistoryEntry? = Database.INSTANCE.historyEntryDao().getLast()
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package dev.lowrespalmtree.comet
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dev.lowrespalmtree.comet.History.HistoryEntry
|
||||
import dev.lowrespalmtree.comet.HistoryItemAdapter.HistoryItemAdapterListener
|
||||
import dev.lowrespalmtree.comet.databinding.FragmentHistoryListBinding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class HistoryFragment : Fragment(), HistoryItemAdapterListener {
|
||||
private lateinit var binding: FragmentHistoryListBinding
|
||||
private lateinit var historyViewModel: HistoryViewModel
|
||||
private lateinit var adapter: HistoryItemAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentHistoryListBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
|
||||
adapter = HistoryItemAdapter(this)
|
||||
binding.list.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.list.adapter = adapter
|
||||
|
||||
historyViewModel.items.observe(viewLifecycleOwner, { adapter.setItems(it) })
|
||||
|
||||
historyViewModel.refreshHistory()
|
||||
}
|
||||
|
||||
override fun onItemClick(url: String) {
|
||||
val bundle = bundleOf("url" to url)
|
||||
findNavController().navigate(R.id.action_global_pageViewFragment, bundle)
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class HistoryViewModel : ViewModel() {
|
||||
val items: MutableLiveData<List<HistoryEntry>>
|
||||
by lazy { MutableLiveData<List<HistoryEntry>>() }
|
||||
|
||||
fun refreshHistory() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
items.postValue(History.getAll())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package dev.lowrespalmtree.comet
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dev.lowrespalmtree.comet.History.HistoryEntry
|
||||
import dev.lowrespalmtree.comet.databinding.FragmentHistoryItemBinding
|
||||
|
||||
class HistoryItemAdapter(private val listener: HistoryItemAdapterListener) :
|
||||
RecyclerView.Adapter<HistoryItemAdapter.ViewHolder>() {
|
||||
private var items = listOf<HistoryEntry>()
|
||||
|
||||
interface HistoryItemAdapterListener {
|
||||
fun onItemClick(url: String)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
|
||||
ViewHolder(
|
||||
FragmentHistoryItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
holder.binding.uriText.text = item.uri
|
||||
holder.binding.titleText.visibility =
|
||||
if (item.title.isNullOrBlank()) View.GONE else View.VISIBLE
|
||||
holder.binding.titleText.text = item.title ?: ""
|
||||
holder.binding.card.setOnClickListener { listener.onItemClick(item.uri) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun setItems(items: List<HistoryEntry>) {
|
||||
this.items = items.toList()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: FragmentHistoryItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
}
|
|
@ -1,16 +1,19 @@
|
|||
package dev.lowrespalmtree.comet
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import dev.lowrespalmtree.comet.databinding.ActivityMainBinding
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private var navHost: NavHostFragment? = null
|
||||
private var nhf: NavHostFragment? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -19,22 +22,15 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
Database.init(applicationContext) // TODO move to App Startup?
|
||||
|
||||
navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||
navHost?.also { binding.drawerNavigation.setupWithNavController(it.navController) }
|
||||
nhf = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||
nhf?.also { binding.drawerNavigation.setupWithNavController(it.navController) }
|
||||
}
|
||||
|
||||
fun goHome() {
|
||||
navHost?.navController?.navigate(R.id.action_global_pageViewFragment)
|
||||
binding.drawerLayout.closeDrawers()
|
||||
}
|
||||
|
||||
fun openHistory() {
|
||||
binding.drawerLayout.closeDrawers()
|
||||
// TODO
|
||||
}
|
||||
|
||||
fun openSettings() {
|
||||
navHost?.navController?.navigate(R.id.action_global_settingsFragment)
|
||||
/** Navigate to the PageViewFragment; this will automatically use the home URL if any. */
|
||||
fun goHome(item: MenuItem) {
|
||||
val bundle = bundleOf()
|
||||
Preferences.getHomeUrl(this)?.let { bundle.putString("url", it) }
|
||||
nhf?.navController?.navigate(R.id.action_global_pageViewFragment, bundle)
|
||||
binding.drawerLayout.closeDrawers()
|
||||
}
|
||||
}
|
|
@ -22,11 +22,12 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dev.lowrespalmtree.comet.ContentAdapter.ContentAdapterListener
|
||||
import dev.lowrespalmtree.comet.databinding.FragmentPageViewBinding
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener {
|
||||
class PageViewFragment : Fragment(), ContentAdapterListener {
|
||||
private lateinit var binding: FragmentPageViewBinding
|
||||
private lateinit var pageViewModel: PageViewModel
|
||||
private lateinit var adapter: ContentAdapter
|
||||
|
@ -64,9 +65,11 @@ class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener {
|
|||
|
||||
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() }
|
||||
|
||||
if (visitedUrls.isEmpty()) {
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
.getString("home", null)?.let { openUrl(it) }
|
||||
val url = arguments?.getString("url")
|
||||
if (!url.isNullOrEmpty()) {
|
||||
openUrl(url)
|
||||
} else if (visitedUrls.isEmpty()) {
|
||||
Preferences.getHomeUrl(requireContext())?.let { openUrl(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package dev.lowrespalmtree.comet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
object Preferences {
|
||||
fun getHomeUrl(context: Context): String? =
|
||||
PreferenceManager.getDefaultSharedPreferences(context).getString("home", null)
|
||||
}
|
43
app/src/main/res/layout/fragment_history_item.xml
Normal file
43
app/src/main/res/layout/fragment_history_item.xml
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_margin="4dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="4dp"
|
||||
android:foreground="?android:selectableItemBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/uriText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/text"
|
||||
android:layout_margin="8dp"
|
||||
android:fontFamily="@font/preformatted"
|
||||
android:typeface="monospace" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/text"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAppearance="?attr/textAppearanceListItem" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
13
app/src/main/res/layout/fragment_history_list.xml
Normal file
13
app/src/main/res/layout/fragment_history_list.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/list"
|
||||
android:name="dev.lowrespalmtree.comet.HistoryFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:context=".HistoryFragment"
|
||||
tools:listitem="@layout/fragment_history_item" />
|
|
@ -1,17 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/home"
|
||||
android:id="@+id/homeItem"
|
||||
android:icon="@android:drawable/ic_menu_myplaces"
|
||||
android:title="@string/home"
|
||||
android:onClick="goHome" />
|
||||
android:onClick="goHome"
|
||||
android:title="@string/home" />
|
||||
<item
|
||||
android:id="@+id/history"
|
||||
android:id="@+id/historyFragment"
|
||||
android:icon="@android:drawable/ic_menu_recent_history"
|
||||
android:title="@string/history" />
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:id="@+id/settingsFragment"
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:title="@string/settings"
|
||||
android:onClick="openSettings" />
|
||||
android:title="@string/settings" />
|
||||
</menu>
|
|
@ -15,6 +15,17 @@
|
|||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
<fragment
|
||||
android:id="@+id/historyFragment"
|
||||
android:name="dev.lowrespalmtree.comet.HistoryFragment"
|
||||
android:label="HistoryFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_historyFragment"
|
||||
app:destination="@id/historyFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
<fragment
|
||||
android:id="@+id/settingsFragment"
|
||||
android:name="dev.lowrespalmtree.comet.SettingsFragment"
|
||||
|
|
Reference in a new issue