Attention guides for gemtext

This commit is contained in:
Corewala 2022-05-24 15:38:47 -04:00
parent a7877cfc41
commit 0b7e24e764
11 changed files with 102 additions and 57 deletions

View File

@ -20,6 +20,8 @@ Buran is a simple Gemini protocol browser for Android.
- [X] Inline rendering of images - [X] Inline rendering of images
- [ ] Page navigation feature - [ ] Page navigation feature
- [X] Update notifier - [X] Update notifier
- [X] Attention guide mode
## Statement about Ariane ## Statement about Ariane

View File

@ -50,15 +50,4 @@ class BuranKeyManager(val context: Context, val onKeyError: (error: String) -> U
} }
} }
} }
//Working example with cert packaged with app
fun getFactoryDemo(context: Context): KeyManagerFactory? {
val keyStore: KeyStore = KeyStore.getInstance("pkcs12")
keyStore.load(context.resources.openRawResource(R.raw.cert), "PASSWORD".toCharArray())
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("X509")
keyManagerFactory.init(keyStore, "PASSWORD".toCharArray())
return keyManagerFactory
}
} }

View File

@ -7,7 +7,6 @@ import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
@ -182,11 +181,10 @@ class GemActivity : AppCompatActivity() {
) )
} }
val isSideLoaded = packageManager.getInstallerPackageName(BuildConfig.APPLICATION_ID).isNullOrEmpty()
if(PreferenceManager.getDefaultSharedPreferences(this).getBoolean( if(PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
"check_for_updates", "check_for_updates",
false false
) and isSideLoaded) { )){
val updates = BuranUpdates() val updates = BuranUpdates()
val latestVersion = updates.getLatestVersion() val latestVersion = updates.getLatestVersion()
@ -331,14 +329,7 @@ class GemActivity : AppCompatActivity() {
binding.pullToRefresh.setOnRefreshListener { binding.pullToRefresh.setOnRefreshListener {
if(getInternetStatus()){ if(getInternetStatus()){
if(initialised){ refresh()
refresh()
}else{
val intent = baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
finish()
startActivity(intent)
}
}else{ }else{
binding.pullToRefresh.isRefreshing = false binding.pullToRefresh.isRefreshing = false
Snackbar.make(binding.root, getString(R.string.no_internet), Snackbar.LENGTH_LONG).show() Snackbar.make(binding.root, getString(R.string.no_internet), Snackbar.LENGTH_LONG).show()
@ -380,6 +371,12 @@ class GemActivity : AppCompatActivity() {
) )
adapter.linkButtons(showLinkButtons) adapter.linkButtons(showLinkButtons)
val useAttentionGuides = prefs.getBoolean(
"use_attention_guides",
false
)
adapter.attentionGuides(useAttentionGuides)
val showInlineImages = prefs.getBoolean( val showInlineImages = prefs.getBoolean(
"show_inline_images", "show_inline_images",
@ -784,18 +781,13 @@ class GemActivity : AppCompatActivity() {
private fun getInternetStatus(): Boolean { private fun getInternetStatus(): Boolean {
val connectivityManager = this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager val connectivityManager = this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) { return if(capabilities != null){
println("Internet access found") println("Internet access found")
if(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { true
return true }else{
}else if(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { println("No internet access found")
return true false
}else if(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return true
}
} }
println("No internet access found")
return false
} }
private fun isHostSigned(uri: URI): Boolean{ private fun isHostSigned(uri: URI): Boolean{

View File

@ -11,6 +11,7 @@ abstract class AbstractGemtextAdapter(
var showInlineIcons: Boolean = false var showInlineIcons: Boolean = false
var showLinkButtons: Boolean = false var showLinkButtons: Boolean = false
var useAttentionGuides: Boolean = false
var showInlineImages: Boolean = false var showInlineImages: Boolean = false
abstract fun render(lines: List<String>) abstract fun render(lines: List<String>)
@ -18,7 +19,7 @@ abstract class AbstractGemtextAdapter(
abstract fun inlineIcons(visible: Boolean) abstract fun inlineIcons(visible: Boolean)
abstract fun inlineImages(visible: Boolean) abstract fun inlineImages(visible: Boolean)
abstract fun linkButtons(visible: Boolean) abstract fun linkButtons(visible: Boolean)
abstract fun attentionGuides(enabled: Boolean)
abstract fun inferTitle(): String? abstract fun inferTitle(): String?
companion object{ companion object{

View File

@ -2,16 +2,19 @@ package corewala.buran.ui.gemtext_adapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import android.text.SpannableStringBuilder
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.gemtext_code_block.view.* import androidx.core.text.bold
import kotlinx.android.synthetic.main.gemtext_image_link.view.*
import kotlinx.android.synthetic.main.gemtext_link.view.gemtext_text_link
import kotlinx.android.synthetic.main.gemtext_text.view.*
import corewala.buran.R import corewala.buran.R
import corewala.endsWithImage import corewala.endsWithImage
import corewala.visible import corewala.visible
import kotlinx.android.synthetic.main.gemtext_code_block.view.*
import kotlinx.android.synthetic.main.gemtext_image_link.view.*
import kotlinx.android.synthetic.main.gemtext_link.view.gemtext_text_link
import kotlinx.android.synthetic.main.gemtext_quote.view.*
import kotlinx.android.synthetic.main.gemtext_text.view.*
import java.net.URI import java.net.URI
class GemtextAdapter( class GemtextAdapter(
@ -80,16 +83,25 @@ class GemtextAdapter(
val line = lines[position] val line = lines[position]
when(holder){ when(holder){
is GmiViewHolder.Text -> holder.itemView.gemtext_text_textview.text = line is GmiViewHolder.Text -> {
when {
useAttentionGuides -> holder.itemView.gemtext_text_textview.text = getAttentionGuideText(line)
else -> holder.itemView.gemtext_text_textview.text = line
}
}
is GmiViewHolder.Code -> { is GmiViewHolder.Code -> {
if(line.startsWith("```<|ALT|>")){ if(line.startsWith("```<|ALT|>")){
holder.itemView.gemtext_text_monospace_textview.text = line.substring(line.indexOf("</|ALT>") + 7) holder.itemView.gemtext_text_monospace_textview.text = line.substring(line.indexOf("</|ALT>") + 7)
}else{ }else{
holder.itemView.gemtext_text_monospace_textview.text = line.substring(3) holder.itemView.gemtext_text_monospace_textview.text = line.substring(3)
} }
} }
is GmiViewHolder.Quote -> holder.itemView.gemtext_text_monospace_textview.text = line.substring(1).trim() is GmiViewHolder.Quote -> {
when {
useAttentionGuides -> holder.itemView.gemtext_quote_textview.text = getAttentionGuideText(line.substring(1).trim())
else -> holder.itemView.gemtext_quote_textview.text = line.substring(1).trim()
}
}
is GmiViewHolder.H1 -> { is GmiViewHolder.H1 -> {
when { when {
line.length > 2 -> holder.itemView.gemtext_text_textview.text = line.substring(2).trim() line.length > 2 -> holder.itemView.gemtext_text_textview.text = line.substring(2).trim()
@ -108,7 +120,12 @@ class GemtextAdapter(
else -> holder.itemView.gemtext_text_textview.text = "" else -> holder.itemView.gemtext_text_textview.text = ""
} }
} }
is GmiViewHolder.ListItem -> holder.itemView.gemtext_text_textview.text = "${line.substring(1)}".trim() is GmiViewHolder.ListItem -> {
when {
useAttentionGuides -> holder.itemView.gemtext_text_textview.text = getAttentionGuideText("${line.substring(1)}".trim())
else -> holder.itemView.gemtext_text_textview.text = "${line.substring(1)}".trim()
}
}
is GmiViewHolder.Link -> { is GmiViewHolder.Link -> {
val linkParts = line.substring(2).trim().split("\\s+".toRegex(), 2) val linkParts = line.substring(2).trim().split("\\s+".toRegex(), 2)
var linkName = linkParts[0] var linkName = linkParts[0]
@ -184,11 +201,11 @@ class GemtextAdapter(
holder.itemView.gemtext_link_button.visible(true) holder.itemView.gemtext_link_button.visible(true)
holder.itemView.gemtext_link_button.text = displayText holder.itemView.gemtext_link_button.text = displayText
} else -> { } else -> {
holder.itemView.gemtext_link_button.visible(false) holder.itemView.gemtext_link_button.visible(false)
holder.itemView.gemtext_text_link.visible(true) holder.itemView.gemtext_text_link.visible(true)
holder.itemView.gemtext_text_link.text = displayText holder.itemView.gemtext_text_link.text = displayText
holder.itemView.gemtext_text_link.paint.isUnderlineText = true holder.itemView.gemtext_text_link.paint.isUnderlineText = true
} }
} }
holder.itemView.gemtext_text_link.setOnClickListener { holder.itemView.gemtext_text_link.setOnClickListener {
@ -265,6 +282,35 @@ class GemtextAdapter(
return URI.create(linkParts.first()) return URI.create(linkParts.first())
} }
private fun getAttentionGuideText(text: String): SpannableStringBuilder {
val wordList = text.split(" ")
val attentionGuideText = SpannableStringBuilder()
for(word in wordList){
if(word.length > 1){
if(word.first().isLetterOrDigit()){
val index = word.length/2
attentionGuideText
.bold{append(word.substring(0, index))}
.append("${word.substring(index)} ")
}else{
var offset = 1
while(!word.substring(offset).first().isLetterOrDigit()){
offset += 1
}
val index = (word.length - offset)/2
attentionGuideText
.append(word.substring(0, offset))
.bold{append(word.substring(offset, index + offset))}
.append("${word.substring(index + offset)} ")
}
}else{
attentionGuideText.append("$word ")
}
}
return attentionGuideText
}
override fun inferTitle(): String? { override fun inferTitle(): String? {
lines.forEach { line -> lines.forEach { line ->
if(line.startsWith("#")) return line.replace("#", "").trim() if(line.startsWith("#")) return line.replace("#", "").trim()
@ -288,6 +334,11 @@ class GemtextAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun attentionGuides(enabled: Boolean){
this.useAttentionGuides = enabled
notifyDataSetChanged()
}
override fun inlineImages(visible: Boolean){ override fun inlineImages(visible: Boolean){
this.showInlineImages = visible this.showInlineImages = visible
notifyDataSetChanged() notifyDataSetChanged()

View File

@ -14,7 +14,6 @@ import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.preference.* import androidx.preference.*
import corewala.buran.BuildConfig
import corewala.buran.Buran import corewala.buran.Buran
import corewala.buran.R import corewala.buran.R
import corewala.buran.io.keymanager.BuranBiometricManager import corewala.buran.io.keymanager.BuranBiometricManager
@ -93,18 +92,18 @@ class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChang
appCategory.addPreference(searchPreference) appCategory.addPreference(searchPreference)
//Updates --------------------------------------------- //Updates ---------------------------------------------
val aboutUpdater = Preference(context)
aboutUpdater.summary = getString(R.string.self_update_summary)
aboutUpdater.isPersistent = false
aboutUpdater.isSelectable = false
appCategory.addPreference(aboutUpdater)
val checkForUpdates = SwitchPreferenceCompat(context) val checkForUpdates = SwitchPreferenceCompat(context)
checkForUpdates.setDefaultValue(true) checkForUpdates.setDefaultValue(true)
checkForUpdates.key = "check_for_updates" checkForUpdates.key = "check_for_updates"
checkForUpdates.title = getString(R.string.check_for_updates) checkForUpdates.title = getString(R.string.check_for_updates)
appCategory.addPreference(checkForUpdates) appCategory.addPreference(checkForUpdates)
val isSideLoaded = context.packageManager.getInstallerPackageName(BuildConfig.APPLICATION_ID).isNullOrEmpty()
checkForUpdates.isVisible = isSideLoaded
if(!isSideLoaded){
checkForUpdates.equals(false)
}
//Certificates //Certificates
buildClientCertificateSection(context, screen) buildClientCertificateSection(context, screen)
@ -232,6 +231,13 @@ class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChang
showLinkButtonsPreference.key = "show_link_buttons" showLinkButtonsPreference.key = "show_link_buttons"
showLinkButtonsPreference.title = getString(R.string.show_link_buttons) showLinkButtonsPreference.title = getString(R.string.show_link_buttons)
accessibilityCategory.addPreference(showLinkButtonsPreference) accessibilityCategory.addPreference(showLinkButtonsPreference)
//Accessibility - gemtext attention guides
val focusGuidingText = SwitchPreferenceCompat(context)
focusGuidingText.setDefaultValue(false)
focusGuidingText.key = "use_attention_guides"
focusGuidingText.title = getString(R.string.use_attention_guides)
accessibilityCategory.addPreference(focusGuidingText)
} }
private fun buildClientCertificateSection(context: Context?, screen: PreferenceScreen) { private fun buildClientCertificateSection(context: Context?, screen: PreferenceScreen) {

View File

@ -21,7 +21,7 @@
android:textSize="@dimen/code_text_size" android:textSize="@dimen/code_text_size"
android:typeface="monospace" android:typeface="monospace"
android:textColor="@color/stroke" android:textColor="@color/stroke"
android:id="@id/gemtext_text_monospace_textview" android:id="@+id/gemtext_text_monospace_textview"
android:paddingLeft="@dimen/default_margin_big" android:paddingLeft="@dimen/default_margin_big"
android:paddingTop="@dimen/default_margin_big" android:paddingTop="@dimen/default_margin_big"
android:paddingRight="@dimen/default_margin_big" android:paddingRight="@dimen/default_margin_big"

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gemtext_text_monospace_textview" android:id="@+id/gemtext_quote_textview"
android:textSize="@dimen/default_text_size" android:textSize="@dimen/default_text_size"
android:textColor="@color/stroke" android:textColor="@color/stroke"
android:layout_marginLeft="@dimen/screen_margin" android:layout_marginLeft="@dimen/screen_margin"

Binary file not shown.

View File

@ -63,7 +63,7 @@
<string name="web_content_label">Ouvrir les sites web en interne en utilisant des \'Onglets Personnalisés\', plutôt que d\'utiliser le navigateur par défaut. Cela pourrait vous aider à rester dans le Geminispace plutôt que d\'être distrait·e par le vaste web. Cela requiert un navigateur par défaut compatible.</string> <string name="web_content_label">Ouvrir les sites web en interne en utilisant des \'Onglets Personnalisés\', plutôt que d\'utiliser le navigateur par défaut. Cela pourrait vous aider à rester dans le Geminispace plutôt que d\'être distrait·e par le vaste web. Cela requiert un navigateur par défaut compatible.</string>
<string name="web_content_switch_label">Ouvrir en interne</string> <string name="web_content_switch_label">Ouvrir en interne</string>
<string name="show_inline_images">Images locales en ligne</string> <string name="show_inline_images">Images locales en ligne</string>
<string name="pkcs_notice">Seuls les magasins de clés client PKCS12 sont actuellement supportés</string> <string name="pkcs_notice">Seuls les magasins de clés client PKCS12 sont actuellement supportés.</string>
<string name="client_certificate">Certificat Client</string> <string name="client_certificate">Certificat Client</string>
<string name="tap_to_select_client_certificate">Cliquez pour sélectionner un certificat client</string> <string name="tap_to_select_client_certificate">Cliquez pour sélectionner un certificat client</string>
<string name="client_certificate_password">Mot de passe du Certificat Client</string> <string name="client_certificate_password">Mot de passe du Certificat Client</string>
@ -79,12 +79,14 @@
<string name="cert_unloaded">Certificat dechargé</string> <string name="cert_unloaded">Certificat dechargé</string>
<string name="set_home_capsule">Choisir comme capsule d\'accueil</string> <string name="set_home_capsule">Choisir comme capsule d\'accueil</string>
<string name="check_for_updates">Vérifier pour des mises à jour</string> <string name="check_for_updates">Vérifier pour des mises à jour</string>
<string name="self_update_summary">L\'instalation automatique des mises à jour ne fonctionne que si Bourane est installée manuellement.</string>
<string name="new_version_available">Nouvelle version disponible</string> <string name="new_version_available">Nouvelle version disponible</string>
<string name="no_internet">Aucun accès internet</string> <string name="no_internet">Aucun accès internet</string>
<string name="history_cleared">Historique vidé</string> <string name="history_cleared">Historique vidé</string>
<string name="runtime_cache_cleared">Cache d\'exécution vidé</string> <string name="runtime_cache_cleared">Cache d\'exécution vidé</string>
<string name="show_inline_icons">Icônes de lien en ligne</string> <string name="show_inline_icons">Icônes de lien en ligne</string>
<string name="show_link_buttons">Boutons de lien</string> <string name="show_link_buttons">Boutons de lien</string>
<string name="use_attention_guides">Utiliser des guides d\'attention</string>
<string name="bookmarks_empty">Vous n\'avez encore aucun marque-pages</string> <string name="bookmarks_empty">Vous n\'avez encore aucun marque-pages</string>
<string name="import_bookmarks">Importer des marque-pages</string> <string name="import_bookmarks">Importer des marque-pages</string>
<string name="export_bookmarks">Exporter des marque-pages</string> <string name="export_bookmarks">Exporter des marque-pages</string>

View File

@ -63,7 +63,7 @@
<string name="web_content_label">Open websites internally using \'Custom Tabs\', instead of using the default browser. This might help you stay in Geminispace instead of being distracted by the wider web. Requires compatible default browser.</string> <string name="web_content_label">Open websites internally using \'Custom Tabs\', instead of using the default browser. This might help you stay in Geminispace instead of being distracted by the wider web. Requires compatible default browser.</string>
<string name="web_content_switch_label">Open internally</string> <string name="web_content_switch_label">Open internally</string>
<string name="show_inline_images">Inline local images</string> <string name="show_inline_images">Inline local images</string>
<string name="pkcs_notice">Only PKCS12 client keystores are currently supported</string> <string name="pkcs_notice">Only PKCS12 client keystores are currently supported.</string>
<string name="client_certificate">Client Certificate</string> <string name="client_certificate">Client Certificate</string>
<string name="tap_to_select_client_certificate">Tap to select client certificate</string> <string name="tap_to_select_client_certificate">Tap to select client certificate</string>
<string name="client_certificate_password">Client Certificate Password</string> <string name="client_certificate_password">Client Certificate Password</string>
@ -79,12 +79,14 @@
<string name="cert_unloaded">Certificate unloaded</string> <string name="cert_unloaded">Certificate unloaded</string>
<string name="set_home_capsule">Set home capsule</string> <string name="set_home_capsule">Set home capsule</string>
<string name="check_for_updates">Check for updates</string> <string name="check_for_updates">Check for updates</string>
<string name="self_update_summary">Automatic update installation will only work if Buran is sideloaded.</string>
<string name="new_version_available">New version available</string> <string name="new_version_available">New version available</string>
<string name="no_internet">No internet access</string> <string name="no_internet">No internet access</string>
<string name="history_cleared">History cleared</string> <string name="history_cleared">History cleared</string>
<string name="runtime_cache_cleared">Runtime cache cleared</string> <string name="runtime_cache_cleared">Runtime cache cleared</string>
<string name="show_inline_icons">Inline link icons</string> <string name="show_inline_icons">Inline link icons</string>
<string name="show_link_buttons">Show link buttons</string> <string name="show_link_buttons">Show link buttons</string>
<string name="use_attention_guides">Use attention guides</string>
<string name="bookmarks_empty">You don\'t have any bookmarks yet</string> <string name="bookmarks_empty">You don\'t have any bookmarks yet</string>
<string name="import_bookmarks">Import bookmarks</string> <string name="import_bookmarks">Import bookmarks</string>
<string name="export_bookmarks">Export bookmarks</string> <string name="export_bookmarks">Export bookmarks</string>