73: Reimplement LobstersItem to not require swipe actions r=msfjarvis a=msfjarvis

Work towards #65

TODO:
- [x] ~~Figure out why only the last tag shows in the UI~~ Fixed by using `Row` to lay it out like earlier


<details>
<summary>Screenshot</summary>

![screenshot-20201109-113553](https://user-images.githubusercontent.com/13348378/98505720-ce2a4600-227f-11eb-8342-863f2122b928.png)


</details>

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
bors[bot] 2020-11-09 06:41:07 +00:00 committed by GitHub
commit 3cb1c4b027
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 116 additions and 106 deletions

View file

@ -85,8 +85,8 @@ fun LobstersApp() {
HottestPosts(
posts = hottestPosts,
listState = hottestPostsListState,
saveAction = viewModel::savePost,
overscrollAction = viewModel::getMorePosts,
saveAction = viewModel::savePost,
)
}
composable(Destination.Saved.route) {

View file

@ -14,8 +14,8 @@ fun HottestPosts(
posts: List<LobstersPost>,
listState: LazyListState,
modifier: Modifier = Modifier,
saveAction: (LobstersPost) -> Unit,
overscrollAction: () -> Unit,
saveAction: (LobstersPost) -> Unit,
) {
val urlLauncher = UrlLauncherAmbient.current
@ -32,9 +32,9 @@ fun HottestPosts(
}
LobstersItem(
post = item,
linkOpenAction = { post -> urlLauncher.launch(post.url.ifEmpty { post.commentsUrl }) },
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
saveAction = saveAction,
onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) },
onLongClick = { urlLauncher.launch(item.commentsUrl) },
onSaveButtonClick = { saveAction.invoke(item) },
)
}
}

View file

@ -4,116 +4,31 @@ import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ConstraintLayout
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.offsetPx
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.material.Surface
import androidx.compose.material.ripple.RippleIndication
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ConfigurationAmbient
import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview
import coil.transform.CircleCropTransformation
import dev.chrisbanes.accompanist.coil.CoilImage
import dev.msfjarvis.lobsters.R
import dev.msfjarvis.lobsters.model.LobstersPost
import dev.msfjarvis.lobsters.model.Submitter
import dev.msfjarvis.lobsters.ui.theme.LobstersTheme
import dev.msfjarvis.lobsters.ui.theme.titleColor
import dev.msfjarvis.lobsters.util.IconResource
private enum class SwipeState {
NotSwiped,
FullySwiped,
}
@Composable
fun LazyItemScope.LobstersItem(
post: LobstersPost,
modifier: Modifier = Modifier,
linkOpenAction: (LobstersPost) -> Unit,
commentOpenAction: (LobstersPost) -> Unit,
saveAction: (LobstersPost) -> Unit,
) {
val width = with(DensityAmbient.current) {
ConfigurationAmbient.current.screenWidthDp.toDp().toPx()
}
val swipeableState = rememberSwipeableState(SwipeState.NotSwiped)
val anchors = mapOf(0f to SwipeState.NotSwiped, width to SwipeState.FullySwiped)
if (swipeableState.offset.value >= (width / 2)) {
saveAction.invoke(post)
swipeableState.animateTo(SwipeState.NotSwiped)
}
Column(
modifier = modifier
.fillParentMaxWidth()
.swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.5f) },
orientation = Orientation.Horizontal
)
.offsetPx(swipeableState.offset)
.clickable(
onClick = { linkOpenAction.invoke(post) },
onLongClick = { commentOpenAction.invoke(post) },
),
) {
Text(
text = post.title,
color = titleColor,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 4.dp)
)
Row(
modifier = Modifier.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
post.tags.take(4).forEach { tag ->
Text(
text = tag,
modifier = Modifier
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
.padding(vertical = 2.dp, horizontal = 6.dp),
color = Color.DarkGray,
)
}
}
Row(
modifier = Modifier.wrapContentHeight(),
) {
CoilImage(
data = "https://lobste.rs/${post.submitterUser.avatarUrl}",
fadeIn = true,
requestBuilder = {
transformations(CircleCropTransformation())
},
modifier = Modifier.width(30.dp).padding(4.dp).align(Alignment.CenterVertically),
)
Text(
text = "submitted by ${post.submitterUser.username}",
modifier = Modifier.padding(bottom = 4.dp).align(Alignment.CenterVertically),
)
}
}
}
@Composable
@Preview
fun PreviewLobstersItem() {
val post = LobstersPost(
val TEST_POST = LobstersPost(
"zqyydb",
"https://lobste.rs/s/zqyydb",
"2020-09-21T07:11:14.000-05:00",
@ -137,15 +52,110 @@ fun PreviewLobstersItem() {
null,
emptyList(),
),
listOf("openbsd"),
listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
)
@Composable
fun LobstersItem(
post: LobstersPost,
onClick: () -> Unit,
onLongClick: () -> Unit,
onSaveButtonClick: () -> Unit,
) {
Surface(
modifier = Modifier.fillMaxWidth()
.clickable(
onClick = onClick,
onLongClick = onLongClick,
),
) {
ConstraintLayout(
modifier = Modifier.padding(start = 4.dp, end = 4.dp),
) {
val (title, tags, avatar, submitter, saveButton) = createRefs()
Text(
text = post.title,
color = titleColor,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 4.dp)
.constrainAs(title) {
top.linkTo(parent.top)
start.linkTo(parent.start)
},
)
TagRow(
tags = post.tags,
modifier = Modifier.constrainAs(tags) {
top.linkTo(title.bottom)
}.padding(vertical = 8.dp),
)
CoilImage(
data = "https://lobste.rs/${post.submitterUser.avatarUrl}",
fadeIn = true,
requestBuilder = {
transformations(CircleCropTransformation())
},
modifier = Modifier.width(30.dp).padding(4.dp)
.constrainAs(avatar) {
top.linkTo(tags.bottom)
start.linkTo(parent.start)
},
)
Text(
text = "submitted by ${post.submitterUser.username}",
modifier = Modifier.padding(bottom = 4.dp).constrainAs(submitter) {
top.linkTo(tags.bottom)
start.linkTo(avatar.end)
},
)
IconResource(
resourceId = R.drawable.ic_favorite_border_24px,
modifier = Modifier.padding(8.dp)
.clickable(
onClick = onSaveButtonClick,
indication = RippleIndication(),
)
.constrainAs(saveButton) {
end.linkTo(parent.end)
centerVerticallyTo(parent)
},
tint = Color(0xFFD97373),
)
}
}
}
@Composable
fun TagRow(
tags: List<String>,
modifier: Modifier = Modifier,
) {
Row(
modifier = Modifier.then(modifier),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
tags.take(3).forEach { tag ->
Text(
text = tag,
modifier = Modifier
.background(Color(0xFFFFFCD7), RoundedCornerShape(8.dp))
.padding(vertical = 2.dp, horizontal = 6.dp),
color = Color.DarkGray,
)
}
}
}
@Composable
@Preview
fun Preview() {
LobstersTheme {
LazyColumnFor(items = listOf(post)) { item ->
LazyColumnFor(items = listOf(TEST_POST, TEST_POST, TEST_POST, TEST_POST, TEST_POST)) { item ->
LobstersItem(
post = item,
linkOpenAction = {},
commentOpenAction = {},
saveAction = {},
onClick = {},
onLongClick = {},
onSaveButtonClick = {},
)
}
}

View file

@ -13,7 +13,7 @@ import dev.msfjarvis.lobsters.ui.urllauncher.UrlLauncherAmbient
fun SavedPosts(
posts: List<LobstersPost>,
modifier: Modifier = Modifier,
saveAction: (LobstersPost) -> Unit
saveAction: (LobstersPost) -> Unit,
) {
val listState = rememberLazyListState()
val urlLauncher = UrlLauncherAmbient.current
@ -28,9 +28,9 @@ fun SavedPosts(
) { item ->
LobstersItem(
post = item,
linkOpenAction = { post -> urlLauncher.launch(post.url.ifEmpty { post.commentsUrl }) },
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
saveAction = saveAction,
onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) },
onLongClick = { urlLauncher.launch(item.commentsUrl) },
onSaveButtonClick = { saveAction.invoke(item) },
)
}
}