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( HottestPosts(
posts = hottestPosts, posts = hottestPosts,
listState = hottestPostsListState, listState = hottestPostsListState,
saveAction = viewModel::savePost,
overscrollAction = viewModel::getMorePosts, overscrollAction = viewModel::getMorePosts,
saveAction = viewModel::savePost,
) )
} }
composable(Destination.Saved.route) { composable(Destination.Saved.route) {

View file

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

View file

@ -4,107 +4,143 @@ import androidx.compose.foundation.Text
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement 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.Row
import androidx.compose.foundation.layout.offsetPx import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumnFor import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.FractionalThreshold import androidx.compose.material.Surface
import androidx.compose.material.rememberSwipeableState import androidx.compose.material.ripple.RippleIndication
import androidx.compose.material.swipeable
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.gesture.scrollorientationlocking.Orientation
import androidx.compose.ui.graphics.Color 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.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview import androidx.ui.tooling.preview.Preview
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import dev.chrisbanes.accompanist.coil.CoilImage import dev.chrisbanes.accompanist.coil.CoilImage
import dev.msfjarvis.lobsters.R
import dev.msfjarvis.lobsters.model.LobstersPost import dev.msfjarvis.lobsters.model.LobstersPost
import dev.msfjarvis.lobsters.model.Submitter import dev.msfjarvis.lobsters.model.Submitter
import dev.msfjarvis.lobsters.ui.theme.LobstersTheme import dev.msfjarvis.lobsters.ui.theme.LobstersTheme
import dev.msfjarvis.lobsters.ui.theme.titleColor import dev.msfjarvis.lobsters.ui.theme.titleColor
import dev.msfjarvis.lobsters.util.IconResource
private enum class SwipeState { val TEST_POST = LobstersPost(
NotSwiped, "zqyydb",
FullySwiped, "https://lobste.rs/s/zqyydb",
} "2020-09-21T07:11:14.000-05:00",
"k2k20 hackathon report: Bob Beck on LibreSSL progress",
"https://undeadly.org/cgi?action=article;sid=20200921105847",
4,
0,
0,
"",
"https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
Submitter(
"Vigdis",
"2017-02-27T21:08:14.000-06:00",
false,
"Alleycat for the fun, sys/net admin for a living and OpenBSD contributions for the pleasure. (Not so) French dude in Montreal\r\n\r\nhttps://chown.me",
false,
76,
"/avatars/Vigdis-100.png",
"sevan",
null,
null,
emptyList(),
),
listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
)
@Composable @Composable
fun LazyItemScope.LobstersItem( fun LobstersItem(
post: LobstersPost, post: LobstersPost,
modifier: Modifier = Modifier, onClick: () -> Unit,
linkOpenAction: (LobstersPost) -> Unit, onLongClick: () -> Unit,
commentOpenAction: (LobstersPost) -> Unit, onSaveButtonClick: () -> Unit,
saveAction: (LobstersPost) -> Unit,
) { ) {
val width = with(DensityAmbient.current) { Surface(
ConfigurationAmbient.current.screenWidthDp.toDp().toPx() modifier = Modifier.fillMaxWidth()
}
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( .clickable(
onClick = { linkOpenAction.invoke(post) }, onClick = onClick,
onLongClick = { commentOpenAction.invoke(post) }, onLongClick = onLongClick,
), ),
) { ) {
Text( ConstraintLayout(
text = post.title, modifier = Modifier.padding(start = 4.dp, end = 4.dp),
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(),
) { ) {
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( CoilImage(
data = "https://lobste.rs/${post.submitterUser.avatarUrl}", data = "https://lobste.rs/${post.submitterUser.avatarUrl}",
fadeIn = true, fadeIn = true,
requestBuilder = { requestBuilder = {
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
}, },
modifier = Modifier.width(30.dp).padding(4.dp).align(Alignment.CenterVertically), modifier = Modifier.width(30.dp).padding(4.dp)
.constrainAs(avatar) {
top.linkTo(tags.bottom)
start.linkTo(parent.start)
},
) )
Text( Text(
text = "submitted by ${post.submitterUser.username}", text = "submitted by ${post.submitterUser.username}",
modifier = Modifier.padding(bottom = 4.dp).align(Alignment.CenterVertically), 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,
) )
} }
} }
@ -112,40 +148,14 @@ fun LazyItemScope.LobstersItem(
@Composable @Composable
@Preview @Preview
fun PreviewLobstersItem() { fun Preview() {
val post = LobstersPost(
"zqyydb",
"https://lobste.rs/s/zqyydb",
"2020-09-21T07:11:14.000-05:00",
"k2k20 hackathon report: Bob Beck on LibreSSL progress",
"https://undeadly.org/cgi?action=article;sid=20200921105847",
4,
0,
0,
"",
"https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
Submitter(
"Vigdis",
"2017-02-27T21:08:14.000-06:00",
false,
"Alleycat for the fun, sys/net admin for a living and OpenBSD contributions for the pleasure. (Not so) French dude in Montreal\r\n\r\nhttps://chown.me",
false,
76,
"/avatars/Vigdis-100.png",
"sevan",
null,
null,
emptyList(),
),
listOf("openbsd"),
)
LobstersTheme { LobstersTheme {
LazyColumnFor(items = listOf(post)) { item -> LazyColumnFor(items = listOf(TEST_POST, TEST_POST, TEST_POST, TEST_POST, TEST_POST)) { item ->
LobstersItem( LobstersItem(
post = item, post = item,
linkOpenAction = {}, onClick = {},
commentOpenAction = {}, onLongClick = {},
saveAction = {}, onSaveButtonClick = {},
) )
} }
} }

View file

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