Skip to content
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

[Tags Feed] Implement no connection state #20842

Merged
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
Expand Up @@ -112,8 +112,8 @@ private fun FetchErrorContent() {
@Composable
private fun NetworkErrorContent() {
EmptyContent(
title = stringResource(R.string.blogging_prompts_list_error_network_title),
subtitle = stringResource(R.string.blogging_prompts_list_error_network_subtitle),
title = stringResource(R.string.no_connection_error_title),
subtitle = stringResource(R.string.no_connection_error_description),
image = R.drawable.img_illustration_cloud_off_152dp,
modifier = Modifier.fillMaxSize(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme
observeErrorMessageEvents()
observeSnackbarEvents()
observeOpenMoreMenuEvents()
viewModel.onViewCreated()
}

override fun onDestroy() {
Expand Down Expand Up @@ -282,29 +283,27 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme

private fun observeErrorMessageEvents() {
viewModel.errorMessageEvents.observeEvent(viewLifecycleOwner) { stringRes ->
activity?.findViewById<View?>(android.R.id.content)?.let { view ->
WPSnackbar.make(view, getString(stringRes), Snackbar.LENGTH_LONG).show()
if (isAdded) {
WPSnackbar.make(binding.root, getString(stringRes), Snackbar.LENGTH_LONG).show()
}
}
}

private fun observeSnackbarEvents() {
viewModel.snackbarEvents.observeEvent(viewLifecycleOwner) { snackbarMessageHolder ->
if (isAdded) {
activity?.findViewById<View>(R.id.coordinator)?.let { coordinator ->
with(snackbarMessageHolder) {
val snackbar = WPSnackbar.make(
coordinator,
uiHelpers.getTextOfUiString(requireContext(), message),
Snackbar.LENGTH_LONG
)
if (buttonTitle != null) {
snackbar.setAction(uiHelpers.getTextOfUiString(requireContext(), buttonTitle)) {
buttonAction.invoke()
}
with(snackbarMessageHolder) {
val snackbar = WPSnackbar.make(
binding.root,
uiHelpers.getTextOfUiString(requireContext(), message),
Snackbar.LENGTH_LONG
)
if (buttonTitle != null) {
snackbar.setAction(uiHelpers.getTextOfUiString(requireContext(), buttonTitle)) {
buttonAction.invoke()
}
snackbar.show()
}
snackbar.show()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
Expand All @@ -32,6 +33,7 @@ import org.wordpress.android.ui.reader.repository.usecases.PostLikeUseCase
import org.wordpress.android.ui.reader.tracker.ReaderTracker
import org.wordpress.android.ui.reader.views.compose.tagsfeed.TagsFeedPostItem
import org.wordpress.android.util.DisplayUtilsWrapper
import org.wordpress.android.util.NetworkUtilsWrapper
import org.wordpress.android.viewmodel.Event
import org.wordpress.android.viewmodel.ScopedViewModel
import org.wordpress.android.viewmodel.SingleLiveEvent
Expand All @@ -50,6 +52,7 @@ class ReaderTagsFeedViewModel @Inject constructor(
private val readerPostUiStateBuilder: ReaderPostUiStateBuilder,
private val displayUtilsWrapper: DisplayUtilsWrapper,
private val readerTracker: ReaderTracker,
private val networkUtilsWrapper: NetworkUtilsWrapper,
) : ScopedViewModel(bgDispatcher) {
private val _uiStateFlow: MutableStateFlow<UiState> = MutableStateFlow(UiState.Initial)
val uiStateFlow: StateFlow<UiState> = _uiStateFlow
Expand All @@ -75,6 +78,16 @@ class ReaderTagsFeedViewModel @Inject constructor(

private var hasInitialized = false

fun onViewCreated() {
if (!hasInitialized) {
hasInitialized = true
readerPostCardActionsHandler.initScope(viewModelScope)
initNavigationEvents()
initSnackbarEvents()
initUiState()
}
}

/**
* Fetch multiple tag posts in parallel. Each tag load causes a new state to be emitted, so multiple emissions of
* [uiStateFlow] are expected when calling this method for each tag, since each can go through the following
Expand All @@ -93,13 +106,6 @@ class ReaderTagsFeedViewModel @Inject constructor(
return
}

if (!hasInitialized) {
hasInitialized = true
readerPostCardActionsHandler.initScope(viewModelScope)
initNavigationEvents()
initSnackbarEvents()
}

// Add tags to the list with the posts loading UI
_uiStateFlow.update {
readerTagsFeedUiStateMapper.mapInitialPostsUiState(
Expand All @@ -125,6 +131,27 @@ class ReaderTagsFeedViewModel @Inject constructor(
}
}

private fun initUiState() {
_uiStateFlow.value = if (networkUtilsWrapper.isNetworkAvailable()) {
UiState.Loading
} else {
UiState.NoConnection(::onNoConnectionRetryClick)
}
}

private fun onNoConnectionRetryClick() {
_uiStateFlow.value = UiState.Loading
if (networkUtilsWrapper.isNetworkAvailable()) {
_actionEvents.value = ActionEvent.RefreshTags
} else {
// delay a bit before returning to NoConnection for a better feedback to the user
launch {
delay(NO_CONNECTION_DELAY)
_uiStateFlow.value = UiState.NoConnection(::onNoConnectionRetryClick)
}
}
}

/**
* Fetch posts for a single tag. This method will emit a new state to [uiStateFlow] for different [UiState]s:
* [UiState.Initial], [UiState.Loaded], [UiState.Loading], [UiState.Empty], but only for the tag being fetched.
Expand Down Expand Up @@ -210,13 +237,20 @@ class ReaderTagsFeedViewModel @Inject constructor(

@VisibleForTesting
fun onRefresh() {
if (!networkUtilsWrapper.isNetworkAvailable()) {
_errorMessageEvents.postValue(Event(R.string.no_network_message))
return
}

_uiStateFlow.update {
(it as? UiState.Loaded)?.copy(isRefreshing = true) ?: it
}
_actionEvents.value = ActionEvent.RefreshTags
}

fun onBackFromTagDetails() {
if (!networkUtilsWrapper.isNetworkAvailable()) return

_actionEvents.value = ActionEvent.RefreshTags
}

Expand Down Expand Up @@ -501,6 +535,8 @@ class ReaderTagsFeedViewModel @Inject constructor(
data object Loading : UiState()

data class Empty(val onOpenTagsListClick: () -> Unit) : UiState()

data class NoConnection(val onRetryClick: () -> Unit) : UiState()
}

data class TagFeedItem(
Expand Down Expand Up @@ -542,4 +578,8 @@ class ReaderTagsFeedViewModel @Inject constructor(
val readerCardUiState: ReaderCardUiState.ReaderPostNewUiState,
val readerPostCardActions: List<ReaderPostCardAction>,
)

companion object {
private const val NO_CONNECTION_DELAY = 500L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
Expand Down Expand Up @@ -78,6 +79,7 @@ fun ReaderTagsFeed(uiState: UiState) {
is UiState.Loading -> Loading()
is UiState.Loaded -> Loaded(uiState)
is UiState.Empty -> Empty(uiState)
is UiState.NoConnection -> NoConnection(uiState)
is UiState.Initial -> {
// no-op
}
Expand Down Expand Up @@ -134,7 +136,7 @@ private fun Loaded(uiState: UiState.Loaded) {
when (postList) {
is PostList.Initial, is PostList.Loading -> PostListLoading()
is PostList.Loaded -> PostListLoaded(postList, tagChip, backgroundColor)
is PostList.Error -> PostListError(backgroundColor, tagChip, postList)
is PostList.Error -> PostListError(postList, tagChip, backgroundColor)
}
Spacer(modifier = Modifier.height(Margin.ExtraExtraMediumLarge.value))
}
Expand Down Expand Up @@ -266,6 +268,28 @@ private fun Empty(uiState: UiState.Empty) {
}
}

@Composable
fun NoConnection(uiState: UiState.NoConnection) {
val backgroundColor = if (isSystemInDarkTheme()) {
AppColor.White.copy(alpha = 0.12F)
} else {
AppColor.Black.copy(alpha = 0.08F)
}

Box(modifier = Modifier.fillMaxSize()) {
ErrorMessage(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth(),
backgroundColor = backgroundColor,
titleText = stringResource(R.string.no_connection_error_title),
descriptionText = stringResource(R.string.no_connection_error_description),
actionText = stringResource(R.string.reader_tags_feed_error_retry),
onActionClick = uiState.onRetryClick,
)
}
}

@Composable
private fun PostListLoading() {
val loadingLabel = stringResource(id = R.string.loading)
Expand Down Expand Up @@ -369,47 +393,65 @@ private fun PostListLoaded(

@Composable
private fun PostListError(
backgroundColor: Color,
tagChip: TagChip,
postList: PostList.Error,
tagChip: TagChip,
backgroundColor: Color,
) {
Column(
val tagName = tagChip.tag.tagDisplayName
val errorMessage = when (postList.type) {
is ErrorType.Default -> stringResource(R.string.reader_tags_feed_loading_error_description)
is ErrorType.NoContent -> stringResource(R.string.reader_tags_feed_no_content_error_description, tagName)
}

ErrorMessage(
modifier = Modifier
.heightIn(min = ReaderTagsFeedComposeUtils.PostItemHeight)
.fillMaxWidth()
.fillMaxWidth(),
backgroundColor = backgroundColor,
titleText = stringResource(id = R.string.reader_tags_feed_error_title, tagName),
descriptionText = errorMessage,
actionText = stringResource(R.string.reader_tags_feed_error_retry),
onActionClick = { postList.onRetryClick(tagChip.tag) }
)
}

@Composable
private fun ErrorMessage(
backgroundColor: Color,
titleText: String,
descriptionText: String,
actionText: String,
modifier: Modifier = Modifier,
onActionClick: () -> Unit,
) {
Column(
modifier = modifier
.semantics(mergeDescendants = true) {}
.padding(start = 60.dp, end = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Spacer(modifier = Modifier.height(Margin.Medium.value))
Icon(
modifier = Modifier
.drawBehind {
drawCircle(
color = backgroundColor,
radius = this.size.maxDimension
)
},
.background(
color = backgroundColor,
shape = CircleShape
)
.padding(Margin.Medium.value),
painter = painterResource(R.drawable.ic_wifi_off_24px),
tint = MaterialTheme.colors.onSurface,
contentDescription = null
)
Spacer(modifier = Modifier.height(Margin.ExtraMediumLarge.value))
val tagName = tagChip.tag.tagDisplayName
Text(
text = stringResource(id = R.string.reader_tags_feed_error_title, tagName),
text = titleText,
style = androidx.compose.material3.MaterialTheme.typography.labelLarge,
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(Margin.Small.value))
val errorMessage = when (postList.type) {
is ErrorType.Default -> stringResource(R.string.reader_tags_feed_loading_error_description)
is ErrorType.NoContent -> stringResource(R.string.reader_tags_feed_no_content_error_description, tagName)
}
Text(
text = errorMessage,
text = descriptionText,
style = androidx.compose.material3.MaterialTheme.typography.bodySmall,
color = if (isSystemInDarkTheme()) {
AppColor.White.copy(alpha = 0.4F)
Expand All @@ -420,7 +462,7 @@ private fun PostListError(
)
Spacer(modifier = Modifier.height(Margin.Large.value))
Button(
onClick = { postList.onRetryClick(tagChip.tag) },
onClick = onActionClick,
modifier = Modifier
.height(36.dp)
.widthIn(min = 114.dp),
Expand All @@ -440,7 +482,7 @@ private fun PostListError(
style = androidx.compose.material3.MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colors.surface,
text = stringResource(R.string.reader_tags_feed_error_retry),
text = actionText,
)
}
}
Expand Down
4 changes: 2 additions & 2 deletions WordPress/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -534,14 +534,14 @@ Language: ar
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_notifications">ستنتقل ميزة التنبيهات إلى Jetpack</string>
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_reader">ستنتقل ميزة القارئ إلى تطبيق Jetpack</string>
<string name="wp_jetpack_feature_removal_overlay_switch_to_new_jetpack_app">التبديل إلى تطبيق Jetpack الجديد</string>
<string name="blogging_prompts_list_error_network_title">يتعذر تحميل هذا المحتوى حاليًا.</string>
<string name="no_connection_error_title">يتعذر تحميل هذا المحتوى حاليًا.</string>
<string name="blogging_prompts_list_error_fetch_subtitle">حدث خطأ في أثناء تحميل المطالبات.</string>
<string name="blogging_prompts_list_error_fetch_title">عذرًا</string>
<string name="blogging_prompts_list_no_prompts">لا توجد مطالبات بعد</string>
<string name="blogging_prompts_list_number_of_answers_other">%d من الإجابات</string>
<string name="blogging_prompts_list_number_of_answers_one">إجابة واحدة</string>
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_stats">ستنتقل ميزة الإحصاءات لديك إلى تطبيق Jetpack</string>
<string name="blogging_prompts_list_error_network_subtitle">تحقق من اتصالك بالشبكة وحاول مرة أخرى.</string>
<string name="no_connection_error_description">تحقق من اتصالك بالشبكة وحاول مرة أخرى.</string>
<string name="blogging_prompts_list_number_of_answers_zero">0 من الإجابات</string>
<string name="blogging_prompts_list_answered_prompt">✓ تم الرد</string>
<string name="blogging_prompts_list_title">المطالبات</string>
Expand Down
4 changes: 2 additions & 2 deletions WordPress/src/main/res/values-cs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ Language: cs_CZ
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_reader">Čtečka se přesouvá do aplikace Jetpack</string>
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_stats">Vaše statistiky se přesouvají do aplikace Jetpack</string>
<string name="wp_jetpack_feature_removal_overlay_switch_to_new_jetpack_app">Přepněte na novou aplikaci Jetpack</string>
<string name="blogging_prompts_list_error_network_subtitle">Zkontrolujte připojení k síti a zkuste to znovu.</string>
<string name="blogging_prompts_list_error_network_title">Momentálně tento obsah nelze načíst</string>
<string name="no_connection_error_description">Zkontrolujte připojení k síti a zkuste to znovu.</string>
<string name="no_connection_error_title">Momentálně tento obsah nelze načíst</string>
<string name="blogging_prompts_list_error_fetch_subtitle">Došlo k chybě při načítání to se mi líbí</string>
<string name="blogging_prompts_list_error_fetch_title">Jejda</string>
<string name="blogging_prompts_list_no_prompts">Zatím žádné výzvy</string>
Expand Down
4 changes: 2 additions & 2 deletions WordPress/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ Language: de
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_reader">Der Reader wird in die Jetpack-App verschoben</string>
<string name="wp_jetpack_feature_removal_overlay_phase_two_and_three_title_stats">Deine Statistiken werden in die Jetpack-App verschoben</string>
<string name="wp_jetpack_feature_removal_overlay_switch_to_new_jetpack_app">Zur neuen Jetpack-App wechseln</string>
<string name="blogging_prompts_list_error_network_subtitle">Prüfe deine Netzwerkverbindung und versuche es erneut.</string>
<string name="blogging_prompts_list_error_network_title">Dieser Inhalt kann gerade nicht geladen werden</string>
<string name="no_connection_error_description">Prüfe deine Netzwerkverbindung und versuche es erneut.</string>
<string name="no_connection_error_title">Dieser Inhalt kann gerade nicht geladen werden</string>
<string name="blogging_prompts_list_error_fetch_subtitle">Beim Laden der Schreibanregungen ist ein Fehler aufgetreten.</string>
<string name="blogging_prompts_list_error_fetch_title">Ups</string>
<string name="blogging_prompts_list_no_prompts">Noch keine Schreibanregungen</string>
Expand Down
4 changes: 2 additions & 2 deletions WordPress/src/main/res/values-en-rCA/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ Language: en_CA
<string name="jetpack_feature_card_content_phase_three">Stats, Reader, Notifications and other features will soon move to the Jetpack mobile app.</string>
<string name="blogging_prompts_list_error_fetch_subtitle">There was an error loading prompts.</string>
<string name="blogging_prompts_list_error_fetch_title">Oops</string>
<string name="blogging_prompts_list_error_network_subtitle">Check your network connection and try again.</string>
<string name="blogging_prompts_list_error_network_title">Unable to load this content right now</string>
<string name="no_connection_error_description">Check your network connection and try again.</string>
<string name="no_connection_error_title">Unable to load this content right now</string>
<string name="blogging_prompts_list_no_prompts">No prompts yet</string>
<string name="blogging_prompts_list_number_of_answers_one">1 answer</string>
<string name="blogging_prompts_list_number_of_answers_other">%d answers</string>
Expand Down
Loading
Loading