diff --git a/app/src/main/java/org/yrovas/linklater/data/local/PrefStore.kt b/app/src/main/java/org/yrovas/linklater/data/local/PrefStore.kt index 3f56574..49c461d 100644 --- a/app/src/main/java/org/yrovas/linklater/data/local/PrefStore.kt +++ b/app/src/main/java/org/yrovas/linklater/data/local/PrefStore.kt @@ -2,6 +2,7 @@ package org.yrovas.linklater.data.local import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.stringPreferencesKey @@ -17,6 +18,10 @@ import org.yrovas.linklater.AppScope object Prefs { val LINKDING_URL = stringPreferencesKey("linkding_url") val LINKDING_TOKEN = stringPreferencesKey("linkding_token") + val BOOKMARK_DEFAULT_TAG_NAMES = stringPreferencesKey("bookmark_default_tag_names") + val BOOKMARK_DEFAULT_UNREAD = booleanPreferencesKey("bookmark_default_unread") + val BOOKMARK_DEFAULT_SHARED = booleanPreferencesKey("bookmark_default_shared") + val BOOKMARK_DEFAULT_ARCHIVED = booleanPreferencesKey("bookmark_default_archived") } @AppScope diff --git a/app/src/main/java/org/yrovas/linklater/data/remote/EmptyBookmarkAPI.kt b/app/src/main/java/org/yrovas/linklater/data/remote/EmptyBookmarkAPI.kt index 23d4d52..aecec47 100644 --- a/app/src/main/java/org/yrovas/linklater/data/remote/EmptyBookmarkAPI.kt +++ b/app/src/main/java/org/yrovas/linklater/data/remote/EmptyBookmarkAPI.kt @@ -1,6 +1,7 @@ package org.yrovas.linklater.data.remote import android.content.Context +import kotlinx.coroutines.delay import org.yrovas.linklater.data.Bookmark import org.yrovas.linklater.data.LocalBookmark import org.yrovas.linklater.domain.APIError @@ -21,8 +22,9 @@ class EmptyBookmarkAPI : BookmarkAPI { page: Int, query: String?, ): Res, APIError> { -// return Ok(emptyList()) - return Err(APIError.AUTH) + delay(4300) + return Ok(emptyList()) +// return Err(APIError.AUTH) } override suspend fun getCachedBookmarks(context: Context): List { diff --git a/app/src/main/java/org/yrovas/linklater/data/remote/LinkDingAPI.kt b/app/src/main/java/org/yrovas/linklater/data/remote/LinkDingAPI.kt index 0cf7076..613d45a 100644 --- a/app/src/main/java/org/yrovas/linklater/data/remote/LinkDingAPI.kt +++ b/app/src/main/java/org/yrovas/linklater/data/remote/LinkDingAPI.kt @@ -8,6 +8,7 @@ import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.setBody +import kotlinx.coroutines.delay import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement @@ -71,6 +72,7 @@ class LinkDingAPI( page: Int, query: String?, ): Res, APIError> { +// delay(4300) if (!authProvided) return Err(APIError.AUTH) return runCatching { Log.d("DEBUG/net", "getBookmarks: starting request") diff --git a/app/src/main/java/org/yrovas/linklater/ui/common/AppBar.kt b/app/src/main/java/org/yrovas/linklater/ui/common/AppBar.kt index 3c315b5..c8d2afb 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/common/AppBar.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/common/AppBar.kt @@ -1,12 +1,19 @@ package org.yrovas.linklater.ui.common import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.* +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,13 +29,15 @@ fun AppBar( content: @Composable () -> Unit = {}, ) { AppBar(left = { - Spacer(modifier = Modifier.width(padding.standard)) - back?.let { + if (back != null) { IconButton(onClick = { back() }) { - Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, tint = colorScheme.primary ) } + } else { + Spacer(modifier = Modifier.width(padding.standard)) } Text( text = page, @@ -67,27 +76,37 @@ fun AppBar( } } } - -@Composable -fun LargeHomeBar() { -} +// +//@Composable +//fun LargeHomeBar() { +//} @ThemePreview @Composable -fun AppBarPreview () { +fun HomeAppBarPreview() { AppTheme { Surface { - AppBar(page = "Settings", back = {}) + AppBar(page = "Home") } } } @ThemePreview @Composable -fun LargeHomeBarPreview() { +fun AppBarPreview () { AppTheme { Surface { - LargeHomeBar() + AppBar(page = "Settings", back = {}) } } } + +//@ThemePreview +//@Composable +//fun LargeHomeBarPreview() { +// AppTheme { +// Surface { +// LargeHomeBar() +// } +// } +//} diff --git a/app/src/main/java/org/yrovas/linklater/ui/common/Components.kt b/app/src/main/java/org/yrovas/linklater/ui/common/BookmarkRow.kt similarity index 77% rename from app/src/main/java/org/yrovas/linklater/ui/common/Components.kt rename to app/src/main/java/org/yrovas/linklater/ui/common/BookmarkRow.kt index 5ccc696..c34c13f 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/common/Components.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/common/BookmarkRow.kt @@ -3,11 +3,9 @@ package org.yrovas.linklater.ui.common import android.content.Context import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -16,12 +14,8 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow @@ -34,37 +28,6 @@ import org.yrovas.linklater.timeAgo import org.yrovas.linklater.ui.theme.padding import java.net.URI -@Composable -fun Frame( - appBar: @Composable () -> Unit, - fab: (@Composable () -> Unit)? = null, - snackState: SnackbarHostState, - content: @Composable () -> Unit, -) { - Surface( - modifier = Modifier.fillMaxSize(), - color = colorScheme.background, - contentColor = colorScheme.onBackground - ) { - Box(modifier = Modifier.fillMaxSize()) { - Column { - appBar() - content() - } - Column(modifier = Modifier.align(Alignment.BottomStart)) { - fab?.let { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { fab() } - } - SnackbarHost(hostState = snackState) - } - } - } -} - - @Composable fun BookmarkRow( bookmark: Bookmark, diff --git a/app/src/main/java/org/yrovas/linklater/ui/common/Frame.kt b/app/src/main/java/org/yrovas/linklater/ui/common/Frame.kt new file mode 100644 index 0000000..ea90240 --- /dev/null +++ b/app/src/main/java/org/yrovas/linklater/ui/common/Frame.kt @@ -0,0 +1,60 @@ +package org.yrovas.linklater.ui.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun Frame( + page: String, + back: (() -> Unit), + fab: (@Composable () -> Unit)? = null, + snackState: SnackbarHostState, + content: @Composable () -> Unit, +) { + Frame(appBar = { AppBar(page, back)}, + fab = fab, + snackState = snackState) { + content() + } +} + +@Composable +fun Frame( + appBar: @Composable () -> Unit, + fab: (@Composable () -> Unit)? = null, + snackState: SnackbarHostState, + content: @Composable () -> Unit, +) { + Surface( + modifier = Modifier.fillMaxSize(), + color = colorScheme.background, + contentColor = colorScheme.onBackground + ) { + Box(modifier = Modifier.fillMaxSize()) { + Column { + appBar() + content() + } + Column(modifier = Modifier.align(Alignment.BottomStart)) { + fab?.let { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { fab() } + } + SnackbarHost(hostState = snackState) + } + } + } +} diff --git a/app/src/main/java/org/yrovas/linklater/ui/common/RefreshIcon.kt b/app/src/main/java/org/yrovas/linklater/ui/common/RefreshIcon.kt new file mode 100644 index 0000000..8f7ff43 --- /dev/null +++ b/app/src/main/java/org/yrovas/linklater/ui/common/RefreshIcon.kt @@ -0,0 +1,76 @@ +package org.yrovas.linklater.ui.common + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.Easing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.tween +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +@Composable +fun RefreshIcon( + modifier: Modifier = Modifier, + refreshing: StateFlow, + icon: ImageVector = Icons.Default.Refresh, + tint: Color = MaterialTheme.colorScheme.primary, +) { + val slowInFastOutEasing: Easing = + remember { CubicBezierEasing(0.25f, 0.1f, 0.25f, 1.0f) } + val linearInFastOutEasing: Easing = + remember { CubicBezierEasing(0.4f, 0.0f, 0.25f, 1.0f) } + + val isRefreshing by refreshing.collectAsState() + var didRefresh by remember { mutableStateOf(false) } + val rotation = remember { Animatable(0f) } + + LaunchedEffect(isRefreshing) { + launch { + val duration = 750 + val target = 360f + if (isRefreshing) { + didRefresh = true + rotation.animateTo( + targetValue = 360f, animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = duration, easing = slowInFastOutEasing + ), repeatMode = RepeatMode.Restart + ) + ) + } else if (didRefresh) { + val remainingMillis: Int = ((target - rotation.value) / target * duration).toInt() + val easing = if (remainingMillis > (duration/2)) linearInFastOutEasing else LinearEasing + rotation.animateTo( + targetValue = 360f, + initialVelocity = rotation.velocity, + animationSpec = tween( + durationMillis = remainingMillis, easing = easing + ) + ) + rotation.snapTo(0f) + } + } + } + + Icon( + imageVector = icon, + tint = tint, + modifier = modifier.rotate(rotation.value) + ) +} diff --git a/app/src/main/java/org/yrovas/linklater/ui/common/PreferenceComponents.kt b/app/src/main/java/org/yrovas/linklater/ui/common/TextPreference.kt similarity index 98% rename from app/src/main/java/org/yrovas/linklater/ui/common/PreferenceComponents.kt rename to app/src/main/java/org/yrovas/linklater/ui/common/TextPreference.kt index 34a6e87..ecfd5cb 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/common/PreferenceComponents.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/common/TextPreference.kt @@ -39,7 +39,7 @@ fun TextPreference( info: (@Composable () -> Unit)? = null, state: State, onSave: (String) -> Unit, - onCheck: (String) -> Boolean, + onCheck: (String) -> Boolean = { true }, ) { TextPreference( icon = { Icon(icon) }, @@ -88,11 +88,13 @@ private fun TextPreference( } } - Surface( + Box( modifier = Modifier .fillMaxWidth() - .padding(padding.standard), - onClick = { showDialog = true }, + .padding(padding.standard) + .clip(RoundedCornerShape(8.dp)) + .clickable { showDialog = true } + , ) { Column { Row( diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt index a127a56..593eca0 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/HomeScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddLink -import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.FloatingActionButton @@ -45,6 +44,7 @@ import org.yrovas.linklater.ui.common.AppBar import org.yrovas.linklater.ui.common.BookmarkRow import org.yrovas.linklater.ui.common.Frame import org.yrovas.linklater.ui.common.Icon +import org.yrovas.linklater.ui.common.RefreshIcon import org.yrovas.linklater.ui.state.HomeScreenState import org.yrovas.linklater.ui.theme.AppTheme import org.yrovas.linklater.ui.theme.padding @@ -57,8 +57,8 @@ fun HomeScreen( setup_complete: StateFlow, state: () -> HomeScreenState, ) { - val state = viewModel { state() } - val setup_complete by setup_complete.collectAsState() + @Suppress("NAME_SHADOWING") val state = viewModel { state() } + @Suppress("NAME_SHADOWING") val setup_complete by setup_complete.collectAsState() val scope = rememberCoroutineScope() val bookmarks by state.displayedBookmarks.collectAsState() val listState = rememberLazyListState() @@ -76,18 +76,19 @@ fun HomeScreen( } LaunchedEffect(setup_complete) { - if (state.displayedBookmarks.value.isEmpty()) { + if (setup_complete && bookmarks.isEmpty()) { Log.d("DEBUG", "HomeScreen: REFRESHING") refresh() } } + LaunchedEffect(false) { + + } + Frame(appBar = { AppBar(page = "Bookmarks", back = null) { IconButton(onClick = { refresh() }) { - Icon( - imageVector = Icons.Default.Refresh, - tint = colorScheme.primary - ) + RefreshIcon(refreshing = state.isRefreshing) } IconButton(onClick = { nav.navigate(PreferencesScreenDestination) }) { Icon( @@ -102,7 +103,7 @@ fun HomeScreen( onClick = { nav.navigate(SaveBookmarkScreenDestination) }, content = { Icon(imageVector = Icons.Default.AddLink) }) }, snackState = snackState) { - if (!setup_complete) { + if (!setup_complete && bookmarks.isEmpty()) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/PreferencesScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/PreferencesScreen.kt index d7e33fd..5a7635e 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/PreferencesScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/PreferencesScreen.kt @@ -2,30 +2,43 @@ package org.yrovas.linklater.ui.screens import android.content.Context import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll 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.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.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph @@ -37,8 +50,9 @@ 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.ui.common.AppBar +import org.yrovas.linklater.openUri 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 @@ -52,18 +66,24 @@ fun PreferencesScreen( state: () -> PreferencesScreenState, context: Context = LocalContext.current, ) { - val state = viewModel { state() } - Frame(appBar = { - AppBar(page = "Preferences", back = { nav.popBackStack() }) - }, snackState = snackState) { + @Suppress("NAME_SHADOWING") val state = viewModel { state() } + + val defaultBookmark by state.defaultBookmark.collectAsState() + + Frame( + page = "Preferences", + back = { nav.popBackStack() }, + snackState = snackState + ) { Column( modifier = Modifier .fillMaxSize() .padding(padding.standard) .verticalScroll(rememberScrollState()) ) { + StyledTitle(title = "LinkDing Account") TextPreference( - name = "LinkDing URL", + name = "LinkDing API Endpoint", placeholder = "URL/IP incl. port and https://", icon = Icons.Default.Create, infoPreview = "include /api", @@ -141,9 +161,33 @@ fun PreferencesScreen( onSave = { state.saveBookmarkAPIToken(it) }, onCheck = { checkBookmarkAPIToken(it) }, ) + Spacer(modifier = Modifier.height(padding.double)) + StyledTitle(title = "Bookmark Defaults") + TextPreference( + icon = Icons.Default.Tag, + name = "Tags", + state = state.tag_names.collectAsState(), + onSave = { state.saveDefaultBookmark(tag_names = it) }, + ) + StyledCheckPreference(name = "Unread", + icon = Icons.Default.Visibility, + checked = defaultBookmark.unread, + onCheckedChange = { + state.saveDefaultBookmark(unread = it) + }) + StyledCheckPreference(name = "Shared", + icon = Icons.Default.Public, + checked = defaultBookmark.shared, + onCheckedChange = { + state.saveDefaultBookmark(shared = it) + }) Spacer(modifier = Modifier.weight(1F)) Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .align(Alignment.CenterHorizontally) + .clickable { + context.openUri("https://github.com/danielyrovas/linklater/releases".toUri()) + }, text = "Application Version: ${context.getAppVersion()}", style = typography.bodySmall, fontStyle = FontStyle.Italic @@ -152,6 +196,48 @@ fun PreferencesScreen( } } +@Composable +private fun StyledTitle(title: String) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(padding.large), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = title, style = typography.titleMedium, color = colorScheme.primary) + Spacer(modifier = Modifier.height(padding.half)) + HorizontalDivider(color = colorScheme.primary) + } +} + +@Composable +private fun StyledCheckPreference( + name: String, + icon: ImageVector, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + ) { + Spacer(modifier = Modifier.width(padding.standard)) + Icon(imageVector = icon) + Spacer(modifier = Modifier.width(padding.standard)) + Spacer(modifier = Modifier.width(padding.half)) + Text( + text = name, + style = typography.bodyMedium, + ) + Spacer(modifier = Modifier.weight(1f)) + Checkbox(checked = checked, onCheckedChange = { onCheckedChange(it) }) + Spacer(modifier = Modifier.width(padding.half)) + } +} + @ThemePreview @Composable fun PreferencesScreenPreview() { diff --git a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt index 525899a..98c569b 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/screens/SaveBookmarkScreen.kt @@ -71,6 +71,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import org.yrovas.linklater.ThemePreview +import org.yrovas.linklater.data.local.EmptyPrefStore import org.yrovas.linklater.data.remote.EmptyBookmarkAPI import org.yrovas.linklater.domain.APIError import org.yrovas.linklater.domain.Err @@ -381,7 +382,7 @@ fun StyledTextField( } @Composable -fun StyledCheckBox( +private fun StyledCheckBox( name: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit, @@ -459,7 +460,7 @@ private fun StyledBoxIcon( @Composable fun SaveBookmarkScreenPreview() { AppTheme { - val state = SaveBookmarkScreenState(EmptyBookmarkAPI()) + val state = SaveBookmarkScreenState(EmptyBookmarkAPI(), EmptyPrefStore()) state.updateBookmark("https://alpinelinux.org/arbitrary/URL/that-is-far-to-long-andhassomelongerwordsthatareannoying-especially-for-a-text-field.html") state.setTags( listOf( diff --git a/app/src/main/java/org/yrovas/linklater/ui/state/HomeScreenState.kt b/app/src/main/java/org/yrovas/linklater/ui/state/HomeScreenState.kt index 6a3c157..e8fdb0f 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/state/HomeScreenState.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/state/HomeScreenState.kt @@ -3,7 +3,9 @@ package org.yrovas.linklater.ui.state import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -17,8 +19,8 @@ import org.yrovas.linklater.domain.ok @Inject class HomeScreenState(private val api: BookmarkAPI) : ViewModel() { fun refreshBookmarks(onRefresh: suspend (Res) -> Unit) { + _isRefreshing.update { true } viewModelScope.launch(Dispatchers.IO) { - _isRefreshing.update { true } val res = api.getBookmarks(page = 0) res.ok { setDisplayedBookmarks(it) } _isRefreshing.update { false } diff --git a/app/src/main/java/org/yrovas/linklater/ui/state/PreferencesScreenState.kt b/app/src/main/java/org/yrovas/linklater/ui/state/PreferencesScreenState.kt index 5442161..10f7b0d 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/state/PreferencesScreenState.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/state/PreferencesScreenState.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Inject +import org.yrovas.linklater.data.LocalBookmark import org.yrovas.linklater.data.local.PrefDataStore import org.yrovas.linklater.data.local.Prefs import org.yrovas.linklater.data.remote.BookmarkAPI @@ -21,12 +22,6 @@ class PreferencesScreenState( private val bookmarkAPI: BookmarkAPI, private val prefStore: PrefDataStore, ) : ViewModel() { - init { - viewModelScope.launch(Dispatchers.IO) { - saveBookmarkAPIToken(prefStore.getPref(Prefs.LINKDING_TOKEN, "")) - saveBookmarkURL(prefStore.getPref(Prefs.LINKDING_URL, "")) - } - } private val _bookmarkEndpoint: MutableStateFlow = MutableStateFlow("") var bookmarkEndpoint = _bookmarkEndpoint.asStateFlow() private val _bookmarkAPIToken: MutableStateFlow = MutableStateFlow("") @@ -55,4 +50,76 @@ class PreferencesScreenState( fun saveBookmarkAPIToken(token: String) { saveBookmarkConf(token = token) } + + private val _tag_names: MutableStateFlow = MutableStateFlow("") + var tag_names = _tag_names.asStateFlow() + private val _defaultBookmark: MutableStateFlow = + MutableStateFlow(LocalBookmark("")) + var defaultBookmark = _defaultBookmark.asStateFlow() + + fun saveDefaultBookmark( + tag_names: String? = null, + unread: Boolean? = null, + shared: Boolean? = null, + is_archived: Boolean? = null, + ) { + updateDefaultBookmark( + tag_names = tag_names, + unread = unread, + shared = shared, + is_archived = is_archived, + ) + viewModelScope.launch { + prefStore.setPref( + Prefs.BOOKMARK_DEFAULT_TAG_NAMES, + defaultBookmark.value.tags.joinToString(separator = " ") + ) + prefStore.setPref( + Prefs.BOOKMARK_DEFAULT_UNREAD, defaultBookmark.value.unread + ) + prefStore.setPref( + Prefs.BOOKMARK_DEFAULT_SHARED, defaultBookmark.value.shared + ) + prefStore.setPref( + Prefs.BOOKMARK_DEFAULT_ARCHIVED, defaultBookmark.value.is_archived + ) + } + } + + private fun updateDefaultBookmark( + tag_names: String? = null, + unread: Boolean? = null, + shared: Boolean? = null, + is_archived: Boolean? = null, + ) { + tag_names?.let { this._tag_names.update { tag_names } } + val tags = (defaultBookmark.value.tags + this.tag_names.value.split(" ") + .filter { it.isNotBlank() }).distinct() + _defaultBookmark.update { + it.withUpdates(is_archived = is_archived, + unread = unread, + shared = shared, + tags = tags.ifEmpty { null }) + } + } + + init { + viewModelScope.launch { + saveBookmarkAPIToken(prefStore.getPref(Prefs.LINKDING_TOKEN, "")) + saveBookmarkURL(prefStore.getPref(Prefs.LINKDING_URL, "")) + updateDefaultBookmark( + tag_names = prefStore.getPref( + Prefs.BOOKMARK_DEFAULT_TAG_NAMES, "" + ), + unread = prefStore.getPref(Prefs.BOOKMARK_DEFAULT_UNREAD, false), + shared = prefStore.getPref(Prefs.BOOKMARK_DEFAULT_SHARED, false), + is_archived = prefStore.getPref( + Prefs.BOOKMARK_DEFAULT_ARCHIVED, false + ), + ) + } + } } + +//fun String.intoTags(): List = +// split(" ").filter { it.isNotBlank() }.distinct() diff --git a/app/src/main/java/org/yrovas/linklater/ui/state/SaveBookmarkScreenState.kt b/app/src/main/java/org/yrovas/linklater/ui/state/SaveBookmarkScreenState.kt index a7f4b83..7c1e74f 100644 --- a/app/src/main/java/org/yrovas/linklater/ui/state/SaveBookmarkScreenState.kt +++ b/app/src/main/java/org/yrovas/linklater/ui/state/SaveBookmarkScreenState.kt @@ -11,6 +11,8 @@ import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Inject import org.yrovas.linklater.checkURL import org.yrovas.linklater.data.LocalBookmark +import org.yrovas.linklater.data.local.PrefDataStore +import org.yrovas.linklater.data.local.Prefs import org.yrovas.linklater.data.remote.BookmarkAPI import org.yrovas.linklater.domain.APIError import org.yrovas.linklater.domain.Err @@ -18,11 +20,15 @@ import org.yrovas.linklater.domain.Ok import org.yrovas.linklater.domain.Res @Inject -class SaveBookmarkScreenState(private val bookmarkAPI: BookmarkAPI) : +class SaveBookmarkScreenState( + private val bookmarkAPI: BookmarkAPI, + private val prefStore: PrefDataStore, +) : ViewModel() { private val _bookmarkToSave: MutableStateFlow = MutableStateFlow(LocalBookmark("")) var bookmarkToSave = _bookmarkToSave.asStateFlow() + fun updateBookmark( url: String? = null, title: String? = null, @@ -107,4 +113,19 @@ class SaveBookmarkScreenState(private val bookmarkAPI: BookmarkAPI) : fun validateBookmark(): Boolean { return checkURL(bookmarkToSave.value.url) } + + init { + viewModelScope.launch { + updateBookmark( + is_archived = prefStore.getPref( + Prefs.BOOKMARK_DEFAULT_ARCHIVED, false + ), + unread = prefStore.getPref(Prefs.BOOKMARK_DEFAULT_UNREAD, false), + shared = prefStore.getPref(Prefs.BOOKMARK_DEFAULT_SHARED, false) + ) + updateTagNames( + prefStore.getPref(Prefs.BOOKMARK_DEFAULT_TAG_NAMES, "") + ) + } + } }