history rewrite, plus tls client cert wip

This commit is contained in:
Jonathan Fisher 2020-11-16 14:02:56 +00:00
parent 244459b3c3
commit 3f6b78ed08
29 changed files with 401 additions and 237 deletions

View File

@ -1 +1 @@
Två
a.kt

View File

@ -7,7 +7,7 @@
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="gradleJvm" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -32,7 +32,10 @@ class GeminiDatasourceTests {
val capsule = capsules.random()
println("Using $capsule for Gemini tests")
capsuleIndex = capsules.indexOf(capsule)
gemini = Datasource.factory(InstrumentationRegistry.getInstrumentation().targetContext)
gemini = Datasource.factory(
InstrumentationRegistry.getInstrumentation().targetContext,
db.history()
)
}
@Test

View File

@ -1,9 +0,0 @@
package oppen.ariane.io.bookmarks
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [BookmarkEntity::class], version = 2)
abstract class BookmarksDB: RoomDatabase() {
abstract fun bookmarksDao(): BookmarksDao
}

View File

@ -1,20 +0,0 @@
package oppen.ariane.io.bookmarks
import android.content.Context
interface BookmarksDatasource {
fun get(onBookmarks: (List<Bookmark>) -> Unit)
fun add(bookmark: Bookmark, onAdded: () -> Unit)
fun delete(bookmark: Bookmark, onDelete: () -> Unit)
fun moveUp(bookmark: Bookmark, onMoved: () -> Unit)
fun moveDown(bookmark: Bookmark, onMoved: () -> Unit)
fun update(bookmark: Bookmark, label: String?, uri: String?, onUpdate: () -> Unit)
companion object{
fun getDefault(applicationContext: Context): BookmarksDatasource{
return RoomBookmarks(applicationContext)
}
}
}

View File

@ -1,83 +0,0 @@
package oppen.ariane.io.bookmarks
import android.content.Context
import androidx.room.Room
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.net.URI
class RoomBookmarks(applicationContext: Context): BookmarksDatasource {
private val db: BookmarksDB = Room.databaseBuilder(applicationContext, BookmarksDB::class.java, "tva_bookmarks").build()
override fun get(onBookmarks: (List<Bookmark>) -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val dbBookmarks = db.bookmarksDao().getAll()
val bookmarks = mutableListOf<Bookmark>()
dbBookmarks.forEach { bookmarkEntity ->
bookmarks.add(
Bookmark(
uid = bookmarkEntity.uid,
label = bookmarkEntity.label ?: "Unknown",
uri = URI.create(bookmarkEntity.uri),
index = bookmarkEntity.uiIndex ?: 0)
)
}
onBookmarks(bookmarks)
}
}
override fun add(bookmark: Bookmark, onAdded: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val bookmarkEntity = BookmarkEntity(
label = bookmark.label,
uri = bookmark.uri.toString(),
uiIndex = bookmark.index,
folder = "~/")
db.bookmarksDao().insertAll(bookmarkEntity)
onAdded()
}
}
override fun moveUp(bookmark: Bookmark, onMoved: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
//todo - this method is broken:
val prev = db.bookmarksDao().getBookmark(bookmark.index -1)
val target = db.bookmarksDao().getBookmark(bookmark.index)
db.bookmarksDao().updateUIIndex(prev.uid, bookmark.index)
db.bookmarksDao().updateUIIndex(target.uid, bookmark.index - 1)
onMoved()
}
}
override fun moveDown(bookmark: Bookmark, onMoved: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val next = db.bookmarksDao().getBookmark(bookmark.index + 1)
val target = db.bookmarksDao().getBookmark(bookmark.index)
db.bookmarksDao().updateUIIndex(next.uid, bookmark.index)
db.bookmarksDao().updateUIIndex(target.uid, bookmark.index + 1)
onMoved()
}
}
override fun update(bookmark: Bookmark, label: String?, uri: String?, onUpdate: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
db.bookmarksDao().updateContent(bookmark.uid, label ?: "", uri ?: "")
onUpdate()
}
}
override fun delete(bookmark: Bookmark, onDelete: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val entity = db.bookmarksDao().getBookmark(bookmark.index)
db.bookmarksDao().delete(entity)
onDelete()
}
}
}

View File

@ -0,0 +1,14 @@
package oppen.ariane.io.database
import androidx.room.Database
import androidx.room.RoomDatabase
import oppen.ariane.io.database.bookmarks.BookmarkEntity
import oppen.ariane.io.database.bookmarks.BookmarksDao
import oppen.ariane.io.database.history.HistoryDao
import oppen.ariane.io.database.history.HistoryEntity
@Database(entities = [BookmarkEntity::class, HistoryEntity::class], version = 3)
abstract class ArianeAbstractDatabase: RoomDatabase() {
abstract fun bookmarks(): BookmarksDao
abstract fun history(): HistoryDao
}

View File

@ -0,0 +1,14 @@
package oppen.ariane.io.database
import android.content.Context
import androidx.room.Room
import oppen.ariane.io.database.bookmarks.ArianeBookmarks
import oppen.ariane.io.database.history.ArianeHistory
class ArianeDatabase(context: Context) {
private val db: ArianeAbstractDatabase = Room.databaseBuilder(context, ArianeAbstractDatabase::class.java, "ariane_database_v1").build()
fun bookmarks(): ArianeBookmarks = ArianeBookmarks(db)
fun history(): ArianeHistory = ArianeHistory(db)
}

View File

@ -0,0 +1,80 @@
package oppen.ariane.io.database.bookmarks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import oppen.ariane.io.database.ArianeAbstractDatabase
import java.net.URI
class ArianeBookmarks(private val db: ArianeAbstractDatabase): BookmarksDatasource {
override fun get(onBookmarks: (List<BookmarkEntry>) -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val dbBookmarks = db.bookmarks().getAll()
val bookmarks = mutableListOf<BookmarkEntry>()
dbBookmarks.forEach { bookmarkEntity ->
bookmarks.add(
BookmarkEntry(
uid = bookmarkEntity.uid,
label = bookmarkEntity.label ?: "Unknown",
uri = URI.create(bookmarkEntity.uri),
index = bookmarkEntity.uiIndex ?: 0)
)
}
onBookmarks(bookmarks)
}
}
override fun add(bookmarkEntry: BookmarkEntry, onAdded: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val bookmarkEntity = BookmarkEntity(
label = bookmarkEntry.label,
uri = bookmarkEntry.uri.toString(),
uiIndex = bookmarkEntry.index,
folder = "~/")
db.bookmarks().insertAll(bookmarkEntity)
onAdded()
}
}
override fun moveUp(bookmarkEntry: BookmarkEntry, onMoved: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
//todo - this method is broken:
val prev = db.bookmarks().getBookmark(bookmarkEntry.index -1)
val target = db.bookmarks().getBookmark(bookmarkEntry.index)
db.bookmarks().updateUIIndex(prev.uid, bookmarkEntry.index)
db.bookmarks().updateUIIndex(target.uid, bookmarkEntry.index - 1)
onMoved()
}
}
override fun moveDown(bookmarkEntry: BookmarkEntry, onMoved: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val next = db.bookmarks().getBookmark(bookmarkEntry.index + 1)
val target = db.bookmarks().getBookmark(bookmarkEntry.index)
db.bookmarks().updateUIIndex(next.uid, bookmarkEntry.index)
db.bookmarks().updateUIIndex(target.uid, bookmarkEntry.index + 1)
onMoved()
}
}
override fun update(bookmarkEntry: BookmarkEntry, label: String?, uri: String?, onUpdate: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
db.bookmarks().updateContent(bookmarkEntry.uid, label ?: "", uri ?: "")
onUpdate()
}
}
override fun delete(bookmarkEntry: BookmarkEntry, onDelete: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val entity = db.bookmarks().getBookmark(bookmarkEntry.index)
db.bookmarks().delete(entity)
onDelete()
}
}
}

View File

@ -1,4 +1,4 @@
package oppen.ariane.io.bookmarks
package oppen.ariane.io.database.bookmarks
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@ -1,8 +1,8 @@
package oppen.ariane.io.bookmarks
package oppen.ariane.io.database.bookmarks
import java.net.URI
class Bookmark(
class BookmarkEntry(
val uid: Int,
val label: String,
val uri: URI,

View File

@ -1,4 +1,4 @@
package oppen.ariane.io.bookmarks
package oppen.ariane.io.database.bookmarks
import androidx.room.*

View File

@ -0,0 +1,12 @@
package oppen.ariane.io.database.bookmarks
interface BookmarksDatasource {
fun get(onBookmarks: (List<BookmarkEntry>) -> Unit)
fun add(bookmarkEntry: BookmarkEntry, onAdded: () -> Unit)
fun delete(bookmarkEntry: BookmarkEntry, onDelete: () -> Unit)
fun moveUp(bookmarkEntry: BookmarkEntry, onMoved: () -> Unit)
fun moveDown(bookmarkEntry: BookmarkEntry, onMoved: () -> Unit)
fun update(bookmarkEntry: BookmarkEntry, label: String?, uri: String?, onUpdate: () -> Unit)
}

View File

@ -0,0 +1,70 @@
package oppen.ariane.io.database.history
import android.net.Uri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import oppen.ariane.io.database.ArianeAbstractDatabase
class ArianeHistory(private val db: ArianeAbstractDatabase): HistoryDatasource {
override fun get(onHistory: (List<HistoryEntry>) -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val dbBookmarks = db.history().getAll()
val history = mutableListOf<HistoryEntry>()
dbBookmarks.forEach { entity ->
history.add(HistoryEntry(entity.uid, entity.timestamp ?: 0L, Uri.parse(entity.uri)))
}
onHistory(history)
}
}
override fun add(entry: HistoryEntry, onAdded: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val lastAdded = db.history().getLastAdded()
val entity = HistoryEntity(entry.uri.toString(), System.currentTimeMillis())
when (lastAdded) {
null -> db.history().insert(entity)
else -> {
when {
lastAdded.uri.toString() != entry.uri.toString() -> db.history().insert(entity)
}
}
}
onAdded()
}
}
override fun add(uri: Uri, onAdded: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val lastAdded = db.history().getLastAdded()
val entity = HistoryEntity(uri.toString(), System.currentTimeMillis())
when (lastAdded) {
null -> db.history().insert(entity)
else -> {
when {
lastAdded.uri.toString() != uri.toString() -> db.history().insert(entity)
}
}
}
onAdded()
}
}
override fun clear(onClear: () -> Unit) {
//todo
}
override fun delete(entry: HistoryEntry, onDelete: () -> Unit) {
GlobalScope.launch(Dispatchers.IO){
val entity = db.history().getEntry(entry.uid)
db.history().delete(entity)
onDelete()
}
}
}

View File

@ -0,0 +1,24 @@
package oppen.ariane.io.database.history
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface HistoryDao {
@Query("SELECT * FROM history ORDER BY timestamp DESC")
suspend fun getAll(): List<HistoryEntity>
@Query("SELECT * FROM history WHERE uid = :uid LIMIT 1")
fun getEntry(uid: Int): HistoryEntity
@Query("SELECT * FROM history ORDER BY timestamp DESC LIMIT 1")
fun getLastAdded(): HistoryEntity?
@Insert
fun insert(vararg history: HistoryEntity)
@Delete
fun delete(history: HistoryEntity)
}

View File

@ -0,0 +1,12 @@
package oppen.ariane.io.database.history
import android.net.Uri
interface HistoryDatasource {
fun get(onHistory: (List<HistoryEntry>) -> Unit)
fun add(entry: HistoryEntry, onAdded: () -> Unit)
fun add(uri: Uri, onAdded: () -> Unit)
fun clear(onClear: () -> Unit)
fun delete(entry: HistoryEntry, onDelete: () -> Unit)
}

View File

@ -0,0 +1,14 @@
package oppen.ariane.io.database.history
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "history")
class HistoryEntity(
@ColumnInfo(name = "uri") val uri: String?,
@ColumnInfo(name = "timestamp") val timestamp: Long?
){
@PrimaryKey(autoGenerate = true)
var uid: Int = 0
}

View File

@ -0,0 +1,9 @@
package oppen.ariane.io.database.history
import android.net.Uri
class HistoryEntry(
val uid: Int,
val timestamp: Long,
val uri: Uri
)

View File

@ -2,6 +2,7 @@ package oppen.ariane.io.gemini
import android.content.Context
import oppen.ariane.io.GemState
import oppen.ariane.io.database.history.ArianeHistory
import java.net.URI
interface Datasource {
@ -11,8 +12,8 @@ interface Datasource {
fun goBack(onUpdate: (state: GemState) -> Unit)
companion object{
fun factory(context: Context): Datasource {
return GeminiDatasource(context)
fun factory(context: Context, history: ArianeHistory): Datasource {
return GeminiDatasource(context, history)
}
}
}

View File

@ -6,6 +6,8 @@ import androidx.preference.PreferenceManager
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import oppen.ariane.io.GemState
import oppen.ariane.io.database.history.ArianeHistory
import oppen.ariane.io.database.history.HistoryEntry
import oppen.ariane.io.keymanager.ArianeKeyManager
import oppen.isGemini
import oppen.toURI
@ -18,7 +20,7 @@ import javax.net.ssl.*
const val GEMINI_SCHEME = "gemini"
class GeminiDatasource(private val context: Context): Datasource {
class GeminiDatasource(private val context: Context, val history: ArianeHistory): Datasource {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
private val addressBuilder = AddressBuilder()
@ -88,7 +90,7 @@ class GeminiDatasource(private val context: Context): Datasource {
else -> SSLContext.getInstance(protocol)
}
sslContext.init(arianeKeyManager.getFactory()?.keyManagers, DummyTrustManager.get(), null)
sslContext.init(arianeKeyManager.getFactory(context)?.keyManagers, DummyTrustManager.get(), null)
val factory: SSLSocketFactory = sslContext.socketFactory
//todo to here ----------------------------------------------------------------------------
@ -140,6 +142,11 @@ class GeminiDatasource(private val context: Context): Datasource {
println("Ariane: response header: $headerLine")
if(headerLine == null){
onUpdate(GemState.ResponseError(GeminiResponse.Header(-2, "Server did not respond with a Gemini header")))
return
}
val header = GeminiResponse.parseHeader(headerLine)
when {
@ -197,6 +204,8 @@ class GeminiDatasource(private val context: Context): Datasource {
runtimeHistory.add(uri)
println("Ariane added $uri to runtime history (size ${runtimeHistory.size})")
}
history.add(uri.toUri()){}
}
private fun getString(socket: SSLSocket?, uri: URI, header: GeminiResponse.Header, onUpdate: (state: GemState) -> Unit){

View File

@ -1,7 +1,7 @@
package oppen.ariane.io.keymanager
import android.R
import android.content.Context
import oppen.ariane.R
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.KeyStore
@ -17,7 +17,7 @@ class ArianeKeyManager {
val hasLoadedKey = false
return when {
hasLoadedKey -> {
val keyStore: KeyStore = KeyStore.getInstance("BKS")
val keyStore: KeyStore = KeyStore.getInstance("BKS")//or "pkcs12"v?
val inputStream: InputStream = ByteArrayInputStream("dummy".toByteArray())
keyStore.load(inputStream, "yourKeyStorePassword".toCharArray())
inputStream.close()
@ -30,4 +30,14 @@ class ArianeKeyManager {
else -> null
}
}
fun getFactoryDemo(context: Context): KeyManagerFactory? {
val keyStore: KeyStore = KeyStore.getInstance("pkcs12")//or "pkcs12"v?
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

@ -2,7 +2,6 @@ package oppen.ariane.ui
import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.media.MediaPlayer
import android.net.Uri
@ -19,11 +18,11 @@ import oppen.ariane.Ariane
import oppen.ariane.R
import oppen.ariane.databinding.ActivityGemBinding
import oppen.ariane.io.GemState
import oppen.ariane.io.bookmarks.BookmarksDatasource
import oppen.ariane.io.database.ArianeDatabase
import oppen.ariane.io.database.bookmarks.BookmarksDatasource
import oppen.ariane.io.gemini.Datasource
import oppen.ariane.io.gemini.GeminiResponse
import oppen.ariane.io.gemini.RuntimeCache
import oppen.ariane.io.history.uris.HistoryInterface
import oppen.ariane.ui.audio_player.AudioPlayer
import oppen.ariane.ui.bookmarks.BookmarkDialog
import oppen.ariane.ui.bookmarks.BookmarksDialog
@ -36,7 +35,6 @@ import oppen.ariane.ui.modals_menus.input.InputDialog
import oppen.ariane.ui.modals_menus.overflow.OverflowPopup
import oppen.ariane.ui.settings.SettingsActivity
import oppen.hideKeyboard
import oppen.toURI
import oppen.visibleRetainingSpace
import java.io.File
import java.io.FileInputStream
@ -50,14 +48,12 @@ const val CREATE_BINARY_FILE_REQ = 630
class GemActivity : AppCompatActivity() {
private var inSearch = false
private val mediaPlayer = MediaPlayer()
private lateinit var bookmarkDatasource: BookmarksDatasource
private val model by viewModels<GemViewModel>()
private lateinit var binding: ActivityGemBinding
private lateinit var history: HistoryInterface
private val adapter = GemtextAdapter { adapter, uri, longTap, position: Int, view ->
if(longTap){
LinkPopup.show(view, uri){ menuId ->
@ -89,6 +85,7 @@ class GemActivity : AppCompatActivity() {
binding.addressEdit.hint = getString(R.string.main_input_hint)
inSearch = false
}
model.request(uri)
}
}
@ -96,7 +93,8 @@ class GemActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bookmarkDatasource = BookmarksDatasource.getDefault(applicationContext)
val db = ArianeDatabase(applicationContext)
bookmarkDatasource = db.bookmarks()
binding = DataBindingUtil.setContentView(this, R.layout.activity_gem)
binding.viewmodel = model
@ -105,52 +103,11 @@ class GemActivity : AppCompatActivity() {
binding.gemtextRecycler.layoutManager = LinearLayoutManager(this)
binding.gemtextRecycler.adapter = adapter
history = HistoryInterface.default(this)
model.initialise(
home = PreferenceManager.getDefaultSharedPreferences(this).getString("home_capsule", Ariane.DEFAULT_HOME_CAPSULE) ?: Ariane.DEFAULT_HOME_CAPSULE,
gemini = Datasource.factory(this),
bookmarks = BookmarksDatasource.getDefault(applicationContext)
){ state ->
binding.pullToRefresh.isRefreshing = false
when(state){
is GemState.AppQuery -> runOnUiThread { showAlert("App backdoor/query not implemented yet") }
is GemState.ResponseInput -> runOnUiThread {
loadingView(false)
InputDialog.show(this, state) { queryAddress ->
model.request(queryAddress)
}
}
is GemState.Requesting -> loadingView(true)
is GemState.NotGeminiRequest -> externalProtocol(state)
is GemState.ResponseError -> showAlert("${GeminiResponse.getCodeString(state.header.code)}: ${state.header.meta}")
is GemState.ResponseGemtext -> renderGemtext(state)
is GemState.ResponseText -> renderText(state)
is GemState.ResponseImage -> renderImage(state)
is GemState.ResponseAudio -> renderAudio(state)
is GemState.ResponseBinary -> renderBinary(state)
is GemState.Blank -> {
binding.addressEdit.setText("")
adapter.render(arrayListOf())
}
is GemState.ResponseUnknownMime -> {
runOnUiThread {
loadingView(false)
AlertDialog.Builder(this, R.style.AppDialogTheme)
.setTitle(R.string.unknown_mime_dialog_title)
.setMessage("Address: ${state.uri}\nMeta: ${state.header.meta}")
.setPositiveButton("Download") { _, _ ->
loadingView(true)
model.requestBinaryDownload(state.uri)
}
.setNegativeButton("Cancel") { _, _ -> }
.show()
}
}
}
}
gemini = Datasource.factory(this, db.history()),
db = db,
onState = this::handleState)
binding.addressEdit.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
@ -210,7 +167,7 @@ class GemActivity : AppCompatActivity() {
startActivity(Intent.createChooser(this, null))
}
}
R.id.overflow_menu_history -> HistoryDialog.show(this) { historyAddress ->
R.id.overflow_menu_history -> HistoryDialog.show(this, db.history()) { historyAddress ->
model.request(historyAddress)
}
R.id.overflow_menu_about -> AboutDialog.show(this)
@ -235,6 +192,46 @@ class GemActivity : AppCompatActivity() {
checkIntentExtras(intent)
}
private fun handleState(state: GemState) {
binding.pullToRefresh.isRefreshing = false
when (state) {
is GemState.AppQuery -> runOnUiThread { showAlert("App backdoor/query not implemented yet") }
is GemState.ResponseInput -> runOnUiThread {
loadingView(false)
InputDialog.show(this, state) { queryAddress ->
model.request(queryAddress)
}
}
is GemState.Requesting -> loadingView(true)
is GemState.NotGeminiRequest -> externalProtocol(state)
is GemState.ResponseError -> showAlert("${GeminiResponse.getCodeString(state.header.code)}: ${state.header.meta}")
is GemState.ResponseGemtext -> renderGemtext(state)
is GemState.ResponseText -> renderText(state)
is GemState.ResponseImage -> renderImage(state)
is GemState.ResponseAudio -> renderAudio(state)
is GemState.ResponseBinary -> renderBinary(state)
is GemState.Blank -> {
binding.addressEdit.setText("")
adapter.render(arrayListOf())
}
is GemState.ResponseUnknownMime -> {
runOnUiThread {
loadingView(false)
AlertDialog.Builder(this, R.style.AppDialogTheme)
.setTitle(R.string.unknown_mime_dialog_title)
.setMessage("Address: ${state.uri}\nMeta: ${state.header.meta}")
.setPositiveButton("Download") { _, _ ->
loadingView(true)
model.requestBinaryDownload(state.uri)
}
.setNegativeButton("Cancel") { _, _ -> }
.show()
}
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
@ -305,8 +302,6 @@ class GemActivity : AppCompatActivity() {
binding.gemtextRecycler.post {
binding.gemtextRecycler.scrollToPosition(0)
}
history.add(state.uri.toString())
}
private fun renderText(state: GemState.ResponseText) = runOnUiThread {
@ -401,10 +396,14 @@ class GemActivity : AppCompatActivity() {
if(visible) binding.appBar.setExpanded(true)
}
@ExperimentalStdlibApi
override fun onBackPressed() {
if(model.canGoBack()){
model.goBack()
model.goBack{ state ->
/*
Passing the callback here so we can eventually add a mechanism to restore scroll position
*/
handleState(state)
}
}else{
println("Ariane history is empty - exiting")
super.onBackPressed()

View File

@ -2,21 +2,21 @@ package oppen.ariane.ui
import android.net.Uri
import androidx.lifecycle.ViewModel
import oppen.ariane.Ariane
import oppen.ariane.io.gemini.Datasource
import oppen.ariane.io.GemState
import oppen.ariane.io.bookmarks.BookmarksDatasource
import oppen.ariane.io.database.ArianeDatabase
import oppen.ariane.io.database.bookmarks.BookmarksDatasource
import java.net.URI
class GemViewModel: ViewModel() {
private lateinit var gemini: Datasource
private lateinit var bookmarks: BookmarksDatasource
private lateinit var db: ArianeDatabase
private var onState: (state: GemState) -> Unit = {}
fun initialise(home: String, gemini: Datasource, bookmarks: BookmarksDatasource, onState: (state: GemState) -> Unit){
fun initialise(home: String, gemini: Datasource, db: ArianeDatabase, onState: (state: GemState) -> Unit){
this.gemini = gemini
this.bookmarks = bookmarks
this.db = db
this.onState = onState
request(home)
@ -49,5 +49,5 @@ class GemViewModel: ViewModel() {
}
fun canGoBack(): Boolean = gemini.canGoBack()
fun goBack() = gemini.goBack(onState)
fun goBack(onGoBack: (state: GemState) -> Unit) = gemini.goBack(onGoBack)
}

View File

@ -7,8 +7,8 @@ import android.view.View
import androidx.appcompat.app.AppCompatDialog
import kotlinx.android.synthetic.main.fragment_bookmark_dialog.view.*
import oppen.ariane.R
import oppen.ariane.io.bookmarks.Bookmark
import oppen.ariane.io.bookmarks.BookmarksDatasource
import oppen.ariane.io.database.bookmarks.BookmarkEntry
import oppen.ariane.io.database.bookmarks.BookmarksDatasource
import java.net.URI
@ -55,7 +55,7 @@ class BookmarkDialog(
bookmarkDatasource.add(
Bookmark(
BookmarkEntry(
uid = -1,
label = view.bookmark_name.text.toString(),
uri = URI.create(view.bookmark_uri.text.toString()),

View File

@ -6,18 +6,18 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.bookmark.view.*
import oppen.ariane.R
import oppen.ariane.io.bookmarks.Bookmark
import oppen.ariane.io.database.bookmarks.BookmarkEntry
import oppen.visible
class BookmarksAdapter(val onBookmark: (bookmark: Bookmark) -> Unit, val onOverflow: (view: View, bookmark: Bookmark, isFirst: Boolean, isLast: Boolean) -> Unit): RecyclerView.Adapter<BookmarksAdapter.ViewHolder>() {
class BookmarksAdapter(val onBookmark: (bookmarkEntry: BookmarkEntry) -> Unit, val onOverflow: (view: View, bookmarkEntry: BookmarkEntry, isFirst: Boolean, isLast: Boolean) -> Unit): RecyclerView.Adapter<BookmarksAdapter.ViewHolder>() {
val bookmarks = mutableListOf<Bookmark>()
val bookmarks = mutableListOf<BookmarkEntry>()
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
fun update(bookmarks: List<Bookmark>){
fun update(bookmarkEntries: List<BookmarkEntry>){
this.bookmarks.clear()
this.bookmarks.addAll(bookmarks)
this.bookmarks.addAll(bookmarkEntries)
notifyDataSetChanged()
}
@ -51,19 +51,19 @@ class BookmarksAdapter(val onBookmark: (bookmark: Bookmark) -> Unit, val onOverf
override fun getItemCount(): Int = bookmarks.size
fun hide(bookmark: Bookmark) {
bookmark.visible = false
notifyItemChanged(bookmarks.indexOf(bookmark))
fun hide(bookmarkEntry: BookmarkEntry) {
bookmarkEntry.visible = false
notifyItemChanged(bookmarks.indexOf(bookmarkEntry))
}
fun show(bookmark: Bookmark) {
bookmark.visible = true
notifyItemChanged(bookmarks.indexOf(bookmark))
fun show(bookmarkEntry: BookmarkEntry) {
bookmarkEntry.visible = true
notifyItemChanged(bookmarks.indexOf(bookmarkEntry))
}
fun remove(bookmark: Bookmark){
val index = bookmarks.indexOf(bookmark)
bookmarks.remove(bookmark)
fun remove(bookmarkEntry: BookmarkEntry){
val index = bookmarks.indexOf(bookmarkEntry)
bookmarks.remove(bookmarkEntry)
notifyItemRemoved(index)
}
}

View File

@ -11,15 +11,15 @@ import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_bookmarks.view.*
import oppen.ariane.R
import oppen.ariane.io.bookmarks.Bookmark
import oppen.ariane.io.bookmarks.BookmarksDatasource
import oppen.ariane.io.database.bookmarks.BookmarkEntry
import oppen.ariane.io.database.bookmarks.BookmarksDatasource
import oppen.visible
class BookmarksDialog(
context: Context,
private val bookmarkDatasource: BookmarksDatasource,
onBookmark: (bookmark: Bookmark) -> Unit): AppCompatDialog(context, R.style.FSDialog) {
onBookmark: (bookmarkEntry: BookmarkEntry) -> Unit): AppCompatDialog(context, R.style.FSDialog) {
var bookmarksAdapter: BookmarksAdapter
@ -82,9 +82,9 @@ class BookmarksDialog(
}
}
private fun edit(bookmark: Bookmark){
BookmarkDialog(context, BookmarkDialog.mode_edit, null, bookmark.uri.toString(), bookmark.label){ label, uri ->
bookmarkDatasource.update(bookmark, label, uri){
private fun edit(bookmarkEntry: BookmarkEntry){
BookmarkDialog(context, BookmarkDialog.mode_edit, null, bookmarkEntry.uri.toString(), bookmarkEntry.label){ label, uri ->
bookmarkDatasource.update(bookmarkEntry, label, uri){
bookmarkDatasource.get {bookmarks ->
Handler(Looper.getMainLooper()).post {
bookmarksAdapter.update(bookmarks)
@ -99,15 +99,15 @@ class BookmarksDialog(
* Bookmark isn't actually deleted from the DB until the Snackbar disappears. Which is nice.
*
*/
private fun delete(bookmark: Bookmark){
private fun delete(bookmarkEntry: BookmarkEntry){
//OnDelete
bookmarksAdapter.hide(bookmark)
Snackbar.make(view, "Deleted ${bookmark.label}", Snackbar.LENGTH_SHORT).addCallback(object: Snackbar.Callback() {
bookmarksAdapter.hide(bookmarkEntry)
Snackbar.make(view, "Deleted ${bookmarkEntry.label}", Snackbar.LENGTH_SHORT).addCallback(object: Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) = when (event) {
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_ACTION -> bookmarksAdapter.show(bookmark)
else -> bookmarkDatasource.delete(bookmark){
BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_ACTION -> bookmarksAdapter.show(bookmarkEntry)
else -> bookmarkDatasource.delete(bookmarkEntry){
Handler(Looper.getMainLooper()).post {
bookmarksAdapter.remove(bookmark)
bookmarksAdapter.remove(bookmarkEntry)
}
}
}
@ -116,8 +116,8 @@ class BookmarksDialog(
}.show()
}
private fun moveUp(bookmark: Bookmark){
bookmarkDatasource.moveUp(bookmark){
private fun moveUp(bookmarkEntry: BookmarkEntry){
bookmarkDatasource.moveUp(bookmarkEntry){
bookmarkDatasource.get { bookmarks ->
Handler(Looper.getMainLooper()).post {
bookmarksAdapter.update(bookmarks)
@ -126,8 +126,8 @@ class BookmarksDialog(
}
}
private fun moveDown(bookmark: Bookmark){
bookmarkDatasource.moveDown(bookmark){
private fun moveDown(bookmarkEntry: BookmarkEntry){
bookmarkDatasource.moveDown(bookmarkEntry){
bookmarkDatasource.get { bookmarks ->
Handler(Looper.getMainLooper()).post {
bookmarksAdapter.update(bookmarks)

View File

@ -7,8 +7,9 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row_history.view.*
import oppen.delay
import oppen.ariane.R
import oppen.ariane.io.database.history.HistoryEntry
class HistoryAdapter(val history: List<String>, val onClick:(address: String) -> Unit): RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
class HistoryAdapter(val history: List<HistoryEntry>, val onClick:(entry: HistoryEntry) -> Unit): RecyclerView.Adapter<HistoryAdapter.ViewHolder>() {
class ViewHolder(view: View): RecyclerView.ViewHolder(view)
@ -19,7 +20,7 @@ class HistoryAdapter(val history: List<String>, val onClick:(address: String) ->
override fun getItemCount(): Int = history.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.history_address.text = history[position]
holder.itemView.history_address.text = history[position].uri.toString()
holder.itemView.history_row.setOnClickListener {
delay(500){
onClick(history[holder.adapterPosition])

View File

@ -1,6 +1,8 @@
package oppen.ariane.ui.modals_menus.history
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.MenuInflater
import android.view.View
import android.widget.Toast
@ -11,14 +13,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.dialog_about.view.close_tab_dialog
import kotlinx.android.synthetic.main.dialog_history.view.*
import oppen.ariane.R
import oppen.ariane.io.database.history.ArianeHistory
import oppen.ariane.io.gemini.RuntimeCache
import oppen.ariane.io.history.uris.HistoryInterface
object HistoryDialog {
fun show(context: Context, onHistoryItem: (address: String) -> Unit){
val historyCache = HistoryInterface.default(context)
val history = historyCache.get()
fun show(context: Context, history: ArianeHistory, onHistoryItem: (address: String) -> Unit){
val dialog = AppCompatDialog(context, R.style.AppTheme)
val view = View.inflate(context, R.layout.dialog_history, null)
@ -34,9 +33,10 @@ object HistoryDialog {
inflater.inflate(R.menu.history_overflow_menu, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
if(menuItem.itemId == R.id.history_overflow_clear_history){
historyCache.clear()
dialog.dismiss()
Toast.makeText(context, "History cleared", Toast.LENGTH_SHORT).show()
history.clear {
dialog.dismiss()
Toast.makeText(context, "History cleared", Toast.LENGTH_SHORT).show()
}
}else if(menuItem.itemId == R.id.history_overflow_clear_runtime_cache){
RuntimeCache.clear()
dialog.dismiss()
@ -49,12 +49,16 @@ object HistoryDialog {
}
view.history_recycler.layoutManager = LinearLayoutManager(context)
view.history_recycler.adapter = HistoryAdapter(history.asReversed()){ address ->
onHistoryItem(address)
dialog.dismiss()
history.get { history ->
Handler(Looper.getMainLooper()).post {
view.history_recycler.adapter = HistoryAdapter(history) { entry ->
onHistoryItem(entry.uri.toString())
dialog.dismiss()
}
dialog.show()
}
}
dialog.show()
}
}

Binary file not shown.