Change-Id: I94ca19fad5527c11a2f0f28cc43a23ab22f419a7pull/545/head
@ -0,0 +1,35 @@
|
|||||||
|
name: Android CI with GMD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
android-ci:
|
||||||
|
runs-on: macos-12
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '11'
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v2
|
||||||
|
|
||||||
|
- name: Run instrumented tests with GMD
|
||||||
|
run: ./gradlew cleanManagedDevices --unused-only &&
|
||||||
|
./gradlew pixel4api30DemoDebugAndroidTest -Dorg.gradle.workers.max=1
|
||||||
|
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true --info
|
||||||
|
|
||||||
|
- name: Upload test reports
|
||||||
|
if: success() || failure()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-reports
|
||||||
|
path: |
|
||||||
|
'**/*/build/reports/androidTests/'
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.designsystem.component
|
||||||
|
|
||||||
|
import androidx.compose.material3.FilledIconToggleButton
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now in Android toggle button with icon and checked icon content slots. Wraps Material 3
|
||||||
|
* [IconButton].
|
||||||
|
*
|
||||||
|
* @param checked Whether the toggle button is currently checked.
|
||||||
|
* @param onCheckedChange Called when the user clicks the toggle button and toggles checked.
|
||||||
|
* @param modifier Modifier to be applied to the toggle button.
|
||||||
|
* @param enabled Controls the enabled state of the toggle button. When `false`, this toggle button
|
||||||
|
* will not be clickable and will appear disabled to accessibility services.
|
||||||
|
* @param icon The icon content to show when unchecked.
|
||||||
|
* @param checkedIcon The icon content to show when checked.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NiaIconToggleButton(
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
icon: @Composable () -> Unit,
|
||||||
|
checkedIcon: @Composable () -> Unit = icon
|
||||||
|
) {
|
||||||
|
// TODO: File bug
|
||||||
|
// Can't use regular IconToggleButton as it doesn't include a shape (appears square)
|
||||||
|
FilledIconToggleButton(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = IconButtonDefaults.iconToggleButtonColors(
|
||||||
|
checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
disabledContainerColor = if (checked) {
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
|
alpha = NiaIconButtonDefaults.DisabledIconButtonContainerAlpha
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (checked) checkedIcon() else icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now in Android icon button default values.
|
||||||
|
*/
|
||||||
|
object NiaIconButtonDefaults {
|
||||||
|
// TODO: File bug
|
||||||
|
// IconToggleButton disabled container alpha not exposed by IconButtonDefaults
|
||||||
|
const val DisabledIconButtonContainerAlpha = 0.12f
|
||||||
|
}
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.designsystem.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
|
||||||
import androidx.compose.foundation.selection.toggleable
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.contentColorFor
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.drawBehind
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Now in Android toggle button with icon and checked icon content slots. Wraps Material 3
|
|
||||||
* [IconButton].
|
|
||||||
*
|
|
||||||
* @param checked Whether the toggle button is currently checked.
|
|
||||||
* @param onCheckedChange Called when the user clicks the toggle button and toggles checked.
|
|
||||||
* @param modifier Modifier to be applied to the toggle button.
|
|
||||||
* @param enabled Controls the enabled state of the toggle button. When `false`, this toggle button
|
|
||||||
* will not be clickable and will appear disabled to accessibility services.
|
|
||||||
* @param icon The icon content to show when unchecked.
|
|
||||||
* @param checkedIcon The icon content to show when checked.
|
|
||||||
* @param size The size of the toggle button.
|
|
||||||
* @param iconSize The size of the icon.
|
|
||||||
* @param backgroundColor The background color when unchecked.
|
|
||||||
* @param checkedBackgroundColor The background color when checked.
|
|
||||||
* @param iconColor The icon color when unchecked.
|
|
||||||
* @param iconColor The icon color when checked.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun NiaToggleButton(
|
|
||||||
checked: Boolean,
|
|
||||||
onCheckedChange: (Boolean) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
enabled: Boolean = true,
|
|
||||||
icon: @Composable () -> Unit,
|
|
||||||
checkedIcon: @Composable () -> Unit = icon,
|
|
||||||
size: Dp = NiaToggleButtonDefaults.ToggleButtonSize,
|
|
||||||
iconSize: Dp = NiaToggleButtonDefaults.ToggleButtonIconSize,
|
|
||||||
backgroundColor: Color = Color.Transparent,
|
|
||||||
checkedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
iconColor: Color = contentColorFor(backgroundColor),
|
|
||||||
checkedIconColor: Color = contentColorFor(checkedBackgroundColor)
|
|
||||||
) {
|
|
||||||
val radius = with(LocalDensity.current) { (size / 2).toPx() }
|
|
||||||
IconButton(
|
|
||||||
onClick = { onCheckedChange(!checked) },
|
|
||||||
modifier = modifier
|
|
||||||
.size(size)
|
|
||||||
.toggleable(value = checked, enabled = enabled, role = Role.Button, onValueChange = {
|
|
||||||
onCheckedChange(!checked)
|
|
||||||
})
|
|
||||||
.drawBehind {
|
|
||||||
drawCircle(
|
|
||||||
color = if (checked) checkedBackgroundColor else backgroundColor,
|
|
||||||
radius = radius
|
|
||||||
)
|
|
||||||
},
|
|
||||||
enabled = enabled,
|
|
||||||
content = {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.sizeIn(
|
|
||||||
maxWidth = iconSize,
|
|
||||||
maxHeight = iconSize
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
val contentColor = if (checked) checkedIconColor else iconColor
|
|
||||||
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
|
||||||
if (checked) checkedIcon() else icon()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Now in Android toggle button default values.
|
|
||||||
*/
|
|
||||||
object NiaToggleButtonDefaults {
|
|
||||||
val ToggleButtonSize = 40.dp
|
|
||||||
val ToggleButtonIconSize = 18.dp
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.samples.apps.nowinandroid.core.domain.model
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [NewsResource] with the additional information for whether it is saved.
|
|
||||||
*/
|
|
||||||
data class SaveableNewsResource(
|
|
||||||
val newsResource: NewsResource,
|
|
||||||
val isSaved: Boolean,
|
|
||||||
)
|
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.domain
|
||||||
|
|
||||||
|
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
|
||||||
|
import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig.FOLLOW_SYSTEM
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Article
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand.DEFAULT
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.Topic
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.UserData
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class UserNewsResourceTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given: Some user data and news resources
|
||||||
|
* When: They are combined using `UserNewsResource.from`
|
||||||
|
* Then: The correct UserNewsResources are constructed
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun userNewsResourcesAreConstructedFromNewsResourcesAndUserData() {
|
||||||
|
|
||||||
|
val newsResource1 = NewsResource(
|
||||||
|
id = "N1",
|
||||||
|
title = "Test news title",
|
||||||
|
content = "Test news content",
|
||||||
|
url = "Test URL",
|
||||||
|
headerImageUrl = "Test image URL",
|
||||||
|
publishDate = Clock.System.now(),
|
||||||
|
type = Article,
|
||||||
|
topics = listOf(
|
||||||
|
Topic(
|
||||||
|
id = "T1",
|
||||||
|
name = "Topic 1",
|
||||||
|
shortDescription = "Topic 1 short description",
|
||||||
|
longDescription = "Topic 1 long description",
|
||||||
|
url = "Topic 1 URL",
|
||||||
|
imageUrl = "Topic 1 image URL"
|
||||||
|
),
|
||||||
|
Topic(
|
||||||
|
id = "T2",
|
||||||
|
name = "Topic 2",
|
||||||
|
shortDescription = "Topic 2 short description",
|
||||||
|
longDescription = "Topic 2 long description",
|
||||||
|
url = "Topic 2 URL",
|
||||||
|
imageUrl = "Topic 2 image URL"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val userData = UserData(
|
||||||
|
bookmarkedNewsResources = setOf("N1"),
|
||||||
|
followedTopics = setOf("T1"),
|
||||||
|
themeBrand = DEFAULT,
|
||||||
|
darkThemeConfig = FOLLOW_SYSTEM,
|
||||||
|
shouldHideOnboarding = true
|
||||||
|
)
|
||||||
|
|
||||||
|
val userNewsResource = UserNewsResource(newsResource1, userData)
|
||||||
|
|
||||||
|
// Check that the simple field mappings have been done correctly.
|
||||||
|
assertEquals(newsResource1.id, userNewsResource.id)
|
||||||
|
assertEquals(newsResource1.title, userNewsResource.title)
|
||||||
|
assertEquals(newsResource1.content, userNewsResource.content)
|
||||||
|
assertEquals(newsResource1.url, userNewsResource.url)
|
||||||
|
assertEquals(newsResource1.headerImageUrl, userNewsResource.headerImageUrl)
|
||||||
|
assertEquals(newsResource1.publishDate, userNewsResource.publishDate)
|
||||||
|
|
||||||
|
// Check that each Topic has been converted to a FollowedTopic correctly.
|
||||||
|
assertEquals(newsResource1.topics.size, userNewsResource.followableTopics.size)
|
||||||
|
for (topic in newsResource1.topics) {
|
||||||
|
|
||||||
|
// Construct the expected FollowableTopic.
|
||||||
|
val followableTopic = FollowableTopic(
|
||||||
|
topic = topic,
|
||||||
|
isFollowed = userData.followedTopics.contains(topic.id)
|
||||||
|
)
|
||||||
|
assertTrue(userNewsResource.followableTopics.contains(followableTopic))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the saved flag is set correctly.
|
||||||
|
assertEquals(
|
||||||
|
userData.bookmarkedNewsResources.contains(newsResource1.id),
|
||||||
|
userNewsResource.isSaved
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.core.ui
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.assertContentDescriptionEquals
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import com.google.samples.apps.nowinandroid.core.domain.model.previewFollowableTopics
|
||||||
|
import com.google.samples.apps.nowinandroid.core.domain.model.previewUserNewsResources
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class NewsResourceCardTest {
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMetaDataDisplay_withCodelabResource() {
|
||||||
|
val newsWithKnownResourceType = previewUserNewsResources[0]
|
||||||
|
var dateFormatted = ""
|
||||||
|
|
||||||
|
composeTestRule.setContent {
|
||||||
|
NewsResourceCardExpanded(
|
||||||
|
userNewsResource = newsWithKnownResourceType,
|
||||||
|
isBookmarked = false,
|
||||||
|
onToggleBookmark = {},
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
dateFormatted = dateFormatted(publishDate = newsWithKnownResourceType.publishDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(
|
||||||
|
composeTestRule.activity.getString(
|
||||||
|
R.string.card_meta_data_text,
|
||||||
|
dateFormatted,
|
||||||
|
newsWithKnownResourceType.type.displayText
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.assertExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMetaDataDisplay_withUnknownResource() {
|
||||||
|
val newsWithUnknownResourceType = previewUserNewsResources[3]
|
||||||
|
var dateFormatted = ""
|
||||||
|
|
||||||
|
composeTestRule.setContent {
|
||||||
|
NewsResourceCardExpanded(
|
||||||
|
userNewsResource = newsWithUnknownResourceType,
|
||||||
|
isBookmarked = false,
|
||||||
|
onToggleBookmark = {},
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
dateFormatted = dateFormatted(publishDate = newsWithUnknownResourceType.publishDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(dateFormatted)
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTopicsChipColorBackground_matchesFollowedState() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
NewsResourceTopics(topics = previewFollowableTopics)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (followableTopic in previewFollowableTopics) {
|
||||||
|
val topicName = followableTopic.topic.name
|
||||||
|
val expectedContentDescription = if (followableTopic.isFollowed) {
|
||||||
|
"$topicName is followed"
|
||||||
|
} else {
|
||||||
|
"$topicName is not followed"
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText(topicName.uppercase())
|
||||||
|
.assertContentDescriptionEquals(expectedContentDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 370 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 146 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 558 KiB After Width: | Height: | Size: 245 KiB |
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|