Add pull to refresh on entry list view

This commit is contained in:
William Brawner 2022-09-13 21:22:30 -06:00
parent 2d02416101
commit ed0a7d813f
11 changed files with 83 additions and 49 deletions

View file

@ -24,6 +24,8 @@
<entry key="../../../../layout/compose-model-1663094305778.xml" value="0.1" />
<entry key="../../../../layout/compose-model-1663094326622.xml" value="0.1" />
<entry key="../../../../layout/compose-model-1663094326625.xml" value="1.0" />
<entry key="../../../../layout/compose-model-1663101323996.xml" value="0.1" />
<entry key="../../../../layout/compose-model-1663188478804.xml" value="0.47846283783783783" />
</map>
</option>
</component>

View file

@ -57,6 +57,7 @@ dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.accompanist.swiperefresh)
implementation(libs.bundles.compose)
implementation(libs.lifecycle)
implementation(libs.hilt.android.core)

View file

@ -6,15 +6,10 @@ import com.wbrawner.nanoflux.network.repository.CategoryRepository
import com.wbrawner.nanoflux.network.repository.EntryRepository
import com.wbrawner.nanoflux.network.repository.FeedRepository
import com.wbrawner.nanoflux.network.repository.IconRepository
import com.wbrawner.nanoflux.storage.model.Entry
import com.wbrawner.nanoflux.storage.model.EntryAndFeed
import com.wbrawner.nanoflux.syncAll
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
@ -28,11 +23,9 @@ class MainViewModel @Inject constructor(
) : ViewModel() {
init {
viewModelScope.launch {
withContext(Dispatchers.IO) {
if (entryRepository.getCount() == 0L) {
syncAll(categoryRepository, feedRepository, iconRepository, entryRepository)
}
viewModelScope.launch(Dispatchers.IO) {
if (entryRepository.getCount() == 0L) {
syncAll(categoryRepository, feedRepository, iconRepository, entryRepository)
}
}
}

View file

@ -37,4 +37,12 @@ class UnreadViewModel @Inject constructor(
// TODO: Get Base URL
fun getShareUrl(entry: Entry) = "baseUrl/${entry.shareCode}"
fun refresh() = viewModelScope.launch(Dispatchers.IO) {
_loading.emit(true)
feedRepository.getAll(fetch = true)
categoryRepository.getAll(fetch = true)
entryRepository.getAll(fetch = true)
_loading.emit(false)
}
}

View file

@ -20,6 +20,8 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.wbrawner.nanoflux.NanofluxApp
import com.wbrawner.nanoflux.storage.model.*
import java.util.*
@ -32,20 +34,26 @@ fun EntryList(
onFeedClicked: (feed: Feed) -> Unit,
onToggleReadClicked: (entry: Entry) -> Unit,
onStarClicked: (entry: Entry) -> Unit,
onExternalLinkClicked: (entry: Entry) -> Unit
onExternalLinkClicked: @Composable (entry: Entry) -> Unit,
isRefreshing: Boolean,
onRefresh: () -> Unit
) {
// TODO: Add pull to refresh
LazyColumn {
items(entries) { entry ->
EntryListItem(
entry.entry,
entry.feed,
onEntryItemClicked,
onFeedClicked,
onToggleReadClicked,
onStarClicked,
onExternalLinkClicked
)
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing = isRefreshing),
onRefresh = onRefresh
) {
LazyColumn {
items(entries) { entry ->
EntryListItem(
entry.entry,
entry.feed,
onEntryItemClicked,
onFeedClicked,
onToggleReadClicked,
onStarClicked,
onExternalLinkClicked
)
}
}
}
}
@ -58,7 +66,7 @@ fun EntryListItem(
onFeedClicked: (feed: Feed) -> Unit,
onToggleReadClicked: (entry: Entry) -> Unit,
onStarClicked: (entry: Entry) -> Unit,
onExternalLinkClicked: (entry: Entry) -> Unit
onExternalLinkClicked: @Composable (entry: Entry) -> Unit
) {
// val swipeState = rememberSwipeableState(initialValue = entry.status)
Column(
@ -121,13 +129,15 @@ fun EntryListItem(
}
val context = LocalContext.current
IconButton(onClick = {
context.startActivity(Intent(Intent.ACTION_SEND).apply {
val intent = Intent(Intent.ACTION_SEND).apply {
// TODO: Get base url from viewmodel or something
type = "text/plain"
putExtra(
Intent.EXTRA_TEXT,
"https://wbrawner.com/entry/share/${entry.shareCode}"
)
})
}
context.startActivity(Intent.createChooser(intent, null))
}) {
Icon(
imageVector = Icons.Default.Share,

View file

@ -109,6 +109,12 @@ fun MainScaffold(
composable("unread") {
UnreadScreen(navController, snackbarHostState, hiltViewModel())
}
composable("starred") {
UnreadScreen(navController, snackbarHostState, hiltViewModel())
}
composable("history") {
UnreadScreen(navController, snackbarHostState, hiltViewModel())
}
composable(
"entries/{entryId}",
arguments = listOf(navArgument("entryId") { type = NavType.LongType })

View file

@ -1,7 +1,10 @@
package com.wbrawner.nanoflux.ui
import android.content.Intent
import android.net.Uri
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.wbrawner.nanoflux.data.viewmodel.UnreadViewModel
@ -17,30 +20,30 @@ fun UnreadScreen(
val errorMessage by unreadViewModel.errorMessage.collectAsState()
val entries by unreadViewModel.entries.collectAsState(emptyList())
val coroutineScope = rememberCoroutineScope()
if (loading) {
CircularProgressIndicator()
}
errorMessage?.let {
coroutineScope.launch {
when (snackbarHostState.showSnackbar(it, "Retry")) {
// SnackbarResult.ActionPerformed -> unreadViewModel.loadUnread()
SnackbarResult.ActionPerformed -> unreadViewModel.refresh()
else -> unreadViewModel.dismissError()
}
}
}
if (entries.isEmpty()) {
Text("TODO: No entries")
} else {
EntryList(
entries = entries,
onEntryItemClicked = {
navController.navigate("entries/${it.id}")
},
onFeedClicked = {
navController.navigate("feeds/${it.id}")
},
onToggleReadClicked = { /*TODO*/ },
onStarClicked = { /*TODO*/ }) {
EntryList(
entries = entries,
onEntryItemClicked = {
navController.navigate("entries/${it.id}")
},
onFeedClicked = {
navController.navigate("feeds/${it.id}")
},
onToggleReadClicked = { /*TODO*/ },
onStarClicked = { /*TODO*/ },
onExternalLinkClicked = {
LocalContext.current.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.url)))
},
isRefreshing = loading,
onRefresh = {
unreadViewModel.refresh()
}
}
)
}

View file

@ -18,6 +18,7 @@ versionName = "1.0"
work = "2.7.1"
[libraries]
accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version = "0.26.3-beta"}
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
material = { module = "com.google.android.material:material", version.ref = "material" }

View file

@ -14,11 +14,11 @@ class EntryRepository @Inject constructor(
private val entryDao: EntryDao,
private val logger: Timber.Tree
) {
fun observeUnread(): Flow<List<EntryAndFeed>> = entryDao.observeAll()
fun observeUnread(): Flow<List<EntryAndFeed>> = entryDao.observeUnread()
fun getCount(): Long = entryDao.getCount()
suspend fun getAll(fetch: Boolean = false, afterId: Long = 0): List<EntryAndFeed> {
suspend fun getAll(fetch: Boolean = false, afterId: Long? = null): List<EntryAndFeed> {
if (fetch) {
getEntries { page ->
apiService.getEntries(
@ -77,3 +77,9 @@ class EntryRepository @Inject constructor(
}
}
}
enum class EntryStatus {
UNREAD,
STARRED,
HISTORY
}

View file

@ -11,11 +11,15 @@ interface EntryDao {
fun getCount(): Long
@Transaction
@Query("SELECT * FROM Entry ORDER BY createdAt DESC")
@Query("SELECT * FROM Entry ORDER BY publishedAt DESC")
fun observeAll(): Flow<List<EntryAndFeed>>
@Transaction
@Query("SELECT * FROM Entry ORDER BY createdAt DESC")
@Query("SELECT * FROM Entry WHERE status = \"UNREAD\" ORDER BY publishedAt DESC")
fun observeUnread(): Flow<List<EntryAndFeed>>
@Transaction
@Query("SELECT * FROM Entry ORDER BY publishedAt DESC")
fun getAll(): List<EntryAndFeed>
@Transaction

View file

@ -7,7 +7,7 @@ import com.wbrawner.nanoflux.storage.model.FeedCategoryIcon
@Dao
interface FeedDao {
@Transaction
@Query("SELECT * FROM Feed")
@Query("SELECT * FROM Feed ORDER BY title ASC")
fun getAll(): List<FeedCategoryIcon>
@Transaction