Skip to content

Commit

Permalink
Add filter UI
Browse files Browse the repository at this point in the history
Non-functioning, but selection state works
  • Loading branch information
jocmp committed Dec 31, 2023
1 parent aefa445 commit b338587
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,15 @@ class AccountViewModel(
val article: Article?
get() = articleState.value

val filterStatus: ArticleFilter.Status
get() = filter.value.status

fun articles(): Pager<Int, Article>? = pager.value

fun selectStatus(status: ArticleFilter.Status) {
filter.value = filter.value.withStatus(status = status)
}

fun selectFeed(feedID: String, onComplete: () -> Unit) {
val feed = account?.findFeed(feedID) ?: return
val feedFilter = ArticleFilter.Feeds(feed = feed, status = filter.value.status)
Expand All @@ -84,7 +91,7 @@ class AccountViewModel(
suspend fun refreshFeed() {
when (val currentFilter = filter.value) {
is ArticleFilter.Feeds -> account?.refreshFeed(currentFilter.feed)
is ArticleFilter.Folders -> account?.refreshFeeds(currentFilter.folder.feeds)
is ArticleFilter.Folders -> account?.refreshFeeds(currentFilter.folder.feeds)
is ArticleFilter.Articles -> account?.refreshAll()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.jocmp.basilreader.ui.articles

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.jocmp.basil.ArticleFilter
import com.jocmp.basilreader.R

@Composable
fun ArticleFilterNavigationBar(
selected: ArticleFilter.Status,
onSelect: (status: ArticleFilter.Status) -> Unit
) {
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
label = { Text(stringResource(id = R.string.article_filters_starred)) },
selected = selected === ArticleFilter.Status.STARRED,
onClick = { onSelect(ArticleFilter.Status.STARRED) },
alwaysShowLabel = false
)
NavigationBarItem(
icon = {
Icon(
painter = painterResource(R.drawable.unread),
contentDescription = null
)
},
label = { Text(stringResource(R.string.article_filters_unread)) },
selected = selected === ArticleFilter.Status.UNREAD,
onClick = { onSelect(ArticleFilter.Status.UNREAD) },
alwaysShowLabel = false
)
NavigationBarItem(
icon = {
Icon(
painter = painterResource(R.drawable.notes),
contentDescription = null
)
},
label = { Text(stringResource(R.string.article_filters_all)) },
selected = selected === ArticleFilter.Status.ALL,
onClick = { onSelect(ArticleFilter.Status.ALL) },
alwaysShowLabel = false
)
}
}

@Preview
@Composable
fun ArticleFilterNavigationBarPreview() {
ArticleFilterNavigationBar(
selected = ArticleFilter.Status.ALL,
onSelect = {}
)
}
67 changes: 49 additions & 18 deletions app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@ package com.jocmp.basilreader.ui.articles
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
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.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
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.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.Pager
import androidx.paging.compose.collectAsLazyPagingItems
import com.jocmp.basil.Article
import com.jocmp.basil.ArticleFilter
import com.jocmp.basilreader.R
import kotlinx.coroutines.launch
import java.time.format.DateTimeFormatter

Expand All @@ -31,6 +42,8 @@ fun ArticleList(
pager: Pager<Int, Article>,
onRefresh: suspend () -> Unit,
onSelect: suspend (articleID: String) -> Unit,
onStatusSelect: (status: ArticleFilter.Status) -> Unit,
selectedStatus: ArticleFilter.Status,
) {
val composableScope = rememberCoroutineScope()
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
Expand All @@ -46,30 +59,48 @@ fun ArticleList(

val state = rememberPullRefreshState(refreshing, ::refresh)

Box(Modifier.pullRefresh(state)) {

LazyColumn(Modifier.fillMaxWidth()) {
items(count = lazyPagingItems.itemCount) { index ->
val item = lazyPagingItems[index]
Column(
modifier = Modifier
.padding(8.dp)
.clickable {
item?.let {
composableScope.launch {
onSelect(it.id)
Scaffold(
bottomBar = {
ArticleFilterNavigationBar(
selected = selectedStatus,
onSelect = onStatusSelect
)
}
) { innerPadding ->
Box(
Modifier
.padding(innerPadding)
.pullRefresh(state)
) {
LazyColumn(Modifier.fillMaxWidth()) {
items(count = lazyPagingItems.itemCount) { index ->
val item = lazyPagingItems[index]
Box(
modifier = Modifier
.clickable {
item?.let {
composableScope.launch {
onSelect(it.id)
}
}
}
) {
Column(Modifier.padding(8.dp)) {
item?.let { article ->
Text(article.title, fontSize = 20.sp)
Text(article.arrivedAt.format(DateTimeFormatter.BASIC_ISO_DATE))
}
}
) {
item?.let { article ->
Text(article.title, fontSize = 20.sp)
Text(article.arrivedAt.format(DateTimeFormatter.BASIC_ISO_DATE))
}
}
}
}

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

data class ArticleStatusNavigationItem(
val icon: Icons,
val label: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import androidx.navigation.compose.composable

const val articlesRoute = "articles"

fun feedRoute(feedID: String) = "articles"

fun NavController.navigateToArticles() =
navigate(articlesRoute)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
package com.jocmp.basilreader.ui.articles

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.PaneScaffoldDirective
import androidx.compose.material3.adaptive.ThreePaneScaffoldState
import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
import androidx.compose.material3.adaptive.calculateStandardPaneScaffoldDirective
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.jocmp.basilreader.ui.theme.BasilReaderTheme

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ArticleScaffold(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
listDetailState: ThreePaneScaffoldState = calculateListDetailPaneScaffoldState(
currentPaneDestination = ListDetailPaneScaffoldRole.List
currentPaneDestination = ListDetailPaneScaffoldRole.List,
scaffoldDirective = calculateArticleDirective()
),
drawerPane: @Composable () -> Unit,
listPane: @Composable () -> Unit,
Expand All @@ -39,7 +50,7 @@ fun ArticleScaffold(
ModalDrawerSheet {
drawerPane()
}
}
},
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
Expand All @@ -58,6 +69,28 @@ fun ArticleScaffold(
}
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun calculateArticleDirective(): PaneScaffoldDirective {
val calculated = calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo())

return copyDirectiveWithoutPadding(calculated)
}



@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private fun copyDirectiveWithoutPadding(directive: PaneScaffoldDirective): PaneScaffoldDirective {
return PaneScaffoldDirective(
contentPadding = PaddingValues(0.dp),
maxHorizontalPartitions = directive.maxHorizontalPartitions,
horizontalPartitionSpacerSize = 0.dp,
maxVerticalPartitions = directive.maxVerticalPartitions,
verticalPartitionSpacerSize = directive.verticalPartitionSpacerSize,
excludedBounds = directive.excludedBounds
)
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Preview(device = Devices.FOLDABLE)
@Composable
Expand All @@ -68,7 +101,13 @@ fun ArticlesLayoutPreview() {
Text("List here!")
},
listPane = {
Text("Index list here...")
Surface(
Modifier
.background(Color.Cyan)
.fillMaxSize()
) {
Text("Index list here...")
}
},
detailPane = {
Text("Detail!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import com.jocmp.basil.ArticleFilter
import com.jocmp.basilreader.ui.accounts.AccountViewModel
import com.jocmp.basilreader.ui.components.EmptyView
import kotlinx.coroutines.launch
Expand All @@ -23,7 +24,10 @@ fun ArticleScreen(
val drawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val (destination, setDestination) = rememberSaveable { mutableStateOf(ListDetailPaneScaffoldRole.List) }
val scaffoldState = calculateListDetailPaneScaffoldState(currentPaneDestination = destination)
val scaffoldState = calculateListDetailPaneScaffoldState(
currentPaneDestination = destination,
scaffoldDirective = calculateArticleDirective()
)

val navigateToDetail = {
setDestination(ListDetailPaneScaffoldRole.Detail)
Expand Down Expand Up @@ -55,6 +59,8 @@ fun ArticleScreen(
onRefresh = {
viewModel.refreshFeed()
},
selectedStatus = viewModel.filterStatus,
onStatusSelect = { viewModel.selectStatus(it) },
onSelect = {
viewModel.selectArticle(it)
navigateToDetail()
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/notes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,18h12v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h18v-2L3,11v2z"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/unread.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z"/>
</vector>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
<string name="add_feed_submit">Add</string>
<string name="add_feed_cancel">Cancel</string>
<string name="required_placeholder">Required</string>
<string name="article_filters_starred">Starred</string>
<string name="article_filters_unread">Unread</string>
<string name="article_filters_all">All</string>
</resources>
12 changes: 10 additions & 2 deletions basil/src/main/java/com/jocmp/basil/ArticleFilter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ package com.jocmp.basil
sealed class ArticleFilter(open val status: Status) {
enum class Status(value: String) {
ALL("all"),
READ("read"),
UNREAD("unread"),
STARRED("starred")
}

fun withStatus(status: Status): ArticleFilter {
return when (this) {
is Articles -> copy(status = status)
is Feeds -> copy(status = status)
is Folders -> copy(status = status)
}
}

data class Articles(override val status: Status) : ArticleFilter(status)

data class Feeds(val feed: Feed, override val status: Status) : ArticleFilter(status)

data class Folders(val folder: Folder, override val status: Status) : ArticleFilter(status)

companion object {
fun default() = ArticleFilter.Articles(status = Status.ALL)
fun default() = Articles(status = Status.ALL)
}
}
Loading

0 comments on commit b338587

Please sign in to comment.