Merge branch 'main' into tm/generate-bp-configuration

pull/385/head
mlykotom 2 years ago
commit 07e80adf4a

@ -2,5 +2,15 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": [
"config:base", "group:all", ":dependencyDashboard", "schedule:daily" "config:base", "group:all", ":dependencyDashboard", "schedule:daily"
],
"packageRules": [
{
"matchPackageNames": ["org.objenesis:objenesis"],
"allowedVersions": "<=2.6"
},
{
"matchPackageNames": ["com.google.protobuf"],
"allowedVersions": "<=0.8.19"
}
] ]
} }

@ -1,12 +1,12 @@
![Now in Android](docs/images/nia-splash.jpg "Now in Android") ![Now in Android](docs/images/nia-splash.jpg "Now in Android")
Now in Android App [Work in progress 🚧] Now in Android App
================== ==================
**Learn how this app was designed and built in the [design case study](https://goo.gle/nia-figma), [architecture learning journey](docs/ArchitectureLearningJourney.md) and [modularization learning journey](docs/ModularizationLearningJourney.md).** **Learn how this app was designed and built in the [design case study](https://goo.gle/nia-figma), [architecture learning journey](docs/ArchitectureLearningJourney.md) and [modularization learning journey](docs/ModularizationLearningJourney.md).**
This is the repository for the [Now in Android](https://developer.android.com/series/now-in-android) This is the repository for the [Now in Android](https://developer.android.com/series/now-in-android)
app. app. It is a **work in progress** 🚧.
**Now in Android** is a fully functional Android app built entirely with Kotlin and Jetpack Compose. It **Now in Android** is a fully functional Android app built entirely with Kotlin and Jetpack Compose. It
follows Android design and development best practices and is intended to be a useful reference follows Android design and development best practices and is intended to be a useful reference

@ -26,8 +26,8 @@ plugins {
android { android {
defaultConfig { defaultConfig {
applicationId = "com.google.samples.apps.nowinandroid" applicationId = "com.google.samples.apps.nowinandroid"
versionCode = 1 versionCode = 2
versionName = "0.0.1" // X.Y.Z; X = Major, Y = minor, Z = Patch level versionName = "0.0.2" // X.Y.Z; X = Major, Y = minor, Z = Patch level
// Custom test runner to set up Hilt dependency graph // Custom test runner to set up Hilt dependency graph
testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"

@ -255,7 +255,8 @@ class NavigationTest {
fun navigationBar_multipleBackStackInterests() { fun navigationBar_multipleBackStackInterests() {
composeTestRule.apply { composeTestRule.apply {
onNodeWithText(interests).performClick() onNodeWithText(interests).performClick()
onNodeWithText("Android Studio").performClick() // TODO: Grab string from fake data // TODO: Grab string from fake data
onNodeWithText("Android Studio & Tools").performClick()
// Switch tab // Switch tab
onNodeWithText(forYou).performClick() onNodeWithText(forYou).performClick()

@ -31,5 +31,4 @@ plugins {
alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.hilt) apply false alias(libs.plugins.hilt) apply false
alias(libs.plugins.secrets) apply false alias(libs.plugins.secrets) apply false
id("org.jetbrains.kotlin.android") version "1.7.10" apply false
} }

@ -61,10 +61,9 @@ class GetFollowableTopicsStreamUseCase @Inject constructor(
isFollowed = topic.id in followedIds isFollowed = topic.id in followedIds
) )
} }
if (sortBy == NAME) { when (sortBy) {
followedTopics.sortedBy { it.topic.name } NAME -> followedTopics.sortedBy { it.topic.name }
} else { else -> followedTopics
followedTopics
} }
} }
} }

@ -49,7 +49,7 @@ class FakeNiaNetworkDataSourceTest {
fun testDeserializationOfNewsResources() = runTest(testDispatcher) { fun testDeserializationOfNewsResources() = runTest(testDispatcher) {
assertEquals( assertEquals(
FakeDataSource.sampleResource, FakeDataSource.sampleResource,
subject.getNewsResources().first() subject.getNewsResources().find { it.id == FakeDataSource.sampleResource.id }
) )
} }
} }

@ -43,10 +43,12 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -54,7 +56,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
@ -73,8 +74,8 @@ fun AuthorsCarousel(
LazyRow( LazyRow(
modifier = modifier.testTag(tag), modifier = modifier.testTag(tag),
contentPadding = PaddingValues(24.dp), contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
state = lazyListState state = lazyListState
) { ) {
items(items = authors, key = { item -> item.author.id }) { followableAuthor -> items(items = authors, key = { item -> item.author.id }) { followableAuthor ->
@ -97,24 +98,32 @@ fun AuthorItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val followDescription = if (following) { val followDescription = if (following) {
stringResource(id = R.string.following) stringResource(R.string.following)
} else { } else {
stringResource(id = R.string.not_following) stringResource(R.string.not_following)
}
val followActionLabel = if (following) {
stringResource(R.string.unfollow)
} else {
stringResource(R.string.follow)
} }
Column( Column(
modifier = modifier modifier = modifier
.toggleable( .toggleable(
value = following, value = following,
enabled = true,
role = Role.Button, role = Role.Button,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false), indication = rememberRipple(bounded = false),
onValueChange = { newFollowing -> onAuthorClick(newFollowing) }, onValueChange = { newFollowing -> onAuthorClick(newFollowing) },
) )
.padding(8.dp)
.sizeIn(maxWidth = 48.dp) .sizeIn(maxWidth = 48.dp)
.semantics(mergeDescendants = true) { .semantics(mergeDescendants = true) {
// Add information for A11y services, explaining what each state means and
// what will happen when the user interacts with the author item.
stateDescription = "$followDescription ${author.name}" stateDescription = "$followDescription ${author.name}"
onClick(label = followActionLabel, action = null)
} }
) { ) {
Box( Box(
@ -136,28 +145,27 @@ fun AuthorItem(
AsyncImage( AsyncImage(
modifier = authorImageModifier, modifier = authorImageModifier,
model = author.imageUrl, model = author.imageUrl,
contentScale = ContentScale.Fit, contentScale = ContentScale.Crop,
contentDescription = null contentDescription = null
) )
} }
NiaToggleButton( val backgroundColor =
checked = following, if (following)
onCheckedChange = onAuthorClick, MaterialTheme.colorScheme.primaryContainer
modifier = Modifier.align(Alignment.BottomEnd), else
icon = { MaterialTheme.colorScheme.surface
Icon( Icon(
imageVector = NiaIcons.Add, imageVector = if (following) NiaIcons.Check else NiaIcons.Add,
contentDescription = null contentDescription = null,
modifier = Modifier
.align(Alignment.BottomEnd)
.size(18.dp)
.drawBehind {
drawCircle(
color = backgroundColor,
radius = 12.dp.toPx()
) )
}, }
checkedIcon = {
Icon(
imageVector = NiaIcons.Check,
contentDescription = null
)
},
size = 24.dp,
backgroundColor = MaterialTheme.colorScheme.surface
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))

@ -27,5 +27,7 @@
<!-- Authors--> <!-- Authors-->
<string name="following">You are following</string> <string name="following">You are following</string>
<string name="not_following">You are not following</string> <string name="not_following">You are not following</string>
<string name="follow">Follow</string>
<string name="unfollow">Unfollow</string>
</resources> </resources>

@ -82,7 +82,7 @@ class SettingsDialogTest {
} }
@Test @Test
fun whenStateIsSuccess_allLegalLinksAreDisplayed() { fun whenStateIsSuccess_allLinksAreDisplayed() {
composeTestRule.setContent { composeTestRule.setContent {
SettingsDialog( SettingsDialog(
settingsUiState = Success( settingsUiState = Success(
@ -100,5 +100,6 @@ class SettingsDialogTest {
composeTestRule.onNodeWithText(getString(R.string.privacy_policy)).assertExists() composeTestRule.onNodeWithText(getString(R.string.privacy_policy)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.licenses)).assertExists() composeTestRule.onNodeWithText(getString(R.string.licenses)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.brand_guidelines)).assertExists() composeTestRule.onNodeWithText(getString(R.string.brand_guidelines)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feedback)).assertExists()
} }
} }

@ -110,7 +110,7 @@ fun SettingsDialog(
} }
} }
Divider(Modifier.padding(top = 8.dp)) Divider(Modifier.padding(top = 8.dp))
LegalPanel() LinksPanel()
} }
}, },
confirmButton = { confirmButton = {
@ -201,7 +201,7 @@ fun SettingsDialogThemeChooserRow(
} }
@Composable @Composable
private fun LegalPanel() { private fun LinksPanel() {
Row( Row(
modifier = Modifier.padding(top = 16.dp) modifier = Modifier.padding(top = 16.dp)
) { ) {
@ -226,6 +226,11 @@ private fun LegalPanel() {
text = stringResource(string.brand_guidelines), text = stringResource(string.brand_guidelines),
url = BRAND_GUIDELINES_URL url = BRAND_GUIDELINES_URL
) )
Spacer(Modifier.width(16.dp))
TextLink(
text = stringResource(string.feedback),
url = FEEDBACK_URL
)
} }
} }
} }
@ -283,3 +288,4 @@ fun PreviewSettingsDialogLoading() {
private const val PRIVACY_POLICY_URL = "https://policies.google.com/privacy" private const val PRIVACY_POLICY_URL = "https://policies.google.com/privacy"
private const val LICENSES_URL = "https://github.com/android/nowinandroid/blob/main/app/LICENSES.md#open-source-licenses-and-copyright-notices" private const val LICENSES_URL = "https://github.com/android/nowinandroid/blob/main/app/LICENSES.md#open-source-licenses-and-copyright-notices"
private const val BRAND_GUIDELINES_URL = "https://developer.android.com/distribute/marketing-tools/brand-guidelines" private const val BRAND_GUIDELINES_URL = "https://developer.android.com/distribute/marketing-tools/brand-guidelines"
private const val FEEDBACK_URL = "https://goo.gle/nia-app-feedback"

@ -21,6 +21,7 @@
<string name="privacy_policy">Privacy policy</string> <string name="privacy_policy">Privacy policy</string>
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>
<string name="brand_guidelines">Brand Guidelines</string> <string name="brand_guidelines">Brand Guidelines</string>
<string name="feedback">Feedback</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="brand_default">Default</string> <string name="brand_default">Default</string>
<string name="brand_android">Android</string> <string name="brand_android">Android</string>

@ -1,6 +1,6 @@
[versions] [versions]
accompanist = "0.27.0" accompanist = "0.27.0"
androidDesugarJdkLibs = "1.1.5" androidDesugarJdkLibs = "1.2.0"
androidGradlePlugin = "7.3.1" androidGradlePlugin = "7.3.1"
androidxActivity = "1.6.1" androidxActivity = "1.6.1"
androidxAppCompat = "1.5.1" androidxAppCompat = "1.5.1"
@ -12,10 +12,9 @@ androidxCoreSplashscreen = "1.0.0"
androidxDataStore = "1.0.0" androidxDataStore = "1.0.0"
androidxEspresso = "3.4.0" androidxEspresso = "3.4.0"
androidxHiltNavigationCompose = "1.0.0" androidxHiltNavigationCompose = "1.0.0"
# Skipping version 2.6.0-alpha02 due to https://issuetracker.google.com/249686765 androidxLifecycle = "2.6.0-alpha03"
androidxLifecycle = "2.6.0-alpha01"
androidxMacroBenchmark = "1.1.0" androidxMacroBenchmark = "1.1.0"
androidxNavigation = "2.5.2" androidxNavigation = "2.5.3"
androidxMetrics = "1.0.0-alpha03" androidxMetrics = "1.0.0-alpha03"
androidxProfileinstaller = "1.2.0" androidxProfileinstaller = "1.2.0"
androidxStartup = "1.1.1" androidxStartup = "1.1.1"
@ -28,24 +27,24 @@ androidxTracing = "1.1.0"
androidxUiAutomator = "2.2.0" androidxUiAutomator = "2.2.0"
androidxWork = "2.7.1" androidxWork = "2.7.1"
coil = "2.2.2" coil = "2.2.2"
hilt = "2.42" hilt = "2.44"
hiltExt = "1.0.0" hiltExt = "1.0.0"
jacoco = "0.8.7" jacoco = "0.8.7"
junit4 = "4.13.2" junit4 = "4.13.2"
kotlin = "1.7.20" kotlin = "1.7.20"
kotlinxCoroutines = "1.6.4" kotlinxCoroutines = "1.6.4"
kotlinxDatetime = "0.4.0" kotlinxDatetime = "0.4.0"
kotlinxSerializationJson = "1.4.0" kotlinxSerializationJson = "1.4.1"
ksp = "1.7.20-1.0.7" ksp = "1.7.20-1.0.8"
lint = "30.2.2" lint = "30.3.1"
okhttp = "4.10.0" okhttp = "4.10.0"
protobuf = "3.21.9" protobuf = "3.21.9"
protobufPlugin = "0.8.19" protobufPlugin = "0.8.19"
retrofit = "2.9.0" retrofit = "2.9.0"
retrofitKotlinxSerializationJson = "0.8.0" retrofitKotlinxSerializationJson = "0.8.0"
room = "2.5.0-alpha03" room = "2.5.0-beta01"
secrets = "2.0.1" secrets = "2.0.1"
turbine = "0.8.0" turbine = "0.12.1"
[libraries] [libraries]
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" }

Loading…
Cancel
Save