Merge remote-tracking branch 'goog/github/main'

Change-Id: Ie643c6ba30d558d6c6519ef12102160411836372
pull/146/head
Jolanda Verhoef 2 years ago
commit 4b5587340f

@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1
@ -46,27 +46,37 @@ jobs:
- name: Check lint - name: Check lint
run: ./gradlew lintDebug --stacktrace run: ./gradlew lintDebug --stacktrace
- name: Build debug - name: Build all build type and flavor permutations
run: ./gradlew assembleDebug --stacktrace run: ./gradlew assemble --stacktrace
- name: Build release
run: ./gradlew assembleRelease --stacktrace
- name: Run local tests - name: Run local tests
run: ./gradlew testDebug --stacktrace run: ./gradlew testDemoDebug testProdDebug --stacktrace
- name: Upload Demo build outputs (APKs)
uses: actions/upload-artifact@v2
with:
name: build-outputs-demo
path: app/demo/build/outputs
- name: Upload build outputs (APKs) - name: Upload Prod build outputs (APKs)
uses: actions/upload-artifact@v2
with:
name: build-outputs-prod
path: app/prod/build/outputs
- name: Upload Demo build reports
if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: build-outputs name: build-reports-demo
path: app/build/outputs path: app/demo/build/reports
- name: Upload build reports - name: Upload Prod build reports
if: always() if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: build-reports name: build-reports-prod
path: app/build/reports path: app/prod/build/reports
androidTest: androidTest:
needs: build needs: build
@ -78,7 +88,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
@ -107,7 +117,7 @@ jobs:
disable-animations: true disable-animations: true
disk-size: 1500M disk-size: 1500M
heap-size: 512M heap-size: 512M
script: ./gradlew connectedAndroidTest -x :benchmark:connectedBenchmarkAndroidTest script: ./gradlew connectedProdDebugAndroidTest -x :benchmark:connectedBenchmarkAndroidTest
- name: Upload test reports - name: Upload test reports
if: always() if: always()

@ -12,7 +12,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1

@ -16,6 +16,8 @@
package com.google.samples.apps.nowinandroid.core.model.data package com.google.samples.apps.nowinandroid.core.model.data
/* ktlint-disable max-line-length */
/** /**
* External data layer representation of an NiA Author * External data layer representation of an NiA Author
*/ */
@ -27,3 +29,22 @@ data class Author(
val mediumPage: String, val mediumPage: String,
val bio: String, val bio: String,
) )
val previewAuthors = listOf(
Author(
id = "22",
name = "Alex Vanyo",
mediumPage = "https://medium.com/@alexvanyo",
twitter = "https://twitter.com/alex_vanyo",
imageUrl = "https://pbs.twimg.com/profile_images/1431339735931305989/nOE2mmi2_400x400.jpg",
bio = "Alex joined Android DevRel in 2021, and has worked supporting form factors from small watches to large foldables and tablets. His special interests include insets, Compose, testing and state."
),
Author(
id = "3",
name = "Simona Stojanovic",
mediumPage = "https://medium.com/@anomisSi",
twitter = "https://twitter.com/anomisSi",
imageUrl = "https://pbs.twimg.com/profile_images/1437506849016778756/pG0NZALw_400x400.jpg",
bio = "Android Developer Relations Engineer @Google, working on the Compose team and taking care of Layouts & Navigation."
)
)

@ -16,7 +16,13 @@
package com.google.samples.apps.nowinandroid.core.model.data package com.google.samples.apps.nowinandroid.core.model.data
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Codelab
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
/* ktlint-disable max-line-length */
/** /**
* External data layer representation of a fully populated NiA news resource * External data layer representation of a fully populated NiA news resource
@ -33,3 +39,26 @@ data class NewsResource(
val authors: List<Author>, val authors: List<Author>,
val topics: List<Topic> val topics: List<Topic>
) )
val previewNewsResources = listOf(
NewsResource(
id = "1",
episodeId = "60",
title = "Android Basics with Compose",
content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. Youll learn the fundamentals of programming in Kotlin while building Android apps using Jetpack Compose, Androids modern toolkit that simplifies and accelerates native UI development. These two units are just the beginning; more will be coming soon. Check out Android Basics with Compose to get started on your Android development journey",
url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html",
headerImageUrl = "https://developer.android.com/images/hero-assets/android-basics-compose.svg",
authors = listOf(previewAuthors[0]),
publishDate = LocalDateTime(
year = 2022,
monthNumber = 5,
dayOfMonth = 4,
hour = 23,
minute = 0,
second = 0,
nanosecond = 0
).toInstant(TimeZone.UTC),
type = Codelab,
topics = listOf(previewTopics[1])
)
)

@ -16,6 +16,8 @@
package com.google.samples.apps.nowinandroid.core.model.data package com.google.samples.apps.nowinandroid.core.model.data
/* ktlint-disable max-line-length */
/** /**
* External data layer representation of a NiA Topic * External data layer representation of a NiA Topic
*/ */
@ -27,3 +29,30 @@ data class Topic(
val url: String, val url: String,
val imageUrl: String, val imageUrl: String,
) )
val previewTopics = listOf(
Topic(
id = "2",
name = "Headlines",
shortDescription = "News we want everyone to see",
longDescription = "Stay up to date with the latest events and announcements from Android!",
imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Headlines.svg?alt=media&token=506faab0-617a-4668-9e63-4a2fb996603f",
url = ""
),
Topic(
id = "3",
name = "UI",
shortDescription = "Material Design, Navigation, Text, Paging, Accessibility (a11y), Internationalization (i18n), Localization (l10n), Animations, Large Screens, Widgets",
longDescription = "Learn how to optimize your app's user interface - everything that users can see and interact with. Stay up to date on tocpis such as Material Design, Navigation, Text, Paging, Compose, Accessibility (a11y), Internationalization (i18n), Localization (l10n), Animations, Large Screens, Widgets, and many more!",
imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_UI.svg?alt=media&token=0ee1842b-12e8-435f-87ba-a5bb02c47594",
url = ""
),
Topic(
id = "4",
name = "Testing",
shortDescription = "CI, Espresso, TestLab, etc",
longDescription = "Testing is an integral part of the app development process. By running tests against your app consistently, you can verify your app's correctness, functional behavior, and usability before you release it publicly. Stay up to date on the latest tricks in CI, Espresso, and Firebase TestLab.",
imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Testing.svg?alt=media&token=a11533c4-7cc8-4b11-91a3-806158ebf428",
url = ""
),
)

@ -35,5 +35,5 @@ object NewsResourceTypeSerializer : KSerializer<NewsResourceType> {
) )
override fun serialize(encoder: Encoder, value: NewsResourceType) = override fun serialize(encoder: Encoder, value: NewsResourceType) =
encoder.encodeString(value.name) encoder.encodeString(value.serializedName)
} }

@ -94,4 +94,13 @@ class NewsResourceTypeSerializerTest {
Json.decodeFromString(NewsResourceTypeSerializer, """"umm"""") Json.decodeFromString(NewsResourceTypeSerializer, """"umm"""")
) )
} }
@Test
fun test_serialize_and_deserialize() {
val json = Json.encodeToString(NewsResourceTypeSerializer, NewsResourceType.Video)
assertEquals(
NewsResourceType.Video,
Json.decodeFromString(NewsResourceTypeSerializer, json)
)
}
} }

@ -156,8 +156,14 @@ fun NewsResourceAuthors(
if (authors.isNotEmpty()) { if (authors.isNotEmpty()) {
// Only display first author for now // Only display first author for now
val author = authors[0] val author = authors[0]
val authorNameFormatted =
author.name.uppercase(ConfigurationCompat.getLocales(LocalConfiguration.current).get(0)) val locale = ConfigurationCompat.getLocales(LocalConfiguration.current).get(0)
val authorNameFormatted = if (locale != null) {
author.name.uppercase(locale)
} else {
author.name.uppercase()
}
val authorImageUrl = author.imageUrl val authorImageUrl = author.imageUrl

@ -17,7 +17,6 @@
package com.google.samples.apps.nowinandroid.feature.author package com.google.samples.apps.nowinandroid.feature.author
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -53,11 +52,13 @@ import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.model.data.Author import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
import com.google.samples.apps.nowinandroid.core.ui.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
import com.google.samples.apps.nowinandroid.feature.author.AuthorUiState.Loading import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
import com.google.samples.apps.nowinandroid.feature.author.R.string
@Composable @Composable
fun AuthorRoute( fun AuthorRoute(
@ -76,7 +77,6 @@ fun AuthorRoute(
) )
} }
@OptIn(ExperimentalFoundationApi::class)
@VisibleForTesting @VisibleForTesting
@Composable @Composable
internal fun AuthorScreen( internal fun AuthorScreen(
@ -100,11 +100,11 @@ internal fun AuthorScreen(
) )
} }
when (authorState) { when (authorState) {
Loading -> { AuthorUiState.Loading -> {
item { item {
LoadingWheel( LoadingWheel(
modifier = modifier, modifier = modifier,
contentDesc = stringResource(id = string.author_loading), contentDesc = stringResource(id = R.string.author_loading),
) )
} }
} }
@ -221,29 +221,45 @@ private fun AuthorToolbar(
onCheckedChange = onFollowClick, onCheckedChange = onFollowClick,
) { ) {
if (selected) { if (selected) {
Text(stringResource(id = string.author_following)) Text(stringResource(id = R.string.author_following))
} else { } else {
Text(stringResource(id = string.author_not_following)) Text(stringResource(id = R.string.author_not_following))
} }
} }
} }
} }
@Preview @Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable @Composable
private fun AuthorBodyPreview() { fun AuthorScreenPopulated() {
MaterialTheme { NiaTheme {
LazyColumn { NiaBackground {
authorBody( AuthorScreen(
author = Author( authorState = AuthorUiState.Success(FollowableAuthor(previewAuthors[0], false)),
id = "0", newsState = NewsUiState.Success(previewNewsResources),
name = "Android Dev", onBackClick = {},
bio = "Works on Compose", onFollowClick = {}
twitter = "dev", )
mediumPage = "", }
imageUrl = "", }
), }
news = NewsUiState.Success(emptyList())
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun AuthorScreenLoading() {
NiaTheme {
NiaBackground {
AuthorScreen(
authorState = AuthorUiState.Loading,
newsState = NewsUiState.Loading,
onBackClick = {},
onFollowClick = {}
) )
} }
} }

@ -78,8 +78,6 @@ class AuthorViewModelTest {
successAuthorUiState.followableAuthor.author successAuthorUiState.followableAuthor.author
assertEquals(authorFromRepository, successAuthorUiState.followableAuthor.author) assertEquals(authorFromRepository, successAuthorUiState.followableAuthor.author)
cancel()
} }
} }
@ -87,7 +85,6 @@ class AuthorViewModelTest {
fun uiStateNews_whenInitialized_thenShowLoading() = runTest { fun uiStateNews_whenInitialized_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(NewsUiState.Loading, awaitItem().newsState) assertEquals(NewsUiState.Loading, awaitItem().newsState)
cancel()
} }
} }
@ -95,7 +92,6 @@ class AuthorViewModelTest {
fun uiStateAuthor_whenInitialized_thenShowLoading() = runTest { fun uiStateAuthor_whenInitialized_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(AuthorUiState.Loading, awaitItem().authorState) assertEquals(AuthorUiState.Loading, awaitItem().authorState)
cancel()
} }
} }
@ -104,7 +100,6 @@ class AuthorViewModelTest {
viewModel.uiState.test { viewModel.uiState.test {
userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id)) userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
assertEquals(AuthorUiState.Loading, awaitItem().authorState) assertEquals(AuthorUiState.Loading, awaitItem().authorState)
cancel()
} }
} }
@ -118,7 +113,6 @@ class AuthorViewModelTest {
val item = awaitItem() val item = awaitItem()
assertTrue(item.authorState is AuthorUiState.Success) assertTrue(item.authorState is AuthorUiState.Success)
assertTrue(item.newsState is NewsUiState.Loading) assertTrue(item.newsState is NewsUiState.Loading)
cancel()
} }
} }
@ -133,7 +127,6 @@ class AuthorViewModelTest {
val item = awaitItem() val item = awaitItem()
assertTrue(item.authorState is AuthorUiState.Success) assertTrue(item.authorState is AuthorUiState.Success)
assertTrue(item.newsState is NewsUiState.Success) assertTrue(item.newsState is NewsUiState.Success)
cancel()
} }
} }
@ -152,7 +145,6 @@ class AuthorViewModelTest {
AuthorUiState.Success(followableAuthor = testOutputAuthors[0]), AuthorUiState.Success(followableAuthor = testOutputAuthors[0]),
awaitItem().authorState awaitItem().authorState
) )
cancel()
} }
} }
} }

@ -79,13 +79,12 @@ import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
import com.google.samples.apps.nowinandroid.core.ui.NewsResourceCardExpanded import com.google.samples.apps.nowinandroid.core.ui.NewsResourceCardExpanded
import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilledButton import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilledButton
@ -96,7 +95,6 @@ import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTypography import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTypography
import kotlin.math.floor import kotlin.math.floor
import kotlinx.datetime.Instant
@Composable @Composable
fun ForYouRoute( fun ForYouRoute(
@ -482,17 +480,22 @@ private fun LazyListScope.Feed(
} }
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Preview(device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480") @Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480") @Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480") @Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable @Composable
fun ForYouScreenLoading() { fun ForYouScreenPopulatedFeed() {
BoxWithConstraints { BoxWithConstraints {
NiaTheme { NiaTheme {
ForYouScreen( ForYouScreen(
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)), windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)),
interestsSelectionState = ForYouInterestsSelectionUiState.Loading, interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection,
feedState = ForYouFeedUiState.Loading, feedState = ForYouFeedUiState.Success(
feed = previewNewsResources.map {
SaveableNewsResource(it, false)
}
),
onTopicCheckedChanged = { _, _ -> }, onTopicCheckedChanged = { _, _ -> },
onAuthorCheckedChanged = { _, _ -> }, onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {}, saveFollowedTopics = {},
@ -503,9 +506,10 @@ fun ForYouScreenLoading() {
} }
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Preview(device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480") @Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480") @Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480") @Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable @Composable
fun ForYouScreenTopicSelection() { fun ForYouScreenTopicSelection() {
BoxWithConstraints { BoxWithConstraints {
@ -513,79 +517,13 @@ fun ForYouScreenTopicSelection() {
ForYouScreen( ForYouScreen(
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)), windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)),
interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection( interestsSelectionState = ForYouInterestsSelectionUiState.WithInterestsSelection(
topics = listOf( topics = previewTopics.map { FollowableTopic(it, false) },
FollowableTopic( authors = previewAuthors.map { FollowableAuthor(it, false) }
topic = Topic(
id = "0",
name = "Headlines",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
),
isFollowed = false
),
FollowableTopic(
topic = Topic(
id = "1",
name = "UI",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
),
isFollowed = false
),
FollowableTopic(
topic = Topic(
id = "2",
name = "Publishing and Distribution",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
),
isFollowed = false
),
),
authors = listOf(
FollowableAuthor(
author = Author(
id = "0",
name = "Android Dev",
imageUrl = "",
twitter = "",
mediumPage = "",
bio = "",
),
isFollowed = false
),
FollowableAuthor(
author = Author(
id = "1",
name = "Android Dev 2",
imageUrl = "",
twitter = "",
mediumPage = "",
bio = "",
),
isFollowed = false
),
FollowableAuthor(
author = Author(
id = "2",
name = "Android Dev 3",
imageUrl = "",
twitter = "",
mediumPage = "",
bio = "",
),
isFollowed = false
)
)
), ),
feedState = ForYouFeedUiState.Success( feedState = ForYouFeedUiState.Success(
feed = saveableNewsResource, feed = previewNewsResources.map {
SaveableNewsResource(it, false)
}
), ),
onAuthorCheckedChanged = { _, _ -> }, onAuthorCheckedChanged = { _, _ -> },
onTopicCheckedChanged = { _, _ -> }, onTopicCheckedChanged = { _, _ -> },
@ -597,19 +535,18 @@ fun ForYouScreenTopicSelection() {
} }
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Preview(device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480") @Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480") @Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480") @Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable @Composable
fun PopulatedFeed() { fun ForYouScreenLoading() {
BoxWithConstraints { BoxWithConstraints {
NiaTheme { NiaTheme {
ForYouScreen( ForYouScreen(
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)), windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(maxWidth, maxHeight)),
interestsSelectionState = ForYouInterestsSelectionUiState.NoInterestsSelection, interestsSelectionState = ForYouInterestsSelectionUiState.Loading,
feedState = ForYouFeedUiState.Success( feedState = ForYouFeedUiState.Loading,
feed = saveableNewsResource
),
onTopicCheckedChanged = { _, _ -> }, onTopicCheckedChanged = { _, _ -> },
onAuthorCheckedChanged = { _, _ -> }, onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {}, saveFollowedTopics = {},
@ -618,84 +555,3 @@ fun PopulatedFeed() {
} }
} }
} }
private val saveableNewsResource = listOf(
SaveableNewsResource(
newsResource = NewsResource(
id = "1",
episodeId = "52",
title = "Thanks for helping us reach 1M YouTube Subscribers",
content = "Thank you everyone for following the Now in Android series " +
"and everything the Android Developers YouTube channel has to offer. " +
"During the Android Developer Summit, our YouTube channel reached 1 " +
"million subscribers! Heres a small video to thank you all.",
url = "https://youtu.be/-fJ6poHQrjM",
headerImageUrl = "https://i.ytimg.com/vi/-fJ6poHQrjM/maxresdefault.jpg",
publishDate = Instant.parse("2021-11-09T00:00:00.000Z"),
type = Video,
topics = listOf(
Topic(
id = "0",
name = "Headlines",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
)
),
authors = emptyList()
),
isSaved = false
),
SaveableNewsResource(
newsResource = NewsResource(
id = "2",
episodeId = "52",
title = "Transformations and customisations in the Paging Library",
content = "A demonstration of different operations that can be performed " +
"with Paging. Transformations like inserting separators, when to " +
"create a new pager, and customisation options for consuming " +
"PagingData.",
url = "https://youtu.be/ZARz0pjm5YM",
headerImageUrl = "https://i.ytimg.com/vi/ZARz0pjm5YM/maxresdefault.jpg",
publishDate = Instant.parse("2021-11-01T00:00:00.000Z"),
type = Video,
topics = listOf(
Topic(
id = "1",
name = "UI",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
),
),
authors = emptyList()
),
isSaved = false
),
SaveableNewsResource(
newsResource = NewsResource(
id = "3",
episodeId = "52",
title = "Community tip on Paging",
content = "Tips for using the Paging library from the developer community",
url = "https://youtu.be/r5JgIyS3t3s",
headerImageUrl = "https://i.ytimg.com/vi/r5JgIyS3t3s/maxresdefault.jpg",
publishDate = Instant.parse("2021-11-08T00:00:00.000Z"),
type = Video,
topics = listOf(
Topic(
id = "1",
name = "UI",
shortDescription = "",
longDescription = "",
url = "",
imageUrl = ""
),
),
authors = emptyList()
),
isSaved = false
),
)

@ -87,7 +87,6 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -102,8 +101,6 @@ class ForYouViewModelTest {
awaitItem() awaitItem()
) )
topicsRepository.sendTopics(sampleTopics) topicsRepository.sendTopics(sampleTopics)
cancel()
} }
} }
@ -118,8 +115,6 @@ class ForYouViewModelTest {
awaitItem() awaitItem()
) )
authorsRepository.sendAuthors(sampleAuthors) authorsRepository.sendAuthors(sampleAuthors)
cancel()
} }
} }
@ -134,8 +129,6 @@ class ForYouViewModelTest {
awaitItem() awaitItem()
) )
userDataRepository.setFollowedTopicIds(emptySet()) userDataRepository.setFollowedTopicIds(emptySet())
cancel()
} }
} }
@ -150,8 +143,6 @@ class ForYouViewModelTest {
awaitItem() awaitItem()
) )
userDataRepository.setFollowedAuthorIds(emptySet()) userDataRepository.setFollowedAuthorIds(emptySet())
cancel()
} }
} }
@ -247,8 +238,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -344,7 +333,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -385,7 +373,6 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -426,7 +413,6 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -695,7 +681,6 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -964,7 +949,6 @@ class ForYouViewModelTest {
), ),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -1062,7 +1046,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -1160,7 +1143,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -1202,7 +1184,6 @@ class ForYouViewModelTest {
) )
assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics())
assertEquals(emptySet<Int>(), userDataRepository.getCurrentFollowedAuthors()) assertEquals(emptySet<Int>(), userDataRepository.getCurrentFollowedAuthors())
cancel()
} }
} }
@ -1240,7 +1221,6 @@ class ForYouViewModelTest {
) )
assertEquals(emptySet<Int>(), userDataRepository.getCurrentFollowedTopics()) assertEquals(emptySet<Int>(), userDataRepository.getCurrentFollowedTopics())
assertEquals(setOf("0"), userDataRepository.getCurrentFollowedAuthors()) assertEquals(setOf("0"), userDataRepository.getCurrentFollowedAuthors())
cancel()
} }
} }
@ -1283,7 +1263,6 @@ class ForYouViewModelTest {
) )
assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics()) assertEquals(setOf("1"), userDataRepository.getCurrentFollowedTopics())
assertEquals(setOf("1"), userDataRepository.getCurrentFollowedAuthors()) assertEquals(setOf("1"), userDataRepository.getCurrentFollowedAuthors())
cancel()
} }
} }
@ -1387,7 +1366,6 @@ class ForYouViewModelTest {
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -1490,7 +1468,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
@ -1525,7 +1502,6 @@ class ForYouViewModelTest {
), ),
expectMostRecentItem() expectMostRecentItem()
) )
cancel()
} }
} }
} }

@ -34,12 +34,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
import com.google.samples.apps.nowinandroid.core.ui.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.ui.component.NiaTab import com.google.samples.apps.nowinandroid.core.ui.component.NiaTab
import com.google.samples.apps.nowinandroid.core.ui.component.NiaTabRow import com.google.samples.apps.nowinandroid.core.ui.component.NiaTabRow
import com.google.samples.apps.nowinandroid.core.ui.component.NiaTopAppBar import com.google.samples.apps.nowinandroid.core.ui.component.NiaTopAppBar
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
@Composable @Composable
fun InterestsRoute( fun InterestsRoute(
@ -164,3 +171,78 @@ private fun InterestsContent(
private fun InterestsEmptyScreen() { private fun InterestsEmptyScreen() {
Text(text = stringResource(id = R.string.interests_empty_header)) Text(text = stringResource(id = R.string.interests_empty_header))
} }
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun InterestsScreenPopulated() {
NiaTheme {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Interests(
authors = previewAuthors.map { FollowableAuthor(it, false) },
topics = previewTopics.map { FollowableTopic(it, false) }
),
tabState = InterestsTabState(
titles = listOf(R.string.interests_topics, R.string.interests_people),
currentIndex = 0
),
followAuthor = { _, _ -> },
followTopic = { _, _ -> },
navigateToAuthor = {},
navigateToTopic = {},
switchTab = {}
)
}
}
}
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun InterestsScreenLoading() {
NiaTheme {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Loading,
tabState = InterestsTabState(
titles = listOf(R.string.interests_topics, R.string.interests_people),
currentIndex = 0
),
followAuthor = { _, _ -> },
followTopic = { _, _ -> },
navigateToAuthor = {},
navigateToTopic = {},
switchTab = {},
)
}
}
}
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun InterestsScreenEmpty() {
NiaTheme {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Empty,
tabState = InterestsTabState(
titles = listOf(R.string.interests_topics, R.string.interests_people),
currentIndex = 0
),
followAuthor = { _, _ -> },
followTopic = { _, _ -> },
navigateToAuthor = {},
navigateToTopic = {},
switchTab = {}
)
}
}
}

@ -56,7 +56,6 @@ class InterestsViewModelTest {
fun uiState_whenInitialized_thenShowLoading() = runTest { fun uiState_whenInitialized_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(InterestsUiState.Loading, awaitItem()) assertEquals(InterestsUiState.Loading, awaitItem())
cancel()
} }
} }
@ -66,7 +65,6 @@ class InterestsViewModelTest {
assertEquals(InterestsUiState.Loading, awaitItem()) assertEquals(InterestsUiState.Loading, awaitItem())
userDataRepository.setFollowedAuthorIds(setOf("1")) userDataRepository.setFollowedAuthorIds(setOf("1"))
userDataRepository.setFollowedTopicIds(emptySet()) userDataRepository.setFollowedTopicIds(emptySet())
cancel()
} }
} }
@ -76,7 +74,6 @@ class InterestsViewModelTest {
assertEquals(InterestsUiState.Loading, awaitItem()) assertEquals(InterestsUiState.Loading, awaitItem())
userDataRepository.setFollowedAuthorIds(emptySet()) userDataRepository.setFollowedAuthorIds(emptySet())
userDataRepository.setFollowedTopicIds(setOf("1")) userDataRepository.setFollowedTopicIds(setOf("1"))
cancel()
} }
} }
@ -106,7 +103,6 @@ class InterestsViewModelTest {
InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()), InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -130,7 +126,6 @@ class InterestsViewModelTest {
InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors), InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -162,7 +157,6 @@ class InterestsViewModelTest {
InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()), InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()),
awaitItem() awaitItem()
) )
cancel()
} }
} }
@ -188,7 +182,6 @@ class InterestsViewModelTest {
InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors), InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors),
awaitItem() awaitItem()
) )
cancel()
} }
} }
} }

@ -49,9 +49,13 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
import com.google.samples.apps.nowinandroid.core.ui.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
import com.google.samples.apps.nowinandroid.feature.topic.R.string import com.google.samples.apps.nowinandroid.feature.topic.R.string
import com.google.samples.apps.nowinandroid.feature.topic.TopicUiState.Loading import com.google.samples.apps.nowinandroid.feature.topic.TopicUiState.Loading
@ -188,19 +192,6 @@ private fun LazyListScope.TopicCards(news: NewsUiState) {
} }
} }
@Preview
@Composable
private fun TopicBodyPreview() {
MaterialTheme {
LazyColumn {
TopicBody(
"Jetpack Compose", "Lorem ipsum maximum",
NewsUiState.Success(emptyList()), ""
)
}
}
}
@Composable @Composable
private fun TopicToolbar( private fun TopicToolbar(
uiState: FollowableTopic, uiState: FollowableTopic,
@ -235,3 +226,39 @@ private fun TopicToolbar(
} }
} }
} }
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun TopicScreenPopulated() {
NiaTheme {
NiaBackground {
TopicScreen(
topicState = TopicUiState.Success(FollowableTopic(previewTopics[0], false)),
newsState = NewsUiState.Success(previewNewsResources),
onBackClick = {},
onFollowClick = {}
)
}
}
}
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
@Composable
fun TopicScreenLoading() {
NiaTheme {
NiaBackground {
TopicScreen(
topicState = TopicUiState.Loading,
newsState = NewsUiState.Loading,
onBackClick = {},
onFollowClick = {}
)
}
}
}

@ -72,7 +72,6 @@ class TopicViewModelTest {
).first() ).first()
assertEquals(topicFromRepository, successTopicState.followableTopic.topic) assertEquals(topicFromRepository, successTopicState.followableTopic.topic)
cancel()
} }
} }
@ -80,7 +79,6 @@ class TopicViewModelTest {
fun uiStateNews_whenInitialized_thenShowLoading() = runTest { fun uiStateNews_whenInitialized_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(NewsUiState.Loading, awaitItem().newsState) assertEquals(NewsUiState.Loading, awaitItem().newsState)
cancel()
} }
} }
@ -88,7 +86,6 @@ class TopicViewModelTest {
fun uiStateTopic_whenInitialized_thenShowLoading() = runTest { fun uiStateTopic_whenInitialized_thenShowLoading() = runTest {
viewModel.uiState.test { viewModel.uiState.test {
assertEquals(TopicUiState.Loading, awaitItem().topicState) assertEquals(TopicUiState.Loading, awaitItem().topicState)
cancel()
} }
} }
@ -97,7 +94,6 @@ class TopicViewModelTest {
viewModel.uiState.test { viewModel.uiState.test {
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id)) userDataRepository.setFollowedTopicIds(setOf(testInputTopics[1].topic.id))
assertEquals(TopicUiState.Loading, awaitItem().topicState) assertEquals(TopicUiState.Loading, awaitItem().topicState)
cancel()
} }
} }
@ -111,7 +107,6 @@ class TopicViewModelTest {
val item = awaitItem() val item = awaitItem()
assertTrue(item.topicState is TopicUiState.Success) assertTrue(item.topicState is TopicUiState.Success)
assertTrue(item.newsState is NewsUiState.Loading) assertTrue(item.newsState is NewsUiState.Loading)
cancel()
} }
} }
@ -126,7 +121,6 @@ class TopicViewModelTest {
val item = awaitItem() val item = awaitItem()
assertTrue(item.topicState is TopicUiState.Success) assertTrue(item.topicState is TopicUiState.Success)
assertTrue(item.newsState is NewsUiState.Success) assertTrue(item.newsState is NewsUiState.Success)
cancel()
} }
} }
@ -145,7 +139,6 @@ class TopicViewModelTest {
TopicUiState.Success(followableTopic = testOutputTopics[0]), TopicUiState.Success(followableTopic = testOutputTopics[0]),
awaitItem().topicState awaitItem().topicState
) )
cancel()
} }
} }
} }

@ -3,10 +3,10 @@ accompanist = "0.24.8-beta"
androidDesugarJdkLibs = "1.1.5" androidDesugarJdkLibs = "1.1.5"
androidGradlePlugin = "7.2.1" androidGradlePlugin = "7.2.1"
androidxActivity = "1.4.0" androidxActivity = "1.4.0"
androidxAppCompat = "1.3.0" androidxAppCompat = "1.4.2"
androidxCompose = "1.2.0-beta03" androidxCompose = "1.2.0-beta03"
androidxComposeMaterial3 = "1.0.0-alpha13" androidxComposeMaterial3 = "1.0.0-alpha13"
androidxCore = "1.7.0" androidxCore = "1.8.0"
androidxCustomView = "1.0.0-beta02" androidxCustomView = "1.0.0-beta02"
androidxDataStore = "1.0.0" androidxDataStore = "1.0.0"
androidxEspresso = "3.4.0" androidxEspresso = "3.4.0"
@ -28,14 +28,14 @@ hiltExt = "1.0.0"
jacoco = "0.8.7" jacoco = "0.8.7"
junit4 = "4.13.2" junit4 = "4.13.2"
kotlin = "1.6.21" kotlin = "1.6.21"
kotlinxCoroutines = "1.6.0" kotlinxCoroutines = "1.6.2"
kotlinxDatetime = "0.3.3" kotlinxDatetime = "0.3.3"
kotlinxSerializationJson = "1.3.3" kotlinxSerializationJson = "1.3.3"
ksp = "1.6.21-1.0.5" ksp = "1.6.21-1.0.5"
ktlint = "0.43.0" ktlint = "0.43.0"
material3 = "1.6.1" material3 = "1.6.1"
okhttp = "4.9.3" okhttp = "4.9.3"
protobuf = "3.20.0" protobuf = "3.21.1"
protobufPlugin = "0.8.18" protobufPlugin = "0.8.18"
retrofit = "2.9.0" retrofit = "2.9.0"
retrofitKotlinxSerializationJson = "0.8.0" retrofitKotlinxSerializationJson = "0.8.0"

Binary file not shown.

@ -1,6 +1,5 @@
#Thu Feb 24 14:19:14 GMT 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

Loading…
Cancel
Save