diff --git a/.idea/misc.xml b/.idea/misc.xml index 9f98f69..07ec910 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,6 +24,8 @@ + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f8fc09d..9979963 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/MainViewModel.kt b/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/MainViewModel.kt index 9aea217..aeda9dc 100644 --- a/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/MainViewModel.kt @@ -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) } } } diff --git a/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/UnreadViewModel.kt b/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/UnreadViewModel.kt index d22402b..6ad7052 100644 --- a/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/UnreadViewModel.kt +++ b/app/src/main/java/com/wbrawner/nanoflux/data/viewmodel/UnreadViewModel.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/nanoflux/ui/Entries.kt b/app/src/main/java/com/wbrawner/nanoflux/ui/Entries.kt index 9efa378..0551935 100644 --- a/app/src/main/java/com/wbrawner/nanoflux/ui/Entries.kt +++ b/app/src/main/java/com/wbrawner/nanoflux/ui/Entries.kt @@ -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, diff --git a/app/src/main/java/com/wbrawner/nanoflux/ui/MainScreen.kt b/app/src/main/java/com/wbrawner/nanoflux/ui/MainScreen.kt index 92b0fea..c51c36a 100644 --- a/app/src/main/java/com/wbrawner/nanoflux/ui/MainScreen.kt +++ b/app/src/main/java/com/wbrawner/nanoflux/ui/MainScreen.kt @@ -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 }) diff --git a/app/src/main/java/com/wbrawner/nanoflux/ui/UnreadScreen.kt b/app/src/main/java/com/wbrawner/nanoflux/ui/UnreadScreen.kt index ac50c82..fadf45e 100644 --- a/app/src/main/java/com/wbrawner/nanoflux/ui/UnreadScreen.kt +++ b/app/src/main/java/com/wbrawner/nanoflux/ui/UnreadScreen.kt @@ -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() } - } + ) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 52ed3d9..ef07eed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/network/src/main/java/com/wbrawner/nanoflux/network/repository/EntryRepository.kt b/network/src/main/java/com/wbrawner/nanoflux/network/repository/EntryRepository.kt index 1f376e5..b750f75 100644 --- a/network/src/main/java/com/wbrawner/nanoflux/network/repository/EntryRepository.kt +++ b/network/src/main/java/com/wbrawner/nanoflux/network/repository/EntryRepository.kt @@ -14,11 +14,11 @@ class EntryRepository @Inject constructor( private val entryDao: EntryDao, private val logger: Timber.Tree ) { - fun observeUnread(): Flow> = entryDao.observeAll() + fun observeUnread(): Flow> = entryDao.observeUnread() fun getCount(): Long = entryDao.getCount() - suspend fun getAll(fetch: Boolean = false, afterId: Long = 0): List { + suspend fun getAll(fetch: Boolean = false, afterId: Long? = null): List { if (fetch) { getEntries { page -> apiService.getEntries( @@ -76,4 +76,10 @@ class EntryRepository @Inject constructor( totalPages = response.total / 100 } } +} + +enum class EntryStatus { + UNREAD, + STARRED, + HISTORY } \ No newline at end of file diff --git a/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/EntryDao.kt b/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/EntryDao.kt index 52ec07b..fd8c19e 100644 --- a/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/EntryDao.kt +++ b/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/EntryDao.kt @@ -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> @Transaction - @Query("SELECT * FROM Entry ORDER BY createdAt DESC") + @Query("SELECT * FROM Entry WHERE status = \"UNREAD\" ORDER BY publishedAt DESC") + fun observeUnread(): Flow> + + @Transaction + @Query("SELECT * FROM Entry ORDER BY publishedAt DESC") fun getAll(): List @Transaction diff --git a/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/FeedDao.kt b/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/FeedDao.kt index 9ee945b..67ec955 100644 --- a/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/FeedDao.kt +++ b/storage/src/main/java/com/wbrawner/nanoflux/storage/dao/FeedDao.kt @@ -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 @Transaction