Skip to content

Commit

Permalink
add save result screen
Browse files Browse the repository at this point in the history
  • Loading branch information
danielyrovas committed Mar 1, 2024
1 parent a3cb47e commit 689d3a9
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 253 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ An unofficial Android client for [LinkDing](https://github.com/sissbruecker/link


## Features
- Provides the ability to save bookmarks through the Android share menu.
- Save bookmarks through the Android share menu.
- View recent bookmarks.

## TODO:
- Provide typical CRUD operations on bookmarks
- Simplify selecting/searching for tags when saving bookmarks
125 changes: 1 addition & 124 deletions app/src/main/java/org/yrovas/linklater/MainActivityState.kt
Original file line number Diff line number Diff line change
@@ -1,126 +1,3 @@
package org.yrovas.linklater

import android.content.Context
import android.widget.Toast
import androidx.annotation.Keep
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.yrovas.linklater.data.Bookmark

object Prefs {
val LINKDING_URL = stringPreferencesKey("linkding_url")
val LINKDING_TOKEN = stringPreferencesKey("linkding_token")
}

class MainActivityState : ViewModel() {
private var bookmarkAPI: BookmarkAPI = EmptyBookmarkAPI()

private val _isRefreshing: MutableStateFlow<Boolean> = MutableStateFlow(false)
var isRefreshing = _isRefreshing.asStateFlow()

private val _bookmarkURL: MutableStateFlow<String> = MutableStateFlow("")
var bookmarkURL = _bookmarkURL.asStateFlow()
suspend fun saveBookmarkURL(url: String, context: Context) {
_bookmarkURL.value = url
context.dataStore.edit { preferences ->
preferences[Prefs.LINKDING_URL] = url
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}

private val _bookmarkAPIToken: MutableStateFlow<String> = MutableStateFlow("")
var bookmarkAPIToken = _bookmarkAPIToken.asStateFlow()
suspend fun saveBookmarkAPIToken(token: String, context: Context) {
_bookmarkAPIToken.value = token
context.dataStore.edit { preferences ->
preferences[Prefs.LINKDING_TOKEN] = token
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}

fun checkBookmarkAPIToken(token: String) = token.length > 10

private suspend fun loadPrefs(context: Context) {
context.dataStore.data.first { preferences ->
_bookmarkURL.update { preferences[Prefs.LINKDING_URL].orEmpty() }
_bookmarkAPIToken.update { preferences[Prefs.LINKDING_TOKEN].orEmpty() }
true
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}

// Expose screen UI state
private val _displayedBookmarks = MutableStateFlow(listOf<Bookmark>())
val displayedBookmarks: StateFlow<List<Bookmark>> =
_displayedBookmarks.asStateFlow()

private val _displayedTags = MutableStateFlow(listOf<String>())
val displayedTags: StateFlow<List<String>> = _displayedTags.asStateFlow()

private suspend fun getBookmarks(page: Int = 0) = _displayedBookmarks.update {
bookmarkAPI.getBookmarks(page = page, query = null)
.getOrDefault(emptyList())
}

private suspend fun cacheBookmarks(context: Context) {
bookmarkAPI.cacheBookmarks(context, displayedBookmarks.value)
}

private suspend fun cacheTags(context: Context) {
bookmarkAPI.cacheTags(context, displayedTags.value)
}

private suspend fun loadLocalBookmarks(context: Context) {
val bookmarks = bookmarkAPI.getCachedBookmarks(context)
if (displayedBookmarks.value.isEmpty()) {
_displayedBookmarks.emit(bookmarks)
}
}

private suspend fun loadLocalTags(context: Context) {
val tags = bookmarkAPI.getCachedTags(context)
if (displayedTags.value.isEmpty()) {
_displayedTags.emit(tags)
}
}

private suspend fun tagsFromCachedBookmarks() {
val tags = displayedBookmarks.value.flatMap { it.tags }.distinct()
_displayedTags.update {
(it + tags).distinct()
}
}

suspend fun setup(context: Context) {
viewModelScope.launch {
loadPrefs(context)
loadLocalBookmarks(context) // get cached page
loadLocalTags(context) // get cached page
}.join()
refresh(context)
}

private suspend fun doRefresh(context: Context) {
viewModelScope.launch {
_isRefreshing.emit(true)
getBookmarks()
cacheBookmarks(context)
_isRefreshing.emit(false)
tagsFromCachedBookmarks()
cacheTags(context)
}.join()
}

private fun toast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}

fun refresh(context: Context) {
viewModelScope.launch { doRefresh(context) }
}
}
class MainActivityState : MainActivityViewModel()
130 changes: 130 additions & 0 deletions app/src/main/java/org/yrovas/linklater/MainActivityViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package org.yrovas.linklater

import android.content.Context
import android.widget.Toast
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.yrovas.linklater.data.Bookmark

object Prefs {
val LINKDING_URL = stringPreferencesKey("linkding_url")
val LINKDING_TOKEN = stringPreferencesKey("linkding_token")
}

abstract class MainActivityViewModel : ViewModel() {
private var bookmarkAPI: BookmarkAPI = EmptyBookmarkAPI()

private val _isRefreshing: MutableStateFlow<Boolean> =
MutableStateFlow(false)
var isRefreshing = _isRefreshing.asStateFlow()

private val _bookmarkURL: MutableStateFlow<String> = MutableStateFlow("")
var bookmarkURL = _bookmarkURL.asStateFlow()

private val _bookmarkAPIToken: MutableStateFlow<String> =
MutableStateFlow("")
var bookmarkAPIToken = _bookmarkAPIToken.asStateFlow()

fun checkBookmarkAPIToken(token: String) = token.length > 10

protected val _displayedBookmarks = MutableStateFlow(listOf<Bookmark>())
val displayedBookmarks: StateFlow<List<Bookmark>> =
_displayedBookmarks.asStateFlow()

private val _displayedTags = MutableStateFlow(listOf<String>())
val displayedTags: StateFlow<List<String>> = _displayedTags.asStateFlow()

suspend fun saveBookmarkURL(url: String, context: Context) {
_bookmarkURL.value = url
context.dataStore.edit { preferences ->
preferences[Prefs.LINKDING_URL] = url
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}

suspend fun saveBookmarkAPIToken(token: String, context: Context) {
_bookmarkAPIToken.value = token
context.dataStore.edit { preferences ->
preferences[Prefs.LINKDING_TOKEN] = token
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}


private suspend fun loadPrefs(context: Context) {
context.dataStore.data.first { preferences ->
_bookmarkURL.update { preferences[Prefs.LINKDING_URL].orEmpty() }
_bookmarkAPIToken.update { preferences[Prefs.LINKDING_TOKEN].orEmpty() }
true
}
bookmarkAPI = LinkDingAPI(bookmarkURL.value, bookmarkAPIToken.value)
}

// Expose screen UI state

private suspend fun getBookmarks(page: Int = 0) = _displayedBookmarks.update {
bookmarkAPI.getBookmarks(page = page, query = null)
.getOrDefault(emptyList())
}

private suspend fun cacheBookmarks(context: Context) {
bookmarkAPI.cacheBookmarks(context, displayedBookmarks.value)
}

private suspend fun cacheTags(context: Context) {
bookmarkAPI.cacheTags(context, displayedTags.value)
}

private suspend fun loadLocalBookmarks(context: Context) {
val bookmarks = bookmarkAPI.getCachedBookmarks(context)
if (displayedBookmarks.value.isEmpty()) {
_displayedBookmarks.emit(bookmarks)
}
}

private suspend fun loadLocalTags(context: Context) {
val tags = bookmarkAPI.getCachedTags(context)
if (displayedTags.value.isEmpty()) {
_displayedTags.emit(tags)
}
}

private suspend fun tagsFromCachedBookmarks() {
val tags = displayedBookmarks.value.flatMap { it.tags }.distinct()
_displayedTags.update {
(it + tags).distinct()
}
}

private fun toast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}

open suspend fun doRefresh(context: Context) {
viewModelScope.launch {
_isRefreshing.emit(true)
getBookmarks()
cacheBookmarks(context)
_isRefreshing.emit(false)
tagsFromCachedBookmarks()
cacheTags(context)
}.join()
}

fun refresh(context: Context) {
viewModelScope.launch { doRefresh(context) }
}

suspend fun setup(context: Context) {
viewModelScope.launch {
loadPrefs(context)
loadLocalBookmarks(context) // get cached page
loadLocalTags(context) // get cached page
}.join()
refresh(context)
}
}
9 changes: 5 additions & 4 deletions app/src/main/java/org/yrovas/linklater/SaveActivityState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@ class SaveActivityState : ViewModel() {
val tags =
(bookmarkToSave.value.tags + selectedTags.value + tagNames.value.split(
" "
).filter { it.isNotBlank() } ).distinct()
).filter { it.isNotBlank() }).distinct()
val bookmark =
bookmarkToSave.value.withUpdates(tags = tags.ifEmpty { null })
Log.d("DEBUG/save", "submitBookmark: $bookmark")
return bookmarkAPI.saveBookmark(bookmark)
}

fun setTags(tagList: List<String>) = _tags.update {
tagList
}
private val _submitResult: MutableStateFlow<Pair<String, Boolean>> = MutableStateFlow("" to true)
var submitResult = _submitResult.asStateFlow()
fun setSubmitResult(result: Pair<String, Boolean>) = _submitResult.update { result }
fun setTags(tagList: List<String>) = _tags.update { tagList }

suspend fun setup(context: Context) {
var url = ""
Expand Down
23 changes: 1 addition & 22 deletions app/src/main/java/org/yrovas/linklater/SaveBookmarkActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import com.ramcosta.composedestinations.navigation.dependency
import kotlinx.coroutines.*
import org.yrovas.linklater.ui.theme.AppTheme

val globalScope = CoroutineScope(SupervisorJob())

@ActivityDestination
class SaveBookmarkActivity : ComponentActivity() {
private val saveActivityState: SaveActivityState by viewModels()
Expand Down Expand Up @@ -48,12 +46,6 @@ class SaveBookmarkActivity : ComponentActivity() {
}
}

fun launch(function: suspend () -> Unit) {
globalScope.launch {
function()
}
}

private fun extractURL(intent: Intent): String {
var s = intent.data.toString()
if (s.isNotBlank() && s != "null") {
Expand All @@ -70,20 +62,7 @@ class SaveBookmarkActivity : ComponentActivity() {
Log.d("DEBUG/extract", "extractURL: intent.extra_subject")
return s
}

// Log.d("DEBUG/extract", "extractURL: clipBoard")
return "" // clipboardData()
return ""
}

// private fun clipboardData(): String {
// val clipboardManager =
// getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// val clip: ClipData? = clipboardManager.primaryClip
// var txt: String = (clip?.getItemAt(0)?.text ?: "").toString()
// if (txt.isNotBlank()) return txt
// txt = (clip?.getItemAt(1)?.text ?: "").toString()
// if (txt.isNotBlank()) return txt
//
// return ""
// }
}
11 changes: 4 additions & 7 deletions app/src/main/java/org/yrovas/linklater/data/Bookmark.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.yrovas.linklater.data

import kotlinx.serialization.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

// A 1:1 representation of a LinkDing bookmark.
@Serializable
Expand All @@ -20,10 +21,7 @@ data class Bookmark(
@SerialName("tag_names") val tags: List<String> = emptyList(),
)

// A datatype representing bookmarks which have not been
// sent to the remote API yet.
// This is the datatype used to send bookmarks to the
// remote API.
// A datatype representing bookmarks which will be sent to the remote API.
@Serializable
data class LocalBookmark(
var url: String,
Expand All @@ -35,8 +33,7 @@ data class LocalBookmark(
var shared: Boolean = false,
@SerialName("tag_names") var tags: List<String> = emptyList(),
) {

/// Returns a new local bookmark with the new values if provided
/// Returns a new local bookmark with the new values
fun withUpdates(
url: String? = null,
title: String? = null,
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/yrovas/linklater/ui/common/AppBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fun AppBar(
)
}
}
Text(text = page, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
Text(text = page, style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary)
}) {
content()
}
Expand All @@ -43,7 +43,7 @@ fun AppBar(left: @Composable () -> Unit, right: @Composable () -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.height(64.dp)
.background(MaterialTheme.colorScheme.background),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
Expand Down
Loading

0 comments on commit 689d3a9

Please sign in to comment.