Skip to content

Commit

Permalink
fetch all bookmarks button
Browse files Browse the repository at this point in the history
  • Loading branch information
danielyrovas committed Apr 21, 2024
1 parent 9e80b2e commit dd42515
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 80 deletions.
14 changes: 14 additions & 0 deletions app/src/main/java/org/yrovas/linklater/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import io.ktor.client.request.header
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.json.Json
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import me.tatarka.inject.annotations.Scope
import org.yrovas.linklater.data.local.BookmarkDataSourceImpl
Expand All @@ -44,13 +47,24 @@ val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "pr
)
annotation class AppScope

@Inject
class ApplicationScope(private val context: Context) {
fun launch(
dispatcher: CoroutineDispatcher = Dispatchers.Main,
job: suspend () -> Unit,
) {
context.launch(dispatcher, job)
}
}

@Component
@AppScope
abstract class AppComponent(
@get:Provides val context: Context,
) {
abstract val destinationHost: DestinationHost
abstract val prefStore: PrefDataStore
abstract val appScope: ApplicationScope

val store: DataStore<Preferences>
@AppScope @Provides get() = context.dataStore
Expand Down
25 changes: 15 additions & 10 deletions app/src/main/java/org/yrovas/linklater/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import android.content.*
import android.content.res.Configuration
import android.net.Uri
import android.widget.Toast
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.Instant
import org.yrovas.linklater.domain.APIError
import org.yrovas.linklater.ui.activity.AppActivity
import kotlin.math.abs
fun checkURL(url: String) = url.contains(Regex("^https?://.+[.].+"))
Expand Down Expand Up @@ -81,17 +85,18 @@ fun Context.getAppVersion(): String {
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
annotation class ThemePreview

fun Context.launch(job: suspend () -> Unit) {
(this as AppActivity).launch { job() }
}

fun String?.isNull(): Boolean {
return this == null
}

fun String?.isNotNull(): Boolean {
return this != null
fun Context.launch(dispatcher: CoroutineDispatcher = Dispatchers.Main, job: suspend () -> Unit) {
(this as AppActivity).launch(dispatcher, job)
}

fun String.intoTags(): List<String> =
split(" ").filter { it.isNotBlank() }.distinct()

suspend fun SnackbarHostState.show(error: APIError) {
when (error) {
APIError.NO_CONNECTION -> showSnackbar("Could not connect to LinkDing ")
APIError.INCORRECT_AUTH -> showSnackbar("Invalid Token")
APIError.INCORRECT_ENDPOINT -> showSnackbar("Invalid API Endpoint")
APIError.NO_AUTH_PROVIDED -> {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.remember
import androidx.lifecycle.lifecycleScope
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainCoroutineDispatcher
import kotlinx.coroutines.launch
import org.yrovas.linklater.AppComponent
import org.yrovas.linklater.create
Expand All @@ -22,8 +25,8 @@ abstract class AppActivity : ComponentActivity() {
AppComponent::class.create(this)
}

fun launch(job: suspend () -> Unit) {
lifecycleScope.launch { job() }
fun launch(dispatcher: CoroutineDispatcher = Dispatchers.Main, job: suspend () -> Unit) {
lifecycleScope.launch(dispatcher) { job() }
}

protected fun setContent(navGraph: NavHostGraphSpec) {
Expand Down
10 changes: 1 addition & 9 deletions app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.ramcosta.composedestinations.generated.destinations.SaveBookmarkScree
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import org.yrovas.linklater.domain.APIError
import org.yrovas.linklater.show
import org.yrovas.linklater.ui.common.AppBar
import org.yrovas.linklater.ui.common.BookmarkRow
import org.yrovas.linklater.ui.common.Frame
Expand Down Expand Up @@ -106,15 +107,6 @@ fun HomeScreen(
}
}

private suspend fun SnackbarHostState.show(error: APIError) {
when (error) {
APIError.NO_CONNECTION -> showSnackbar("Could not connect to LinkDing ")
APIError.INCORRECT_AUTH -> showSnackbar("Invalid Token")
APIError.INCORRECT_ENDPOINT -> showSnackbar("Invalid API Endpoint")
APIError.NO_AUTH_PROVIDED -> {}
}
}

//@ThemePreview
//@Composable
//fun HomeScreenPreview() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Tag
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand All @@ -43,19 +45,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import org.yrovas.linklater.ThemePreview
import kotlinx.coroutines.launch
import org.yrovas.linklater.checkBookmarkAPIToken
import org.yrovas.linklater.checkURL
import org.yrovas.linklater.data.local.EmptyPrefStore
import org.yrovas.linklater.data.remote.EmptyBookmarkAPI
import org.yrovas.linklater.getAppVersion
import org.yrovas.linklater.openUri
import org.yrovas.linklater.show
import org.yrovas.linklater.ui.common.Frame
import org.yrovas.linklater.ui.common.Icon
import org.yrovas.linklater.ui.common.TextPreference
import org.yrovas.linklater.ui.state.PreferencesScreenState
import org.yrovas.linklater.ui.theme.AppTheme
import org.yrovas.linklater.ui.state.PreferencesScreenState.Effect
import org.yrovas.linklater.ui.state.PreferencesScreenState.Event
import org.yrovas.linklater.ui.theme.padding

@Destination<RootGraph>
Expand All @@ -69,6 +70,23 @@ fun PreferencesScreen(
@Suppress("NAME_SHADOWING") val state = viewModel { state() }

val defaultBookmark by state.defaultBookmark.collectAsState()
val scope = rememberCoroutineScope()

LaunchedEffect(true) {
scope.launch {
state.effect.collect { effect ->
when (effect) {
is Effect.RefreshError -> {
snackState.show(effect.error)
}

Effect.RefreshComplete -> {
snackState.showSnackbar("Refresh Completed")
}
}
}
}
}

Frame(
page = "Preferences",
Expand Down Expand Up @@ -181,7 +199,13 @@ fun PreferencesScreen(
onCheckedChange = {
state.saveDefaultBookmark(shared = it)
})

Spacer(modifier = Modifier.weight(1F))
Button(modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = { state.sendEvent(Event.FetchAllBookmarks) }) {
Text(text = "Fetch All Bookmarks")
}
Spacer(modifier = Modifier.height(padding.double))
Text(
modifier = Modifier
.align(Alignment.CenterHorizontally)
Expand Down Expand Up @@ -238,12 +262,14 @@ private fun StyledCheckPreference(
}
}

@ThemePreview
@Composable
fun PreferencesScreenPreview() {
AppTheme {
PreferencesScreen(EmptyDestinationsNavigator,
SnackbarHostState(),
{ PreferencesScreenState(EmptyBookmarkAPI(), EmptyPrefStore()) })
}
}
//@ThemePreview
//@Composable
//fun PreferencesScreenPreview() {
// AppTheme {
// PreferencesScreen(EmptyDestinationsNavigator,
// SnackbarHostState(),
// { PreferencesScreenState(EmptyBookmarkAPI(),
// EmptyPrefStore()
// ) })
// }
//}
55 changes: 20 additions & 35 deletions app/src/main/java/org/yrovas/linklater/ui/state/HomeScreenState.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.yrovas.linklater.ui.state

import android.util.Log
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -43,40 +42,7 @@ class HomeScreenState(
private val _bookmarkCount = MutableStateFlow(0)
val bookmarkCount = _bookmarkCount.asStateFlow()

private fun refreshBookmarks() {
_isRefreshing.update { true }
viewModelScope.launch(Dispatchers.IO) {
val res = api.getBookmarks(page = 0)
sendEffect {
if (res.isOk) Effect.RefreshOk
else Effect.RefreshError(res.errorOrThrow())
}
_isRefreshing.update { false }
res.ifOk { bookmarkSource.insertBookmarks(it) }
}
}

// fun fullSync() {
// viewModelScope.launch(Dispatchers.IO) {
// var res = api.getBookmarks(0)
// var page = 0
// while (true) when (res) {
// is Res.Err -> break
// is Res.Ok -> {
// if (res.data.isEmpty()) {
// break
// }
// Log.d(
// "DEBUG", "fullSync: FETCHED ${res.data} from page $page"
// )
// bookmarkSource.insertBookmarks(res.data)
// res = api.getBookmarks(page++)
// }
// }
// }
// }

init {
private fun fetchLocalBookmarks() =
viewModelScope.launch(Dispatchers.IO) {
bookmarkSource.getBookmarks().collect { bookmarks ->
_displayedBookmarks.update {
Expand All @@ -85,6 +51,8 @@ class HomeScreenState(
_bookmarkCount.emit(bookmarkSource.getBookmarkCount())
}
}

private fun fetchRemoteBookmarks() =
viewModelScope.launch(Dispatchers.IO) {
// when authenticated refresh
api.authProvided.transformWhile { emit(it); !it }.collect {
Expand All @@ -93,11 +61,28 @@ class HomeScreenState(
}
}
}

private fun refreshBookmarks() {
_isRefreshing.update { true }
viewModelScope.launch(Dispatchers.IO) {
val res = api.getBookmarks(page = 0)
sendEffect(
if (res.isOk) Effect.RefreshOk
else Effect.RefreshError(res.errorOrThrow())
)
_isRefreshing.update { false }
res.ifOk { bookmarkSource.insertBookmarks(it) }
}
}

override fun handleEvent(event: Event) {
when (event) {
Event.RefreshBookmarks -> refreshBookmarks()
}
}

init {
fetchLocalBookmarks()
fetchRemoteBookmarks()
}
}
Loading

0 comments on commit dd42515

Please sign in to comment.