Add TimelineItem large layout base

This commit is contained in:
Shinokuni 2023-08-14 22:40:29 +02:00
parent 8a04fad0d7
commit b15eb9fa91
6 changed files with 167 additions and 73 deletions

View File

@ -103,5 +103,6 @@ dependencies {
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.9.0' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.9.0'
implementation "com.github.skydoves:landscapist-coil:2.2.2" implementation "io.coil-kt:coil:2.4.0"
implementation "io.coil-kt:coil-compose:2.4.0"
} }

View File

@ -9,9 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.request.ImageRequest
import com.readrops.db.entities.Feed import com.readrops.db.entities.Feed
import com.skydoves.landscapist.coil.CoilImage
@Composable @Composable
fun FeedItem(feed: Feed) { fun FeedItem(feed: Feed) {
@ -22,11 +20,11 @@ fun FeedItem(feed: Feed) {
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp) .padding(8.dp)
) { ) {
CoilImage(imageRequest = { /*CoilImage(imageRequest = {
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(feed.url) .data(feed.url)
.build() .build()
}) })*/
Text( Text(
text = feed.name!!, text = feed.name!!,

View File

@ -1,73 +1,152 @@
package com.readrops.app.compose.timelime package com.readrops.app.compose.timelime
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.readrops.api.utils.DateUtils
import com.readrops.app.compose.R
import com.readrops.app.compose.utils.theme.ShortSpacer
import com.readrops.app.compose.utils.theme.VeryShortSpacer
import com.readrops.app.compose.utils.theme.spacing
import com.readrops.db.pojo.ItemWithFeed
@Preview
@Composable @Composable
fun TimelineItem( fun TimelineItem(
onClick: () -> Unit, itemWithFeed: ItemWithFeed,
onClick: () -> Unit,
onFavorite: () -> Unit,
onReadLater: () -> Unit,
onShare: () -> Unit
) { ) {
Card( Card(
// elevation = 4.card, modifier = Modifier
modifier = Modifier.background(Color.White) .padding(
.padding(8.dp) PaddingValues(
.clickable { onClick() } horizontal = MaterialTheme.spacing.shortSpacing,
vertical = MaterialTheme.spacing.veryShortSpacing
)
)
.clickable { onClick() }
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp)
) { ) {
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.padding(
start = MaterialTheme.spacing.shortSpacing,
end = MaterialTheme.spacing.shortSpacing,
top = MaterialTheme.spacing.shortSpacing,
)
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
/* Icon( Icon(
painter = painterResource(id = com.readrops.app.R.drawable.ic_rss_feed_grey), painter = painterResource(id = R.drawable.ic_rss_feed_grey),
contentDescription = null, contentDescription = null,
// modifier = Modifier.size((MaterialTheme.typography.subtitle2.fontSize.value * 1.5).dp) modifier = Modifier.size(MaterialTheme.typography.labelLarge.fontSize.value.dp)
)*/
// Spacer(Modifier.padding(4.dp))
Text(
text = "feed name",
//style = MaterialTheme.typography.
) )
VeryShortSpacer()
Column {
Text(
text = itemWithFeed.feedName,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
VeryShortSpacer()
if (itemWithFeed.folder != null) {
Text(
text = itemWithFeed.folder!!.name!!,
style = MaterialTheme.typography.labelMedium
)
}
}
} }
Text( Text(
text = "Item date", text = DateUtils.formattedDateByLocal(itemWithFeed.item.pubDate!!),
// style = MaterialTheme.typography.subtitle2 style = MaterialTheme.typography.labelMedium,
) )
} }
Spacer(Modifier.size(8.dp)) ShortSpacer()
Text( Text(
text = "title example", text = itemWithFeed.item.title!!,
//style = MaterialTheme.typography.h5, style = MaterialTheme.typography.titleMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = MaterialTheme.spacing.shortSpacing)
) )
Spacer(Modifier.size(8.dp)) ShortSpacer()
/* Image( if (itemWithFeed.item.imageLink != null) {
painter = painterResource(id = com.readrops.app.R.drawable.header_background), AsyncImage(
contentDescription = null model = itemWithFeed.item.imageLink,
)*/ contentDescription = itemWithFeed.item.title!!,
contentScale = ContentScale.Crop,
modifier = Modifier
.aspectRatio(3f / 2f)
.fillMaxWidth()
)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(MaterialTheme.spacing.shortSpacing)
) {
Icon(
imageVector = Icons.Outlined.FavoriteBorder,
contentDescription = null,
modifier = Modifier.clickable { onFavorite() }
)
Icon(
imageVector = Icons.Outlined.Add, // placeholder icon
contentDescription = null,
modifier = Modifier.clickable { onReadLater() }
)
Icon(
imageVector = Icons.Outlined.Share,
contentDescription = null,
modifier = Modifier.clickable { onShare() }
)
}
} }
} }
} }

View File

@ -34,8 +34,8 @@ object TimelineTab : Tab {
@Composable @Composable
get() { get() {
return TabOptions( return TabOptions(
index = 1u, index = 1u,
title = "Timeline", title = "Timeline",
) )
} }
@ -51,30 +51,35 @@ object TimelineTab : Tab {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text(text = "Timeline") } title = { Text(text = "Timeline") }
) )
} }
) { paddingValues -> ) { paddingValues ->
SwipeRefresh( SwipeRefresh(
state = swipeToRefreshState, state = swipeToRefreshState,
onRefresh = { onRefresh = {
viewModel.refreshTimeline() viewModel.refreshTimeline()
}, },
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {
when (state) { when (state) {
is TimelineState.LoadedState -> { is TimelineState.Loaded -> {
val items = (state as TimelineState.LoadedState).items val items = (state as TimelineState.Loaded).items
if (items.isNotEmpty()) { if (items.isNotEmpty()) {
LazyColumn { LazyColumn {
items( items(
items = items items = items,
) { key = { it.item.id },
) { itemWithFeed ->
TimelineItem( TimelineItem(
onClick = { navigator.push(ItemScreen()) } itemWithFeed = itemWithFeed,
onClick = { navigator.push(ItemScreen()) },
onFavorite = {},
onReadLater = {},
onShare = {},
) )
} }
} }
@ -99,15 +104,15 @@ fun NoItemPlaceholder() {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .verticalScroll(scrollState)
) { ) {
Text( Text(
text = "No item", text = "No item",
style = MaterialTheme.typography.displayMedium style = MaterialTheme.typography.displayMedium
) )
} }
} }

View File

@ -3,7 +3,9 @@ package com.readrops.app.compose.timelime
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.readrops.app.compose.base.TabViewModel import com.readrops.app.compose.base.TabViewModel
import com.readrops.db.Database import com.readrops.db.Database
import com.readrops.db.entities.Item import com.readrops.db.pojo.ItemWithFeed
import com.readrops.db.queries.ItemsQueryBuilder
import com.readrops.db.queries.QueryFilters
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -14,7 +16,7 @@ class TimelineViewModel(
private val database: Database, private val database: Database,
) : TabViewModel(database) { ) : TabViewModel(database) {
private val _timelineState = MutableStateFlow<TimelineState>(TimelineState.InitialState) private val _timelineState = MutableStateFlow<TimelineState>(TimelineState.Loading)
val timelineState = _timelineState.asStateFlow() val timelineState = _timelineState.asStateFlow()
private var _isRefreshing = MutableStateFlow(false) private var _isRefreshing = MutableStateFlow(false)
@ -22,10 +24,12 @@ class TimelineViewModel(
init { init {
viewModelScope.launch(context = Dispatchers.IO) { viewModelScope.launch(context = Dispatchers.IO) {
database.newItemDao().selectAll() val query = ItemsQueryBuilder.buildItemsQuery(QueryFilters(accountId = 1))
.catch { _timelineState.value = TimelineState.ErrorState(Exception(it)) }
database.newItemDao().selectAll(query)
.catch { _timelineState.value = TimelineState.Error(Exception(it)) }
.collect { .collect {
_timelineState.value = TimelineState.LoadedState(it) _timelineState.value = TimelineState.Loaded(it)
} }
} }
} }
@ -42,13 +46,13 @@ class TimelineViewModel(
} }
override fun invalidate() { override fun invalidate() {
refreshTimeline()
} }
} }
sealed class TimelineState { sealed class TimelineState {
object InitialState : TimelineState() object Loading : TimelineState()
data class ErrorState(val exception: Exception) : TimelineState() data class Error(val exception: Exception) : TimelineState()
data class LoadedState(val items: List<Item>) : TimelineState() data class Loaded(val items: List<ItemWithFeed>) : TimelineState()
} }

View File

@ -1,13 +1,20 @@
package com.readrops.db.dao.newdao package com.readrops.db.dao.newdao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery
import com.readrops.db.entities.Feed
import com.readrops.db.entities.Folder
import com.readrops.db.entities.Item import com.readrops.db.entities.Item
import com.readrops.db.entities.ItemState
import com.readrops.db.pojo.ItemWithFeed
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
abstract class NewItemDao : NewBaseDao<Item> { abstract class NewItemDao : NewBaseDao<Item> {
@Query("Select * From Item") @RawQuery(observedEntities = [Item::class, Feed::class, Folder::class, ItemState::class])
abstract fun selectAll(): Flow<List<Item>> abstract fun selectAll(query: SupportSQLiteQuery): Flow<List<ItemWithFeed>>
} }