Skip to content

Commit 7970c27

Browse files
authored
NSFW Filter v1 (#4)
1 parent 204f82a commit 7970c27

File tree

66 files changed

+836
-72
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+836
-72
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies {
4242
implementation project(":data")
4343
implementation project(":domain")
4444
implementation project(":network")
45+
implementation project(":nsfw")
4546
implementation project(":presentation")
4647
implementation project(":storage")
4748
implementation di.koinCore

app/src/main/java/com/shifthackz/joyreactor/JoyReactorApplication.kt

+6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.shifthackz.joyreactor
22

33
import android.app.Application
4+
import android.util.Log
45
import com.shifthackz.joyreactor.data.di.dataModule
56
import com.shifthackz.joyreactor.domain.di.domainModule
67
import com.shifthackz.joyreactor.network.di.networkModule
8+
import com.shifthackz.joyreactor.nsfw.di.nsfwModule
79
import com.shifthackz.joyreactor.presentation.di.viewModelModule
810
import com.shifthackz.joyreactor.storage.di.storageModule
911
import org.koin.android.ext.koin.androidContext
@@ -13,9 +15,13 @@ class JoyReactorApplication : Application() {
1315

1416
override fun onCreate() {
1517
super.onCreate()
18+
Thread.currentThread().setUncaughtExceptionHandler { _, t ->
19+
Log.e("Fatal", "Uncaught", t)
20+
}
1621
startKoin {
1722
androidContext(this@JoyReactorApplication)
1823
modules(
24+
nsfwModule,
1925
networkModule,
2026
storageModule,
2127
*dataModule,

data/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ dependencies {
1515
implementation project(":network")
1616
implementation project(":storage")
1717
implementation di.koinCore
18+
implementation di.koinAndroid
1819
implementation org.coroutines
20+
implementation androidx.datastore
1921
}

data/src/main/java/com/shifthackz/joyreactor/data/di/Modules.kt

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.shifthackz.joyreactor.data.di
22

3+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
34
import com.shifthackz.joyreactor.data.datasource.local.PostsLocalDataSource
45
import com.shifthackz.joyreactor.data.datasource.local.TagsLocalDataSource
56
import com.shifthackz.joyreactor.data.datasource.remote.CommentsRemoteDataSource
67
import com.shifthackz.joyreactor.data.datasource.remote.PostsRemoteDataSource
78
import com.shifthackz.joyreactor.data.datasource.remote.SectionsRemoteDataSource
89
import com.shifthackz.joyreactor.data.datasource.remote.TagsRemoteDataSource
10+
import com.shifthackz.joyreactor.data.preference.PreferenceManagerImpl
911
import com.shifthackz.joyreactor.data.repository.CommentsRepositoryImpl
1012
import com.shifthackz.joyreactor.data.repository.PostsRepositoryImpl
1113
import com.shifthackz.joyreactor.data.repository.SectionsRepositoryImpl
@@ -14,13 +16,30 @@ import com.shifthackz.joyreactor.domain.datasource.CommentsDataSource
1416
import com.shifthackz.joyreactor.domain.datasource.PostsDataSource
1517
import com.shifthackz.joyreactor.domain.datasource.SectionsDataSource
1618
import com.shifthackz.joyreactor.domain.datasource.TagsDataSource
19+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
1720
import com.shifthackz.joyreactor.domain.repository.CommentsRepository
1821
import com.shifthackz.joyreactor.domain.repository.PostsRepository
1922
import com.shifthackz.joyreactor.domain.repository.SectionsRepository
2023
import com.shifthackz.joyreactor.domain.repository.TagsRepository
24+
import kotlinx.coroutines.CoroutineScope
25+
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.Job
27+
import org.koin.android.ext.koin.androidContext
2128
import org.koin.core.module.dsl.factoryOf
2229
import org.koin.dsl.bind
2330
import org.koin.dsl.module
31+
import java.io.File
32+
33+
internal val preferenceModule = module {
34+
single<PreferenceManager> {
35+
36+
val cc = PreferenceDataStoreFactory.create(null, emptyList(), CoroutineScope(Dispatchers.IO + Job())
37+
) {
38+
File(androidContext().filesDir, "datastore/preferences.preferences_pb")
39+
}
40+
PreferenceManagerImpl(cc)
41+
}
42+
}
2443

2544
internal val remoteDataSourceModule = module {
2645
factoryOf(::PostsRemoteDataSource) bind PostsDataSource.Remote::class
@@ -41,4 +60,5 @@ internal val repositoryModule = module {
4160
factoryOf(::TagsRepositoryImpl) bind TagsRepository::class
4261
}
4362

44-
val dataModule = (remoteDataSourceModule + localDataSourceModule + repositoryModule).toTypedArray()
63+
val dataModule =
64+
(preferenceModule + remoteDataSourceModule + localDataSourceModule + repositoryModule).toTypedArray()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.shifthackz.joyreactor.data.preference
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.booleanPreferencesKey
6+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
7+
import kotlinx.coroutines.coroutineScope
8+
import kotlinx.coroutines.flow.Flow
9+
import kotlinx.coroutines.flow.map
10+
11+
internal class PreferenceManagerImpl(
12+
private val dataStore: DataStore<Preferences>,
13+
) : PreferenceManager {
14+
15+
override suspend fun getNsfwFilter(): Flow<Boolean> {
16+
return dataStore.data.map { prefs -> prefs[fieldNsfw] ?: true }
17+
}
18+
19+
override suspend fun setNsfwFilter(value: Boolean): Unit = coroutineScope {
20+
dataStore.updateData { prefs ->
21+
prefs.toMutablePreferences().apply {
22+
set(fieldNsfw, value)
23+
}
24+
}
25+
}
26+
27+
companion object {
28+
val fieldNsfw = booleanPreferencesKey("key_nsfw")
29+
}
30+
}

dependencies.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ext {
2525
accompanistSystemUiControllerVersion = '0.30.1'
2626
zoomableVersion = '1.5.0'
2727
splashVersion = '1.0.1'
28+
dataStoreVersion = '1.0.0'
2829

2930
testJunitVersion = '4.13.2'
3031

@@ -49,6 +50,7 @@ ext {
4950
pagingCompose : "androidx.paging:paging-compose:$pagingComposeVersion",
5051
palette : "androidx.palette:palette:$paletteVersion",
5152
splash : "androidx.core:core-splashscreen:$splashVersion",
53+
datastore : "androidx.datastore:datastore-preferences:$dataStoreVersion",
5254
]
5355
google = [
5456
gson : "com.google.code.gson:gson:$gsonVersion",

domain/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ android {
1212
dependencies {
1313
api project(":entity")
1414
implementation di.koinCore
15+
implementation org.coroutines
1516
}

domain/src/main/java/com/shifthackz/joyreactor/domain/di/Modules.kt

+31-4
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,52 @@ package com.shifthackz.joyreactor.domain.di
22

33
import com.shifthackz.joyreactor.domain.usecase.comment.FetchPostCommentsUseCase
44
import com.shifthackz.joyreactor.domain.usecase.comment.FetchPostCommentsUseCaseImpl
5+
import com.shifthackz.joyreactor.domain.usecase.nsfw.ObserveNsfwFilterUseCase
6+
import com.shifthackz.joyreactor.domain.usecase.nsfw.ObserveNsfwFilterUseCaseImpl
57
import com.shifthackz.joyreactor.domain.usecase.post.FetchPostsPageUseCase
68
import com.shifthackz.joyreactor.domain.usecase.post.FetchPostsPageUseCaseImpl
79
import com.shifthackz.joyreactor.domain.usecase.post.GetFullPostUseCase
810
import com.shifthackz.joyreactor.domain.usecase.post.GetFullPostUseCaseImpl
911
import com.shifthackz.joyreactor.domain.usecase.sections.FetchSectionsUseCase
1012
import com.shifthackz.joyreactor.domain.usecase.sections.FetchSectionsUseCaseImpl
13+
import com.shifthackz.joyreactor.domain.usecase.settings.GetSettingsUseCase
14+
import com.shifthackz.joyreactor.domain.usecase.settings.GetSettingsUseCaseImpl
15+
import com.shifthackz.joyreactor.domain.usecase.settings.ObserveSettingsUseCase
16+
import com.shifthackz.joyreactor.domain.usecase.settings.ObserveSettingsUseCaseImpl
17+
import com.shifthackz.joyreactor.domain.usecase.settings.UpdateSettingsUseCase
18+
import com.shifthackz.joyreactor.domain.usecase.settings.UpdateSettingsUseCaseImpl
1119
import com.shifthackz.joyreactor.domain.usecase.tags.FetchTagsUseCase
1220
import com.shifthackz.joyreactor.domain.usecase.tags.FetchTagsUseCaseImpl
1321
import com.shifthackz.joyreactor.domain.usecase.tags.SearchTagsUseCase
1422
import com.shifthackz.joyreactor.domain.usecase.tags.SearchTagsUseCaseImpl
23+
import com.shifthackz.joyreactor.entity.postQualifier
24+
import com.shifthackz.joyreactor.entity.sectionQualifier
25+
import com.shifthackz.joyreactor.entity.tagQualifier
1526
import org.koin.core.module.dsl.factoryOf
1627
import org.koin.dsl.bind
1728
import org.koin.dsl.module
1829

1930
val domainModule = module {
20-
factoryOf(::FetchPostsPageUseCaseImpl) bind FetchPostsPageUseCase::class
2131
factoryOf(::GetFullPostUseCaseImpl) bind GetFullPostUseCase::class
2232
factoryOf(::FetchPostCommentsUseCaseImpl) bind FetchPostCommentsUseCase::class
23-
factoryOf(::FetchSectionsUseCaseImpl) bind FetchSectionsUseCase::class
24-
factoryOf(::FetchTagsUseCaseImpl) bind FetchTagsUseCase::class
25-
factoryOf(::SearchTagsUseCaseImpl) bind SearchTagsUseCase::class
33+
factoryOf(::GetSettingsUseCaseImpl) bind GetSettingsUseCase::class
34+
factoryOf(::UpdateSettingsUseCaseImpl) bind UpdateSettingsUseCase::class
35+
factoryOf(::ObserveSettingsUseCaseImpl) bind ObserveSettingsUseCase::class
36+
factoryOf(::ObserveNsfwFilterUseCaseImpl) bind ObserveNsfwFilterUseCase::class
37+
38+
factory<SearchTagsUseCase> {
39+
SearchTagsUseCaseImpl(get(), get(tagQualifier))
40+
}
41+
42+
factory<FetchTagsUseCase> {
43+
FetchTagsUseCaseImpl(get(), get(tagQualifier))
44+
}
45+
46+
factory<FetchSectionsUseCase> {
47+
FetchSectionsUseCaseImpl(get(), get(sectionQualifier))
48+
}
49+
50+
factory<FetchPostsPageUseCase> {
51+
FetchPostsPageUseCaseImpl(get(), get(postQualifier))
52+
}
2653
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.shifthackz.joyreactor.domain.nsfw
2+
3+
import com.shifthackz.joyreactor.entity.Nsfw
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface NsfwFilter<T : Any> {
7+
8+
suspend fun nsfwEnabled(): Flow<Boolean>
9+
10+
suspend fun filter(data: List<T>): List<Nsfw<T>>
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.shifthackz.joyreactor.domain.preference
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
6+
interface PreferenceManager {
7+
suspend fun getNsfwFilter(): Flow<Boolean>
8+
suspend fun setNsfwFilter(value: Boolean)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.shifthackz.joyreactor.domain.usecase.nsfw
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
interface ObserveNsfwFilterUseCase {
6+
suspend operator fun invoke(): Flow<Boolean>
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.shifthackz.joyreactor.domain.usecase.nsfw
2+
3+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
4+
import kotlinx.coroutines.flow.Flow
5+
6+
internal class ObserveNsfwFilterUseCaseImpl(
7+
private val preferenceManager: PreferenceManager,
8+
): ObserveNsfwFilterUseCase {
9+
10+
override suspend fun invoke(): Flow<Boolean> = preferenceManager.getNsfwFilter()
11+
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.shifthackz.joyreactor.domain.usecase.post
22

3+
import com.shifthackz.joyreactor.entity.Nsfw
34
import com.shifthackz.joyreactor.entity.PagePayload
45
import com.shifthackz.joyreactor.entity.Post
56

67
interface FetchPostsPageUseCase {
7-
suspend operator fun invoke(url: String): Result<PagePayload<Post>>
8+
suspend operator fun invoke(url: String): Result<PagePayload<Nsfw<Post>>>
89
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
package com.shifthackz.joyreactor.domain.usecase.post
22

3+
import com.shifthackz.joyreactor.domain.nsfw.NsfwFilter
34
import com.shifthackz.joyreactor.domain.repository.PostsRepository
5+
import com.shifthackz.joyreactor.entity.Nsfw
46
import com.shifthackz.joyreactor.entity.PagePayload
57
import com.shifthackz.joyreactor.entity.Post
8+
import kotlinx.coroutines.coroutineScope
69

710
internal class FetchPostsPageUseCaseImpl(
811
private val postsRepository: PostsRepository,
12+
private val nsfwFilter: NsfwFilter<Post>,
913
) : FetchPostsPageUseCase {
1014

11-
override suspend fun invoke(url: String): Result<PagePayload<Post>> {
12-
return postsRepository.fetchPage(url)
15+
override suspend fun invoke(url: String): Result<PagePayload<Nsfw<Post>>> = coroutineScope {
16+
postsRepository.fetchPage(url).fold(
17+
onSuccess = { payload ->
18+
Result.success(
19+
PagePayload(
20+
nsfwFilter.filter(payload.data),
21+
payload.next,
22+
payload.prev,
23+
)
24+
)
25+
},
26+
onFailure = Result.Companion::failure,
27+
)
1328
}
1429
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.shifthackz.joyreactor.domain.usecase.sections
22

3+
import com.shifthackz.joyreactor.entity.Nsfw
34
import com.shifthackz.joyreactor.entity.Section
45

56
interface FetchSectionsUseCase {
6-
suspend operator fun invoke(): Result<List<Section>>
7+
suspend operator fun invoke(): Result<List<Nsfw<Section>>>
78
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package com.shifthackz.joyreactor.domain.usecase.sections
22

3+
import com.shifthackz.joyreactor.domain.nsfw.NsfwFilter
34
import com.shifthackz.joyreactor.domain.repository.SectionsRepository
5+
import com.shifthackz.joyreactor.entity.Nsfw
46
import com.shifthackz.joyreactor.entity.Section
7+
import kotlinx.coroutines.coroutineScope
58

69
internal class FetchSectionsUseCaseImpl(
710
private val sectionsRepository: SectionsRepository,
11+
private val nsfwFilter: NsfwFilter<Section>,
812
) : FetchSectionsUseCase {
913

10-
override suspend fun invoke(): Result<List<Section>> {
11-
return sectionsRepository.fetchSections()
14+
override suspend fun invoke(): Result<List<Nsfw<Section>>> = coroutineScope {
15+
sectionsRepository.fetchSections().fold(
16+
onSuccess = { data -> nsfwFilter.filter(data).let(Result.Companion::success) },
17+
onFailure = Result.Companion::failure,
18+
)
1219
}
1320
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.entity.Settings
4+
5+
interface GetSettingsUseCase {
6+
suspend operator fun invoke(): Result<Settings>
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
4+
import com.shifthackz.joyreactor.entity.Settings
5+
import kotlinx.coroutines.flow.first
6+
7+
internal class GetSettingsUseCaseImpl(
8+
private val preferenceManager: PreferenceManager,
9+
) : GetSettingsUseCase {
10+
11+
override suspend fun invoke(): Result<Settings> {
12+
return Result.success(
13+
Settings(
14+
preferenceManager.getNsfwFilter().first(),
15+
)
16+
)
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.entity.Settings
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface ObserveSettingsUseCase {
7+
suspend operator fun invoke(): Flow<Settings>
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
4+
import com.shifthackz.joyreactor.entity.Settings
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.map
7+
8+
internal class ObserveSettingsUseCaseImpl(
9+
private val preferenceManager: PreferenceManager,
10+
) : ObserveSettingsUseCase {
11+
override suspend fun invoke(): Flow<Settings> {
12+
//combine(listOf(flow1,flow2,flow3)){}.collect{}
13+
return preferenceManager.getNsfwFilter().map {
14+
Settings(it)
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.entity.Settings
4+
5+
interface UpdateSettingsUseCase {
6+
suspend operator fun invoke(settings: Settings)
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.shifthackz.joyreactor.domain.usecase.settings
2+
3+
import com.shifthackz.joyreactor.domain.preference.PreferenceManager
4+
import com.shifthackz.joyreactor.entity.Settings
5+
import kotlinx.coroutines.coroutineScope
6+
7+
internal class UpdateSettingsUseCaseImpl(
8+
private val preferenceManager: PreferenceManager,
9+
) : UpdateSettingsUseCase {
10+
11+
override suspend fun invoke(settings: Settings) = coroutineScope {
12+
preferenceManager.setNsfwFilter(settings.nsfw)
13+
}
14+
}

0 commit comments

Comments
 (0)