Compare commits
No commits in common. "020f48149be23dcad96ed668dca39c889d090c43" and "8231a7080a7fff1919ad0c2ecf98c19eba6b6d5b" have entirely different histories.
020f48149b
...
8231a7080a
|
@ -56,7 +56,7 @@ 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.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 "androidx.cardview:cardview:1.0.0"
|
||||||
|
|
|
@ -19,8 +19,8 @@ import dev.lowrespalmtree.comet.databinding.*
|
||||||
* it could be a n-to-1 relation: many lines belong to the same block. This of course changes a bit
|
* it could be a n-to-1 relation: many lines belong to the same block. This of course changes a bit
|
||||||
* the way we have to render things.
|
* the way we have to render things.
|
||||||
*/
|
*/
|
||||||
class PageAdapter(private val listener: ContentAdapterListener) :
|
class ContentAdapter(private val listener: ContentAdapterListener) :
|
||||||
RecyclerView.Adapter<PageAdapter.ContentViewHolder>() {
|
RecyclerView.Adapter<ContentAdapter.ContentViewHolder>() {
|
||||||
|
|
||||||
private var lines = listOf<Line>()
|
private var lines = listOf<Line>()
|
||||||
private var currentLine = 0
|
private var currentLine = 0
|
||||||
|
@ -161,7 +161,7 @@ class PageAdapter(private val listener: ContentAdapterListener) :
|
||||||
override fun getItemCount(): Int = blocks.size
|
override fun getItemCount(): Int = blocks.size
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PageAdapter"
|
private const val TAG = "ContentRecycler"
|
||||||
private const val TYPE_EMPTY = 0
|
private const val TYPE_EMPTY = 0
|
||||||
private const val TYPE_TITLE_1 = 1
|
private const val TYPE_TITLE_1 = 1
|
||||||
private const val TYPE_TITLE_2 = 2
|
private const val TYPE_TITLE_2 = 2
|
|
@ -47,7 +47,7 @@ class HistoryFragment : Fragment(), HistoryItemAdapterListener {
|
||||||
|
|
||||||
override fun onItemClick(url: String) {
|
override fun onItemClick(url: String) {
|
||||||
val bundle = bundleOf("url" to url)
|
val bundle = bundleOf("url" to url)
|
||||||
findNavController().navigate(R.id.action_global_pageFragment, bundle)
|
findNavController().navigate(R.id.action_global_pageViewFragment, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.os.bundleOf
|
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
|
||||||
|
|
||||||
|
@ -26,10 +27,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Navigate to the PageViewFragment; this will automatically use the home URL if any. */
|
/** Navigate to the PageViewFragment; this will automatically use the home URL if any. */
|
||||||
fun goHome(@Suppress("unused_parameter") item: MenuItem) {
|
fun goHome(item: MenuItem) {
|
||||||
val bundle = bundleOf()
|
val bundle = bundleOf()
|
||||||
Preferences.getHomeUrl(this)?.let { bundle.putString("url", it) }
|
Preferences.getHomeUrl(this)?.let { bundle.putString("url", it) }
|
||||||
nhf?.navController?.navigate(R.id.action_global_pageFragment, bundle)
|
nhf?.navController?.navigate(R.id.action_global_pageViewFragment, bundle)
|
||||||
binding.drawerLayout.closeDrawers()
|
binding.drawerLayout.closeDrawers()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,40 +19,39 @@ import androidx.activity.addCallback
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.setMargins
|
import androidx.core.view.setMargins
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
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.PageAdapter.ContentAdapterListener
|
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 PageFragment : Fragment(), ContentAdapterListener {
|
class PageViewFragment : Fragment(), ContentAdapterListener {
|
||||||
private val vm: PageViewModel by viewModels()
|
|
||||||
private lateinit var binding: FragmentPageViewBinding
|
private lateinit var binding: FragmentPageViewBinding
|
||||||
private lateinit var adapter: PageAdapter
|
private lateinit var pageViewModel: PageViewModel
|
||||||
|
private lateinit var adapter: ContentAdapter
|
||||||
|
|
||||||
/** Property to access and set the current address bar URL value. */
|
/** Property to access and set the current address bar URL value. */
|
||||||
private var currentUrl: String
|
private var currentUrl
|
||||||
get() = vm.currentUrl
|
get() = binding.addressBar.text.toString()
|
||||||
set(value) {
|
set(value) = binding.addressBar.setText(value)
|
||||||
vm.currentUrl = value
|
|
||||||
binding.addressBar.setText(value)
|
/** A non-saved list of visited URLs. Not an history, just used for going back. */
|
||||||
}
|
private val visitedUrls = mutableListOf<String>()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
Log.d(TAG, "onCreateView")
|
|
||||||
binding = FragmentPageViewBinding.inflate(layoutInflater)
|
binding = FragmentPageViewBinding.inflate(layoutInflater)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
Log.d(TAG, "onViewCreated")
|
pageViewModel = ViewModelProvider(this)[PageViewModel::class.java]
|
||||||
adapter = PageAdapter(this)
|
adapter = ContentAdapter(this)
|
||||||
binding.contentRecycler.layoutManager = LinearLayoutManager(requireContext())
|
binding.contentRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
binding.contentRecycler.adapter = adapter
|
binding.contentRecycler.adapter = adapter
|
||||||
|
|
||||||
|
@ -60,20 +59,16 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
|
|
||||||
binding.contentSwipeLayout.setOnRefreshListener { openUrl(currentUrl) }
|
binding.contentSwipeLayout.setOnRefreshListener { openUrl(currentUrl) }
|
||||||
|
|
||||||
vm.state.observe(viewLifecycleOwner, { updateState(it) })
|
pageViewModel.state.observe(viewLifecycleOwner, { updateState(it) })
|
||||||
vm.lines.observe(viewLifecycleOwner, { updateLines(it) })
|
pageViewModel.lines.observe(viewLifecycleOwner, { updateLines(it) })
|
||||||
vm.event.observe(viewLifecycleOwner, { handleEvent(it) })
|
pageViewModel.event.observe(viewLifecycleOwner, { handleEvent(it) })
|
||||||
|
|
||||||
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() }
|
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() }
|
||||||
|
|
||||||
val url = arguments?.getString("url")
|
val url = arguments?.getString("url")
|
||||||
if (!url.isNullOrEmpty()) {
|
if (!url.isNullOrEmpty()) {
|
||||||
Log.d(TAG, "onViewCreated: open \"$url\"")
|
|
||||||
openUrl(url)
|
openUrl(url)
|
||||||
} else if (vm.currentUrl.isNotEmpty()) {
|
} else if (visitedUrls.isEmpty()) {
|
||||||
Log.d(TAG, "onViewCreated: reuse current URL, probably fragment recreation")
|
|
||||||
} else if (vm.visitedUrls.isEmpty()) {
|
|
||||||
Log.d(TAG, "onViewCreated: no current URL, open home if configured")
|
|
||||||
Preferences.getHomeUrl(requireContext())?.let { openUrl(it) }
|
Preferences.getHomeUrl(requireContext())?.let { openUrl(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +78,9 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBackPressed() {
|
private fun onBackPressed() {
|
||||||
if (vm.visitedUrls.size >= 2) {
|
if (visitedUrls.size >= 2) {
|
||||||
vm.visitedUrls.removeLastOrNull() // Always remove current page first.
|
visitedUrls.removeLastOrNull() // Always remove current page first.
|
||||||
vm.visitedUrls.removeLastOrNull()?.also { openUrl(it) }
|
visitedUrls.removeLastOrNull()?.also { openUrl(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +127,7 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
prefs.getInt("connection_timeout", Request.DEFAULT_CONNECTION_TIMEOUT_SEC)
|
prefs.getInt("connection_timeout", Request.DEFAULT_CONNECTION_TIMEOUT_SEC)
|
||||||
val readTimeout =
|
val readTimeout =
|
||||||
prefs.getInt("read_timeout", Request.DEFAULT_READ_TIMEOUT_SEC)
|
prefs.getInt("read_timeout", Request.DEFAULT_READ_TIMEOUT_SEC)
|
||||||
vm.sendGeminiRequest(uri, connectionTimeout, readTimeout)
|
pageViewModel.sendGeminiRequest(uri, connectionTimeout, readTimeout)
|
||||||
}
|
}
|
||||||
else -> openUnknownScheme(uri)
|
else -> openUnknownScheme(uri)
|
||||||
}
|
}
|
||||||
|
@ -162,15 +157,37 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
|
|
||||||
private fun handleEvent(event: PageViewModel.Event) {
|
private fun handleEvent(event: PageViewModel.Event) {
|
||||||
Log.d(TAG, "handleEvent: $event")
|
Log.d(TAG, "handleEvent: $event")
|
||||||
if (event.handled)
|
if (!event.handled) {
|
||||||
return
|
|
||||||
when (event) {
|
when (event) {
|
||||||
is PageViewModel.InputEvent -> {
|
is PageViewModel.InputEvent -> {
|
||||||
askForInput(event.prompt, event.uri)
|
val editText = EditText(requireContext())
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_TEXT
|
||||||
|
val inputView = FrameLayout(requireContext()).apply {
|
||||||
|
addView(FrameLayout(requireContext()).apply {
|
||||||
|
addView(editText)
|
||||||
|
val params = FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
params.setMargins(resources.getDimensionPixelSize(R.dimen.text_margin))
|
||||||
|
layoutParams = params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setMessage(if (event.prompt.isNotEmpty()) event.prompt else "Input required")
|
||||||
|
.setView(inputView)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
val newUri =
|
||||||
|
event.uri.buildUpon().query(editText.text.toString()).build()
|
||||||
|
openUrl(newUri.toString(), base = currentUrl)
|
||||||
|
}
|
||||||
|
.setOnDismissListener { updateState(PageViewModel.State.IDLE) }
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
is PageViewModel.SuccessEvent -> {
|
is PageViewModel.SuccessEvent -> {
|
||||||
currentUrl = event.uri
|
currentUrl = event.uri
|
||||||
vm.visitedUrls.add(event.uri)
|
visitedUrls.add(event.uri)
|
||||||
}
|
}
|
||||||
is PageViewModel.RedirectEvent -> {
|
is PageViewModel.RedirectEvent -> {
|
||||||
openUrl(event.uri, base = currentUrl, redirections = event.redirects)
|
openUrl(event.uri, base = currentUrl, redirections = event.redirects)
|
||||||
|
@ -187,6 +204,7 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
}
|
}
|
||||||
event.handled = true
|
event.handled = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun alert(message: String, title: String? = null) {
|
private fun alert(message: String, title: String? = null) {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
|
@ -196,32 +214,6 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askForInput(prompt: String, uri: Uri) {
|
|
||||||
val editText = EditText(requireContext())
|
|
||||||
editText.inputType = InputType.TYPE_CLASS_TEXT
|
|
||||||
val inputView = FrameLayout(requireContext()).apply {
|
|
||||||
addView(FrameLayout(requireContext()).apply {
|
|
||||||
addView(editText)
|
|
||||||
val params = FrameLayout.LayoutParams(
|
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
params.setMargins(resources.getDimensionPixelSize(R.dimen.text_margin))
|
|
||||||
layoutParams = params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
AlertDialog.Builder(requireContext())
|
|
||||||
.setMessage(if (prompt.isNotEmpty()) prompt else "Input required")
|
|
||||||
.setView(inputView)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val newUri = uri.buildUpon().query(editText.text.toString()).build()
|
|
||||||
openUrl(newUri.toString(), base = currentUrl)
|
|
||||||
}
|
|
||||||
.setOnDismissListener { updateState(PageViewModel.State.IDLE) }
|
|
||||||
.create()
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openUnknownScheme(uri: Uri) {
|
private fun openUnknownScheme(uri: Uri) {
|
||||||
try {
|
try {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, uri))
|
startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||||
|
@ -231,6 +223,6 @@ class PageFragment : Fragment(), ContentAdapterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PageFragment"
|
private const val TAG = "PageViewFragment"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,6 @@ package dev.lowrespalmtree.comet
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.SavedStateHandle
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
@ -14,22 +13,12 @@ import java.net.UnknownHostException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedStateHandle) :
|
class PageViewModel : ViewModel() {
|
||||||
ViewModel() {
|
|
||||||
/** Currently viewed page URL. */
|
|
||||||
var currentUrl: String = ""
|
|
||||||
/** Observable page viewer state. */
|
|
||||||
val state: MutableLiveData<State> by lazy { MutableLiveData<State>(State.IDLE) }
|
|
||||||
/** Observable page viewer lines (backed up by `linesList` but updated less often). */
|
|
||||||
val lines: MutableLiveData<List<Line>> by lazy { MutableLiveData<List<Line>>() }
|
|
||||||
/** Observable page viewer latest event. */
|
|
||||||
val event: MutableLiveData<Event> by lazy { MutableLiveData<Event>() }
|
|
||||||
/** A non-saved list of visited URLs. Not an history, just used for going back. */
|
|
||||||
val visitedUrls = mutableListOf<String>()
|
|
||||||
/** Latest request job created, stored to cancel it if needed. */
|
|
||||||
private var requestJob: Job? = null
|
private var requestJob: Job? = null
|
||||||
/** Lines for the current page. */
|
val state: MutableLiveData<State> by lazy { MutableLiveData<State>(State.IDLE) }
|
||||||
private var linesList = ArrayList<Line>()
|
private var linesList = ArrayList<Line>()
|
||||||
|
val lines: MutableLiveData<List<Line>> by lazy { MutableLiveData<List<Line>>() }
|
||||||
|
val event: MutableLiveData<Event> by lazy { MutableLiveData<Event>() }
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
IDLE, CONNECTING, RECEIVING
|
IDLE, CONNECTING, RECEIVING
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
app:startDestination="@id/pageFragment">
|
app:startDestination="@id/pageViewFragment">
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/pageFragment"
|
android:id="@+id/pageViewFragment"
|
||||||
android:name="dev.lowrespalmtree.comet.PageFragment"
|
android:name="dev.lowrespalmtree.comet.PageViewFragment"
|
||||||
android:label="PageViewFragment" />
|
android:label="PageViewFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_pageFragment"
|
android:id="@+id/action_global_pageViewFragment"
|
||||||
app:destination="@id/pageFragment"
|
app:destination="@id/pageViewFragment"
|
||||||
app:enterAnim="@anim/nav_default_enter_anim"
|
app:enterAnim="@anim/nav_default_enter_anim"
|
||||||
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"
|
||||||
|
|
Reference in a new issue