Skip to content

Commit

Permalink
Ensure account is always present (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
jocmp authored Jan 1, 2024
1 parent 3df8c58 commit fb954f6
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingData
import com.jocmp.basil.Account
import com.jocmp.basil.AccountManager
import com.jocmp.basil.Article
Expand All @@ -19,6 +19,7 @@ import com.jocmp.basil.Folder
import com.jocmp.basil.buildPager
import com.jocmp.basilreader.selectAccount
import com.jocmp.basilreader.selectedAccount
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
Expand All @@ -34,10 +35,11 @@ class AccountViewModel(

private val filter = mutableStateOf<ArticleFilter>(ArticleFilter.default())

private val _pager = mutableStateOf(account.buildPager(filter.value))

val pager: Pager<Int, Article>
get() = _pager.value
private val pager = mutableStateOf(account.buildPager(filter.value))

val articles: Flow<PagingData<Article>>
get() = pager.value.flow

private val account: Account
get() = accountState.value
Expand All @@ -59,8 +61,7 @@ class AccountViewModel(
fun selectStatus(status: ArticleStatus) {
val nextFilter = filter.value.withStatus(status = status)
filter.value = nextFilter

_pager.value = account.buildPager(nextFilter)
pager.value = account.buildPager(nextFilter)
}

fun selectFeed(feedID: String, onComplete: () -> Unit) {
Expand Down Expand Up @@ -95,7 +96,7 @@ class AccountViewModel(

private fun selectFilter(nextFilter: ArticleFilter, onComplete: () -> Unit) {
filter.value = nextFilter
_pager.value = account.buildPager(nextFilter)
pager.value = account.buildPager(nextFilter)

clearArticle()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ fun ArticleFilterNavigationBar(
selected: ArticleStatus,
onSelect: (status: ArticleStatus) -> Unit
) {
val checkedSelect = { status: ArticleStatus ->
if (selected != status) {
onSelect(status)
}
}

NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(stringResource(id = R.string.article_filters_starred)) },
selected = selected === ArticleStatus.STARRED,
onClick = { onSelect(ArticleStatus.STARRED) },
onClick = { checkedSelect(ArticleStatus.STARRED) },
alwaysShowLabel = false
)
NavigationBarItem(
Expand All @@ -35,7 +41,7 @@ fun ArticleFilterNavigationBar(
},
label = { Text(stringResource(R.string.article_filters_unread)) },
selected = selected === ArticleStatus.UNREAD,
onClick = { onSelect(ArticleStatus.UNREAD) },
onClick = { checkedSelect(ArticleStatus.UNREAD) },
alwaysShowLabel = false
)
NavigationBarItem(
Expand All @@ -47,7 +53,7 @@ fun ArticleFilterNavigationBar(
},
label = { Text(stringResource(R.string.article_filters_all)) },
selected = selected === ArticleStatus.ALL,
onClick = { onSelect(ArticleStatus.ALL) },
onClick = { checkedSelect(ArticleStatus.ALL) },
alwaysShowLabel = false
)
}
Expand Down
99 changes: 22 additions & 77 deletions app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,107 +8,52 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.Pager
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.jocmp.basil.Article
import com.jocmp.basil.ArticleStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import java.time.format.DateTimeFormatter

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ArticleList(
pager: Pager<Int, Article>,
onRefresh: suspend () -> Unit,
articles: Flow<PagingData<Article>>,
onSelect: suspend (articleID: String) -> Unit,
selectedStatus: ArticleStatus,
onStatusSelect: (status: ArticleStatus) -> Unit,
onDrawerNavigation: () -> Unit
) {
val composableScope = rememberCoroutineScope()
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

val refreshScope = rememberCoroutineScope()
val (refreshing, setRefreshing) = remember { mutableStateOf(false) }

fun refresh() = refreshScope.launch {
setRefreshing(true)
onRefresh()
setRefreshing(false)
}

val state = rememberPullRefreshState(refreshing, ::refresh)

Scaffold(
topBar = {
TopAppBar {
Button(onClick = { onDrawerNavigation() }) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
}
},
bottomBar = {
ArticleFilterNavigationBar(
selected = selectedStatus,
onSelect = { onStatusSelect(it) }
)
}
) { innerPadding ->
Box(
Modifier
.padding(innerPadding)
.pullRefresh(state)
) {
LazyColumn(Modifier.fillMaxSize()) {
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val item = lazyPagingItems[index]!!

Row {
Box(
modifier = Modifier
.clickable {
composableScope.launch {
onSelect(item.id)
}
}
) {
Column(Modifier.padding(8.dp)) {
Text(item.title, fontSize = 20.sp)
Text(item.arrivedAt.format(DateTimeFormatter.BASIC_ISO_DATE))
val lazyPagingItems = articles.collectAsLazyPagingItems()

LazyColumn(Modifier.fillMaxSize()) {
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val item = lazyPagingItems[index]!!

Row {
Box(
modifier = Modifier
.clickable {
composableScope.launch {
onSelect(item.id)
}
}
) {
Column(Modifier.padding(8.dp)) {
Text(item.title, fontSize = 20.sp)
Text(item.arrivedAt.format(DateTimeFormatter.BASIC_ISO_DATE))
}
}
}

PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}

data class ArticleStatusNavigationItem(
val icon: Icons,
val label: String
)
135 changes: 85 additions & 50 deletions app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
package com.jocmp.basilreader.ui.articles

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import com.jocmp.basil.ArticleFilter
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.jocmp.basilreader.ui.accounts.AccountViewModel
import com.jocmp.basilreader.ui.components.EmptyView
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterialApi::class)
@Composable
fun ArticleScreen(
viewModel: AccountViewModel = koinViewModel(),
onFeedAdd: () -> Unit,
viewModel: AccountViewModel = koinViewModel(),
onFeedAdd: () -> Unit,
) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val (destination, setDestination) = rememberSaveable { mutableStateOf(ListDetailPaneScaffoldRole.List) }
val scaffoldState = calculateListDetailPaneScaffoldState(
currentPaneDestination = destination,
scaffoldDirective = calculateArticleDirective()
)
val (destination, setDestination) =
rememberSaveable { mutableStateOf(ListDetailPaneScaffoldRole.List) }
val scaffoldState =
calculateListDetailPaneScaffoldState(
currentPaneDestination = destination,
scaffoldDirective = calculateArticleDirective()
)

val navigateToDetail = {
setDestination(ListDetailPaneScaffoldRole.Detail)
}
val navigateToDetail = { setDestination(ListDetailPaneScaffoldRole.Detail) }

val onComplete = {
coroutineScope.launch {
Expand All @@ -40,45 +53,67 @@ fun ArticleScreen(
}
}

val refreshScope = rememberCoroutineScope()
val (refreshing, setRefreshing) = remember { mutableStateOf(false) }

fun refresh() =
refreshScope.launch {
setRefreshing(true)
viewModel.refreshFeed()
setRefreshing(false)
}

val state = rememberPullRefreshState(refreshing, ::refresh)

ArticleScaffold(
drawerState = drawerState,
listDetailState = scaffoldState,
drawerPane = {
FeedList(
folders = viewModel.folders,
feeds = viewModel.feeds,
onFeedAdd = onFeedAdd,
onFolderSelect = { viewModel.selectFolder(it) { onComplete() } },
onFeedSelect = { viewModel.selectFeed(it) { onComplete() } }
)
},
listPane = {
ArticleList(
pager = viewModel.pager,
onRefresh = {
viewModel.refreshFeed()
},
onDrawerNavigation = {
coroutineScope.launch {
drawerState.open()
drawerState = drawerState,
listDetailState = scaffoldState,
drawerPane = {
FeedList(
folders = viewModel.folders,
feeds = viewModel.feeds,
onFeedAdd = onFeedAdd,
onFolderSelect = { viewModel.selectFolder(it) { onComplete() } },
onFeedSelect = { viewModel.selectFeed(it) { onComplete() } }
)
},
listPane = {
Scaffold(
topBar = {
TopAppBar {
Button(onClick = { coroutineScope.launch { drawerState.open() } }) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
}
},
bottomBar = {
ArticleFilterNavigationBar(
selected = viewModel.filterStatus,
onSelect = viewModel::selectStatus,
)
}
) { innerPadding ->
Box(Modifier.padding(innerPadding).pullRefresh(state)) {
ArticleList(
articles = viewModel.articles,
onSelect = {
viewModel.selectArticle(it)
navigateToDetail()
}
)

PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
}
},
selectedStatus = viewModel.filterStatus,
onStatusSelect = viewModel::selectStatus,
onSelect = {
viewModel.selectArticle(it)
navigateToDetail()
}
)
},
detailPane = {
ArticleView(
article = viewModel.article,
onBackPressed = {
viewModel.clearArticle()
setDestination(ListDetailPaneScaffoldRole.List)
}
)
}
},
detailPane = {
ArticleView(
article = viewModel.article,
onBackPressed = {
viewModel.clearArticle()
setDestination(ListDetailPaneScaffoldRole.List)
}
)
}
)
}

0 comments on commit fb954f6

Please sign in to comment.