Skip to content

Commit

Permalink
Avoid tree rerenders by hoisting scaffold
Browse files Browse the repository at this point in the history
  • Loading branch information
jocmp committed Dec 21, 2023
1 parent aafc83c commit 01be42b
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 79 deletions.
64 changes: 14 additions & 50 deletions app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleList.kt
Original file line number Diff line number Diff line change
@@ -1,81 +1,45 @@
package com.jocmp.basilreader.ui.articles

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
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.calculateListDetailPaneScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
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.db.Articles
import com.jocmp.basilreader.ui.components.EmptyView
import kotlinx.coroutines.launch


@ExperimentalMaterial3AdaptiveApi
@Composable
fun ArticleList(
pager: Pager<Int, Articles>,
article: Articles?,
onSelect: suspend (articleID: Long) -> Unit,
goBack: () -> Unit
) {
val composableScope = rememberCoroutineScope()
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
val (destination, setDestination) = rememberSaveable { mutableStateOf(ListDetailPaneScaffoldRole.List) }
val scaffoldState = calculateListDetailPaneScaffoldState(currentPaneDestination = destination)

val navigateToDetail = {
setDestination(ListDetailPaneScaffoldRole.Detail)
}

BackHandler(article != null) {
setDestination(ListDetailPaneScaffoldRole.List)
goBack()
}

ListDetailPaneScaffold(
scaffoldState = scaffoldState,
listPane = {
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)
navigateToDetail()
}
}
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)
}
) {
Text(item?.title ?: "No title", fontSize = 20.sp)
}
}
}
}
},
detailPane = {
if (article != null) {
ArticleView(article = article)
} else {
EmptyView(fullWidth = true)
) {
Text(item?.title ?: "No title", fontSize = 20.sp)
}
}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,78 @@
package com.jocmp.basilreader.ui.articles

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
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.ThreePaneScaffoldState
import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
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.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import com.jocmp.basilreader.ui.theme.BasilReaderTheme

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ArticleScaffold(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
list: @Composable () -> Unit,
content: @Composable () -> Unit,
listDetailState: ThreePaneScaffoldState = calculateListDetailPaneScaffoldState(
currentPaneDestination = ListDetailPaneScaffoldRole.List
),
drawerPane: @Composable () -> Unit,
listPane: @Composable () -> Unit,
detailPane: @Composable () -> Unit,
) {
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
list()
drawerPane()
}
}
) {
content()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
ListDetailPaneScaffold(
scaffoldState = listDetailState,
listPane = {
listPane()
},
detailPane = {
detailPane()
}
)
}
}
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Preview(device = Devices.FOLDABLE)
@Composable
fun ArticlesLayoutPreview() {
BasilReaderTheme {
ArticleScaffold(
list = {
drawerPane = {
Text("List here!")
},
listPane = {
Text("Index list here...")
},
detailPane = {
Text("Detail!")
}
) {
Text("Content here...")
}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.jocmp.basilreader.ui.articles

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.activity.compose.BackHandler
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.runtime.saveable.rememberSaveable
import com.jocmp.basilreader.ui.accounts.AccountViewModel
import com.jocmp.basilreader.ui.components.EmptyView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel

Expand All @@ -21,38 +24,49 @@ fun ArticleScreen(
) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
val (destination, setDestination) = rememberSaveable { mutableStateOf(ListDetailPaneScaffoldRole.List) }
val scaffoldState = calculateListDetailPaneScaffoldState(currentPaneDestination = destination)

val navigateToDetail = {
setDestination(ListDetailPaneScaffoldRole.Detail)
}

BackHandler(viewModel.article != null) {
setDestination(ListDetailPaneScaffoldRole.List)
viewModel.clearArticle()
}

ArticleScaffold(
drawerState = drawerState,
list = {
listDetailState = scaffoldState,
drawerPane = {
FeedList(
folders = viewModel.folders,
feeds = viewModel.feeds,
onFeedAdd = onFeedAdd,
onFeedSelect = {
viewModel.selectFeed(it) {
coroutineScope.launch {
delay(100)
drawerState.close()
}
}
}
)
}
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
},
listPane = {
viewModel.articles()?.let { pager ->
ArticleList(
pager = pager,
article = viewModel.article,
goBack = viewModel::clearArticle,
onSelect = {
viewModel.selectArticle(it)
navigateToDetail()
}
)
}
} ?: EmptyView(fillSize = true)
},
detailPane = {
ArticleView(article = viewModel.article)
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import com.jocmp.basilreader.ui.components.WebView
import com.jocmp.basilreader.ui.components.rememberWebViewStateWithHTMLData

@Composable
fun ArticleView(article: Articles) {
val state = rememberWebViewStateWithHTMLData(article.content_html ?: "<div>nothing</div>")
fun ArticleView(article: Articles?) {
val state = rememberWebViewStateWithHTMLData(article?.content_html ?: "<div />")

WebView(state)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.jocmp.basilreader.ui.components

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun EmptyView(fullWidth: Boolean = false) {
val modifier = if (fullWidth) {
Modifier.fillMaxWidth()
fun EmptyView(fillSize: Boolean = false) {
val modifier = if (fillSize) {
Modifier.fillMaxSize()
} else {
Modifier
}
Expand Down

0 comments on commit 01be42b

Please sign in to comment.