Skip to content

Subscriber detail navigation, take two #21969

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.wordpress.android.ui.dataview

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
Expand All @@ -20,19 +19,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import org.wordpress.android.R
import org.wordpress.android.ui.compose.theme.AppThemeM3
import org.wordpress.android.ui.dataview.DummyDataViewItems.getDummyDataViewItems
import org.wordpress.android.ui.dataview.compose.RemoteImage

/**
* Provides a card for displaying a single [DataViewItem] which contains a primary image,
Expand Down Expand Up @@ -61,6 +57,11 @@ fun DataViewItemCard(
RemoteImage(
imageUrl = image.imageUrl,
fallbackImageRes = image.fallbackImageRes,
modifier = Modifier
.padding(end = 16.dp)
.size(dimensionResource(R.dimen.jp_migration_user_avatar_size))
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
)
Spacer(modifier = Modifier.width(8.dp))
}
Expand Down Expand Up @@ -135,34 +136,6 @@ private fun maxLinesFor(type: DataViewFieldType) = when (type) {
DataViewFieldType.EMAIL -> 1
}

@Composable
private fun RemoteImage(
imageUrl: String?,
fallbackImageRes: Int,
) {
val modifier = Modifier
.padding(end = 16.dp)
.size(dimensionResource(R.dimen.jp_migration_user_avatar_size))
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
if (imageUrl.isNullOrBlank()) {
Image(
painter = painterResource(id = fallbackImageRes),
contentDescription = null,
modifier = modifier
)
} else {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.error(fallbackImageRes)
.crossfade(true)
.build(),
contentDescription = null,
modifier = modifier
)
}
}

@Preview(showBackground = true)
@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
Expand All @@ -26,9 +24,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
Expand All @@ -47,64 +43,73 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.wordpress.android.R
import org.wordpress.android.ui.compose.components.EmptyContentM3
import org.wordpress.android.ui.compose.theme.AppThemeM3
import org.wordpress.android.ui.dataview.DummyDataViewItems.getDummyDataViewItems

/**
* Provides a basic screen for displaying a list of [DataViewItem]s
* which includes search and filter functionality.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DataViewScreen(
title: String,
uiState: State<DataViewUiState>,
items: State<List<DataViewItem>>,
supportedFilters: List<DataViewItemFilter>,
currentFilter: DataViewItemFilter?,
onSearchQueryChange: (String) -> Unit,
onItemClick: (DataViewItem) -> Unit,
onFilterClick: (DataViewItemFilter) -> Unit,
onBackClick: () -> Unit,
onRefresh: () -> Unit,
onFetchMore: () -> Unit,
modifier: Modifier = Modifier,
errorMessage: String? = null,
) {
Screen(
title = title,
onBackClick = onBackClick,
val refreshState = remember { mutableStateOf(false) }
val pullToRefreshState = rememberPullToRefreshState()

PullToRefreshBox(
modifier = modifier
.fillMaxSize(),
isRefreshing = refreshState.value,
state = pullToRefreshState,
onRefresh = onRefresh,
content = {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
SearchAndFilterBar(
onSearchQueryChange = onSearchQueryChange,
onFilterClick = onFilterClick,
supportedFilters = supportedFilters,
currentFilter = currentFilter
)
indicator = {
PullToRefreshDefaults.Indicator(
state = pullToRefreshState,
isRefreshing = refreshState.value,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.align(Alignment.TopCenter),
)
}
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
SearchAndFilterBar(
onSearchQueryChange = onSearchQueryChange,
onFilterClick = onFilterClick,
supportedFilters = supportedFilters,
currentFilter = currentFilter
)

when (uiState.value) {
DataViewUiState.LOADING -> LoadingDataView()
DataViewUiState.EMPTY -> EmptyDataView()
DataViewUiState.EMPTY_SEARCH -> EmptySearchDataView()
DataViewUiState.ERROR -> ErrorDataView(errorMessage)
DataViewUiState.OFFLINE -> OfflineDataView()
DataViewUiState.LOADING_MORE,
DataViewUiState.LOADED -> LoadedDataView(
items = items,
onItemClick = onItemClick,
onFetchMore = onFetchMore,
showProgress = uiState.value == DataViewUiState.LOADING_MORE
)
}
when (uiState.value) {
DataViewUiState.LOADING -> LoadingDataView()
DataViewUiState.EMPTY -> EmptyDataView()
DataViewUiState.EMPTY_SEARCH -> EmptySearchDataView()
DataViewUiState.ERROR -> ErrorDataView(errorMessage)
DataViewUiState.OFFLINE -> OfflineDataView()
DataViewUiState.LOADING_MORE,
DataViewUiState.LOADED -> LoadedDataView(
items = items,
onItemClick = onItemClick,
onFetchMore = onFetchMore,
showProgress = uiState.value == DataViewUiState.LOADING_MORE
)
}
}

)
}
}

@Composable
Expand Down Expand Up @@ -230,7 +235,8 @@ private fun LoadedDataView(
}
if (showProgress) {
CircularProgressIndicator(
modifier = Modifier.size(48.dp)
modifier = Modifier
.size(48.dp)
.align(Alignment.Center)
)
}
Expand Down Expand Up @@ -312,63 +318,11 @@ private fun OfflineDataView() {
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Screen(
title: String,
content: @Composable () -> Unit,
onRefresh: () -> Unit,
onBackClick: () -> Unit
) {
AppThemeM3 {
Scaffold(
topBar = {
TopAppBar(
title = { Text(title) },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back))
}
},
)
},
) { contentPadding ->
val refreshState = remember { mutableStateOf(false) }
val pullToRefreshState = rememberPullToRefreshState()

PullToRefreshBox(
modifier = Modifier
.fillMaxSize(),
isRefreshing = refreshState.value,
state = pullToRefreshState,
onRefresh = onRefresh,
indicator = {
PullToRefreshDefaults.Indicator(
state = pullToRefreshState,
isRefreshing = refreshState.value,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.align(Alignment.TopCenter),
)
}
) {
Column(
modifier = Modifier
.imePadding()
.padding(contentPadding)
) {
content()
}
}
}
}
}

@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun LoadedPreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.LOADED) },
items = remember { mutableStateOf(getDummyDataViewItems()) },
supportedFilters = emptyList(),
Expand All @@ -378,7 +332,6 @@ private fun LoadedPreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}

Expand All @@ -387,7 +340,6 @@ private fun LoadedPreview() {
@Composable
private fun LoadingPreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.LOADING) },
items = remember { mutableStateOf(emptyList()) },
supportedFilters = emptyList(),
Expand All @@ -397,7 +349,6 @@ private fun LoadingPreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}

Expand All @@ -406,7 +357,6 @@ private fun LoadingPreview() {
@Composable
private fun EmptyPreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.EMPTY) },
items = remember { mutableStateOf(emptyList()) },
supportedFilters = emptyList(),
Expand All @@ -416,7 +366,6 @@ private fun EmptyPreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}

Expand All @@ -425,7 +374,6 @@ private fun EmptyPreview() {
@Composable
private fun EmptySearchPreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.EMPTY_SEARCH) },
items = remember { mutableStateOf(emptyList()) },
supportedFilters = emptyList(),
Expand All @@ -435,7 +383,6 @@ private fun EmptySearchPreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}

Expand All @@ -444,7 +391,6 @@ private fun EmptySearchPreview() {
@Composable
private fun OfflinePreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.OFFLINE) },
items = remember { mutableStateOf(emptyList()) },
supportedFilters = emptyList(),
Expand All @@ -454,7 +400,6 @@ private fun OfflinePreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}

Expand All @@ -463,7 +408,6 @@ private fun OfflinePreview() {
@Composable
private fun ErrorPreview() {
DataViewScreen(
title = "Title",
uiState = remember { mutableStateOf(DataViewUiState.ERROR) },
items = remember { mutableStateOf(emptyList()) },
supportedFilters = emptyList(),
Expand All @@ -473,6 +417,5 @@ private fun ErrorPreview() {
onSearchQueryChange = { },
onItemClick = {},
onFilterClick = { },
onBackClick = { },
)
}
Loading