diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7dfe004..5d46d9a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -119,6 +119,7 @@ kotlin { implementation(project(":firebase")) implementation(project(":anilist")) implementation(project(":model")) + implementation(project(":nekos")) implementation(project(":settings")) implementation(project(":trace")) } diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendCompose.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendCompose.kt index f56c95f..35ecb95 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendCompose.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/common/ExtendCompose.kt @@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetState @@ -128,6 +130,50 @@ fun LazyListState.isScrollingUp(): Boolean { }.value } +@Composable +fun LazyGridState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { + mutableStateOf(firstVisibleItemIndex) + } + var previousScrollOffset by remember(this) { + mutableStateOf(firstVisibleItemScrollOffset) + } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +} + +@Composable +fun LazyStaggeredGridState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { + mutableStateOf(firstVisibleItemIndex) + } + var previousScrollOffset by remember(this) { + mutableStateOf(firstVisibleItemScrollOffset) + } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +} + /** * Checks if the modal is currently expanded or a swipe action is in progress to be expanded. */ diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt index 62efb3c..7f05638 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/module/NetworkModule.kt @@ -29,6 +29,8 @@ import org.kodein.di.bindSingleton import org.kodein.di.instance import dev.datlag.aniflow.common.nullableFirebaseInstance import dev.datlag.aniflow.model.safeFirstOrNull +import dev.datlag.aniflow.nekos.Nekos +import dev.datlag.aniflow.nekos.NekosRepository import dev.datlag.aniflow.other.UserHelper import dev.datlag.aniflow.settings.Settings import dev.datlag.aniflow.trace.Trace @@ -113,6 +115,12 @@ data object NetworkModule { baseUrl("https://api.trace.moe/") }.create() } + bindSingleton { + val builder = instance() + builder.build { + baseUrl("https://api.nekosapi.com/v3/") + }.create() + } bindSingleton { val appSettings = instance() @@ -168,5 +176,13 @@ data object NetworkModule { trace = instance(), ) } + bindSingleton { + val appSettings = instance() + + NekosRepository( + nekos = instance(), + nsfw = appSettings.adultContent + ) + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/HidingNavigationBar.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/HidingNavigationBar.kt index 764d728..7d0b9b6 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/HidingNavigationBar.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/component/HidingNavigationBar.kt @@ -5,20 +5,21 @@ import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Wallpaper import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.rounded.Favorite -import androidx.compose.material.icons.rounded.FavoriteBorder -import androidx.compose.material.icons.rounded.Home -import androidx.compose.material.icons.rounded.Wallpaper +import androidx.compose.material.icons.rounded.* import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -29,6 +30,7 @@ import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.SharedRes import dev.datlag.aniflow.common.isScrollingUp import dev.datlag.aniflow.ui.navigation.RootConfig +import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -71,30 +73,48 @@ fun HidingNavigationBar( containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.contentColorFor(NavigationBarDefaults.containerColor) ) { + val isWallpaper = remember(selected) { + selected is NavigationBarState.Wallpaper + } + val isHome = remember(selected) { + selected is NavigationBarState.Home + } + val isFavorites = remember(selected) { + selected is NavigationBarState.Favorite + } + NavigationBarItem( onClick = { - if (selected !is NavigationBarState.Wallpaper) { + if (!isWallpaper) { onWallpaper() } }, - selected = selected is NavigationBarState.Wallpaper, + selected = isWallpaper, icon = { - Icon( - imageVector = selected.wallpaperIcon, - contentDescription = null + Image( + modifier = Modifier.size(24.dp), + painter = painterResource( + if (isWallpaper) { + SharedRes.images.cat_filled + } else { + SharedRes.images.cat_rounded + } + ), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) ) }, label = { - Text(text = "Wallpapers") + Text(text = "Nekos") } ) NavigationBarItem( onClick = { - if (selected !is NavigationBarState.Home) { + if (!isHome) { onHome() } }, - selected = selected is NavigationBarState.Home, + selected = isHome, icon = { Icon( imageVector = selected.homeIcon, @@ -107,11 +127,11 @@ fun HidingNavigationBar( ) NavigationBarItem( onClick = { - if (selected !is NavigationBarState.Favorite) { + if (!isFavorites) { onFavorites() } }, - selected = selected is NavigationBarState.Favorite, + selected = isFavorites, icon = { Icon( imageVector = selected.favoriteIcon, @@ -157,6 +177,9 @@ sealed interface NavigationBarState { data object Wallpaper : NavigationBarState { override val unselectedIcon: ImageVector get() = Icons.Rounded.Wallpaper + + override val selectedIcon: ImageVector + get() = Icons.Rounded.Image } @Serializable diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperComponent.kt index 02759b7..13520bc 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperComponent.kt @@ -1,9 +1,20 @@ package dev.datlag.aniflow.ui.navigation.screen.wallpaper +import dev.datlag.aniflow.nekos.NekosRepository +import dev.datlag.aniflow.nekos.model.Rating import dev.datlag.aniflow.ui.navigation.Component +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow interface WallpaperComponent : Component { + val adultContent: Flow + val rating: StateFlow + + val state: Flow + fun viewHome() fun viewFavorites() + + fun filter(rating: Rating) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreen.kt index 860bf45..6ee9150 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreen.kt @@ -1,23 +1,145 @@ package dev.datlag.aniflow.ui.navigation.screen.wallpaper -import androidx.compose.material3.Scaffold +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.FilterList +import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState +import com.maxkeppeler.sheets.option.OptionDialog +import com.maxkeppeler.sheets.option.models.DisplayMode +import com.maxkeppeler.sheets.option.models.Option +import com.maxkeppeler.sheets.option.models.OptionConfig +import com.maxkeppeler.sheets.option.models.OptionSelection +import dev.chrisbanes.haze.haze +import dev.datlag.aniflow.LocalHaze +import dev.datlag.aniflow.common.isScrollingUp +import dev.datlag.aniflow.common.merge +import dev.datlag.aniflow.nekos.NekosRepository +import dev.datlag.aniflow.nekos.model.Rating import dev.datlag.aniflow.ui.navigation.screen.component.HidingNavigationBar import dev.datlag.aniflow.ui.navigation.screen.component.NavigationBarState +import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle +import io.github.aakira.napier.Napier +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun WallpaperScreen(component: WallpaperComponent) { + val listState = rememberLazyStaggeredGridState() + Scaffold( + floatingActionButton = { + val dialogState = rememberUseCaseState( + visible = false + ) + val adultContent by component.adultContent.collectAsStateWithLifecycle(false) + val rating by component.rating.collectAsStateWithLifecycle() + + OptionDialog( + state = dialogState, + config = OptionConfig( + mode = DisplayMode.LIST + ), + selection = OptionSelection.Single( + options = listOf( + Option( + titleText = "Safe", + selected = rating is Rating.Safe, + ), + Option( + titleText = "Suggestive", + selected = rating is Rating.Suggestive, + ), + Option( + titleText = "Borderline", + selected = rating is Rating.Borderline, + disabled = !adultContent + ), + Option( + titleText = "Explicit", + selected = rating is Rating.Explicit, + disabled = !adultContent + ) + ), + onSelectOption = { option, _ -> + val filterRating = when (option) { + 1 -> Rating.Suggestive + 2 -> Rating.Borderline + 3 -> Rating.Explicit + else -> Rating.Safe + } + component.filter(filterRating) + } + ) + ) + + ExtendedFloatingActionButton( + onClick = { dialogState.show() }, + expanded = listState.isScrollingUp(), + icon = { + Icon( + imageVector = Icons.Rounded.FilterList, + contentDescription = null + ) + }, + text = { + Text(text = "Filter") + } + ) + }, bottomBar = { HidingNavigationBar( - visible = true, + visible = listState.isScrollingUp(), selected = NavigationBarState.Wallpaper, onWallpaper = { }, onHome = component::viewHome, onFavorites = component::viewFavorites ) } - ) { + ) { padding -> + val state by component.state.collectAsStateWithLifecycle(NekosRepository.State.None) + when (val current = state) { + is NekosRepository.State.None -> Text(text = "Loading") + is NekosRepository.State.Error -> Text(text = "Error") + is NekosRepository.State.Success -> { + LazyVerticalStaggeredGrid( + state = listState, + modifier = Modifier.haze(state = LocalHaze.current), + columns = StaggeredGridCells.Adaptive(120.dp), + contentPadding = padding.merge(PaddingValues(16.dp)), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalItemSpacing = 16.dp + ) { + items(current.response.items, key = { it.id }) { + Card( + modifier = Modifier.animateItemPlacement(), + onClick = { + Napier.e(it.toString()) + } + ) { + AsyncImage( + modifier = Modifier.fillMaxSize(), + model = it.imageUrl ?: it.sampleUrl, + contentDescription = null, + contentScale = ContentScale.Crop + ) + } + } + } + } + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreenComponent.kt index 98ea7bc..e8a5088 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/wallpaper/WallpaperScreenComponent.kt @@ -7,7 +7,13 @@ import com.arkivanov.decompose.ComponentContext import dev.chrisbanes.haze.HazeState import dev.datlag.aniflow.LocalHaze import dev.datlag.aniflow.common.onRender +import dev.datlag.aniflow.nekos.NekosRepository +import dev.datlag.aniflow.nekos.model.Rating +import dev.datlag.aniflow.settings.Settings +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import org.kodein.di.DI +import org.kodein.di.instance class WallpaperScreenComponent( componentContext: ComponentContext, @@ -16,6 +22,13 @@ class WallpaperScreenComponent( private val onFavorites: () -> Unit, ) : WallpaperComponent, ComponentContext by componentContext { + private val appSettings by instance() + override val adultContent: Flow = appSettings.adultContent + + private val nekosRepository by instance() + override val rating: StateFlow = nekosRepository.rating + override val state: Flow = nekosRepository.response + @Composable override fun render() { val haze = remember { HazeState() } @@ -36,4 +49,8 @@ class WallpaperScreenComponent( override fun viewFavorites() { onFavorites() } + + override fun filter(rating: Rating) { + nekosRepository.rating(rating) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/moko-resources/images/cat_filled.svg b/composeApp/src/commonMain/moko-resources/images/cat_filled.svg new file mode 100644 index 0000000..a91f29a --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/cat_filled.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/composeApp/src/commonMain/moko-resources/images/cat_rounded.svg b/composeApp/src/commonMain/moko-resources/images/cat_rounded.svg new file mode 100644 index 0000000..a5b058c --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/cat_rounded.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/nekos/build.gradle.kts b/nekos/build.gradle.kts new file mode 100644 index 0000000..a6a8af3 --- /dev/null +++ b/nekos/build.gradle.kts @@ -0,0 +1,53 @@ +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.serialization) + alias(libs.plugins.ksp) +} + +val artifact = VersionCatalog.artifactName("trace") + +kotlin { + androidTarget() + jvm() + + iosX64() + iosArm64() + iosSimulatorArm64() + + applyDefaultHierarchyTemplate() + + sourceSets { + commonMain.dependencies { + api(libs.coroutines) + implementation(libs.tooling) + api(libs.ktorfit) + implementation(libs.serialization) + + api(project(":model")) + implementation(project(":firebase")) + } + } +} + +dependencies { + add("kspCommonMainMetadata", libs.ktorfit.ksp) + add("kspAndroid", libs.ktorfit.ksp) + add("kspJvm", libs.ktorfit.ksp) + add("kspIosX64", libs.ktorfit.ksp) + add("kspIosArm64", libs.ktorfit.ksp) + add("kspIosSimulatorArm64", libs.ktorfit.ksp) +} + +android { + compileSdk = Configuration.compileSdk + namespace = artifact + + defaultConfig { + minSdk = Configuration.minSdk + } + compileOptions { + sourceCompatibility = CompileOptions.sourceCompatibility + targetCompatibility = CompileOptions.targetCompatibility + } +} diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/AdultContent.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/AdultContent.kt new file mode 100644 index 0000000..203d63e --- /dev/null +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/AdultContent.kt @@ -0,0 +1,34 @@ +package dev.datlag.aniflow.nekos + +internal data object AdultContent { + + sealed interface Tag : CharSequence { + val tag: String + + override val length: Int + get() = tag.length + + override operator fun get(index: Int): Char { + return tag[index] + } + + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + return tag.subSequence(startIndex, endIndex) + } + + data object Bikini : Tag { + override val tag: String = "Bikini" + } + + companion object { + val all = listOf(Bikini) + val allTags = all.map { it.tag } + + fun exists(tag: String): Boolean { + return all.any { t -> + t.tag.equals(tag, ignoreCase = true) + } + } + } + } +} \ No newline at end of file diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/Nekos.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/Nekos.kt new file mode 100644 index 0000000..f9df2ce --- /dev/null +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/Nekos.kt @@ -0,0 +1,14 @@ +package dev.datlag.aniflow.nekos + +import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.Query +import dev.datlag.aniflow.nekos.model.ImagesResponse + +interface Nekos { + + @GET("images") + suspend fun images( + @Query("rating") rating: String, + @Query("offset") offset: Int? = null, + ): ImagesResponse +} \ No newline at end of file diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt new file mode 100644 index 0000000..f1e4d84 --- /dev/null +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/NekosRepository.kt @@ -0,0 +1,85 @@ +package dev.datlag.aniflow.nekos + +import dev.datlag.aniflow.model.CatchResult +import dev.datlag.aniflow.nekos.model.ImagesResponse +import dev.datlag.aniflow.nekos.model.Rating +import kotlinx.coroutines.flow.* +import kotlinx.serialization.Serializable + +class NekosRepository( + private val nekos: Nekos, + private val nsfw: Flow = flowOf(false), +) { + + private val offset = MutableStateFlow(null) + val rating = MutableStateFlow(Rating.Safe) + + private val result = combine(offset, rating) { o, r -> + CatchResult.repeat(2) { + nekos.images( + rating = r.query, + offset = o, + ) + } + }.map { + State.fromResponse(it.asNullableSuccess()) + } + val response = combine(nsfw.distinctUntilChanged(), result, rating) { n, q, r -> + if (n) { + q + } else { + if (r is Rating.Explicit || r is Rating.Borderline) { + rating(Rating.Safe) + } + + when (q) { + is State.Success -> { + State.Success( + ImagesResponse( + items = q.response.items.filterNot { it.hasAdultTag }, + count = q.response.count + ) + ) + } + else -> q + } + } + } + + fun offset(value: Int) { + offset.update { value } + } + + fun rating(value: Rating) { + rating.update { + if (it != value) { + offset.update { null } + } + value + } + } + + @Serializable + sealed interface State { + @Serializable + data object None : State + + @Serializable + data class Success( + val response: ImagesResponse + ) : State + + @Serializable + data object Error : State + + companion object { + fun fromResponse(response: ImagesResponse?): State { + return if (response == null || response.isError) { + Error + } else { + Success(response) + } + } + } + } +} \ No newline at end of file diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/ImagesResponse.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/ImagesResponse.kt new file mode 100644 index 0000000..1fc4745 --- /dev/null +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/ImagesResponse.kt @@ -0,0 +1,44 @@ +package dev.datlag.aniflow.nekos.model + +import dev.datlag.aniflow.nekos.AdultContent +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class ImagesResponse( + @SerialName("items") val items: List, + @SerialName("count") val count: Int = items.size, +) { + @Transient + val isError: Boolean = items.isEmpty() + + @Serializable + data class Item( + @SerialName("id") val id: Int, + @SerialName("image_url") val imageUrl: String? = null, + @SerialName("sample_url") val sampleUrl: String? = null, + @SerialName("characters") val characters: List = emptyList(), + @SerialName("tags") val tags: List = emptyList(), + ) { + @Transient + val hasAdultTag: Boolean = tags.any { + it.isNsfw || AdultContent.Tag.exists(it.name) + } + + @Serializable + data class Character( + @SerialName("id") val id: Int, + @SerialName("name") val name: String, + @SerialName("description") val description: String? = null, + @SerialName("gender") val gender: String? = null, + ) + + @Serializable + data class Tag( + @SerialName("id") val id: Int, + @SerialName("name") val name: String, + @SerialName("is_nsfw") val isNsfw: Boolean = false, + ) + } +} diff --git a/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/Rating.kt b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/Rating.kt new file mode 100644 index 0000000..9847979 --- /dev/null +++ b/nekos/src/commonMain/kotlin/dev/datlag/aniflow/nekos/model/Rating.kt @@ -0,0 +1,28 @@ +package dev.datlag.aniflow.nekos.model + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Rating { + val query: String + + @Serializable + data object Safe : Rating { + override val query: String = "safe" + } + + @Serializable + data object Suggestive : Rating { + override val query: String = "suggestive" + } + + @Serializable + data object Borderline : Rating { + override val query: String = "borderline" + } + + @Serializable + data object Explicit : Rating { + override val query: String = "explicit" + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index bfd2268..62a5eed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,7 @@ include(":composeApp", ":composeApp:sekret") include(":firebase") include(":anilist") include(":model") +include(":nekos") include(":settings") include(":trace")