Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/main/java/com/pinakes/app/data/model/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,17 @@ data class GenreNode(
val children: List<GenreNode> = emptyList(),
)

/**
* A distinct language value present in the catalogue, from GET /catalog/languages
* (#282). `libri.lingua` is free text, so the app must offer these real values —
* not a hardcoded ISO-code list that never matched the stored strings.
*/
@Serializable
data class LanguageValue(
val language: String = "",
val count: Int = 0,
)

// ---------- Loans / reservations ----------
@Serializable
data class LoansData(
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/pinakes/app/data/network/PinakesApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.pinakes.app.data.model.Envelope
import com.pinakes.app.data.model.ForgotRequest
import com.pinakes.app.data.model.GenreNode
import com.pinakes.app.data.model.HealthPayload
import com.pinakes.app.data.model.LanguageValue
import com.pinakes.app.data.model.LoanItem
import com.pinakes.app.data.model.LoansData
import com.pinakes.app.data.model.LoginRequest
Expand Down Expand Up @@ -113,6 +114,10 @@ interface PinakesApi {
@GET("catalog/genres")
suspend fun genres(): Envelope<List<GenreNode>>

// #282: real catalogue language values for the language filter.
@GET("catalog/languages")
suspend fun languages(): Envelope<List<LanguageValue>>

// ---- Reviews ----
/** Aggregate rating + the user's own review + a page of other users' reviews for a book. */
@GET("catalog/books/{id}/reviews")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.pinakes.app.data.model.AvailabilityCalendar
import com.pinakes.app.data.model.BookDetail
import com.pinakes.app.data.model.BookSummary
import com.pinakes.app.data.model.GenreNode
import com.pinakes.app.data.model.LanguageValue
import com.pinakes.app.data.network.ApiResult
import com.pinakes.app.data.network.ErrorCodes
import com.pinakes.app.data.network.NetworkModule
Expand Down Expand Up @@ -147,6 +148,13 @@ class CatalogRepository(
return apiCall { api.genres() }
}

// #282: real catalogue language values, so the language filter offers what
// the collection actually holds instead of a hardcoded ISO-code list.
suspend fun languages(): ApiResult<List<LanguageValue>> {
val api = network.api()
return apiCall { api.languages() }
}

/**
* Per-day availability for [id] (~180 days from today) used to drive the colored
* loan-request calendar. Mirrors the website's availability view.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,14 @@ fun SearchFilterSheet(
label = { Text(stringResource(R.string.filters_all_languages)) },
colors = chipColors,
)
SearchLanguageOptions.forEach { opt ->
val selected = state.language == opt.code
// #282: real catalogue language values (free text), loaded from
// GET /catalog/languages, instead of a hardcoded ISO-code list.
state.languages.forEach { lang ->
val selected = state.language == lang.language
FilterChip(
selected = selected,
onClick = { onLanguageChange(if (selected) null else opt.code) },
label = { Text(stringResource(opt.labelRes)) },
onClick = { onLanguageChange(if (selected) null else lang.language) },
label = { Text(lang.language) },
leadingIcon = if (selected) {
{ Icon(Icons.Outlined.Check, contentDescription = null, modifier = Modifier.height(18.dp)) }
} else null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.pinakes.app.R
import com.pinakes.app.data.model.BookSummary
import com.pinakes.app.data.model.GenreNode
import com.pinakes.app.data.model.LanguageValue
import com.pinakes.app.data.network.ApiResult
import com.pinakes.app.data.repository.CatalogRepository
import com.pinakes.app.data.repository.SearchFilters
Expand Down Expand Up @@ -40,6 +41,7 @@ data class SearchUiState(
val language: String? = null,
val sort: BookSort = BookSort.NEWEST,
val genres: List<GenreNode> = emptyList(),
val languages: List<LanguageValue> = emptyList(),
val items: List<BookSummary> = emptyList(),
val nextCursor: String? = null,
val totalCount: Int? = null,
Expand All @@ -66,18 +68,6 @@ data class SearchUiState(
get() = query.isBlank() && !hasActiveFilters && items.isEmpty() && !loading && error == null
}

/** Languages offered in the filter sheet. `code` is sent to the API; `labelRes` is shown. */
data class LanguageOption(val code: String, @StringRes val labelRes: Int)

val SearchLanguageOptions: List<LanguageOption> = listOf(
LanguageOption("ita", R.string.lang_italian),
LanguageOption("eng", R.string.lang_english),
LanguageOption("fra", R.string.lang_french),
LanguageOption("deu", R.string.lang_german),
LanguageOption("spa", R.string.lang_spanish),
LanguageOption("lat", R.string.lang_latin),
)

@HiltViewModel
class SearchViewModel @Inject constructor(private val catalog: CatalogRepository) : ViewModel() {

Expand All @@ -97,6 +87,7 @@ class SearchViewModel @Inject constructor(private val catalog: CatalogRepository

init {
loadGenres()
loadLanguages()
// Catalog lands on the full listing — browse-all (empty query, no filters).
runSearch(reset = true)
}
Expand All @@ -110,6 +101,17 @@ class SearchViewModel @Inject constructor(private val catalog: CatalogRepository
}
}

// #282: populate the language filter from the real catalogue values instead
// of a hardcoded ISO-code list that never matched the free-text libri.lingua.
private fun loadLanguages() {
viewModelScope.launch {
when (val res = catalog.languages()) {
is ApiResult.Success -> _state.update { it.copy(languages = res.data) }
is ApiResult.Failure -> { /* non-fatal: filter simply has no language chips */ }
}
}
}

fun onQueryChange(value: String) {
_state.update { it.copy(query = value) }
// Debounced auto-search as the user types.
Expand Down
Loading