module; move UserNewsResource to model module. Implement unread dot for bookmarked articles. Keep the flows cold in UserNewsResourceRepository.pull/595/head
parent
ebfbb5bafd
commit
57c13d84bd
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.repository
|
||||||
|
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.mapToUserNewsResources
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a [UserNewsResourceRepository] by combining a [NewsRepository] with a
|
||||||
|
* [UserDataRepository].
|
||||||
|
*/
|
||||||
|
class CompositeUserNewsResourceRepository @Inject constructor(
|
||||||
|
val newsRepository: NewsRepository,
|
||||||
|
val userDataRepository: UserDataRepository,
|
||||||
|
) : UserNewsResourceRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns available news resources (joined with user data) matching the given query.
|
||||||
|
*/
|
||||||
|
override fun getUserNewsResources(
|
||||||
|
query: NewsResourceQuery,
|
||||||
|
): Flow<List<UserNewsResource>> =
|
||||||
|
newsRepository.getNewsResources(query)
|
||||||
|
.combine(userDataRepository.userData) { newsResources, userData ->
|
||||||
|
newsResources.mapToUserNewsResources(userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns available news resources (joined with user data) for the followed topics.
|
||||||
|
*/
|
||||||
|
override fun getUserNewsResourcesForFollowedTopics(): Flow<List<UserNewsResource>> =
|
||||||
|
userDataRepository.userData.map { it.followedTopics }.distinctUntilChanged()
|
||||||
|
.flatMapLatest { followedTopics ->
|
||||||
|
when {
|
||||||
|
followedTopics.isEmpty() -> flowOf(emptyList())
|
||||||
|
else -> getUserNewsResources(NewsResourceQuery(filterTopicIds = followedTopics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBookmarkedUserNewsResources(): Flow<List<UserNewsResource>> =
|
||||||
|
userDataRepository.userData.map { it.bookmarkedNewsResources }.distinctUntilChanged()
|
||||||
|
.flatMapLatest { bookmarkedNewsResources ->
|
||||||
|
when {
|
||||||
|
bookmarkedNewsResources.isEmpty() -> flowOf(emptyList())
|
||||||
|
else -> getUserNewsResources(NewsResourceQuery(filterNewsIds = bookmarkedNewsResources))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.di
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Qualifier
|
|
||||||
annotation class ApplicationScope
|
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
@Module
|
|
||||||
object CoroutinesScopesModule {
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@ApplicationScope
|
|
||||||
@Provides
|
|
||||||
fun providesCoroutineScope(): CoroutineScope =
|
|
||||||
CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.repository
|
|
||||||
|
|
||||||
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
|
|
||||||
import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery
|
|
||||||
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
|
|
||||||
import com.google.samples.apps.nowinandroid.core.domain.di.ApplicationScope
|
|
||||||
import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource
|
|
||||||
import com.google.samples.apps.nowinandroid.core.domain.model.mapToUserNewsResources
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
|
||||||
import com.google.samples.apps.nowinandroid.core.model.data.UserData
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.filterNot
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.shareIn
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements a [UserNewsResourceRepository] by combining a [NewsRepository] with a
|
|
||||||
* [UserDataRepository].
|
|
||||||
*/
|
|
||||||
class CompositeUserNewsResourceRepository @Inject constructor(
|
|
||||||
@ApplicationScope private val coroutineScope: CoroutineScope,
|
|
||||||
val newsRepository: NewsRepository,
|
|
||||||
val userDataRepository: UserDataRepository,
|
|
||||||
) : UserNewsResourceRepository {
|
|
||||||
|
|
||||||
private val userNewsResources =
|
|
||||||
newsRepository.getNewsResources().mapToUserNewsResources(userDataRepository.userData)
|
|
||||||
.shareIn(coroutineScope, started = WhileSubscribed(5000), replay = 1)
|
|
||||||
|
|
||||||
override fun getUserNewsResources(
|
|
||||||
query: NewsResourceQuery,
|
|
||||||
): Flow<List<UserNewsResource>> =
|
|
||||||
userNewsResources.map { resources ->
|
|
||||||
resources.filter { resource ->
|
|
||||||
query.filterTopicIds?.let { topics -> resource.hasTopic(topics) } ?: true &&
|
|
||||||
query.filterNewsIds?.contains(resource.id) ?: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserNewsResourcesForFollowedTopics(): Flow<List<UserNewsResource>> =
|
|
||||||
userDataRepository.userData.flatMapLatest { getUserNewsResources(NewsResourceQuery(filterTopicIds = it.followedTopics)) }
|
|
||||||
|
|
||||||
private fun UserNewsResource.hasTopic(filterTopicIds: Set<String>) =
|
|
||||||
followableTopics.any { filterTopicIds.contains(it.topic.id) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Flow<List<NewsResource>>.mapToUserNewsResources(
|
|
||||||
userDataStream: Flow<UserData>,
|
|
||||||
): Flow<List<UserNewsResource>> =
|
|
||||||
filterNot { it.isEmpty() }
|
|
||||||
.combine(userDataStream) { newsResources, userData ->
|
|
||||||
newsResources.mapToUserNewsResources(userData)
|
|
||||||
}
|
|
Loading…
Reference in new issue