From 89d505b1c135053b27b624e12357b22cdcb334fb Mon Sep 17 00:00:00 2001 From: karrar-abbas Date: Wed, 16 Jul 2025 17:44:52 +0300 Subject: [PATCH 1/3] fix: increase sensitivity for content detection fun --- .../detectImageContent/SensitiveContentDetection.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/detectImageContent/src/main/java/com/madrid/detectImageContent/SensitiveContentDetection.kt b/detectImageContent/src/main/java/com/madrid/detectImageContent/SensitiveContentDetection.kt index 84c423a2f..dd19feee7 100644 --- a/detectImageContent/src/main/java/com/madrid/detectImageContent/SensitiveContentDetection.kt +++ b/detectImageContent/src/main/java/com/madrid/detectImageContent/SensitiveContentDetection.kt @@ -19,7 +19,12 @@ class SensitiveContentDetection(private val context: Context) { val result = LABELS.zip(output[0].toList()).toMap() val pornScore = result["porn"] ?: 0f val sexyScore = result["sexy"] ?: 0f - return pornScore > THRESHOLD || sexyScore > THRESHOLD + val drawingsScore = result["drawings"] ?: 0f + val hentaiScore = result["hentai"] ?: 0f + val neutralScore = result["neutral"] ?: 0f + return pornScore > SENSITIVE_THRESHOLD || sexyScore > SENSITIVE_THRESHOLD + || drawingsScore > SENSITIVE_THRESHOLD || hentaiScore > SENSITIVE_THRESHOLD + || neutralScore < NEUTRAL_THRESHOLD } private fun prepareInputBitmap(bitmap: Bitmap): Array>> { @@ -54,7 +59,8 @@ class SensitiveContentDetection(private val context: Context) { companion object { private const val MODEL_FILE_NAME = "nsfw_detect.tflite" private const val MODEL_INPUT_SIZE = 224 - private const val THRESHOLD = 0.7f + private const val SENSITIVE_THRESHOLD = 0.35f + private const val NEUTRAL_THRESHOLD = 0.70f private val LABELS = listOf("drawings", "hentai", "neutral", "porn", "sexy") } From b70bbdd47fbeb338ac8fabe354659709cf4b97a4 Mon Sep 17 00:00:00 2001 From: karrar-abbas Date: Wed, 16 Jul 2025 22:09:11 +0300 Subject: [PATCH 2/3] refactor: reimplement filtered image in detect image content layer --- app/src/main/java/com/madrid/movio/app.kt | 3 +- .../detectImageContent/FilteredImage.kt | 90 +++++++++++++++++++ .../filteredImage/FilteredImage.kt | 75 ---------------- .../filteredImage/FilteredImageUiState.kt | 9 -- .../filteredImage/FilteredImageViewModel.kt | 35 -------- 5 files changed, 91 insertions(+), 121 deletions(-) create mode 100644 detectImageContent/src/main/java/com/madrid/detectImageContent/FilteredImage.kt delete mode 100644 presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImage.kt delete mode 100644 presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageUiState.kt delete mode 100644 presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageViewModel.kt diff --git a/app/src/main/java/com/madrid/movio/app.kt b/app/src/main/java/com/madrid/movio/app.kt index 794754c0b..1f97d76fd 100644 --- a/app/src/main/java/com/madrid/movio/app.kt +++ b/app/src/main/java/com/madrid/movio/app.kt @@ -1,7 +1,6 @@ package com.madrid.movio -import com.madrid.presentation.screens.searchScreen.SearchViewModel import com.madrid.data.dataSource.local.SearchLocalDataSource import com.madrid.data.dataSource.remote.search.SearchRemoteSourceImpl import com.madrid.data.repositories.SearchLocalSource @@ -15,6 +14,7 @@ import com.madrid.domain.usecase.searchUseCase.MediaUseCase import com.madrid.domain.usecase.searchUseCase.PreferredMediaUseCase import com.madrid.domain.usecase.searchUseCase.RecentSearchUseCase import com.madrid.domain.usecase.searchUseCase.TrendingMediaUseCase +import com.madrid.presentation.screens.searchScreen.SearchViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -37,7 +37,6 @@ val app = module { ) } - //domain single { ArtistUseCase(get()) } single { MediaUseCase(get()) } diff --git a/detectImageContent/src/main/java/com/madrid/detectImageContent/FilteredImage.kt b/detectImageContent/src/main/java/com/madrid/detectImageContent/FilteredImage.kt new file mode 100644 index 000000000..523130d87 --- /dev/null +++ b/detectImageContent/src/main/java/com/madrid/detectImageContent/FilteredImage.kt @@ -0,0 +1,90 @@ +package com.madrid.detectImageContent + +import android.graphics.Bitmap + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.DefaultAlpha +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp + + +@Composable +fun FilteredImage( + imageUrl: String, + contentDescription: String?, + modifier: Modifier = Modifier, + alignment: Alignment = Alignment.Center, + contentScale: ContentScale = ContentScale.Fit, + alpha: Float = DefaultAlpha, + colorFilter: ColorFilter? = null, +) { + val context = LocalContext.current + var image by remember { mutableStateOf(null) } + var isNSFW by remember { mutableStateOf(false) } + val getImage = remember(context) { GetImageBitmap(context) } + val detection = remember(context) { SensitiveContentDetection(context) } + + if (imageUrl.isNotBlank()) { + LaunchedEffect(imageUrl) { + try { + image = getImage.getImageBitmapFromUrl(imageUrl) + isNSFW = detection.sensitiveContentDetection(image!!) + } catch (_: Exception) { + + } + } + + FilteredImageContent( + image = image, + isNSFW = isNSFW, + contentDescription = contentDescription, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter + ) + } +} + +@Composable +fun FilteredImageContent( + image: Bitmap?, + isNSFW: Boolean, + contentDescription: String?, + modifier: Modifier, + alignment: Alignment, + contentScale: ContentScale, + alpha: Float, + colorFilter: ColorFilter? +) { + image?.let { + Image( + bitmap = it.asImageBitmap(), + contentDescription = contentDescription, + modifier = modifier + .size(300.dp) + .then( + if (isNSFW) Modifier.blur(16.dp) + else Modifier + ), + contentScale = contentScale, + alignment = alignment, + alpha = alpha, + colorFilter = colorFilter + ) + } +} diff --git a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImage.kt b/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImage.kt deleted file mode 100644 index 81946dd09..000000000 --- a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImage.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.madrid.presentation.composables.filteredImage - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.blur -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.DefaultAlpha -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp -import org.koin.androidx.compose.koinViewModel - - -@Composable -fun FilteredImage( - imageUrl: String, - contentDescription: String?, - modifier: Modifier = Modifier, - alignment: Alignment = Alignment.Center, - contentScale: ContentScale = ContentScale.Fit, - alpha: Float = DefaultAlpha, - colorFilter: ColorFilter? = null, - viewModel: FilteredImageViewModel = koinViewModel() -) { - val state by viewModel.state.collectAsState() - - LaunchedEffect(imageUrl) { - viewModel.loadAndAnalyze(imageUrl) - } - - FilteredImageContent( - state, - contentDescription = contentDescription, - modifier = modifier, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter - ) -} - - -@Composable -fun FilteredImageContent( - state: FilteredImageUiState, - contentDescription: String?, - modifier: Modifier = Modifier, - alignment: Alignment = Alignment.Center, - contentScale: ContentScale = ContentScale.Fit, - alpha: Float = DefaultAlpha, - colorFilter: ColorFilter? = null -) { - state.bitmap?.let { - Image( - bitmap = it.asImageBitmap(), - contentDescription = contentDescription, - modifier = modifier - .size(300.dp) - .then( - if (state.isNSFW) Modifier.blur(16.dp) - else Modifier - ), - contentScale = contentScale, - alignment = alignment, - alpha = alpha, - colorFilter = colorFilter - ) - } -} diff --git a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageUiState.kt b/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageUiState.kt deleted file mode 100644 index e73348ef0..000000000 --- a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageUiState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.madrid.presentation.composables.filteredImage - -import android.graphics.Bitmap - -data class FilteredImageUiState( - val bitmap: Bitmap? = null, - val isNSFW: Boolean = false, - val error: String? = null -) \ No newline at end of file diff --git a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageViewModel.kt b/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageViewModel.kt deleted file mode 100644 index f6ca6c95c..000000000 --- a/presentation/src/main/java/com/madrid/presentation/composables/filteredImage/FilteredImageViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.madrid.presentation.composables.filteredImage - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.madrid.detectImageContent.GetImageBitmap -import com.madrid.detectImageContent.SensitiveContentDetection -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import org.koin.android.annotation.KoinViewModel - -@KoinViewModel -class FilteredImageViewModel( - private val getImageBitmap: GetImageBitmap, - private val contentDetector: SensitiveContentDetection -) : ViewModel() { - - private val _uiState = MutableStateFlow(FilteredImageUiState()) - val state = _uiState.asStateFlow() - - fun loadAndAnalyze(imageUrl: String) { - viewModelScope.launch(Dispatchers.IO) { - val bitmapImage = getImageBitmap.getImageBitmapFromUrl(imageUrl) - val isNSFW = contentDetector.sensitiveContentDetection(bitmapImage) - _uiState.update { - it.copy( - bitmap = bitmapImage, - isNSFW = isNSFW, - ) - } - } - } -} From c1e827ee0e3c4edc421e8389d35b9f8ffae45aed Mon Sep 17 00:00:00 2001 From: karrar-abbas Date: Wed, 16 Jul 2025 22:10:24 +0300 Subject: [PATCH 3/3] fix: compose library in detect image content --- detectImageContent/build.gradle.kts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/detectImageContent/build.gradle.kts b/detectImageContent/build.gradle.kts index eb1cdf6ef..c851f50a0 100644 --- a/detectImageContent/build.gradle.kts +++ b/detectImageContent/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) } android { @@ -30,10 +31,16 @@ android { kotlinOptions { jvmTarget = "17" } + + android { + buildFeatures { + compose = true + } + } } dependencies { - + implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material)