Kotlinify codebase

- Remove unnecessary nullable types
- Replace no-op  method bodies with Unit
- Convert to expression body
- Replace if with when
- Remove braces from 'when' entries
- Remove braces from if statement
- Convert to single line lambda
- oneline if/returns
- Replace 'contains' call with 'in' operator

Following this refactor, it could be great to envision a more "strict" code formatter like ktlint 1.0 (we are currently stuck at 0.48.1)
pull/1039/head
Simon Marquis 8 months ago
parent 5087c86412
commit caa482bc71

@ -90,7 +90,7 @@ class NavigationTest {
lateinit var topicsRepository: TopicsRepository
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }
ReadOnlyProperty<Any, String> { _, _ -> activity.getString(resId) }
// The strings used for matching in these tests
private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up)

@ -90,9 +90,7 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState
.onEach {
uiState = it
}
.onEach { uiState = it }
.collect()
}
}

@ -20,6 +20,7 @@ import android.app.Activity
import android.util.Log
import android.view.Window
import androidx.metrics.performance.JankStats
import androidx.metrics.performance.JankStats.OnFrameListener
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -29,26 +30,20 @@ import dagger.hilt.android.components.ActivityComponent
@InstallIn(ActivityComponent::class)
object JankStatsModule {
@Provides
fun providesOnFrameListener(): JankStats.OnFrameListener {
return JankStats.OnFrameListener { frameData ->
// Make sure to only log janky frames.
if (frameData.isJank) {
// We're currently logging this but would better report it to a backend.
Log.v("NiA Jank", frameData.toString())
}
fun providesOnFrameListener(): OnFrameListener = OnFrameListener { frameData ->
// Make sure to only log janky frames.
if (frameData.isJank) {
// We're currently logging this but would better report it to a backend.
Log.v("NiA Jank", frameData.toString())
}
}
@Provides
fun providesWindow(activity: Activity): Window {
return activity.window
}
fun providesWindow(activity: Activity): Window = activity.window
@Provides
fun providesJankStats(
window: Window,
frameListener: JankStats.OnFrameListener,
): JankStats {
return JankStats.createAndTrack(window, frameListener)
}
frameListener: OnFrameListener,
): JankStats = JankStats.createAndTrack(window, frameListener)
}

@ -97,9 +97,7 @@ fun NiaApp(
) {
val shouldShowGradientBackground =
appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU
var showSettingsDialog by rememberSaveable {
mutableStateOf(false)
}
var showSettingsDialog by rememberSaveable { mutableStateOf(false) }
NiaBackground {
NiaGradientBackground(

@ -164,9 +164,7 @@ class NiaAppState(
}
}
fun navigateToSearch() {
navController.navigateToSearch()
}
fun navigateToSearch() = navController.navigateToSearch()
}
/**

@ -29,15 +29,11 @@ import androidx.test.uiautomator.HasChildrenOp.EXACTLY
fun untilHasChildren(
childCount: Int = 1,
op: HasChildrenOp = AT_LEAST,
): UiObject2Condition<Boolean> {
return object : UiObject2Condition<Boolean>() {
override fun apply(element: UiObject2): Boolean {
return when (op) {
AT_LEAST -> element.childCount >= childCount
EXACTLY -> element.childCount == childCount
AT_MOST -> element.childCount <= childCount
}
}
): UiObject2Condition<Boolean> = object : UiObject2Condition<Boolean>() {
override fun apply(element: UiObject2): Boolean = when (op) {
AT_LEAST -> element.childCount >= childCount
EXACTLY -> element.childCount == childCount
AT_MOST -> element.childCount <= childCount
}
}

@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.baselineprofile
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.benchmark.macro.junit4.BaselineProfileRule
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
@ -30,11 +31,9 @@ class StartupBaselineProfile {
@get:Rule val baselineProfileRule = BaselineProfileRule()
@Test
fun generate() =
baselineProfileRule.collect(
PACKAGE_NAME,
includeInStartupProfile = true,
) {
startActivityAndAllowNotifications()
}
fun generate() = baselineProfileRule.collect(
PACKAGE_NAME,
includeInStartupProfile = true,
profileBlock = MacrobenchmarkScope::startActivityAndAllowNotifications,
)
}

@ -79,14 +79,12 @@ internal abstract class PrintApkLocationTask : DefaultTask() {
fun taskAction() {
val hasFiles = sources.orNull?.any { directory ->
directory.asFileTree.files.any {
it.isFile && it.parentFile.path.contains("build${File.separator}generated").not()
it.isFile && "build${File.separator}generated" !in it.parentFile.path
}
} ?: throw RuntimeException("Cannot check androidTest sources")
// Don't print APK location if there are no androidTest source files
if (!hasFiles) {
return
}
if (!hasFiles) return
val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
?: throw RuntimeException("Cannot load APKs")

@ -23,15 +23,10 @@ import kotlinx.coroutines.flow.onStart
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
data class Error(val exception: Throwable) : Result<Nothing>
data object Loading : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> {
return this
.map<T, Result<T>> {
Result.Success(it)
}
.onStart { emit(Result.Loading) }
.catch { emit(Result.Error(it)) }
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = map<T, Result<T>> { Result.Success(it) }
.onStart { emit(Result.Loading) }
.catch { emit(Result.Error(it)) }

@ -39,9 +39,7 @@ class DefaultRecentSearchRepository @Inject constructor(
override fun getRecentSearchQueries(limit: Int): Flow<List<RecentSearchQuery>> =
recentSearchQueryDao.getRecentSearchQueryEntities(limit).map { searchQueries ->
searchQueries.map {
it.asExternalModel()
}
searchQueries.map { it.asExternalModel() }
}
override suspend fun clearRecentSearches() = recentSearchQueryDao.clearRecentSearchQueries()

@ -26,10 +26,10 @@ import javax.inject.Inject
* Fake implementation of the [RecentSearchRepository]
*/
class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) { /* no-op */ }
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit
override fun getRecentSearchQueries(limit: Int): Flow<List<RecentSearchQuery>> =
flowOf(emptyList())
override suspend fun clearRecentSearches() { /* no-op */ }
override suspend fun clearRecentSearches() = Unit
}

@ -27,7 +27,7 @@ import javax.inject.Inject
*/
class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository {
override suspend fun populateFtsData() { /* no-op */ }
override suspend fun populateFtsData() = Unit
override fun searchContents(searchQuery: String): Flow<SearchResult> = flowOf()
override fun getSearchContentsCount(): Flow<Int> = flowOf(1)
}

@ -55,9 +55,8 @@ class FakeTopicsRepository @Inject constructor(
)
}.flowOn(ioDispatcher)
override fun getTopic(id: String): Flow<Topic> {
return getTopics().map { it.first { topic -> topic.id == id } }
}
override fun getTopic(id: String): Flow<Topic> = getTopics()
.map { it.first { topic -> topic.id == id } }
override suspend fun syncWith(synchronizer: Synchronizer) = true
}

@ -82,7 +82,7 @@ class CompositeUserNewsResourceRepositoryTest {
// Check that only news resources with the given topic id are returned.
assertEquals(
sampleNewsResources
.filter { it.topics.contains(sampleTopic1) }
.filter { sampleTopic1 in it.topics }
.mapToUserNewsResources(emptyUserData),
userNewsResources.first(),
)
@ -104,7 +104,7 @@ class CompositeUserNewsResourceRepositoryTest {
// Check that only news resources with the given topic id are returned.
assertEquals(
sampleNewsResources
.filter { it.topics.contains(sampleTopic1) }
.filter { sampleTopic1 in it.topics }
.mapToUserNewsResources(userData),
userNewsResources.first(),
)

@ -91,14 +91,14 @@ class UserNewsResourceTest {
// Construct the expected FollowableTopic.
val followableTopic = FollowableTopic(
topic = topic,
isFollowed = userData.followedTopics.contains(topic.id),
isFollowed = topic.id in userData.followedTopics,
)
assertTrue(userNewsResource.followableTopics.contains(followableTopic))
}
// Check that the saved flag is set correctly.
assertEquals(
userData.bookmarkedNewsResources.contains(newsResource1.id),
newsResource1.id in userData.bookmarkedNewsResources,
userNewsResource.isSaved,
)
}

@ -34,9 +34,7 @@ val nonPresentInterestsIds = setOf("2")
*/
class TestNewsResourceDao : NewsResourceDao {
private var entitiesStateFlow = MutableStateFlow(
emptyList<NewsResourceEntity>(),
)
private val entitiesStateFlow = MutableStateFlow(emptyList<NewsResourceEntity>())
internal var topicCrossReferences: List<NewsResourceTopicCrossRef> = listOf()
@ -131,7 +129,7 @@ class TestNewsResourceDao : NewsResourceDao {
override suspend fun deleteNewsResources(ids: List<String>) {
val idSet = ids.toSet()
entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) }
entities.filterNot { it.id in idSet }
}
}
}

@ -91,11 +91,10 @@ class TestNiaNetworkDataSource : NiaNetworkDataSource {
}
}
fun List<NetworkChangeList>.after(version: Int?): List<NetworkChangeList> =
when (version) {
null -> this
else -> this.filter { it.changeListVersion > version }
}
fun List<NetworkChangeList>.after(version: Int?): List<NetworkChangeList> = when (version) {
null -> this
else -> filter { it.changeListVersion > version }
}
/**
* Return items from [this] whose id defined by [idGetter] is in [ids] if [ids] is not null
@ -105,7 +104,7 @@ private fun <T> List<T>.matchIds(
idGetter: (T) -> String,
) = when (ids) {
null -> this
else -> ids.toSet().let { idSet -> this.filter { idSet.contains(idGetter(it)) } }
else -> ids.toSet().let { idSet -> filter { idGetter(it) in idSet } }
}
/**

@ -28,20 +28,15 @@ import kotlinx.coroutines.flow.update
*/
class TestTopicDao : TopicDao {
private var entitiesStateFlow = MutableStateFlow(
emptyList<TopicEntity>(),
)
private val entitiesStateFlow = MutableStateFlow(emptyList<TopicEntity>())
override fun getTopicEntity(topicId: String): Flow<TopicEntity> {
override fun getTopicEntity(topicId: String): Flow<TopicEntity> =
throw NotImplementedError("Unused in tests")
}
override fun getTopicEntities(): Flow<List<TopicEntity>> =
entitiesStateFlow
override fun getTopicEntities(): Flow<List<TopicEntity>> = entitiesStateFlow
override fun getTopicEntities(ids: Set<String>): Flow<List<TopicEntity>> =
getTopicEntities()
.map { topics -> topics.filter { it.id in ids } }
getTopicEntities().map { topics -> topics.filter { it.id in ids } }
override suspend fun getOneOffTopicEntities(): List<TopicEntity> = emptyList()
@ -55,15 +50,11 @@ class TestTopicDao : TopicDao {
override suspend fun upsertTopics(entities: List<TopicEntity>) {
// Overwrite old values with new values
entitiesStateFlow.update { oldValues ->
(entities + oldValues).distinctBy(TopicEntity::id)
}
entitiesStateFlow.update { oldValues -> (entities + oldValues).distinctBy(TopicEntity::id) }
}
override suspend fun deleteTopics(ids: List<String>) {
val idSet = ids.toSet()
entitiesStateFlow.update { entities ->
entities.filterNot { idSet.contains(it.id) }
}
entitiesStateFlow.update { entities -> entities.filterNot { it.id in idSet } }
}
}

@ -52,7 +52,6 @@ object ListToMapMigration : DataMigration<UserPreferences> {
hasDoneListToMapMigration = true
}
override suspend fun shouldMigrate(currentData: UserPreferences): Boolean {
return !currentData.hasDoneListToMapMigration
}
override suspend fun shouldMigrate(currentData: UserPreferences): Boolean =
!currentData.hasDoneListToMapMigration
}

@ -103,9 +103,7 @@ class NiaPreferencesDataSource @Inject constructor(
suspend fun setDynamicColorPreference(useDynamicColor: Boolean) {
userPreferences.updateData {
it.copy {
this.useDynamicColor = useDynamicColor
}
it.copy { this.useDynamicColor = useDynamicColor }
}
}
@ -190,9 +188,7 @@ class NiaPreferencesDataSource @Inject constructor(
suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) {
userPreferences.updateData {
it.copy {
this.shouldHideOnboarding = shouldHideOnboarding
}
it.copy { this.shouldHideOnboarding = shouldHideOnboarding }
}
}
}

@ -16,7 +16,8 @@
package com.google.samples.apps.nowinandroid.core.designsystem
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
@ -219,60 +220,41 @@ class ThemeTest {
}
@Composable
private fun dynamicLightColorSchemeWithFallback(): ColorScheme {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicLightColorScheme(LocalContext.current)
} else {
LightDefaultColorScheme
}
private fun dynamicLightColorSchemeWithFallback(): ColorScheme = when {
SDK_INT >= VERSION_CODES.S -> dynamicLightColorScheme(LocalContext.current)
else -> LightDefaultColorScheme
}
@Composable
private fun dynamicDarkColorSchemeWithFallback(): ColorScheme {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
dynamicDarkColorScheme(LocalContext.current)
} else {
DarkDefaultColorScheme
}
private fun dynamicDarkColorSchemeWithFallback(): ColorScheme = when {
SDK_INT >= VERSION_CODES.S -> dynamicDarkColorScheme(LocalContext.current)
else -> DarkDefaultColorScheme
}
private fun emptyGradientColors(colorScheme: ColorScheme): GradientColors {
return GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp))
}
private fun emptyGradientColors(colorScheme: ColorScheme): GradientColors =
GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp))
private fun defaultGradientColors(colorScheme: ColorScheme): GradientColors {
return GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface,
)
}
private fun defaultGradientColors(colorScheme: ColorScheme): GradientColors = GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface,
)
private fun dynamicGradientColorsWithFallback(colorScheme: ColorScheme): GradientColors {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
emptyGradientColors(colorScheme)
} else {
defaultGradientColors(colorScheme)
}
private fun dynamicGradientColorsWithFallback(colorScheme: ColorScheme): GradientColors = when {
SDK_INT >= VERSION_CODES.S -> emptyGradientColors(colorScheme)
else -> defaultGradientColors(colorScheme)
}
private fun defaultBackgroundTheme(colorScheme: ColorScheme): BackgroundTheme {
return BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp,
)
}
private fun defaultBackgroundTheme(colorScheme: ColorScheme): BackgroundTheme = BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp,
)
private fun defaultTintTheme(): TintTheme {
return TintTheme()
}
private fun defaultTintTheme(): TintTheme = TintTheme()
private fun dynamicTintThemeWithFallback(colorScheme: ColorScheme): TintTheme {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
TintTheme(colorScheme.primary)
} else {
TintTheme()
}
private fun dynamicTintThemeWithFallback(colorScheme: ColorScheme): TintTheme = when {
SDK_INT >= VERSION_CODES.S -> TintTheme(colorScheme.primary)
else -> TintTheme()
}
/**

@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color.Companion.Unspecified
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
@ -79,7 +80,7 @@ fun DynamicAsyncImage(
contentScale = ContentScale.Crop,
painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder,
contentDescription = contentDescription,
colorFilter = if (iconTint != null) ColorFilter.tint(iconTint) else null,
colorFilter = if (iconTint != Unspecified) ColorFilter.tint(iconTint) else null,
)
}
}

@ -40,9 +40,9 @@ import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
fun NiaTopAppBar(
@StringRes titleRes: Int,
navigationIcon: ImageVector,
navigationIconContentDescription: String?,
navigationIconContentDescription: String,
actionIcon: ImageVector,
actionIconContentDescription: String?,
actionIconContentDescription: String,
modifier: Modifier = Modifier,
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
onNavigationClick: () -> Unit = {},

@ -229,10 +229,5 @@ fun LazyStaggeredGridState.scrollbarState(
.collect { value = it }
}.value
private inline fun <T> List<T>.floatSumOf(selector: (T) -> Float): Float {
var sum = 0f
for (element in this) {
sum += selector(element)
}
return sum
}
private inline fun <T> List<T>.floatSumOf(selector: (T) -> Float): Float =
fold(0f) { acc, it -> acc + selector(it) }

@ -25,7 +25,7 @@ import androidx.compose.ui.graphics.Color
*/
@Immutable
data class TintTheme(
val iconTint: Color? = null,
val iconTint: Color = Color.Unspecified,
)
/**

@ -37,22 +37,20 @@ class GetFollowableTopicsUseCase @Inject constructor(
*
* @param sortBy - the field used to sort the topics. Default NONE = no sorting.
*/
operator fun invoke(sortBy: TopicSortField = NONE): Flow<List<FollowableTopic>> {
return combine(
userDataRepository.userData,
topicsRepository.getTopics(),
) { userData, topics ->
val followedTopics = topics
.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = topic.id in userData.followedTopics,
)
}
when (sortBy) {
NAME -> followedTopics.sortedBy { it.topic.name }
else -> followedTopics
operator fun invoke(sortBy: TopicSortField = NONE): Flow<List<FollowableTopic>> = combine(
userDataRepository.userData,
topicsRepository.getTopics(),
) { userData, topics ->
val followedTopics = topics
.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = topic.id in userData.followedTopics,
)
}
when (sortBy) {
NAME -> followedTopics.sortedBy { it.topic.name }
else -> followedTopics
}
}
}

@ -45,14 +45,13 @@ data class UserNewsResource internal constructor(
followableTopics = newsResource.topics.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = userData.followedTopics.contains(topic.id),
isFollowed = topic.id in userData.followedTopics,
)
},
isSaved = userData.bookmarkedNewsResources.contains(newsResource.id),
hasBeenViewed = userData.viewedNewsResources.contains(newsResource.id),
isSaved = newsResource.id in userData.bookmarkedNewsResources,
hasBeenViewed = newsResource.id in userData.viewedNewsResources,
)
}
fun List<NewsResource>.mapToUserNewsResources(userData: UserData): List<UserNewsResource> {
return map { UserNewsResource(it, userData) }
}
fun List<NewsResource>.mapToUserNewsResources(userData: UserData): List<UserNewsResource> =
map { UserNewsResource(it, userData) }

@ -24,10 +24,10 @@ import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.checkSelfPermission
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.InboxStyle
import androidx.core.app.NotificationManagerCompat
@ -57,30 +57,24 @@ class SystemTrayNotifier @Inject constructor(
override fun postNewsNotifications(
newsResources: List<NewsResource>,
) = with(context) {
if (ActivityCompat.checkSelfPermission(
this,
permission.POST_NOTIFICATIONS,
) != PackageManager.PERMISSION_GRANTED
) {
if (checkSelfPermission(this, permission.POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
return
}
val truncatedNewsResources = newsResources
.take(MAX_NUM_NOTIFICATIONS)
val truncatedNewsResources = newsResources.take(MAX_NUM_NOTIFICATIONS)
val newsNotifications = truncatedNewsResources
.map { newsResource ->
createNewsNotification {
setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
)
.setContentTitle(newsResource.title)
.setContentText(newsResource.content)
.setContentIntent(newsPendingIntent(newsResource))
.setGroup(NEWS_NOTIFICATION_GROUP)
.setAutoCancel(true)
}
val newsNotifications = truncatedNewsResources.map { newsResource ->
createNewsNotification {
setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
)
.setContentTitle(newsResource.title)
.setContentText(newsResource.content)
.setContentIntent(newsPendingIntent(newsResource))
.setGroup(NEWS_NOTIFICATION_GROUP)
.setAutoCancel(true)
}
}
val summaryNotification = createNewsNotification {
val title = getString(
R.string.news_notification_group_summary,
@ -117,9 +111,7 @@ class SystemTrayNotifier @Inject constructor(
newsResources: List<NewsResource>,
title: String,
): InboxStyle = newsResources
.fold(InboxStyle()) { inboxStyle, newsResource ->
inboxStyle.addLine(newsResource.title)
}
.fold(InboxStyle()) { inboxStyle, newsResource -> inboxStyle.addLine(newsResource.title) }
.setBigContentTitle(title)
.setSummaryText(title)
}

@ -25,7 +25,6 @@ import dagger.hilt.android.testing.HiltTestApplication
* A custom runner to set up the instrumented application class for tests.
*/
class NiaTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
override fun newApplication(cl: ClassLoader, name: String, context: Context): Application =
super.newApplication(cl, HiltTestApplication::class.java.name, context)
}

@ -43,9 +43,7 @@ class TestNewsRepository : NewsRepository {
}
}
query.filterNewsIds?.let { filterNewsIds ->
result = newsResources.filter {
filterNewsIds.contains(it.id)
}
result = newsResources.filter { it.id in filterNewsIds }
}
result
}

@ -32,7 +32,5 @@ class TestRecentSearchRepository : RecentSearchRepository {
cachedRecentSearches.add(RecentSearchQuery(searchQuery))
}
override suspend fun clearRecentSearches() {
cachedRecentSearches.clear()
}
override suspend fun clearRecentSearches() = cachedRecentSearches.clear()
}

@ -29,18 +29,15 @@ class TestSearchContentsRepository : SearchContentsRepository {
private val cachedTopics: MutableList<Topic> = mutableListOf()
private val cachedNewsResources: MutableList<NewsResource> = mutableListOf()
override suspend fun populateFtsData() { /* no-op */ }
override suspend fun populateFtsData() = Unit
override fun searchContents(searchQuery: String): Flow<SearchResult> = flowOf(
SearchResult(
topics = cachedTopics.filter {
it.name.contains(searchQuery) ||
it.shortDescription.contains(searchQuery) ||
it.longDescription.contains(searchQuery)
searchQuery in it.name || searchQuery in it.shortDescription || searchQuery in it.longDescription
},
newsResources = cachedNewsResources.filter {
it.content.contains(searchQuery) ||
it.title.contains(searchQuery)
searchQuery in it.content || searchQuery in it.title
},
),
)

@ -33,9 +33,8 @@ class TestTopicsRepository : TopicsRepository {
override fun getTopics(): Flow<List<Topic>> = topicsFlow
override fun getTopic(id: String): Flow<Topic> {
return topicsFlow.map { topics -> topics.find { it.id == id }!! }
}
override fun getTopic(id: String): Flow<Topic> =
topicsFlow.map { topics -> topics.find { it.id == id }!! }
/**
* A test-only API to allow controlling the list of topics from tests.

@ -32,11 +32,7 @@ import org.junit.runner.Description
class MainDispatcherRule(
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
override fun starting(description: Description) {
Dispatchers.setMain(testDispatcher)
}
override fun starting(description: Description) = Dispatchers.setMain(testDispatcher)
override fun finished(description: Description) {
Dispatchers.resetMain()
}
override fun finished(description: Description) = Dispatchers.resetMain()
}

@ -26,5 +26,5 @@ class TestAnalyticsHelper : AnalyticsHelper {
events.add(event)
}
fun hasLogged(event: AnalyticsEvent) = events.contains(event)
fun hasLogged(event: AnalyticsEvent) = event in events
}

@ -26,9 +26,7 @@ class TestSyncManager : SyncManager {
override val isSyncing: Flow<Boolean> = syncStatusFlow
override fun requestSync() {
TODO("Not yet implemented")
}
override fun requestSync(): Unit = TODO("Not yet implemented")
/**
* A test-only API to set the sync status from tests.

@ -50,7 +50,7 @@ fun rememberMetricsStateHolder(): Holder {
*/
@Composable
fun TrackJank(
vararg keys: Any?,
vararg keys: Any,
reportMetric: suspend CoroutineScope.(state: Holder) -> Unit,
) {
val metrics = rememberMetricsStateHolder()
@ -65,7 +65,7 @@ fun TrackJank(
*/
@Composable
fun TrackDisposableJank(
vararg keys: Any?,
vararg keys: Any,
reportMetric: DisposableEffectScope.(state: Holder) -> DisposableEffectResult,
) {
val metrics = rememberMetricsStateHolder()

@ -64,9 +64,7 @@ fun LazyStaggeredGridScope.newsFeed(
key = { it.id },
contentType = { "newsFeedItem" },
) { userNewsResource ->
val resourceUrl by remember {
mutableStateOf(Uri.parse(userNewsResource.url))
}
val resourceUrl by remember { mutableStateOf(Uri.parse(userNewsResource.url)) }
val context = LocalContext.current
val analyticsHelper = LocalAnalyticsHelper.current
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()

@ -47,6 +47,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
@ -237,7 +238,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
Image(
modifier = Modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.img_empty_bookmarks),
colorFilter = if (iconTint != null) ColorFilter.tint(iconTint) else null,
colorFilter = if (iconTint != Color.Unspecified) ColorFilter.tint(iconTint) else null,
contentDescription = null,
)

@ -24,7 +24,7 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute
const val bookmarksRoute = "bookmarks_route"
fun NavController.navigateToBookmarks(navOptions: NavOptions? = null) {
fun NavController.navigateToBookmarks(navOptions: NavOptions) {
this.navigate(bookmarksRoute, navOptions)
}

@ -30,7 +30,7 @@ const val forYouNavigationRoute = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}"
private const val DEEP_LINK_URI_PATTERN =
"https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}"
fun NavController.navigateToForYou(navOptions: NavOptions? = null) {
fun NavController.navigateToForYou(navOptions: NavOptions) {
this.navigate(forYouNavigationRoute, navOptions)
}

@ -26,7 +26,7 @@ import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute
private const val INTERESTS_GRAPH_ROUTE_PATTERN = "interests_graph"
const val interestsRoute = "interests_route"
fun NavController.navigateToInterestsGraph(navOptions: NavOptions? = null) {
fun NavController.navigateToInterestsGraph(navOptions: NavOptions) {
this.navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions)
}

@ -264,9 +264,7 @@ fun EmptySearchResultBody(
) { offset ->
tryAnotherSearchString.getStringAnnotations(start = offset, end = offset)
.firstOrNull()
?.let {
onInterestsClick()
}
?.let { onInterestsClick() }
}
}
}
@ -519,9 +517,7 @@ private fun SearchTextField(
}
},
onValueChange = {
if (!it.contains("\n")) {
onSearchQueryChanged(it)
}
if ("\n" !in it) onSearchQueryChanged(it)
},
modifier = Modifier
.fillMaxWidth()

@ -117,22 +117,16 @@ private fun topicUiState(
when (followedTopicToTopicResult) {
is Result.Success -> {
val (followedTopics, topic) = followedTopicToTopicResult.data
val followed = followedTopics.contains(topicId)
TopicUiState.Success(
followableTopic = FollowableTopic(
topic = topic,
isFollowed = followed,
isFollowed = topicId in followedTopics,
),
)
}
is Result.Loading -> {
TopicUiState.Loading
}
is Result.Error -> {
TopicUiState.Error
}
is Result.Loading -> TopicUiState.Loading
is Result.Error -> TopicUiState.Error
}
}
}
@ -151,26 +145,13 @@ private fun newsUiState(
val bookmark: Flow<Set<String>> = userDataRepository.userData
.map { it.bookmarkedNewsResources }
return combine(
newsStream,
bookmark,
::Pair,
)
return combine(newsStream, bookmark, ::Pair)
.asResult()
.map { newsToBookmarksResult ->
when (newsToBookmarksResult) {
is Result.Success -> {
val news = newsToBookmarksResult.data.first
NewsUiState.Success(news)
}
is Result.Loading -> {
NewsUiState.Loading
}
is Result.Error -> {
NewsUiState.Error
}
is Result.Success -> NewsUiState.Success(newsToBookmarksResult.data.first)
is Result.Loading -> NewsUiState.Loading
is Result.Error -> NewsUiState.Error
}
}
}

@ -40,7 +40,7 @@ internal class TopicArgs(val topicId: String) {
fun NavController.navigateToTopic(topicId: String) {
val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING)
this.navigate("topic_route/$encodedId") {
navigate("topic_route/$encodedId") {
launchSingleTop = true
}
}

@ -34,15 +34,13 @@ import org.jetbrains.uast.UQualifiedReferenceExpression
*/
class DesignSystemDetector : Detector(), Detector.UastScanner {
override fun getApplicableUastTypes(): List<Class<out UElement>> {
return listOf(
UCallExpression::class.java,
UQualifiedReferenceExpression::class.java,
)
}
override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(
UCallExpression::class.java,
UQualifiedReferenceExpression::class.java,
)
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun createUastHandler(context: JavaContext): UElementHandler =
object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
val name = node.methodName ?: return
val preferredName = METHOD_NAMES[name] ?: return
@ -55,7 +53,6 @@ class DesignSystemDetector : Detector(), Detector.UastScanner {
reportIssue(context, node, name, preferredName)
}
}
}
companion object {
@JvmField

Loading…
Cancel
Save