Skip to content

Commit

Permalink
prepare searching
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed May 19, 2024
1 parent 86126b3 commit 5dcb561
Show file tree
Hide file tree
Showing 16 changed files with 685 additions and 6 deletions.
108 changes: 108 additions & 0 deletions anilist/src/commonMain/graphql/SearchQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
query SearchQuery(
$query: String,
$adultContent: Boolean,
$preventGenres: [String],
$type: MediaType
) {
Page {
media(
search: $query,
isAdult: $adultContent,
genre_not_in: $preventGenres,
sort: [POPULARITY_DESC],
type: $type
) {
id,
idMal,
type,
status(version: 2),
description(asHtml: true),
episodes,
duration,
chapters,
countryOfOrigin,
popularity,
isFavourite,
isFavouriteBlocked,
isAdult,
format,
bannerImage,
coverImage {
extraLarge,
large,
medium,
color
},
averageScore,
title {
english,
native,
romaji,
userPreferred
},
nextAiringEpisode {
episode,
airingAt
},
rankings {
rank,
allTime,
year,
season,
type
},
genres,
characters(sort: [FAVOURITES_DESC,RELEVANCE]) {
nodes {
id,
name {
first
middle
last
full
native
userPreferred
},
image {
large
medium
},
description(asHtml: true)
gender,
dateOfBirth {
year
month
day
},
bloodType,
isFavourite,
isFavouriteBlocked,
}
},
mediaListEntry {
score(format: POINT_5),
status,
progress,
repeat,
startedAt {
year,
month,
day
}
},
trailer {
id,
site,
thumbnail
},
siteUrl,
chapters,
volumes,
startDate {
year,
month,
day
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package dev.datlag.aniflow.anilist

import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dev.datlag.aniflow.anilist.state.CollectionState
import dev.datlag.aniflow.anilist.type.MediaType
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update

class SearchRepository(
private val client: ApolloClient,
private val fallbackClient: ApolloClient,
private val nsfw: Flow<Boolean> = flowOf(false),
private val viewManga: Flow<Boolean> = flowOf(false),
) {

private val search = MutableStateFlow<String?>(null)
private val _type = MutableStateFlow(MediaType.UNKNOWN__)

val searchQuery: String?
get() = search.value?.ifBlank { null }

@OptIn(FlowPreview::class)
private val searchDebounced = search.debounce {
if (it.isNullOrBlank()) {
0
} else {
100
}
}

@OptIn(ExperimentalCoroutinesApi::class)
val type = _type.transformLatest {
return@transformLatest if (it == MediaType.UNKNOWN__) {
emitAll(viewManga.map { m ->
if (m) {
MediaType.MANGA
} else {
MediaType.ANIME
}
})
} else {
emit(it)
}
}.distinctUntilChanged()

private val query = combine(searchDebounced, type, nsfw.distinctUntilChanged()) { s, t, n ->
if (s.isNullOrBlank()) {
null
} else {
Query(
search = s,
nsfw = n,
type = t
)
}
}.distinctUntilChanged()

@OptIn(ExperimentalCoroutinesApi::class)
private val fallbackQuery = query.transformLatest {
return@transformLatest if (it == null) {
emit(it)
} else {
emitAll(fallbackClient.query(it.toGraphQL()).toFlow())
}
}.mapLatest {
if (it == null) {
return@mapLatest CollectionState.None
}

val data = it.data
if (data == null) {
if (it.hasErrors()) {
CollectionState.fromSearchGraphQL(data)
} else {
CollectionState.None
}
} else {
CollectionState.fromSearchGraphQL(data)
}
}.distinctUntilChanged()

@OptIn(ExperimentalCoroutinesApi::class)
val result = query.transformLatest {
return@transformLatest if (it == null) {
emit(it)
} else {
emitAll(client.query(it.toGraphQL()).toFlow())
}
}.mapLatest {
if (it == null) {
return@mapLatest CollectionState.None
}

val data = it.data
if (data == null) {
if (it.hasErrors()) {
CollectionState.fromSearchGraphQL(data)
} else {
CollectionState.None
}
} else {
CollectionState.fromSearchGraphQL(data)
}
}.transformLatest {
return@transformLatest if (it.isError) {
emitAll(fallbackQuery)
} else {
emit(it)
}
}

fun query(query: String) {
search.update { query }
}

fun setType(type: MediaType) {
_type.update { type }
}

fun viewAnime() = setType(MediaType.ANIME)
fun viewManga() = setType(MediaType.MANGA)

fun toggleType() {
_type.update {
if (it == MediaType.MANGA) {
MediaType.ANIME
} else {
MediaType.MANGA
}
}
}

private data class Query(
val search: String,
val nsfw: Boolean,
val type: MediaType
) {
fun toGraphQL() = SearchQuery(
query = Optional.present(search),
adultContent = if (nsfw) {
Optional.absent()
} else {
Optional.present(nsfw)
},
preventGenres = if (nsfw) {
Optional.absent()
} else {
Optional.present(AdultContent.Genre.allTags)
},
type = if (type == MediaType.UNKNOWN__) {
Optional.absent()
} else {
Optional.present(type)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ internal fun ListQuery.StartDate.toLocalDate(): LocalDate? {
)
}

internal fun SearchQuery.StartDate.toLocalDate(): LocalDate? {
return LocalDate(
year = year ?: return null,
monthNumber = month ?: return null,
dayOfMonth = day ?: 1
)
}

internal fun AiringQuery.StartedAt.toLocalDate(): LocalDate? {
return LocalDate(
year = year ?: return null,
Expand Down Expand Up @@ -107,3 +115,11 @@ internal fun ListQuery.StartedAt.toLocalDate(): LocalDate? {
dayOfMonth = day ?: 1
)
}

internal fun SearchQuery.StartedAt.toLocalDate(): LocalDate? {
return LocalDate(
year = year ?: return null,
monthNumber = month ?: return null,
dayOfMonth = day ?: 1
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ data class Character(
native = name.native?.ifBlank { null },
userPreferred = name.userPreferred?.ifBlank { null }
)

constructor(name: SearchQuery.Name) : this(
first = name.first?.ifBlank { null },
middle = name.middle?.ifBlank { null },
last = name.last?.ifBlank { null },
full = name.full?.ifBlank { null },
native = name.native?.ifBlank { null },
userPreferred = name.userPreferred?.ifBlank { null }
)
}

@Serializable
Expand Down Expand Up @@ -174,6 +183,11 @@ data class Character(
large = image.large?.ifBlank { null },
medium = image.medium?.ifBlank { null },
)

constructor(image: SearchQuery.Image) : this(
large = image.large?.ifBlank { null },
medium = image.medium?.ifBlank { null },
)
}

@Serializable
Expand Down Expand Up @@ -292,6 +306,18 @@ data class Character(
year = birth.year
)
}

operator fun invoke(birth: SearchQuery.DateOfBirth): BirthDate? {
if (birth.day == null && birth.month == null && birth.year == null) {
return null
}

return BirthDate(
day = birth.day,
month = birth.month,
year = birth.year
)
}
}
}

Expand Down Expand Up @@ -397,5 +423,22 @@ data class Character(
isFavoriteBlocked = character.isFavouriteBlocked
)
}

operator fun invoke(character: SearchQuery.Node) : Character? {
val name = character.name?.let(::Name) ?: return null
val image = character.image?.let(::Image) ?: return null

return Character(
id = character.id,
name = name,
image = image,
gender = character.gender?.ifBlank { null },
bloodType = character.bloodType?.ifBlank { null },
birthDate = character.dateOfBirth?.let { BirthDate(it) },
description = character.description?.ifBlank { null },
isFavorite = character.isFavourite,
isFavoriteBlocked = character.isFavouriteBlocked
)
}
}
}
Loading

0 comments on commit 5dcb561

Please sign in to comment.