From 7427620464fd02b8739f221193b3a16c9605b308 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 18 Jan 2022 17:22:35 -0500 Subject: [PATCH] Adding site sidebar. --- README.md | 2 +- app/src/main/java/com/jerboa/MainActivity.kt | 13 +- app/src/main/java/com/jerboa/Utils.kt | 12 + .../java/com/jerboa/datatypes/SampleData.kt | 35 ++ .../com/jerboa/ui/components/home/Home.kt | 51 ++- .../jerboa/ui/components/home/HomeActivity.kt | 6 +- .../com/jerboa/ui/components/home/Sidebar.kt | 89 ++++ .../ui/components/home/SidebarActivity.kt | 40 ++ .../ui/components/inbox/InboxActivity.kt | 387 +++++++++++------- .../person/PersonProfileActivity.kt | 241 ++++++----- .../jerboa/ui/components/post/PostListing.kt | 1 - .../jerboa/ui/components/post/PostListings.kt | 4 +- 12 files changed, 610 insertions(+), 271 deletions(-) create mode 100644 app/src/main/java/com/jerboa/ui/components/home/Sidebar.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/home/SidebarActivity.kt diff --git a/README.md b/README.md index a1fbbdf4a..84e2ec01b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ _ | _ ---|--- ![img_1](https://i.imgur.com/W3lT4nO.jpg)|![img_2](https://i.imgur.com/MrAkd4d.jpg) -Jerboa is a native-android client for Lemmy, built using the newest. +Jerboa is a native-android client for Lemmy, built using the native Android Toolkit, Jetpack Compose. **Warning**: You can submit issues, but between Lemmy and lemmy-ui, I probably won't have too much time to work on them. Learn jetpack compose like I did if you want to help make this app better. diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index 945565f29..eb72aa7b0 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -21,10 +21,7 @@ import com.jerboa.ui.components.community.CommunityActivity import com.jerboa.ui.components.community.CommunityViewModel import com.jerboa.ui.components.community.list.CommunityListActivity import com.jerboa.ui.components.community.list.CommunityListViewModel -import com.jerboa.ui.components.home.HomeActivity -import com.jerboa.ui.components.home.HomeViewModel -import com.jerboa.ui.components.home.SiteViewModel -import com.jerboa.ui.components.home.SplashScreenActivity +import com.jerboa.ui.components.home.* import com.jerboa.ui.components.inbox.InboxActivity import com.jerboa.ui.components.login.LoginActivity import com.jerboa.ui.components.login.LoginViewModel @@ -194,6 +191,14 @@ class MainActivity : ComponentActivity() { navController = navController, ) } + composable( + route = "sidebar", + ) { + SidebarActivity( + siteViewModel = siteViewModel, + navController = navController, + ) + } composable( route = "commentEdit", ) { diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index e291b03a4..266bc62a3 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -62,6 +62,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.ocpsoft.prettytime.PrettyTime import java.net.URL +import java.text.DecimalFormat import java.util.* val prettyTime = PrettyTime(Locale.getDefault()) @@ -1050,3 +1051,14 @@ fun validateUrl( ) } } + +private val suffix = arrayOf("", "K", "M", "B", "T") + +fun siFormat(number: Int): String { + var r: String = DecimalFormat("##0E0").format(number) + r = r.replace("E[0-9]".toRegex(), suffix[Character.getNumericValue(r[r.length - 1]) / 3]) + while (r.length > 4 || r.matches(Regex("[0-9]+\\.[a-z]"))) { + r = r.substring(0, r.length - 2) + r.substring(r.length - 1) + } + return r +} diff --git a/app/src/main/java/com/jerboa/datatypes/SampleData.kt b/app/src/main/java/com/jerboa/datatypes/SampleData.kt index 5a96bb0b7..a575ff2d6 100644 --- a/app/src/main/java/com/jerboa/datatypes/SampleData.kt +++ b/app/src/main/java/com/jerboa/datatypes/SampleData.kt @@ -355,3 +355,38 @@ val samplePrivateMessageView = PrivateMessageView( creator = samplePersonSafe, recipient = samplePersonSafe2, ) + +val sampleSite = Site( + id = 23, + name = "Lemmy.ml", + sidebar = "# Hello!\n\n**This is** lemmy's sidebar", + description = "A general purpose instance for lemmy", + creator_id = 82, + published = "2022-01-07T04:12:26.398434", + updated = "2022-01-07T03:15:37.360888", + enable_downvotes = true, + open_registration = true, + enable_nsfw = true, + community_creation_admin_only = true, + icon = "https://lemmy.ml/pictrs/image/LqURxPzFNW.jpg", + banner = "https://lemmy.ml/pictrs/image/386rk5OYWS.jpg" +) + +val sampleSiteAggregates = SiteAggregates( + id = 23, + site_id = 84, + users = 8092, + posts = 888929, + comments = 9882, + communities = 89, + users_active_day = 21, + users_active_week = 82, + users_active_month = 208, + users_active_half_year = 689, +) + +val sampleSiteView = SiteView( + site = sampleSite, + creator = samplePersonSafe, + counts = sampleSiteAggregates, +) diff --git a/app/src/main/java/com/jerboa/ui/components/home/Home.kt b/app/src/main/java/com/jerboa/ui/components/home/Home.kt index ae5031bc7..45adfd147 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/Home.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/Home.kt @@ -147,13 +147,14 @@ fun DrawerItemsMain( onClick = { onClickListingType(ListingType.All) }, ) } - item { - IconAndTextDrawerItem( - text = "Saved", - icon = Icons.Default.Star, - onClick = onClickSaved, - ) - } + // TODO add saved +// item { +// IconAndTextDrawerItem( +// text = "Saved", +// icon = Icons.Default.Star, +// onClick = onClickSaved, +// ) +// } item { Divider() } @@ -440,11 +441,13 @@ fun HomeHeader( onClickListingType: (ListingType) -> Unit = {}, selectedSortType: SortType, selectedListingType: ListingType, + navController: NavController, ) { var showSortOptions by remember { mutableStateOf(false) } var showTopOptions by remember { mutableStateOf(false) } var showListingTypeOptions by remember { mutableStateOf(false) } + var showMoreOptions by remember { mutableStateOf(false) } if (showSortOptions) { SortOptionsDialog( @@ -483,6 +486,13 @@ fun HomeHeader( ) } + if (showMoreOptions) { + HomeMoreDialog( + onDismissRequest = { showMoreOptions = false }, + navController = navController, + ) + } + TopAppBar( title = { HomeHeaderTitle( @@ -523,6 +533,7 @@ fun HomeHeader( ) } IconButton(onClick = { + showMoreOptions = !showMoreOptions }) { Icon( Icons.Default.MoreVert, @@ -544,7 +555,8 @@ fun HomeHeaderPreview() { scope, scaffoldState, selectedSortType = SortType.Hot, - selectedListingType = ListingType.All + selectedListingType = ListingType.All, + navController = rememberNavController(), ) } @@ -636,3 +648,26 @@ fun BottomAppBarAll( fun BottomAppBarAllPreview() { BottomAppBarAll() } + +@Composable +fun HomeMoreDialog( + onDismissRequest: () -> Unit = {}, + navController: NavController, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + text = { + Column { + IconAndTextDrawerItem( + text = "View Sidebar", + icon = Icons.Default.Info, + onClick = { + navController.navigate("sidebar") + onDismissRequest() + }, + ) + } + }, + buttons = {}, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index cc407abc3..bb3b2e9f4 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -59,6 +59,7 @@ fun HomeActivity( homeViewModel = homeViewModel, account = account, ctx = ctx, + navController = navController, ) }, drawerShape = MaterialTheme.shapes.small, @@ -331,10 +332,13 @@ fun MainTopBar( homeViewModel: HomeViewModel, account: Account?, ctx: Context, + navController: NavController, ) { Column { HomeHeader( - scope, scaffoldState, + scope = scope, + scaffoldState = scaffoldState, + navController = navController, selectedSortType = homeViewModel.sortType.value, selectedListingType = homeViewModel.listingType.value, onClickSortType = { sortType -> diff --git a/app/src/main/java/com/jerboa/ui/components/home/Sidebar.kt b/app/src/main/java/com/jerboa/ui/components/home/Sidebar.kt new file mode 100644 index 000000000..25a80a383 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/Sidebar.kt @@ -0,0 +1,89 @@ +package com.jerboa.ui.components.home + +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.jerboa.DotSpacer +import com.jerboa.MyMarkdownText +import com.jerboa.datatypes.SiteView +import com.jerboa.datatypes.sampleSiteView +import com.jerboa.siFormat +import com.jerboa.ui.components.common.LargerCircularIcon +import com.jerboa.ui.components.common.PictrsBannerImage +import com.jerboa.ui.components.common.TimeAgo +import com.jerboa.ui.theme.MEDIUM_PADDING +import com.jerboa.ui.theme.Muted +import com.jerboa.ui.theme.PROFILE_BANNER_SIZE + +@Composable +fun Sidebar(siteView: SiteView) { + val site = siteView.site + Column( + modifier = Modifier.padding(MEDIUM_PADDING), + verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING) + ) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.BottomStart + ) { + site.banner?.also { + PictrsBannerImage( + url = it, modifier = Modifier.height(PROFILE_BANNER_SIZE) + ) + } + Box(modifier = Modifier.padding(MEDIUM_PADDING)) { + site.icon?.also { + LargerCircularIcon(icon = it) + } + } + } + site.description?.also { + Text( + text = it, + style = MaterialTheme.typography.subtitle1 + ) + } + TimeAgo( + precedingString = "Created", + includeAgo = true, + dateStr = site.published + ) + CommentsAndPosts(siteView = siteView) + site.sidebar?.also { + MyMarkdownText( + markdown = it, + color = Muted, + ) + } + } +} + +@Composable +fun CommentsAndPosts(siteView: SiteView) { + Row { + Text( + text = "${siFormat(siteView.counts.users_active_month)} users / month", + color = Muted, + ) + DotSpacer() + Text( + text = "${siFormat(siteView.counts.posts)} posts", + color = Muted, + ) + DotSpacer() + Text( + text = "${siFormat(siteView.counts.comments)} comments", + color = Muted, + ) + } +} + +@Preview +@Composable +fun SidebarPreview() { + Sidebar(siteView = sampleSiteView) +} diff --git a/app/src/main/java/com/jerboa/ui/components/home/SidebarActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/SidebarActivity.kt new file mode 100644 index 000000000..fea32540a --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/home/SidebarActivity.kt @@ -0,0 +1,40 @@ +package com.jerboa.ui.components.home + +import android.util.Log +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import androidx.navigation.NavController +import com.jerboa.SimpleTopAppBar + +@Composable +fun SidebarActivity( + siteViewModel: SiteViewModel, + navController: NavController, +) { + + Log.d("jerboa", "got to sidebar activity") + + val ctx = LocalContext.current + val scope = rememberCoroutineScope() + val title = "${siteViewModel.siteRes?.site_view?.site?.name} Sidebar" + + Surface(color = MaterialTheme.colors.background) { + Scaffold( + topBar = { + SimpleTopAppBar( + text = title, + navController = navController + ) + }, + content = { + siteViewModel.siteRes?.site_view?.also { + Sidebar(it) + } + } + ) + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt index 47625633d..8147b1932 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt @@ -7,11 +7,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -20,6 +19,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.jerboa.* import com.jerboa.db.Account import com.jerboa.db.AccountViewModel @@ -64,47 +65,42 @@ fun InboxActivity( Scaffold( scaffoldState = scaffoldState, topBar = { - Column { - InboxHeader( - unreadCount = unreadCount, - navController = navController, - selectedUnreadOrAll = unreadOrAllFromBool(inboxViewModel.unreadOnly.value), - onClickUnreadOrAll = { unreadOrAll -> - account?.also { - inboxViewModel.fetchReplies( - account = account, - clear = true, - changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, - ctx = ctx, - ) - inboxViewModel.fetchPersonMentions( - account = account, - clear = true, - changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, - ctx = ctx, - ) - inboxViewModel.fetchPrivateMessages( - account = account, - clear = true, - changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, - ctx = ctx, - ) - } - }, - onClickMarkAllAsRead = { - account?.also { acct -> - inboxViewModel.markAllAsRead( - account = acct, - ctx = ctx, - ) - homeViewModel.markAllAsRead() - } + InboxHeader( + unreadCount = unreadCount, + navController = navController, + selectedUnreadOrAll = unreadOrAllFromBool(inboxViewModel.unreadOnly.value), + onClickUnreadOrAll = { unreadOrAll -> + account?.also { acct -> + inboxViewModel.fetchReplies( + account = acct, + clear = true, + changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, + ctx = ctx, + ) + inboxViewModel.fetchPersonMentions( + account = acct, + clear = true, + changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, + ctx = ctx, + ) + inboxViewModel.fetchPrivateMessages( + account = acct, + clear = true, + changeUnreadOnly = unreadOrAll == UnreadOrAll.Unread, + ctx = ctx, + ) + } + }, + onClickMarkAllAsRead = { + account?.also { acct -> + inboxViewModel.markAllAsRead( + account = acct, + ctx = ctx, + ) + homeViewModel.markAllAsRead() } - ) - if (inboxViewModel.loading.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } - } + ) }, content = { InboxTabs( @@ -146,6 +142,7 @@ fun InboxActivity( enum class InboxTab { Replies, + // Mentions, Messages, } @@ -171,7 +168,7 @@ fun InboxTabs( Column { TabRow( selectedTabIndex = pagerState.currentPage, - indicator = { tabPositions -> // 3. + indicator = { tabPositions -> TabRowDefaults.Indicator( Modifier.pagerTabIndicatorOffset( pagerState, @@ -192,6 +189,9 @@ fun InboxTabs( ) } } + if (inboxViewModel.loading.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } HorizontalPager( count = tabTitles.size, state = pagerState, @@ -202,124 +202,104 @@ fun InboxTabs( InboxTab.Replies.ordinal -> { val nodes = sortNodes(commentsToFlatNodes(inboxViewModel.replies)) - LazyColumn { - items(nodes) { node -> - CommentNode( - node = node, - onUpvoteClick = { commentView -> - account?.also { acct -> - inboxViewModel.likeComment( - commentView = commentView, - voteType = VoteType.Upvote, - account = acct, - ctx = ctx, - ) - } - }, - onDownvoteClick = { commentView -> - account?.also { acct -> - inboxViewModel.likeComment( - commentView = commentView, - voteType = VoteType.Downvote, - account = acct, - ctx = ctx, - ) - } - }, - onReplyClick = { commentView -> - // TODO To do replies from elsewhere than postView, - // you need to refetch that post view - postViewModel.replyToCommentParent = commentView - postViewModel.fetchPost( - id = commentView.post.id, - account = account, - ctx = ctx, - ) - navController.navigate("commentReply") - }, - onEditCommentClick = { commentView -> - commentEditClickWrapper( - commentEditViewModel, - commentView, - navController, - ) - }, - onSaveClick = { commentView -> - account?.also { acct -> - inboxViewModel.saveComment( - commentView = commentView, - account = acct, - ctx = ctx, - ) - } - }, - onMarkAsReadClick = { commentView -> - account?.also { acct -> - inboxViewModel.markReplyAsRead( - commentView = commentView, - account = acct, - ctx = ctx, - ) - homeViewModel.updateUnreads(commentView) - } - }, - onPersonClick = { personId -> - personClickWrapper( - personProfileViewModel, - personId, - account, - navController, - ctx - ) - }, - onCommunityClick = { community -> - communityClickWrapper( - communityViewModel = communityViewModel, - communityId = community.id, - account = account, - navController = navController, - ctx = ctx, - ) - }, - onPostClick = { postId -> - postClickWrapper( - postViewModel = postViewModel, - postId = postId, - account = account, - navController = navController, - ctx = ctx, - ) - }, - showPostAndCommunityContext = true, - showRead = true, - account = account, - moderators = listOf() - ) + + val listState = rememberLazyListState() + val loading = inboxViewModel.loading.value && + inboxViewModel.page.value == 1 && + inboxViewModel.replies.isNotEmpty() + + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() } } - } -// InboxTab.Mentions.ordinal -> { -// // TODO Need to do a whole type of its own here -// } - InboxTab.Messages.ordinal -> { - account?.let { acct -> - LazyColumn { - items(inboxViewModel.messages) { message -> - PrivateMessage( - myPersonId = acct.id, - privateMessageView = message, - onReplyClick = { privateMessageView -> - inboxViewModel.replyToPrivateMessageView = privateMessageView - navController.navigate("privateMessageReply") + // act when end of list reached + if (endOfListReached) { + LaunchedEffect(endOfListReached) { + account?.also { acct -> + inboxViewModel.fetchReplies( + account = acct, + nextPage = true, + ctx = ctx, + ) + } + } + } + + SwipeRefresh( + state = rememberSwipeRefreshState(loading), + onRefresh = { + account?.also { acct -> + inboxViewModel.fetchReplies( + account = acct, + clear = true, + ctx = ctx, + ) + } + }, + ) { + LazyColumn(state = listState) { + items(nodes) { node -> + CommentNode( + node = node, + onUpvoteClick = { commentView -> + account?.also { acct -> + inboxViewModel.likeComment( + commentView = commentView, + voteType = VoteType.Upvote, + account = acct, + ctx = ctx, + ) + } + }, + onDownvoteClick = { commentView -> + account?.also { acct -> + inboxViewModel.likeComment( + commentView = commentView, + voteType = VoteType.Downvote, + account = acct, + ctx = ctx, + ) + } }, - onMarkAsReadClick = { privateMessageView -> - inboxViewModel.markPrivateMessageAsRead( - privateMessageView = privateMessageView, + onReplyClick = { commentView -> + // TODO To do replies from elsewhere than postView, + // you need to refetch that post view + postViewModel.replyToCommentParent = commentView + postViewModel.fetchPost( + id = commentView.post.id, account = account, ctx = ctx, ) - homeViewModel.updateUnreads(privateMessageView) + navController.navigate("commentReply") + }, + onEditCommentClick = { commentView -> + commentEditClickWrapper( + commentEditViewModel, + commentView, + navController, + ) + }, + onSaveClick = { commentView -> + account?.also { acct -> + inboxViewModel.saveComment( + commentView = commentView, + account = acct, + ctx = ctx, + ) + } + }, + onMarkAsReadClick = { commentView -> + account?.also { acct -> + inboxViewModel.markReplyAsRead( + commentView = commentView, + account = acct, + ctx = ctx, + ) + homeViewModel.updateUnreads(commentView) + } }, onPersonClick = { personId -> personClickWrapper( @@ -330,11 +310,110 @@ fun InboxTabs( ctx ) }, + onCommunityClick = { community -> + communityClickWrapper( + communityViewModel = communityViewModel, + communityId = community.id, + account = account, + navController = navController, + ctx = ctx, + ) + }, + onPostClick = { postId -> + postClickWrapper( + postViewModel = postViewModel, + postId = postId, + account = account, + navController = navController, + ctx = ctx, + ) + }, + showPostAndCommunityContext = true, + showRead = true, account = account, + moderators = listOf() + ) + } + } + } + } + +// InboxTab.Mentions.ordinal -> { +// // TODO Need to do a whole type of its own here +// } + InboxTab.Messages.ordinal -> { + + val listState = rememberLazyListState() + val loading = inboxViewModel.loading.value && + inboxViewModel.page.value == 1 && + inboxViewModel.messages.isNotEmpty() + + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + // act when end of list reached + if (endOfListReached) { + LaunchedEffect(endOfListReached) { + account?.also { acct -> + inboxViewModel.fetchPrivateMessages( + account = acct, + nextPage = true, + ctx = ctx, ) } } } + + SwipeRefresh( + state = rememberSwipeRefreshState(loading), + onRefresh = { + account?.also { acct -> + inboxViewModel.fetchPrivateMessages( + account = acct, + clear = true, + ctx = ctx, + ) + } + }, + ) { + LazyColumn(state = listState) { + items(inboxViewModel.messages) { message -> + account?.also { acct -> + PrivateMessage( + myPersonId = acct.id, + privateMessageView = message, + onReplyClick = { privateMessageView -> + inboxViewModel.replyToPrivateMessageView = + privateMessageView + navController.navigate("privateMessageReply") + }, + onMarkAsReadClick = { privateMessageView -> + inboxViewModel.markPrivateMessageAsRead( + privateMessageView = privateMessageView, + account = acct, + ctx = ctx, + ) + homeViewModel.updateUnreads(privateMessageView) + }, + onPersonClick = { personId -> + personClickWrapper( + personProfileViewModel, + personId, + account, + navController, + ctx + ) + }, + account = acct, + ) + } + } + } + } } } } diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt index 1421c0994..7b7061898 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt @@ -7,11 +7,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -20,6 +19,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.pagerTabIndicatorOffset import com.google.accompanist.pager.rememberPagerState +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.jerboa.* import com.jerboa.db.Account import com.jerboa.db.AccountViewModel @@ -65,27 +66,21 @@ fun PersonProfileActivity( Scaffold( scaffoldState = scaffoldState, topBar = { - Column { - personProfileViewModel.res?.person_view?.person?.name?.also { - PersonProfileHeader( - personName = it, - selectedSortType = personProfileViewModel.sortType.value, - onClickSortType = { sortType -> - personProfileViewModel.fetchPersonDetails( - id = personProfileViewModel.personId.value!!, - account = account, - clear = true, - changeSortType = sortType, - ctx = ctx, - ) - }, - navController = navController, - ) - } - - if (personProfileViewModel.loading.value) { - LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) - } + personProfileViewModel.res?.person_view?.person?.name?.also { + PersonProfileHeader( + personName = it, + selectedSortType = personProfileViewModel.sortType.value, + onClickSortType = { sortType -> + personProfileViewModel.fetchPersonDetails( + id = personProfileViewModel.personId.value!!, + account = account, + clear = true, + changeSortType = sortType, + ctx = ctx, + ) + }, + navController = navController, + ) } }, content = { @@ -150,7 +145,7 @@ fun UserTabs( Column { TabRow( selectedTabIndex = pagerState.currentPage, - indicator = { tabPositions -> // 3. + indicator = { tabPositions -> TabRowDefaults.Indicator( Modifier.pagerTabIndicatorOffset( pagerState, @@ -171,6 +166,9 @@ fun UserTabs( ) } } + if (personProfileViewModel.loading.value) { + LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) + } HorizontalPager( count = tabTitles.size, state = pagerState, @@ -276,88 +274,129 @@ fun UserTabs( } UserTab.Comments.ordinal -> { val nodes = sortNodes(commentsToFlatNodes(personProfileViewModel.comments)) - LazyColumn { - items(nodes) { node -> - CommentNode( - node = node, - onUpvoteClick = { commentView -> - account?.also { acct -> - personProfileViewModel.likeComment( - commentView = commentView, - voteType = VoteType.Upvote, - account = acct, + + val listState = rememberLazyListState() + val loading = personProfileViewModel.loading.value && + personProfileViewModel.page.value == 1 && + personProfileViewModel.comments.isNotEmpty() + + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + // act when end of list reached + if (endOfListReached) { + LaunchedEffect(endOfListReached) { + personProfileViewModel.personId.value?.also { + personProfileViewModel.fetchPersonDetails( + id = it, + account = account, + nextPage = true, + ctx = ctx, + ) + } + } + } + + SwipeRefresh( + state = rememberSwipeRefreshState(loading), + onRefresh = { + personProfileViewModel.personId.value?.also { + personProfileViewModel.fetchPersonDetails( + id = it, + account = account, + clear = true, + ctx = ctx, + ) + } + }, + ) { + LazyColumn(state = listState) { + items(nodes) { node -> + CommentNode( + node = node, + onUpvoteClick = { commentView -> + account?.also { acct -> + personProfileViewModel.likeComment( + commentView = commentView, + voteType = VoteType.Upvote, + account = acct, + ctx = ctx, + ) + } + }, + onDownvoteClick = { commentView -> + account?.also { acct -> + personProfileViewModel.likeComment( + commentView = commentView, + voteType = VoteType.Downvote, + account = acct, + ctx = ctx, + ) + } + }, + onReplyClick = { commentView -> + // To do replies from elsewhere than postView, + // you need to refetch that post view + postViewModel.replyToCommentParent = commentView + postViewModel.fetchPost( + id = commentView.post.id, + account = account, ctx = ctx, ) - } - }, - onDownvoteClick = { commentView -> - account?.also { acct -> - personProfileViewModel.likeComment( - commentView = commentView, - voteType = VoteType.Downvote, - account = acct, + navController.navigate("commentReply") + }, + onSaveClick = { commentView -> + account?.also { acct -> + personProfileViewModel.saveComment( + commentView = commentView, + account = acct, + ctx = ctx, + ) + } + }, + onPersonClick = { personId -> + personClickWrapper( + personProfileViewModel, + personId, + account, + navController, + ctx + ) + }, + onCommunityClick = { community -> + communityClickWrapper( + communityViewModel = communityViewModel, + communityId = community.id, + account = account, + navController = navController, ctx = ctx, ) - } - }, - onReplyClick = { commentView -> - // To do replies from elsewhere than postView, - // you need to refetch that post view - postViewModel.replyToCommentParent = commentView - postViewModel.fetchPost( - id = commentView.post.id, - account = account, - ctx = ctx, - ) - navController.navigate("commentReply") - }, - onSaveClick = { commentView -> - account?.also { acct -> - personProfileViewModel.saveComment( - commentView = commentView, - account = acct, + }, + onPostClick = { postId -> + postClickWrapper( + postViewModel = postViewModel, + postId = postId, + account = account, + navController = navController, ctx = ctx, ) - } - }, - onPersonClick = { personId -> - personClickWrapper( - personProfileViewModel, - personId, - account, - navController, - ctx - ) - }, - onCommunityClick = { community -> - communityClickWrapper( - communityViewModel = communityViewModel, - communityId = community.id, - account = account, - navController = navController, - ctx = ctx, - ) - }, - onPostClick = { postId -> - postClickWrapper( - postViewModel = postViewModel, - postId = postId, - account = account, - navController = navController, - ctx = ctx, - ) - }, - onEditCommentClick = { commentView -> - commentEditClickWrapper( - commentEditViewModel, - commentView, - navController, - ) - }, - showPostAndCommunityContext = true, - account = account, - moderators = listOf() - ) + }, + onEditCommentClick = { commentView -> + commentEditClickWrapper( + commentEditViewModel, + commentView, + navController, + ) + }, + showPostAndCommunityContext = true, + account = account, + moderators = listOf() + ) + } } } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt index 23c058910..153d5dcda 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt @@ -217,7 +217,6 @@ fun PostBody( markdown = text, modifier = Modifier .padding(MEDIUM_PADDING), - preview = !fullBody, ) } else { PreviewLines( diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 092e45c59..c00b0f0e9 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -36,7 +36,9 @@ fun PostListings( state = rememberSwipeRefreshState(loading), onRefresh = onSwipeRefresh, ) { - LazyColumn(state = listState) { + LazyColumn( + state = listState, + ) { // TODO this should be a .also? item {