Compare commits

...

9 commits

Author SHA1 Message Date
dece 636a096d6f rename a few things around and clean imports 2022-01-28 13:43:32 +01:00
dece 3190d608ca PageFragment: fix loading URLs appearance 2022-01-28 12:10:36 +01:00
dece 30d7ce72b8 PageAdapter: fix empty lines issues in pre blocks 2022-01-28 12:10:36 +01:00
dece 117c70100f consider blockquotes as a blocks instead of lines
This is roughly the same system as for preformatted blocks.

Even though the spec implies that a line starting with ">" is a
blockquote type line and should be a quote on its own, it is so
frequently used as a block-like type that taking the spec literally
causes rendering issues on many capsules. To be clear, we get blocks
like this:

> Lorem ipsum dolor sit amet,
> consectetur adipiscing elit.
>
> Sed do eiusmod tempor incididunt
> ut labore et dolore magna aliqua.

where we should instead have something like:

> Lorem ipsum dolor sit amet, consectetur adipiscing elit.
> Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

To the people's defense, it is usual to quote a message using the first
syntax. As this issue is minor and the spec is not very adamant on
things here, I don't think there is much wrongdoing in treating the
first syntax as the norm, but the code is open to changes!
2022-01-28 12:10:36 +01:00
dece 9c0603cf67 PageFragment: show the URL being loaded
The URL is shown in italic to distinguish between the current URL and
the URL that is currently being loaded. Unsure that it is enough, it
does not look very good to me anyway.
2022-01-28 12:10:36 +01:00
dece e8283d2ed4 PageAdapter: do not underline links, it's boring 2022-01-28 12:10:36 +01:00
dece 2b694a0bc1 Gemtext: fix preformatted issue 2022-01-28 12:10:36 +01:00
dece 54db4e17a6 use Solarized colors 2022-01-28 12:10:36 +01:00
dece caa6f03e20 PageFragment: fix currentUrl messy property 2022-01-26 18:34:22 +01:00
16 changed files with 108 additions and 86 deletions

View file

@ -47,9 +47,10 @@ android {
dependencies {
def nav_version = "2.3.5"
def room_version = "2.4.1"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.fragment:fragment-ktx:1.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
@ -59,7 +60,6 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
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'

View file

@ -55,17 +55,16 @@ fun parseData(
/** Parse a single line into a Line object. */
private fun parseLine(line: CharBuffer, isPreformatted: Boolean): Line =
when {
line.startsWith("```") -> PreFenceLine(getCharsFrom(line, 3))
isPreformatted -> PreTextLine(line.toString())
line.startsWith("###") -> TitleLine(3, getCharsFrom(line, 3))
line.startsWith("##") -> TitleLine(2, getCharsFrom(line, 2))
line.startsWith("#") -> TitleLine(1, getCharsFrom(line, 1))
line.startsWith("```") -> PreFenceLine(getCharsFrom(line, 3))
line.startsWith("* ") -> ListItemLine(getCharsFrom(line, 2))
line.startsWith(">") -> getCharsFrom(line, 1) // eh empty lines in quotes…
.run { if (isBlank()) EmptyLine() else BlockquoteLine(this) }
line.startsWith(">") -> BlockquoteLine(getCharsFrom(line, 1))
line.startsWith("=>") -> getCharsFrom(line, 2)
.split(" ", "\t", limit = 2)
.run { LinkLine(get(0), if (size == 2) get(1).trimStart() else "") }
isPreformatted -> PreTextLine(line.toString())
line.isEmpty() -> EmptyLine()
else -> ParagraphLine(line.toString())
}

View file

@ -8,8 +8,8 @@ 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>() {
class HistoryAdapter(private val listener: HistoryItemAdapterListener) :
RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
private var items = listOf<HistoryEntry>()
interface HistoryItemAdapterListener {

View file

@ -14,7 +14,7 @@ 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.HistoryAdapter.HistoryItemAdapterListener
import dev.lowrespalmtree.comet.databinding.FragmentHistoryListBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -24,7 +24,7 @@ import kotlinx.coroutines.launch
class HistoryFragment : Fragment(), HistoryItemAdapterListener {
private val vm: HistoryViewModel by viewModels()
private lateinit var binding: FragmentHistoryListBinding
private lateinit var adapter: HistoryItemAdapter
private lateinit var adapter: HistoryAdapter
override fun onCreateView(
inflater: LayoutInflater,
@ -36,7 +36,7 @@ class HistoryFragment : Fragment(), HistoryItemAdapterListener {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = HistoryItemAdapter(this)
adapter = HistoryAdapter(this)
binding.list.layoutManager = LinearLayoutManager(requireContext())
binding.list.adapter = adapter

View file

@ -2,16 +2,17 @@ package dev.lowrespalmtree.comet
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import dev.lowrespalmtree.comet.Identities.Identity
import dev.lowrespalmtree.comet.databinding.FragmentIdentityBinding
class IdentityAdapter : RecyclerView.Adapter<HistoryItemAdapter.ViewHolder>() {
private var identities = listOf<Unit>()
class IdentitiesAdapter : RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
private var identities = listOf<Identity>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryItemAdapter.ViewHolder {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryAdapter.ViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: HistoryItemAdapter.ViewHolder, position: Int) {
override fun onBindViewHolder(holder: HistoryAdapter.ViewHolder, position: Int) {
TODO("Not yet implemented")
}

View file

@ -1,8 +1,6 @@
package dev.lowrespalmtree.comet
import android.annotation.SuppressLint
import android.text.SpannableString
import android.text.style.UnderlineSpan
import android.util.Log
import android.view.LayoutInflater
import android.view.View
@ -37,7 +35,7 @@ class PageAdapter(private val listener: ContentAdapterListener) :
class Title(val text: String, val level: Int) : ContentBlock()
class Link(val url: String, val label: String) : ContentBlock()
class Pre(val caption: String, var content: String, var closed: Boolean) : ContentBlock()
class Blockquote(val text: String) : ContentBlock()
class Blockquote(var text: String) : ContentBlock()
class ListItem(val text: String) : ContentBlock()
}
@ -57,7 +55,6 @@ class PageAdapter(private val listener: ContentAdapterListener) :
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 BlockquoteLine -> blocks.add(ContentBlock.Blockquote(line.text))
is ListItemLine -> blocks.add(ContentBlock.ListItem(line.text))
is TitleLine -> blocks.add(ContentBlock.Title(line.text, line.level))
is PreFenceLine -> {
@ -74,12 +71,21 @@ class PageAdapter(private val listener: ContentAdapterListener) :
}
is PreTextLine -> {
val lastBlock = blocks.last()
if (lastBlock is ContentBlock.Pre && !lastBlock.closed)
lastBlock.content += line.text + "\n"
else
if (lastBlock is ContentBlock.Pre && !lastBlock.closed) {
if (lastBlock.content.isNotEmpty())
lastBlock.content += "\n"
lastBlock.content += line.text
} else {
Log.e(TAG, "setLines: unexpected preformatted line")
}
}
is BlockquoteLine -> {
if (blocks.isNotEmpty() && blocks.last() is ContentBlock.Blockquote)
(blocks.last() as ContentBlock.Blockquote).text += "\n" + line.text
else
blocks.add(ContentBlock.Blockquote(line.text))
}
}
currentLine++
}
val numAdded = blocks.size - lastBlockCount
@ -148,9 +154,7 @@ class PageAdapter(private val listener: ContentAdapterListener) :
}
is ContentBlock.Link -> {
val label = if (block.label.isNotBlank()) block.label else block.url
val underlinedLabel = SpannableString(label)
underlinedLabel.setSpan(UnderlineSpan(), 0, underlinedLabel.length, 0)
(holder as ContentViewHolder.Link).binding.textView.text = underlinedLabel
(holder as ContentViewHolder.Link).binding.textView.text = label
holder.binding.root.setOnClickListener { listener.onLinkClick(block.url) }
}
is ContentBlock.Pre ->

View file

@ -32,14 +32,6 @@ class PageFragment : Fragment(), ContentAdapterListener {
private lateinit var binding: FragmentPageViewBinding
private lateinit var adapter: PageAdapter
/** Property to access and set the current address bar URL value. */
private var currentUrl: String
get() = vm.currentUrl
set(value) {
vm.currentUrl = value
binding.addressBar.setText(value)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -58,7 +50,7 @@ class PageFragment : Fragment(), ContentAdapterListener {
binding.addressBar.setOnEditorActionListener { v, id, _ -> onAddressBarAction(v, id) }
binding.contentSwipeLayout.setOnRefreshListener { openUrl(currentUrl) }
binding.contentSwipeLayout.setOnRefreshListener { openUrl(vm.currentUrl) }
vm.state.observe(viewLifecycleOwner, { updateState(it) })
vm.lines.observe(viewLifecycleOwner, { updateLines(it) })
@ -79,7 +71,7 @@ class PageFragment : Fragment(), ContentAdapterListener {
}
override fun onLinkClick(url: String) {
openUrl(url, base = if (currentUrl.isNotEmpty()) currentUrl else null)
openUrl(url, base = if (vm.currentUrl.isNotEmpty()) vm.currentUrl else null)
}
private fun onBackPressed() {
@ -144,12 +136,16 @@ class PageFragment : Fragment(), ContentAdapterListener {
PageViewModel.State.IDLE -> {
binding.contentProgressBar.hide()
binding.contentSwipeLayout.isRefreshing = false
binding.addressBar.setText(vm.currentUrl)
binding.addressBar.setTextColor(resources.getColor(R.color.url_bar, null))
}
PageViewModel.State.CONNECTING -> {
binding.appBarLayout.setExpanded(true, true)
binding.contentProgressBar.show()
binding.addressBar.setText(vm.loadingUrl?.toString() ?: "")
binding.addressBar.setTextColor(resources.getColor(R.color.url_bar_loading, null))
}
PageViewModel.State.RECEIVING -> {
binding.appBarLayout.setExpanded(true, true)
binding.contentSwipeLayout.isRefreshing = false
}
}
@ -169,11 +165,12 @@ class PageFragment : Fragment(), ContentAdapterListener {
askForInput(event.prompt, event.uri)
}
is PageViewModel.SuccessEvent -> {
currentUrl = event.uri
vm.currentUrl = event.uri
vm.visitedUrls.add(event.uri)
binding.addressBar.setText(event.uri)
}
is PageViewModel.RedirectEvent -> {
openUrl(event.uri, base = currentUrl, redirections = event.redirects)
openUrl(event.uri, base = vm.currentUrl, redirections = event.redirects)
}
is PageViewModel.FailureEvent -> {
var message = event.details
@ -215,7 +212,7 @@ class PageFragment : Fragment(), ContentAdapterListener {
.setView(inputView)
.setPositiveButton(android.R.string.ok) { _, _ ->
val newUri = uri.buildUpon().query(editText.text.toString()).build()
openUrl(newUri.toString(), base = currentUrl)
openUrl(newUri.toString(), base = vm.currentUrl)
}
.setOnDismissListener { updateState(PageViewModel.State.IDLE) }
.create()

View file

@ -18,6 +18,8 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState
ViewModel() {
/** Currently viewed page URL. */
var currentUrl: String = ""
/** Latest Uri requested using `sendGeminiRequest`. */
var loadingUrl: Uri? = null
/** 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). */
@ -52,6 +54,7 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState
*/
fun sendGeminiRequest(uri: Uri, connectionTimeout: Int, readTimeout: Int, redirects: Int = 0) {
Log.d(TAG, "sendRequest: URI \"$uri\"")
loadingUrl = uri
state.postValue(State.CONNECTING)
requestJob?.apply { if (isActive) cancel() }
requestJob = viewModelScope.launch(Dispatchers.IO) {

View file

@ -30,6 +30,7 @@
android:importantForAutofill="no"
android:inputType="textUri"
android:text=""
android:textColor="@color/url_bar"
tools:ignore="TextContrastCheck" />
</Toolbar>

View file

@ -1,9 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_view"
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/CometText"
android:textStyle="italic"
android:paddingStart="32dp"
android:paddingEnd="16dp" />
android:background="@color/background_emph"
android:textStyle="italic" />
</FrameLayout>

View file

@ -4,5 +4,5 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/CometText"
android:textColor="@color/teal_700"
android:textColor="@color/link"
android:textIsSelectable="false" />

View file

@ -12,8 +12,6 @@
android:textColor="@color/text"
android:fontFamily="@font/preformatted"
android:typeface="monospace"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
android:padding="4dp"
android:background="@color/background_emph" />
</HorizontalScrollView>

View file

@ -1,4 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text">#D4D4D4</color>
<color name="background">#002b36</color>
<color name="background_emph">#073642</color>
<color name="text_light">#93a1a1</color>
<color name="text">#839496</color>
<color name="main_accent">#d33682</color>
<color name="main_accent_dark">#073642</color>
<color name="second_accent">#2aa198</color>
<color name="second_accent_dark">#073642</color>
<color name="link">#2aa198</color>
<color name="url_bar">#fdf6e3</color>
<color name="url_bar_loading">#586e75</color>
</resources>

View file

@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Comet" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -1,17 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="black">#000000</color>
<color name="bright">#F3F4ED</color>
<color name="text_light">#536162</color>
<color name="text">#424642</color>
<color name="orange">#C06014</color>
<color name="orange_dark">#683C19</color>
<color name="teal">#14C0B2</color>
<color name="teal_dark">#147169</color>
<color name="background">#fdf6e3</color>
<color name="background_emph">#eee8d5</color>
<color name="text_light">#93a1a1</color>
<color name="text">#657b83</color>
<color name="main_accent">#d33682</color>
<color name="main_accent_dark">#6c71c4</color>
<color name="second_accent">#6c71c4</color>
<color name="second_accent_dark">#073642</color>
<color name="link">#268bd2</color>
<color name="url_bar">#002b36</color>
<color name="url_bar_loading">#fdf6e3</color>
<!--
Based on Solarized
SOLARIZED HEX 16/8 TERMCOL XTERM/HEX L*A*B RGB HSB
base03 #002b36 8/4 brblack 234 #1c1c1c 15 -12 -12 0 43 54 193 100 21
base02 #073642 0/4 black 235 #262626 20 -12 -12 7 54 66 192 90 26
base01 #586e75 10/7 brgreen 240 #585858 45 -07 -07 88 110 117 194 25 46
base00 #657b83 11/7 bryellow 241 #626262 50 -07 -07 101 123 131 195 23 51
base0 #839496 12/6 brblue 244 #808080 60 -06 -03 131 148 150 186 13 59
base1 #93a1a1 14/4 brcyan 245 #8a8a8a 65 -05 -02 147 161 161 180 9 63
base2 #eee8d5 7/7 white 254 #e4e4e4 92 -00 10 238 232 213 44 11 93
base3 #fdf6e3 15/7 brwhite 230 #ffffd7 97 00 10 253 246 227 44 10 99
yellow #b58900 3/3 yellow 136 #af8700 60 10 65 181 137 0 45 100 71
orange #cb4b16 9/3 brred 166 #d75f00 50 50 55 203 75 22 18 89 80
red #dc322f 1/1 red 160 #d70000 50 65 45 220 50 47 1 79 86
magenta #d33682 5/5 magenta 125 #af005f 50 65 -05 211 54 130 331 74 83
violet #6c71c4 13/5 brmagenta 61 #5f5faf 50 15 -45 108 113 196 237 45 77
blue #268bd2 4/4 blue 33 #0087ff 55 -10 -45 38 139 210 205 82 82
cyan #2aa198 6/6 cyan 37 #00afaf 60 -35 -05 42 161 152 175 74 63
green #859900 2/2 green 64 #5f8700 60 -20 65 133 153 0 68 100 60
-->
</resources>

View file

@ -2,17 +2,17 @@
<!-- Base application theme. -->
<style name="Theme.Comet" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/orange</item>
<item name="colorPrimaryVariant">@color/orange_dark</item>
<item name="colorOnPrimary">@color/bright</item>
<item name="colorPrimary">@color/main_accent</item>
<item name="colorPrimaryVariant">@color/main_accent_dark</item>
<item name="colorOnPrimary">@color/background</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal</item>
<item name="colorSecondaryVariant">@color/teal_dark</item>
<item name="colorSecondary">@color/second_accent</item>
<item name="colorSecondaryVariant">@color/second_accent_dark</item>
<item name="colorOnSecondary">@color/text</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/bright</item>
<item name="android:windowBackground">@color/background</item>
<item name="android:progressBackgroundTint">?attr/colorSecondaryVariant</item>
</style>
</resources>