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

fix: crash when loading what's new section [WPB-9810] #3130

Merged
merged 4 commits into from
Jun 27, 2024
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 @@ -38,6 +38,7 @@ import com.wire.android.navigation.ExternalUriDirection
import com.wire.android.navigation.WelcomeToNewAndroidAppDestination
import com.wire.android.ui.common.RowItemTemplate
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.shimmerPlaceholder
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
Expand All @@ -51,6 +52,7 @@ fun WhatsNewItem(
text: String? = null,
@DrawableRes trailingIcon: Int? = null,
onRowPressed: Clickable = Clickable(false),
isLoading: Boolean = false,
) {
RowItemTemplate(
title = {
Expand All @@ -59,7 +61,9 @@ fun WhatsNewItem(
style = if (boldTitle) MaterialTheme.wireTypography.body02 else MaterialTheme.wireTypography.body01,
color = MaterialTheme.wireColorScheme.onBackground,
text = title,
modifier = Modifier.padding(start = dimensions().spacing8x)
modifier = Modifier
.padding(start = dimensions().spacing8x)
.shimmerPlaceholder(visible = isLoading)
)
}
},
Expand All @@ -69,7 +73,9 @@ fun WhatsNewItem(
style = MaterialTheme.wireTypography.label04,
color = MaterialTheme.wireColorScheme.secondaryText,
text = text,
modifier = Modifier.padding(start = dimensions().spacing8x, top = dimensions().spacing8x)
modifier = Modifier
.padding(start = dimensions().spacing8x, top = dimensions().spacing8x)
.shimmerPlaceholder(visible = isLoading)
)
}
},
Expand All @@ -82,6 +88,7 @@ fun WhatsNewItem(
modifier = Modifier
.defaultMinSize(dimensions().wireIconButtonSize)
.padding(end = dimensions().spacing8x)
.shimmerPlaceholder(visible = isLoading)
)
} ?: Icons.Filled.ChevronRight
},
Expand Down Expand Up @@ -129,7 +136,7 @@ sealed class WhatsNewItem(

@PreviewMultipleThemes
@Composable
fun previewFileRestrictionDialog() {
fun PreviewFileRestrictionDialog() {
WireTheme {
WhatsNewItem(
title = "What's new item",
Expand All @@ -138,3 +145,16 @@ fun previewFileRestrictionDialog() {
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewFileRestrictionDialogLoading() {
WireTheme {
WhatsNewItem(
title = "What's new item",
text = "This is the text of the item",
trailingIcon = R.drawable.ic_arrow_right,
isLoading = true,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,51 @@ fun WhatsNewScreenContent(
items = buildList {
add(WhatsNewItem.WelcomeToNewAndroidApp)
},
onItemClicked = onItemClicked
onItemClicked = onItemClicked,
isLoading = false,
)

folderWithElements(
header = context.getString(R.string.whats_new_release_notes_group_title),
items = buildList {
state.releaseNotesItems.forEach {
add(
WhatsNewItem.AndroidReleaseNotes(
id = it.id,
title = UIText.DynamicString(it.title),
boldTitle = true,
text = UIText.DynamicString(it.publishDate),
url = it.link,
if (state.isLoading) {
for (i in 0..3) {
add(
WhatsNewItem.AndroidReleaseNotes(
id = "placeholder_$i",
title = UIText.DynamicString("Android X.X.X"), // this text won't be displayed
boldTitle = true,
text = UIText.DynamicString("01 Jan 2024"), // this text won't be displayed
url = "",
)
)
)
}
} else {
state.releaseNotesItems.forEach {
add(
WhatsNewItem.AndroidReleaseNotes(
id = it.id,
title = UIText.DynamicString(it.title),
boldTitle = true,
text = UIText.DynamicString(it.publishDate),
url = it.link,
)
)
}
}
add(WhatsNewItem.AllAndroidReleaseNotes)
},
onItemClicked = onItemClicked
onItemClicked = onItemClicked,
isLoading = state.isLoading,
)
}
}

private fun LazyListScope.folderWithElements(
header: String? = null,
items: List<WhatsNewItem>,
onItemClicked: (WhatsNewItem) -> Unit
onItemClicked: (WhatsNewItem) -> Unit,
isLoading: Boolean,
) {
folderWithElements(
header = header?.uppercase(),
Expand All @@ -114,14 +131,21 @@ private fun LazyListScope.folderWithElements(
title = item.title.asString(),
boldTitle = item.boldTitle,
text = item.text?.asString(),
onRowPressed = remember { Clickable(enabled = true) { onItemClicked(item) } },
onRowPressed = remember { Clickable(enabled = !isLoading) { onItemClicked(item) } },
trailingIcon = R.drawable.ic_arrow_right,
isLoading = isLoading,
)
}
}

@Preview(showBackground = false)
@Composable
fun PreviewWhatsNewScreen() {
WhatsNewScreenContent(WhatsNewState()) {}
WhatsNewScreenContent(WhatsNewState(isLoading = false)) {}
}

@Preview(showBackground = false)
@Composable
fun PreviewWhatsNewScreenLoading() {
WhatsNewScreenContent(WhatsNewState(isLoading = true)) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.wire.android.ui.home.whatsnew

data class WhatsNewState(
val isLoading: Boolean = false,
val releaseNotesItems: List<ReleaseNotesItem> = emptyList()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,55 @@
*/
package com.wire.android.ui.home.whatsnew

import android.icu.text.SimpleDateFormat
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.prof18.rssparser.RssParser
import com.wire.android.BuildConfig
import com.wire.android.util.sha256
import com.wire.android.R
import com.wire.android.util.toMediumOnlyDateTime
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject

@HiltViewModel
class WhatsNewViewModel @Inject constructor() : ViewModel() {
class WhatsNewViewModel @Inject constructor(context: Context) : ViewModel() {
private val rssParser = RssParser()
private val publishDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH)

var state by mutableStateOf(WhatsNewState())
var state by mutableStateOf(WhatsNewState(isLoading = true))
private set

init {
viewModelScope.launch {
if (BuildConfig.URL_RSS_RELEASE_NOTES.isNotBlank()) {
rssParser.getRssChannel(BuildConfig.URL_RSS_RELEASE_NOTES).let {
val feedUrl = context.resources.getString(R.string.url_android_release_notes_feed)
if (feedUrl.isNotBlank()) {
rssParser.getRssChannel(feedUrl).let {
state = state.copy(
isLoading = false,
releaseNotesItems = it.items
.map { item ->
ReleaseNotesItem(
id = item.guid.orEmpty().sha256(),
id = item.guid.orEmpty(),
title = item.title.orEmpty(),
link = item.link.orEmpty(),
publishDate = item.pubDate?.let { publishDateFormat.parse(it).toMediumOnlyDateTime() }.orEmpty(),
publishDate = item.pubDate?.let { publishDateFormat.parse(it)?.toMediumOnlyDateTime() }.orEmpty(),
)
}
.filter {
it.title.isNotBlank() && it.link.isNotBlank() && it.publishDate.isNotBlank()
}
)
}
} else {
state = state.copy(
isLoading = false,
releaseNotesItems = emptyList()
)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
<string name="url_federation_support" translatable="false">https://support.wire.com/hc/categories/4719917054365-Federation</string>
<string name="url_create_account_learn_more" translatable="false">https://support.wire.com/hc/articles/115004082129</string>
<string name="url_android_release_notes" translatable="false">https://medium.com/wire-news/android-updates/home</string>
<string name="url_android_release_notes_feed" translatable="false">https://medium.com/feed/wire-news/tagged/android</string>
<string name="url_maps_location_coordinates_fallback" translatable="false">http://maps.google.com/maps?z=%1d&amp;q=loc:%2f+%2f</string>
<!-- Navigation -->
<string name="vault_screen_title">Vault</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.whatsnew

import android.content.Context
import com.prof18.rssparser.RssParser
import com.prof18.rssparser.model.RssChannel
import com.prof18.rssparser.model.RssItem
import com.wire.android.R
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.util.toMediumOnlyDateTime
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import io.mockk.mockkStatic
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.text.SimpleDateFormat
import java.util.Date

@ExtendWith(CoroutineTestExtension::class)
class WhatsNewViewModelTest {

@Test
fun `given url is not blank, when fetching release notes, then execute getRssChannel`() = runTest {
val url = "url"
val (arrangement, viewModel) = Arrangement()
.withFeedResult(testRssChannel)
.withFeedUrl(url)
.arrange()

advanceUntilIdle()

assertEquals(testReleaseNotes, viewModel.state.releaseNotesItems)
assertEquals(false, viewModel.state.isLoading)
coVerify(exactly = 1) {
arrangement.rssParser.getRssChannel(url)
}
}

@Test
fun `given url is blank, when fetching release notes, then do not execute getRssChannel`() = runTest {
val url = ""
val (arrangement, viewModel) = Arrangement()
.withFeedUrl(url)
.arrange()

advanceUntilIdle()

assertEquals(emptyList(), viewModel.state.releaseNotesItems)
assertEquals(false, viewModel.state.isLoading)
coVerify(exactly = 0) {
arrangement.rssParser.getRssChannel(url)
}
}

inner class Arrangement {

@MockK
lateinit var context: Context

@MockK
lateinit var rssParser: RssParser

val viewModel by lazy {
WhatsNewViewModel(context)
}

fun withFeedUrl(feedUrl: String) = apply {
coEvery { context.resources.getString(R.string.url_android_release_notes_feed) } returns feedUrl
}

fun withFeedResult(rssChannel: RssChannel) = apply {
coEvery { rssParser.getRssChannel(any()) } returns rssChannel
}

init {
MockKAnnotations.init(this, relaxUnitFun = true)
mockkStatic(::RssParser)
coEvery { RssParser() } returns rssParser
mockkStatic(Date::toMediumOnlyDateTime)
coEvery { any<Date>().toMediumOnlyDateTime() } answers {
SimpleDateFormat("dd MMM yyyy").format(firstArg())
}
}

fun arrange() = this to viewModel
}

private val testRssItem = RssItem(
guid = "guid",
title = "itemTitle",
author = "author",
link = "link",
pubDate = "Mon, 01 Jan 2024 00:00:00",
description = null,
content = null,
image = null,
audio = null,
video = null,
sourceName = null,
sourceUrl = null,
categories = emptyList(),
itunesItemData = null,
commentsUrl = null,
)
private val testRssChannel: RssChannel = RssChannel(
title = "title",
items = listOf(testRssItem),
link = null,
description = null,
image = null,
lastBuildDate = null,
updatePeriod = null,
itunesChannelData = null,
)
private val testReleaseNoteItem = ReleaseNotesItem(
id = "guid",
title = "itemTitle",
link = "link",
publishDate = "01 Jan 2024",
)
private val testReleaseNotes: List<ReleaseNotesItem> = listOf(testReleaseNoteItem)
}
1 change: 0 additions & 1 deletion default.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
]
},
"is_password_protected_guest_link_enabled": false,
"url_rss_release_notes": "https://medium.com/feed/wire-news/tagged/android",
"team_app_lock": false,
"team_app_lock_timeout": 60,
"max_remote_search_result_count": 30,
Expand Down
Loading