Commit b691e4ae authored by Waweic's avatar Waweic 💬
Browse files

Refactor, Add Back-Button functionality

parent b3733543
......@@ -17,16 +17,15 @@ If you want to download the Application without building it yourself, you can do
* Open other application when a link is clicked that has a scheme the client can't handle
* Basic redirection
* Does not start a new Activity when you click anywhere (thanks to a much appreciated hint by [@utf8equalsX@fosstodon.org](https://fosstodon.org/@utf8equalsX))
* Avoiding redirection loops
* The "Back"-button
* An (internal and temporary) browsing history
## Stuff that doesn't work (aka wishlist for the future)
* A browsing history
* The "Back"-button
* Avoiding redirection loops
* Other character encodings than utf-8
* Connects to hosts with self signed certificates
* Connecting to hosts with self signed certificates
* Certain SSL/TLS Versions on older Android versions (I probably can't fix this)
* Downloading anything that is not of type text/*
* Crashes when page is too big
* Crashes when page is too big (feels just like a "real" web browser! #inabiaf)
* Caching for offline use
* Useful error messages
* Graceful error handling
......@@ -38,4 +37,5 @@ If you want to download the Application without building it yourself, you can do
* Security
## Stuff that probably should be done differently
* Handling other character encodings than utf-8
* Probably a lot
\ No newline at end of file
package rocks.ism.decentral.geminiclient
import android.content.Context
import android.widget.EditText
import android.widget.TextView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.net.URI
class GeminiClient(applicationContext: Context, mainContentView: TextView, val uriField: EditText) {
val connection : GeminiConnection = GeminiConnection()
val renderer : GeminiRenderer = GeminiRenderer(mainContentView, this)
val responseHandler : GeminiResponseHandler = GeminiResponseHandler(renderer, applicationContext, this)
private val history = mutableListOf<URI>()
fun goToPage(uri: URI) {
if(history.size == 0 || history[history.size - 1] != uri) {
history.add(uri) //No double entries. Useful for reloading and going back //TODO: Make prooisbly make this comparison more reproducible
}
uriField.setText(uri.toString())
val deferredResult = GlobalScope.async { connection.request(uri) }
responseHandler.handle(deferredResult)
}
fun goToPage(uri: String) {
//TODO: Do checking and stuff
goToPage(URI.create(uri))
}
fun removeLastHistoryItem() {
history.removeAt(history.size - 1)
}
fun goBack(){
removeLastHistoryItem()
goToPage(history[history.size - 1]) //There has to be at least one item in the history //TODO: Double-check this
}
}
\ No newline at end of file
......@@ -12,17 +12,17 @@ import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.StringReader
class GeminiRenderer (val textView: TextView) {
class GeminiRenderer (val textView: TextView, val geminiClient: GeminiClient) {
var preRenderMode = false
fun render(connection: GeminiConnection, resultBody: String, mimetype: String) {
fun render(resultBody: String, mimetype: String) {
if(mimetype == "text/gemini") {
renderGemini(resultBody, connection)
renderGemini(resultBody)
}
else {
renderPlaintext(resultBody)
}
}
fun renderGemini(resultBody: String, connection: GeminiConnection) {
fun renderGemini(resultBody: String) {
val bodyReader = BufferedReader(StringReader(resultBody))
preRenderMode = false
textView.text = ""
......@@ -30,15 +30,15 @@ class GeminiRenderer (val textView: TextView) {
var line: String? = null
while ({ line = bodyReader.readLine(); line }() != null) {
line?.let {
renderGeminiLine(it, connection)
renderGeminiLine(it)
}
}
}
fun renderGeminiLine (line: String, connection: GeminiConnection) {
fun renderGeminiLine (line: String) {
if(line.startsWith("=>")) {
val linkParts = line.substring(2).trim().split("\\s+".toRegex(), 2)
val linkUrl = connection.currentURI?.resolve(linkParts[0]).toString()
val linkUrl = geminiClient.connection.currentURI?.resolve(linkParts[0]).toString()
var linkName = linkParts[0]
if(linkParts.size > 1) {
linkName = linkParts[1]
......
......@@ -8,26 +8,41 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class GeminiResponseHandler (val renderer: GeminiRenderer, private val context: Context) {
class GeminiResponseHandler (val renderer: GeminiRenderer, private val context: Context, val geminiClient: GeminiClient) {
var numberOfConsecutiveRedirects = 0
fun handle (deferredResult: Deferred<GeminiResult>) {
CoroutineScope(Dispatchers.Main).launch {
val result = deferredResult.await()
if(result.header.startsWith("3")) {
//TODO: Check stuff (like how many redirects were before this)
val newUriString = result.connection.currentURI?.resolve(result.meta).toString()
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(newUriString), context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
//TODO: Check stuff (like whether we are redirecting to a page without encryption)
if(++numberOfConsecutiveRedirects > 5) {
renderer.render("ERROR: Too many redirections","text/plain")
}
else {
geminiClient.removeLastHistoryItem() //Remove the link that was originally clicked so the Back-Button works
val newUriString = result.connection.currentURI?.resolve(result.meta).toString()
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse(newUriString),
context,
MainActivity::class.java
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent) //TODO: Is this dangerous? Possibly, the camera could probably be opened with this
}
}
else if(result.header.startsWith("2")){
numberOfConsecutiveRedirects = 0
if(result.meta == "text/gemini") {
renderer.render(result.connection, result.body, "text/gemini")
renderer.render(result.body, "text/gemini")
} else if (result.meta.startsWith("text/")) {
renderer.render(result.connection, result.body,"text/plain")
renderer.render(result.body,"text/plain")
}
}
else {
renderer.render(result.connection, result.header,"text/plain")
renderer.render(result.header,"text/plain")
}
}
......
......@@ -2,59 +2,51 @@ package rocks.ism.decentral.geminiclient
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.net.URI
class MainActivity : AppCompatActivity() {
private lateinit var connection : GeminiConnection
private lateinit var renderer : GeminiRenderer
private lateinit var responseHandler : GeminiResponseHandler
private lateinit var backButton: Button
private lateinit var goButton: Button
private lateinit var uriField: EditText
private lateinit var mainContentView: TextView
private lateinit var geminiClient: GeminiClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Find UI elements
backButton = findViewById(R.id.back_button)
goButton = findViewById(R.id.go_button)
uriField = findViewById(R.id.gemini_uri)
mainContentView = findViewById(R.id.mainContentView)
// Initialize Protocol objects
connection = GeminiConnection()
renderer = GeminiRenderer(findViewById(R.id.mainContentView))
responseHandler = GeminiResponseHandler(renderer, applicationContext)
geminiClient = GeminiClient(
applicationContext,
mainContentView,
uriField
)
// Create handlers
// Create handlers
goButton.setOnClickListener {
goToPage(URI.create(uriField.text.toString()))
geminiClient.goToPage(uriField.text.toString())
}
goToPage(URI.create(getString(R.string.default_url)))
backButton.setOnClickListener {
geminiClient.goBack()
}
geminiClient.goToPage(getString(R.string.default_url))
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if(intent?.data != null) {
intent?.data.toString().let{goToPage(URI.create(it))}
}
}
fun goToPage(uri: URI) {
uriField.setText(uri.toString())
val deferredResult = GlobalScope.async { connection.request(uri) }
responseHandler.handle(deferredResult)
intent?.data.toString().let{geminiClient.goToPage(it)}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment