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
|
About
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,10 @@ dependencies {
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
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-runtime:$room_version"
|
||||||
implementation "androidx.room:room-ktx:$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'
|
implementation 'com.google.android.material:material:1.5.0'
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
|
@ -38,5 +38,7 @@ object History {
|
||||||
dao.update(entry.also { it.title = title; it.lastVisit = now })
|
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()
|
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
|
package dev.lowrespalmtree.comet
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import dev.lowrespalmtree.comet.databinding.ActivityMainBinding
|
import dev.lowrespalmtree.comet.databinding.ActivityMainBinding
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private var navHost: NavHostFragment? = null
|
private var nhf: NavHostFragment? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -19,22 +22,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
Database.init(applicationContext) // TODO move to App Startup?
|
Database.init(applicationContext) // TODO move to App Startup?
|
||||||
|
|
||||||
navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
nhf = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||||
navHost?.also { binding.drawerNavigation.setupWithNavController(it.navController) }
|
nhf?.also { binding.drawerNavigation.setupWithNavController(it.navController) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goHome() {
|
/** Navigate to the PageViewFragment; this will automatically use the home URL if any. */
|
||||||
navHost?.navController?.navigate(R.id.action_global_pageViewFragment)
|
fun goHome(item: MenuItem) {
|
||||||
binding.drawerLayout.closeDrawers()
|
val bundle = bundleOf()
|
||||||
}
|
Preferences.getHomeUrl(this)?.let { bundle.putString("url", it) }
|
||||||
|
nhf?.navController?.navigate(R.id.action_global_pageViewFragment, bundle)
|
||||||
fun openHistory() {
|
|
||||||
binding.drawerLayout.closeDrawers()
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
fun openSettings() {
|
|
||||||
navHost?.navController?.navigate(R.id.action_global_settingsFragment)
|
|
||||||
binding.drawerLayout.closeDrawers()
|
binding.drawerLayout.closeDrawers()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,11 +22,12 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dev.lowrespalmtree.comet.ContentAdapter.ContentAdapterListener
|
||||||
import dev.lowrespalmtree.comet.databinding.FragmentPageViewBinding
|
import dev.lowrespalmtree.comet.databinding.FragmentPageViewBinding
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener {
|
class PageViewFragment : Fragment(), ContentAdapterListener {
|
||||||
private lateinit var binding: FragmentPageViewBinding
|
private lateinit var binding: FragmentPageViewBinding
|
||||||
private lateinit var pageViewModel: PageViewModel
|
private lateinit var pageViewModel: PageViewModel
|
||||||
private lateinit var adapter: ContentAdapter
|
private lateinit var adapter: ContentAdapter
|
||||||
|
@ -64,9 +65,11 @@ class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener {
|
||||||
|
|
||||||
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() }
|
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() }
|
||||||
|
|
||||||
if (visitedUrls.isEmpty()) {
|
val url = arguments?.getString("url")
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
if (!url.isNullOrEmpty()) {
|
||||||
.getString("home", null)?.let { openUrl(it) }
|
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"?>
|
<?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
|
<item
|
||||||
android:id="@+id/home"
|
android:id="@+id/homeItem"
|
||||||
android:icon="@android:drawable/ic_menu_myplaces"
|
android:icon="@android:drawable/ic_menu_myplaces"
|
||||||
android:title="@string/home"
|
android:onClick="goHome"
|
||||||
android:onClick="goHome" />
|
android:title="@string/home" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/history"
|
android:id="@+id/historyFragment"
|
||||||
android:icon="@android:drawable/ic_menu_recent_history"
|
android:icon="@android:drawable/ic_menu_recent_history"
|
||||||
android:title="@string/history" />
|
android:title="@string/history" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settingsFragment"
|
||||||
android:icon="@android:drawable/ic_menu_preferences"
|
android:icon="@android:drawable/ic_menu_preferences"
|
||||||
android:title="@string/settings"
|
android:title="@string/settings" />
|
||||||
android:onClick="openSettings" />
|
|
||||||
</menu>
|
</menu>
|
|
@ -15,6 +15,17 @@
|
||||||
app:exitAnim="@anim/nav_default_exit_anim"
|
app:exitAnim="@anim/nav_default_exit_anim"
|
||||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||||
app:popExitAnim="@anim/nav_default_pop_exit_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
|
<fragment
|
||||||
android:id="@+id/settingsFragment"
|
android:id="@+id/settingsFragment"
|
||||||
android:name="dev.lowrespalmtree.comet.SettingsFragment"
|
android:name="dev.lowrespalmtree.comet.SettingsFragment"
|
||||||
|
|
Reference in a new issue