mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
PoC UI for search & discover
This commit is contained in:
parent
d08bd40a68
commit
0281d00907
6 changed files with 109 additions and 17 deletions
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.api.seerr
|
|||
import com.github.damontecres.wholphin.api.seerr.infrastructure.ApiClient
|
||||
import okhttp3.Call
|
||||
import okhttp3.OkHttpClient
|
||||
import timber.log.Timber
|
||||
|
||||
class SeerrApiClient(
|
||||
val baseUrl: String,
|
||||
|
|
@ -13,6 +14,7 @@ class SeerrApiClient(
|
|||
okHttpClient
|
||||
.newBuilder()
|
||||
.addInterceptor {
|
||||
Timber.d("SeerrApiClient: ${it.request().method} ${it.request().url}")
|
||||
it.proceed(
|
||||
it
|
||||
.request()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.services
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.github.damontecres.wholphin.api.seerr.SeerrApiClient
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ class SeerrApi(
|
|||
)
|
||||
private set
|
||||
|
||||
val active: Boolean get() = api.baseUrl.isNotNullOrBlank() && api.apiKey.isNotNullOrBlank()
|
||||
|
||||
fun update(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResul
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
typealias SearchResult = SearchGet200ResponseResultsInner
|
||||
typealias SeerrSearchResult = SearchGet200ResponseResultsInner
|
||||
|
||||
@Singleton
|
||||
class SeerrService
|
||||
|
|
@ -15,6 +15,8 @@ class SeerrService
|
|||
) {
|
||||
private val api: SeerrApiClient get() = seerApi.api
|
||||
|
||||
val active: Boolean get() = seerApi.active
|
||||
|
||||
suspend fun init() {
|
||||
api.searchApi.discoverTvGet(1)
|
||||
}
|
||||
|
|
@ -22,7 +24,7 @@ class SeerrService
|
|||
suspend fun search(
|
||||
query: String,
|
||||
page: Int = 1,
|
||||
): List<SearchResult> =
|
||||
): List<SeerrSearchResult> =
|
||||
api.searchApi
|
||||
.searchGet(query = query, page = page)
|
||||
.results
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrSearchResult
|
||||
import com.github.damontecres.wholphin.services.SeerrService
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.EpisodeCard
|
||||
|
|
@ -51,7 +53,9 @@ import com.github.damontecres.wholphin.ui.components.SearchEditTextBox
|
|||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
|
|
@ -73,11 +77,13 @@ class SearchViewModel
|
|||
constructor(
|
||||
val api: ApiClient,
|
||||
val navigationManager: NavigationManager,
|
||||
private val seerrService: SeerrService,
|
||||
) : ViewModel() {
|
||||
val movies = MutableLiveData<SearchResult>(SearchResult.NoQuery)
|
||||
val series = MutableLiveData<SearchResult>(SearchResult.NoQuery)
|
||||
val episodes = MutableLiveData<SearchResult>(SearchResult.NoQuery)
|
||||
val collections = MutableLiveData<SearchResult>(SearchResult.NoQuery)
|
||||
val seerrResults = MutableLiveData<SearchResult>(SearchResult.NoQuery)
|
||||
|
||||
private var currentQuery: String? = null
|
||||
|
||||
|
|
@ -95,11 +101,13 @@ class SearchViewModel
|
|||
searchInternal(query, BaseItemKind.SERIES, series)
|
||||
searchInternal(query, BaseItemKind.EPISODE, episodes)
|
||||
searchInternal(query, BaseItemKind.BOX_SET, collections)
|
||||
searchSeerr(query)
|
||||
} else {
|
||||
movies.value = SearchResult.NoQuery
|
||||
series.value = SearchResult.NoQuery
|
||||
episodes.value = SearchResult.NoQuery
|
||||
collections.value = SearchResult.NoQuery
|
||||
seerrResults.value = SearchResult.NoQuery
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +141,16 @@ class SearchViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private fun searchSeerr(query: String) {
|
||||
if (seerrService.active) {
|
||||
viewModelScope.launchIO {
|
||||
seerrResults.setValueOnMain(SearchResult.Searching)
|
||||
val results = seerrService.search(query)
|
||||
seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getHints(query: String) {
|
||||
// TODO
|
||||
// api.searchApi.getSearchHints()
|
||||
|
|
@ -151,12 +169,17 @@ sealed interface SearchResult {
|
|||
data class Success(
|
||||
val items: List<BaseItem?>,
|
||||
) : SearchResult
|
||||
|
||||
data class SuccessSeerr(
|
||||
val items: List<SeerrSearchResult>,
|
||||
) : SearchResult
|
||||
}
|
||||
|
||||
private const val MOVIE_ROW = 0
|
||||
private const val COLLECTION_ROW = MOVIE_ROW + 1
|
||||
private const val SERIES_ROW = COLLECTION_ROW + 1
|
||||
private const val EPISODE_ROW = SERIES_ROW + 1
|
||||
private const val SEERR_ROW = EPISODE_ROW + 1
|
||||
|
||||
@Composable
|
||||
fun SearchPage(
|
||||
|
|
@ -171,6 +194,7 @@ fun SearchPage(
|
|||
val collections by viewModel.collections.observeAsState(SearchResult.NoQuery)
|
||||
val series by viewModel.series.observeAsState(SearchResult.NoQuery)
|
||||
val episodes by viewModel.episodes.observeAsState(SearchResult.NoQuery)
|
||||
val seerrResults by viewModel.seerrResults.observeAsState(SearchResult.NoQuery)
|
||||
|
||||
val query = rememberTextFieldState()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -287,6 +311,18 @@ fun SearchPage(
|
|||
)
|
||||
},
|
||||
)
|
||||
searchResultRow(
|
||||
title = context.getString(R.string.discover),
|
||||
result = seerrResults,
|
||||
rowIndex = SEERR_ROW,
|
||||
position = position,
|
||||
focusRequester = focusRequester,
|
||||
onClickItem = { index, item ->
|
||||
// TODO seerr
|
||||
},
|
||||
onClickPosition = { position = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,6 +400,40 @@ fun LazyListScope.searchResultRow(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
is SearchResult.SuccessSeerr -> {
|
||||
if (r.items.isEmpty()) {
|
||||
SearchResultPlaceholder(
|
||||
title = title,
|
||||
message = stringResource(R.string.no_results),
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
ItemRow(
|
||||
title = title,
|
||||
items = r.items,
|
||||
onClickItem = { index, item -> },
|
||||
onLongClickItem = { _, _ -> },
|
||||
modifier = modifier,
|
||||
cardContent = { index: Int, item: SeerrSearchResult?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
SeasonCard(
|
||||
title = item?.title ?: item?.name,
|
||||
subtitle = null,
|
||||
name = item?.title ?: item?.name,
|
||||
imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }, // TODO
|
||||
isFavorite = false,
|
||||
isPlayed = false,
|
||||
unplayedItemCount = -1,
|
||||
playedPercentage = -1.0,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
imageHeight = Cards.height2x3,
|
||||
modifier = mod,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.github.damontecres.wholphin.ui.seerr
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
|
|
@ -14,10 +12,12 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.SeerrService
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -47,17 +47,32 @@ fun SeerrDiscoverPage(
|
|||
) {
|
||||
val movies by viewModel.discoverMovies.observeAsState(listOf())
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.padding(16.dp),
|
||||
) {
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
items(movies) { movie ->
|
||||
Text(
|
||||
text = movie.title,
|
||||
ItemRow(
|
||||
title = "Movies",
|
||||
items = movies,
|
||||
onClickItem = { index, item -> },
|
||||
onLongClickItem = { index, item -> },
|
||||
cardContent = { index: Int, item: MovieResult?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
SeasonCard(
|
||||
title = item?.title,
|
||||
subtitle = null,
|
||||
name = item?.title,
|
||||
imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }, // TODO
|
||||
isFavorite = false,
|
||||
isPlayed = false,
|
||||
unplayedItemCount = -1,
|
||||
playedPercentage = -1.0,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
imageHeight = Cards.height2x3,
|
||||
modifier = mod,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -715,7 +715,7 @@ components:
|
|||
required:
|
||||
- id
|
||||
- mediaType
|
||||
- title
|
||||
# - title
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue