diff --git a/app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt b/app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt index 7301d70..2753c33 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt @@ -13,7 +13,7 @@ interface Line class EmptyLine : Line class ParagraphLine(val text: String) : Line class TitleLine(val level: Int, val text: String) : Line -class LinkLine(val url: String, val label: String) : Line +class LinkLine(val url: String, val label: String, var visited: Boolean = false) : Line class PreFenceLine(val caption: String) : Line class PreTextLine(val text: String) : Line class BlockquoteLine(val text: String) : Line @@ -21,7 +21,7 @@ class ListItemLine(val text: String) : Line private const val TAG = "Gemtext" -/** Pipe incoming gemtext data into parsed Lines. */ +/** Pipe incoming Gemtext data into parsed Lines. */ fun parseData( inChannel: Channel, charset: Charset, diff --git a/app/src/main/java/dev/lowrespalmtree/comet/History.kt b/app/src/main/java/dev/lowrespalmtree/comet/History.kt index e916a8e..bc6e1d9 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/History.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/History.kt @@ -38,6 +38,10 @@ object History { dao.update(entry.also { it.title = title; it.lastVisit = now }) } + suspend fun contains(uri: String): Boolean = get(uri) != null + + suspend fun get(uri: String): HistoryEntry? = Database.INSTANCE.historyEntryDao().get(uri) + suspend fun getAll(): List = Database.INSTANCE.historyEntryDao().getAll() suspend fun getLast(): HistoryEntry? = Database.INSTANCE.historyEntryDao().getLast() diff --git a/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt b/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt index dccf628..fc3ba1b 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt @@ -1,6 +1,7 @@ package dev.lowrespalmtree.comet import android.os.Bundle +import android.util.Log import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf @@ -15,6 +16,7 @@ class MainActivity : AppCompatActivity() { private var nhf: NavHostFragment? = null override fun onCreate(savedInstanceState: Bundle?) { + Log.i(TAG, "onCreate") super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -32,4 +34,8 @@ class MainActivity : AppCompatActivity() { nhf?.navController?.navigate(R.id.action_global_pageFragment, bundle) binding.drawerLayout.closeDrawers() } + + companion object { + private const val TAG = "MainActivity" + } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt index 817bad0..ac44c1e 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt @@ -1,6 +1,7 @@ package dev.lowrespalmtree.comet import android.annotation.SuppressLint +import android.net.Uri import android.util.Log import android.view.LayoutInflater import android.view.View @@ -33,7 +34,7 @@ class PageAdapter(private val listener: Listener) : object Empty : ContentBlock() class Paragraph(val text: String) : ContentBlock() class Title(val text: String, val level: Int) : ContentBlock() - class Link(val url: String, val label: String) : ContentBlock() + class Link(val url: String, val label: String, val visited: Boolean) : ContentBlock() class Pre(val caption: String, var content: String, var closed: Boolean) : ContentBlock() class Blockquote(var text: String) : ContentBlock() class ListItem(val text: String) : ContentBlock() @@ -54,7 +55,7 @@ class PageAdapter(private val listener: Listener) : when (val line = lines[currentLine]) { is EmptyLine -> blocks.add(ContentBlock.Empty) is ParagraphLine -> blocks.add(ContentBlock.Paragraph(line.text)) - is LinkLine -> blocks.add(ContentBlock.Link(line.url, line.label)) + is LinkLine -> blocks.add(ContentBlock.Link(line.url, line.label, line.visited)) is ListItemLine -> blocks.add(ContentBlock.ListItem(line.text)) is TitleLine -> blocks.add(ContentBlock.Title(line.text, line.level)) is PreFenceLine -> { @@ -95,8 +96,8 @@ class PageAdapter(private val listener: Listener) : lastBlockCount = blocks.size } - sealed class ContentViewHolder(view: View) : RecyclerView.ViewHolder(view) { - class Empty(binding: GemtextEmptyBinding) : ContentViewHolder(binding.root) + sealed class ContentViewHolder(val view: View) : RecyclerView.ViewHolder(view) { + class Empty(val binding: GemtextEmptyBinding) : ContentViewHolder(binding.root) class Paragraph(val binding: GemtextParagraphBinding) : ContentViewHolder(binding.root) class Title1(val binding: GemtextTitle1Binding) : ContentViewHolder(binding.root) class Title2(val binding: GemtextTitle2Binding) : ContentViewHolder(binding.root) @@ -156,6 +157,15 @@ class PageAdapter(private val listener: Listener) : val label = block.label.ifBlank { block.url } (holder as ContentViewHolder.Link).binding.textView.text = label holder.binding.root.setOnClickListener { listener.onLinkClick(block.url) } + + // Color links differently if it has been already visited or not. + val resources = holder.binding.root.context.resources + holder.binding.textView.setTextColor( + if (block.visited) + resources.getColor(R.color.link_visited, null) + else + resources.getColor(R.color.link, null) + ) } is ContentBlock.Pre -> (holder as ContentViewHolder.Pre).binding.textView.text = block.content diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt index a367c1a..0dfb564 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt @@ -51,7 +51,7 @@ class PageFragment : Fragment(), PageAdapter.Listener { binding.contentSwipeLayout.setOnRefreshListener { openUrl(vm.currentUrl) } vm.state.observe(viewLifecycleOwner) { updateState(it) } - vm.lines.observe(viewLifecycleOwner) { updateLines(it) } + vm.lines.observe(viewLifecycleOwner) { updateLines(it.second, it.first) } vm.event.observe(viewLifecycleOwner) { handleEvent(it) } activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() } @@ -151,8 +151,8 @@ class PageFragment : Fragment(), PageAdapter.Listener { } } - private fun updateLines(lines: List) { - Log.d(TAG, "updateLines: ${lines.size} lines") + private fun updateLines(lines: List, url: String) { + Log.d(TAG, "updateLines: ${lines.size} lines from $url") adapter.setLines(lines) } diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt index 298a939..ca0a4d1 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dev.lowrespalmtree.comet.utils.joinUrls import kotlinx.coroutines.* import kotlinx.coroutines.channels.onSuccess import java.net.ConnectException @@ -21,8 +22,8 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState var loadingUrl: Uri? = null /** Observable page viewer state. */ val state: MutableLiveData by lazy { MutableLiveData(State.IDLE) } - /** Observable page viewer lines (backed up by `linesList` but updated less often). */ - val lines: MutableLiveData> by lazy { MutableLiveData>() } + /** Observable page viewer lines (backed up by `linesList` but updated less often). Left element is associated URL. */ + val lines: MutableLiveData>> by lazy { MutableLiveData>>() } /** Observable page viewer latest event. */ val event: MutableLiveData by lazy { MutableLiveData() } /** A non-saved list of visited URLs. Not an history, just used for going back. */ @@ -113,9 +114,10 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState @ExperimentalCoroutinesApi private suspend fun handleSuccessResponse(response: Response, uri: Uri) { state.postValue(State.RECEIVING) + val uriString = uri.toString() linesList.clear() - lines.postValue(linesList) + lines.postValue(Pair(uriString, linesList)) val charset = Charset.defaultCharset() var mainTitle: String? = null var lastUpdate = System.currentTimeMillis() @@ -126,7 +128,16 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState while (!lineChannel.isClosedForReceive) { val lineChannelResult = withTimeout(100) { lineChannel.tryReceive() } lineChannelResult.onSuccess { line -> + if (line is LinkLine) { + // Mark visited links here as we have a access to the history. + val fullUrl = + if (Uri.parse(line.url).isAbsolute) line.url + else joinUrls(uriString, line.url).toString() + if (History.contains(fullUrl)) + line.visited = true + } linesList.add(line) + // Get the first level 1 header as the page main title. if (mainTitle == null && line is TitleLine && line.level == 1) mainTitle = line.text } @@ -135,7 +146,7 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState if (linesList.size > lastNumLines) { val time = System.currentTimeMillis() if (time - lastUpdate >= 100) { - lines.postValue(linesList) + lines.postValue(Pair(uriString, linesList)) lastUpdate = time lastNumLines = linesList.size } @@ -147,7 +158,7 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState return } Log.d(TAG, "handleSuccessResponse: done parsing line data") - lines.postValue(linesList) + lines.postValue(Pair(uriString, linesList)) // We record the history entry here: it's nice because we have the main title available // and we're already in a coroutine for database access. diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 7c0e63a..3d92051 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -8,7 +8,8 @@ #073642 #2aa198 #073642 - #2aa198 + #268bd2 + #2aa198 #fdf6e3 #586e75 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 28159d4..3d463eb 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -11,6 +11,7 @@ #6c71c4 #073642 #268bd2 + #2aa198 #002b36 #fdf6e3