Search
This commit is contained in:
@@ -105,8 +105,6 @@ dependencies {
|
|||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
ksp "androidx.room:room-compiler:$room_version"
|
ksp "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
implementation "androidx.paging:paging-compose:3.2.1"
|
|
||||||
|
|
||||||
implementation "io.ktor:ktor-client-core:2.3.8"
|
implementation "io.ktor:ktor-client-core:2.3.8"
|
||||||
implementation "io.ktor:ktor-client-okhttp:2.3.8"
|
implementation "io.ktor:ktor-client-okhttp:2.3.8"
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ data class Group(val group: String): TagLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Series(val series: String): TagLike {
|
data class Series(@SerialName("parody") val series: String): TagLike {
|
||||||
override fun toTag() = SearchQuery.Tag("series", series)
|
override fun toTag() = SearchQuery.Tag("series", series)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package xyz.quaver.pupil.networking
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
||||||
|
class GallerySearchSource(val query: SearchQuery?) {
|
||||||
|
private var searchResult: List<Int>? = null
|
||||||
|
private var job: Job? = null
|
||||||
|
|
||||||
|
suspend fun load(range: IntRange): Result<Pair<List<GalleryInfo>, Int>> = runCatching {
|
||||||
|
val searchResult = searchResult ?: (
|
||||||
|
HitomiHttpClient
|
||||||
|
.search(query)
|
||||||
|
.getOrThrow()
|
||||||
|
.toList()
|
||||||
|
.also { searchResult = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
val galleryResults = coroutineScope {
|
||||||
|
searchResult.slice(range).map { galleryID ->
|
||||||
|
async {
|
||||||
|
HitomiHttpClient.getGalleryInfo(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val galleries = galleryResults.map { result ->
|
||||||
|
result.await().getOrThrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair(galleries, searchResult.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() = job?.cancel()
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import xyz.quaver.pupil.hitomi.max_node_size
|
import xyz.quaver.pupil.hitomi.max_node_size
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
@@ -36,7 +37,7 @@ data class Suggestion(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun IntBuffer.toSet(): Set<Int> {
|
fun IntBuffer.toSet(): Set<Int> {
|
||||||
val result = mutableSetOf<Int>()
|
val result = LinkedHashSet<Int>()
|
||||||
|
|
||||||
while (this.hasRemaining()) {
|
while (this.hasRemaining()) {
|
||||||
result.add(this.get())
|
result.add(this.get())
|
||||||
@@ -45,6 +46,11 @@ fun IntBuffer.toSet(): Set<Int> {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val json = Json {
|
||||||
|
isLenient = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
object HitomiHttpClient {
|
object HitomiHttpClient {
|
||||||
private val httpClient = HttpClient(OkHttp)
|
private val httpClient = HttpClient(OkHttp)
|
||||||
|
|
||||||
@@ -193,6 +199,15 @@ object HitomiHttpClient {
|
|||||||
data?.let { getSuggestionsFromData(field, data) } ?: emptyList()
|
data?.let { getSuggestionsFromData(field, data) } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getGalleryInfo(galleryID: Int) = runCatching {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
json.decodeFromString<GalleryInfo>(
|
||||||
|
httpClient.get("https://$domain/galleries/$galleryID.js").bodyAsText()
|
||||||
|
.replace("var galleryinfo = ", "")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun search(query: SearchQuery?): Result<Set<Int>> = runCatching {
|
suspend fun search(query: SearchQuery?): Result<Set<Int>> = runCatching {
|
||||||
when (query) {
|
when (query) {
|
||||||
is SearchQuery.Tag -> getGalleryIDsForQuery(query).toSet()
|
is SearchQuery.Tag -> getGalleryIDsForQuery(query).toSet()
|
||||||
@@ -203,7 +218,7 @@ object HitomiHttpClient {
|
|||||||
|
|
||||||
val queriedGalleries = search(query.query).getOrThrow()
|
val queriedGalleries = search(query.query).getOrThrow()
|
||||||
|
|
||||||
val result = mutableSetOf<Int>()
|
val result = LinkedHashSet<Int>()
|
||||||
|
|
||||||
with (allGalleries.await()) {
|
with (allGalleries.await()) {
|
||||||
while (this.hasRemaining()) {
|
while (this.hasRemaining()) {
|
||||||
@@ -241,7 +256,7 @@ object HitomiHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = mutableSetOf<Int>()
|
val result = LinkedHashSet<Int>()
|
||||||
|
|
||||||
queries.forEach {
|
queries.forEach {
|
||||||
val queryResult = it.await()
|
val queryResult = it.await()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
import com.google.accompanist.adaptive.calculateDisplayFeatures
|
||||||
import xyz.quaver.pupil.ui.composable.MainApp
|
import xyz.quaver.pupil.ui.composable.MainApp
|
||||||
import xyz.quaver.pupil.ui.theme.AppTheme
|
import xyz.quaver.pupil.ui.theme.AppTheme
|
||||||
|
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
@@ -54,7 +55,9 @@ class MainActivity : BaseActivity() {
|
|||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = viewModel::navigateToDestination,
|
navigateToDestination = viewModel::navigateToDestination,
|
||||||
closeDetailScreen = viewModel::closeDetailScreen
|
closeDetailScreen = viewModel::closeDetailScreen,
|
||||||
|
onQueryChange = viewModel::onQueryChange,
|
||||||
|
loadSearchResult = viewModel::loadSearchResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.window.layout.DisplayFeature
|
import androidx.window.layout.DisplayFeature
|
||||||
import androidx.window.layout.FoldingFeature
|
import androidx.window.layout.FoldingFeature
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -32,7 +33,9 @@ fun MainApp(
|
|||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
uiState: MainUIState,
|
uiState: MainUIState,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
closeDetailScreen: () -> Unit
|
closeDetailScreen: () -> Unit,
|
||||||
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
|
loadSearchResult: (IntRange) -> Unit
|
||||||
) {
|
) {
|
||||||
val navigationType: NavigationType
|
val navigationType: NavigationType
|
||||||
val contentType: ContentType
|
val contentType: ContentType
|
||||||
@@ -85,7 +88,9 @@ fun MainApp(
|
|||||||
navigationContentPosition,
|
navigationContentPosition,
|
||||||
uiState,
|
uiState,
|
||||||
navigateToDestination,
|
navigateToDestination,
|
||||||
closeDetailScreen = closeDetailScreen
|
closeDetailScreen = closeDetailScreen,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
loadSearchResult = loadSearchResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +102,9 @@ private fun MainNavigationWrapper(
|
|||||||
navigationContentPosition: NavigationContentPosition,
|
navigationContentPosition: NavigationContentPosition,
|
||||||
uiState: MainUIState,
|
uiState: MainUIState,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
closeDetailScreen: () -> Unit
|
closeDetailScreen: () -> Unit,
|
||||||
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
|
loadSearchResult: (IntRange) -> Unit
|
||||||
) {
|
) {
|
||||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@@ -126,7 +133,9 @@ private fun MainNavigationWrapper(
|
|||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = navigateToDestination,
|
navigateToDestination = navigateToDestination,
|
||||||
onDrawerClicked = openDrawer,
|
onDrawerClicked = openDrawer,
|
||||||
closeDetailScreen = closeDetailScreen
|
closeDetailScreen = closeDetailScreen,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
loadSearchResult = loadSearchResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -153,7 +162,9 @@ private fun MainNavigationWrapper(
|
|||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
navigateToDestination = navigateToDestination,
|
navigateToDestination = navigateToDestination,
|
||||||
onDrawerClicked = openDrawer,
|
onDrawerClicked = openDrawer,
|
||||||
closeDetailScreen = closeDetailScreen
|
closeDetailScreen = closeDetailScreen,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
loadSearchResult = loadSearchResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +179,9 @@ fun MainContent(
|
|||||||
uiState: MainUIState,
|
uiState: MainUIState,
|
||||||
navigateToDestination: (MainDestination) -> Unit,
|
navigateToDestination: (MainDestination) -> Unit,
|
||||||
onDrawerClicked: () -> Unit,
|
onDrawerClicked: () -> Unit,
|
||||||
closeDetailScreen: () -> Unit
|
closeDetailScreen: () -> Unit,
|
||||||
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
|
loadSearchResult: (IntRange) -> Unit
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
|
AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
|
||||||
@@ -197,7 +210,9 @@ fun MainContent(
|
|||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
displayFeatures = displayFeatures,
|
displayFeatures = displayFeatures,
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
closeDetailScreen = closeDetailScreen
|
closeDetailScreen = closeDetailScreen,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
loadSearchResult = loadSearchResult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
@@ -14,8 +15,13 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -23,9 +29,12 @@ import androidx.compose.foundation.layout.heightIn
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -42,6 +51,7 @@ import androidx.compose.material.icons.filled.Male
|
|||||||
import androidx.compose.material.icons.filled.Translate
|
import androidx.compose.material.icons.filled.Translate
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@@ -61,10 +71,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.window.layout.DisplayFeature
|
import androidx.window.layout.DisplayFeature
|
||||||
@@ -73,7 +83,6 @@ import com.google.accompanist.adaptive.TwoPane
|
|||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.networking.GalleryInfo
|
import xyz.quaver.pupil.networking.GalleryInfo
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.networking.SearchQueryPreviewParameterProvider
|
|
||||||
import xyz.quaver.pupil.ui.theme.Blue600
|
import xyz.quaver.pupil.ui.theme.Blue600
|
||||||
import xyz.quaver.pupil.ui.theme.Pink600
|
import xyz.quaver.pupil.ui.theme.Pink600
|
||||||
import xyz.quaver.pupil.ui.theme.Yellow400
|
import xyz.quaver.pupil.ui.theme.Yellow400
|
||||||
@@ -217,7 +226,7 @@ fun TagChip(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QueryView(
|
fun QueryView(
|
||||||
query: SearchQuery,
|
query: SearchQuery?,
|
||||||
topLevel: Boolean = true
|
topLevel: Boolean = true
|
||||||
) {
|
) {
|
||||||
val modifier = if (topLevel) {
|
val modifier = if (topLevel) {
|
||||||
@@ -227,6 +236,16 @@ fun QueryView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (query) {
|
when (query) {
|
||||||
|
null -> {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(60.dp)
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
text = stringResource(id = R.string.search_hint),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
}
|
||||||
is SearchQuery.Tag -> {
|
is SearchQuery.Tag -> {
|
||||||
TagChip(
|
TagChip(
|
||||||
query,
|
query,
|
||||||
@@ -250,8 +269,8 @@ fun QueryView(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
query.queries.forEach { subquery ->
|
query.queries.forEach { subQuery ->
|
||||||
QueryView(subquery, topLevel = false)
|
QueryView(subQuery, topLevel = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,17 +291,25 @@ fun QueryView(
|
|||||||
fun SearchBar(
|
fun SearchBar(
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
query: SearchQuery?,
|
query: SearchQuery?,
|
||||||
onQueryChange: (SearchQuery) -> Unit,
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
onSearch: () -> Unit,
|
onSearch: () -> Unit,
|
||||||
topOffset: Int,
|
topOffset: Int,
|
||||||
onTopOffsetChange: (Int) -> Unit,
|
onTopOffsetChange: (Int) -> Unit,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
var focused by remember { mutableStateOf(true) }
|
var focused by remember { mutableStateOf(false) }
|
||||||
val scrimAlpha: Float by animateFloatAsState(if (focused && contentType == ContentType.SINGLE_PANE) 0.3f else 0f, label = "skrim alpha")
|
val scrimAlpha: Float by animateFloatAsState(if (focused && contentType == ContentType.SINGLE_PANE) 0.3f else 0f, label = "scrim alpha")
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
||||||
|
val state = remember(query) { query.toEditableState() }
|
||||||
|
|
||||||
|
LaunchedEffect(focused) {
|
||||||
|
if (!focused) {
|
||||||
|
onQueryChange(state.toSearchQuery())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
focused = false
|
focused = false
|
||||||
@@ -299,13 +326,15 @@ fun SearchBar(
|
|||||||
) {
|
) {
|
||||||
focused = false
|
focused = false
|
||||||
}
|
}
|
||||||
.safeDrawingPadding()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
) {
|
||||||
val height: Dp by animateDpAsState(if (focused) maxHeight else 60.dp, label = "searchbar height")
|
val height: Dp by animateDpAsState(if (focused) maxHeight else 60.dp, label = "searchbar height")
|
||||||
|
|
||||||
|
content()
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.safeDrawingPadding()
|
||||||
|
.padding(16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(height)
|
.height(height)
|
||||||
.clickable(
|
.clickable(
|
||||||
@@ -321,17 +350,7 @@ fun SearchBar(
|
|||||||
) else CardDefaults.cardElevation()
|
) else CardDefaults.cardElevation()
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
androidx.compose.animation.AnimatedVisibility(query == null && !focused, enter = fadeIn(), exit = fadeOut()) {
|
androidx.compose.animation.AnimatedVisibility(!focused, enter = fadeIn(), exit = fadeOut()) {
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(60.dp)
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
text = stringResource(id = R.string.search_hint),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
androidx.compose.animation.AnimatedVisibility(query != null && !focused, enter = fadeIn(), exit = fadeOut()) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(min = 60.dp)
|
.heightIn(min = 60.dp)
|
||||||
@@ -339,13 +358,11 @@ fun SearchBar(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Box(Modifier.size(8.dp))
|
Box(Modifier.size(8.dp))
|
||||||
QueryView(query!!)
|
QueryView(query)
|
||||||
Box(Modifier.size(8.dp))
|
Box(Modifier.size(8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidx.compose.animation.AnimatedVisibility(focused, enter = fadeIn(), exit = fadeOut()) {
|
androidx.compose.animation.AnimatedVisibility(focused, enter = fadeIn(), exit = fadeOut()) {
|
||||||
val state = remember(query) { query.toEditableState() }
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -373,7 +390,9 @@ fun MainScreen(
|
|||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
displayFeatures: List<DisplayFeature>,
|
displayFeatures: List<DisplayFeature>,
|
||||||
uiState: MainUIState,
|
uiState: MainUIState,
|
||||||
closeDetailScreen: () -> Unit
|
closeDetailScreen: () -> Unit,
|
||||||
|
onQueryChange: (SearchQuery?) -> Unit,
|
||||||
|
loadSearchResult: (IntRange) -> Unit
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(contentType) {
|
LaunchedEffect(contentType) {
|
||||||
if (contentType == ContentType.SINGLE_PANE && !uiState.isDetailOnlyOpen) {
|
if (contentType == ContentType.SINGLE_PANE && !uiState.isDetailOnlyOpen) {
|
||||||
@@ -383,12 +402,34 @@ fun MainScreen(
|
|||||||
|
|
||||||
val galleryLazyListState = rememberLazyListState()
|
val galleryLazyListState = rememberLazyListState()
|
||||||
|
|
||||||
|
val itemsPerPage by remember { mutableIntStateOf(20) }
|
||||||
|
|
||||||
|
val currentRange = remember(uiState) {
|
||||||
|
if (uiState.currentRange != IntRange.EMPTY) {
|
||||||
|
uiState.currentRange
|
||||||
|
} else {
|
||||||
|
0 ..< itemsPerPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val search = remember(currentRange) {{ loadSearchResult(currentRange) }}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
|
||||||
if (contentType == ContentType.DUAL_PANE) {
|
if (contentType == ContentType.DUAL_PANE) {
|
||||||
TwoPane(
|
TwoPane(
|
||||||
first = {
|
first = {
|
||||||
GalleryList(
|
GalleryList(
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
galleryLazyListState = galleryLazyListState
|
galleries = uiState.galleries,
|
||||||
|
query = uiState.query,
|
||||||
|
loading = uiState.loading,
|
||||||
|
error = uiState.error,
|
||||||
|
galleryLazyListState = galleryLazyListState,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
search = search
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
second = {
|
second = {
|
||||||
@@ -400,7 +441,13 @@ fun MainScreen(
|
|||||||
} else {
|
} else {
|
||||||
GalleryList(
|
GalleryList(
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
galleryLazyListState = galleryLazyListState
|
galleries = uiState.galleries,
|
||||||
|
query = uiState.query,
|
||||||
|
loading = uiState.loading,
|
||||||
|
error = uiState.error,
|
||||||
|
galleryLazyListState = galleryLazyListState,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
search = search
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,11 +455,13 @@ fun MainScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun GalleryList(
|
fun GalleryList(
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
galleries: List<GalleryInfo> = emptyList(),
|
galleries: List<GalleryInfo>,
|
||||||
|
query: SearchQuery?,
|
||||||
|
loading: Boolean = false,
|
||||||
|
error: Boolean = false,
|
||||||
openedGallery: GalleryInfo? = null,
|
openedGallery: GalleryInfo? = null,
|
||||||
query: SearchQuery? = SearchQueryPreviewParameterProvider().values.first(),
|
onQueryChange: (SearchQuery?) -> Unit = {},
|
||||||
onQueryChange: (SearchQuery) -> Unit = {},
|
search: () -> Unit = {},
|
||||||
onSearch: () -> Unit = { },
|
|
||||||
selectedGalleryIds: Set<Int> = emptySet(),
|
selectedGalleryIds: Set<Int> = emptySet(),
|
||||||
toggleGallerySelection: (Int) -> Unit = {},
|
toggleGallerySelection: (Int) -> Unit = {},
|
||||||
galleryLazyListState: LazyListState,
|
galleryLazyListState: LazyListState,
|
||||||
@@ -424,10 +473,43 @@ fun GalleryList(
|
|||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
query = query,
|
query = query,
|
||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
onSearch = onSearch,
|
onSearch = search,
|
||||||
topOffset = topOffset,
|
topOffset = topOffset,
|
||||||
onTopOffsetChange = { topOffset = it },
|
onTopOffsetChange = { topOffset = it },
|
||||||
) {
|
) {
|
||||||
|
AnimatedVisibility (loading) {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
CircularProgressIndicator(Modifier.align(Alignment.Center))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(error) {
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
Column(
|
||||||
|
Modifier.align(Alignment.Center),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text("(´∇`)", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f))
|
||||||
|
Text("No sources found!\nLet's go download one.", textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(!loading && !error) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = WindowInsets.systemBars.asPaddingValues().let { systemBarPaddingValues ->
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
PaddingValues(
|
||||||
|
top = systemBarPaddingValues.calculateTopPadding() + 96.dp,
|
||||||
|
bottom = systemBarPaddingValues.calculateBottomPadding(),
|
||||||
|
start = systemBarPaddingValues.calculateStartPadding(layoutDirection),
|
||||||
|
end = systemBarPaddingValues.calculateEndPadding(layoutDirection),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
items(galleries) {galleryInfo ->
|
||||||
|
Text(galleryInfo.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.Spring
|
import androidx.compose.animation.core.Spring
|
||||||
@@ -10,7 +9,6 @@ import androidx.compose.foundation.gestures.animateScrollBy
|
|||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -92,11 +90,23 @@ private fun SearchQuery.toEditableStateInternal(): EditableSearchQueryState = wh
|
|||||||
fun SearchQuery?.toEditableState(): EditableSearchQueryState.Root
|
fun SearchQuery?.toEditableState(): EditableSearchQueryState.Root
|
||||||
= EditableSearchQueryState.Root(this?.toEditableStateInternal())
|
= EditableSearchQueryState.Root(this?.toEditableStateInternal())
|
||||||
|
|
||||||
|
private fun EditableSearchQueryState.Tag.toSearchQueryInternal(): SearchQuery.Tag? =
|
||||||
|
if (namespace.value != null || tag.value.isNotBlank()) SearchQuery.Tag(namespace.value, tag.value.lowercase().trim()) else null
|
||||||
|
|
||||||
|
private fun EditableSearchQueryState.And.toSearchQueryInternal(): SearchQuery.And? =
|
||||||
|
queries.mapNotNull { it.toSearchQueryInternal() }.let { if (it.isNotEmpty()) SearchQuery.And(it) else null }
|
||||||
|
|
||||||
|
private fun EditableSearchQueryState.Or.toSearchQueryInternal(): SearchQuery.Or? =
|
||||||
|
queries.mapNotNull { it.toSearchQueryInternal() }.let { if (it.isNotEmpty()) SearchQuery.Or(it) else null }
|
||||||
|
|
||||||
|
private fun EditableSearchQueryState.Not.toSearchQueryInternal(): SearchQuery.Not? =
|
||||||
|
query.value?.toSearchQueryInternal()?.let { SearchQuery.Not(it) }
|
||||||
|
|
||||||
private fun EditableSearchQueryState.toSearchQueryInternal(): SearchQuery? = when (this) {
|
private fun EditableSearchQueryState.toSearchQueryInternal(): SearchQuery? = when (this) {
|
||||||
is EditableSearchQueryState.Tag -> SearchQuery.Tag(namespace.value, tag.value)
|
is EditableSearchQueryState.Tag -> this.toSearchQueryInternal()
|
||||||
is EditableSearchQueryState.And -> SearchQuery.And(queries.mapNotNull { it.toSearchQueryInternal() })
|
is EditableSearchQueryState.And -> this.toSearchQueryInternal()
|
||||||
is EditableSearchQueryState.Or -> SearchQuery.Or(queries.mapNotNull { it.toSearchQueryInternal() })
|
is EditableSearchQueryState.Or -> this.toSearchQueryInternal()
|
||||||
is EditableSearchQueryState.Not -> query.value?.toSearchQueryInternal()?.let { SearchQuery.Not(it) }
|
is EditableSearchQueryState.Not -> this.toSearchQueryInternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EditableSearchQueryState.Root.toSearchQuery(): SearchQuery?
|
fun EditableSearchQueryState.Root.toSearchQuery(): SearchQuery?
|
||||||
@@ -162,9 +172,16 @@ fun TagSuggestionList(
|
|||||||
|
|
||||||
LaunchedEffect(namespace, tag) {
|
LaunchedEffect(namespace, tag) {
|
||||||
suggestionList = null
|
suggestionList = null
|
||||||
suggestionList = HitomiHttpClient.getSuggestionsForQuery(SearchQuery.Tag(namespace, tag))
|
|
||||||
.getOrDefault(emptyList())
|
val searchQuery = state.toSearchQueryInternal()
|
||||||
.filterNot { it.tag == SearchQuery.Tag(namespace, tag) }
|
|
||||||
|
suggestionList = if (searchQuery != null) {
|
||||||
|
HitomiHttpClient.getSuggestionsForQuery(searchQuery)
|
||||||
|
.getOrDefault(emptyList())
|
||||||
|
.filterNot { it.tag == SearchQuery.Tag(namespace, tag) }
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val suggestionListSnapshot = suggestionList
|
val suggestionListSnapshot = suggestionList
|
||||||
@@ -576,18 +593,6 @@ fun QueryEditorQueryView(
|
|||||||
EditableTagChip(
|
EditableTagChip(
|
||||||
newSearchQuery,
|
newSearchQuery,
|
||||||
requestScrollTo = requestScrollTo,
|
requestScrollTo = requestScrollTo,
|
||||||
rightIcon = {
|
|
||||||
Icon(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.size(16.dp)
|
|
||||||
.clickable {
|
|
||||||
onQueryRemove(state)
|
|
||||||
},
|
|
||||||
imageVector = Icons.Default.RemoveCircleOutline,
|
|
||||||
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
NewQueryChip(state) { newQueryState ->
|
NewQueryChip(state) { newQueryState ->
|
||||||
state.queries.add(newQueryState)
|
state.queries.add(newQueryState)
|
||||||
@@ -677,26 +682,60 @@ fun QueryEditor(
|
|||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
val rootQuerySnapshot = rootQuery
|
val rootQuerySnapshot = rootQuery
|
||||||
|
|
||||||
|
val requestScrollTo: (Float) -> Unit = { target ->
|
||||||
|
val topYSnapshot = topY
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
scrollState.animateScrollBy(target - topYSnapshot - scrollOffset, spring(stiffness = Spring.StiffnessLow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestScrollBy: (Float) -> Unit = { value ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
scrollState.animateScrollBy(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rootQuerySnapshot != null) {
|
if (rootQuerySnapshot != null) {
|
||||||
QueryEditorQueryView(
|
QueryEditorQueryView(
|
||||||
state = rootQuerySnapshot,
|
state = rootQuerySnapshot,
|
||||||
onQueryRemove = { rootQuery = null },
|
onQueryRemove = { rootQuery = null },
|
||||||
requestScrollTo = { target ->
|
requestScrollTo = requestScrollTo,
|
||||||
val topYSnapshot = topY
|
requestScrollBy = requestScrollBy
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
scrollState.animateScrollBy(target - topYSnapshot - scrollOffset, spring(stiffness = Spring.StiffnessLow))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
requestScrollBy = { value ->
|
|
||||||
coroutineScope.launch {
|
|
||||||
scrollState.animateScrollBy(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootQuerySnapshot is EditableSearchQueryState.Tag?) {
|
if (rootQuerySnapshot is EditableSearchQueryState.Tag?) {
|
||||||
|
val newSearchQuery = remember { EditableSearchQueryState.Tag(expanded = true) }
|
||||||
|
|
||||||
|
var newQueryNamespace by newSearchQuery.namespace
|
||||||
|
var newQueryTag by newSearchQuery.tag
|
||||||
|
var newQueryExpanded by newSearchQuery.expanded
|
||||||
|
|
||||||
|
val offset = with (LocalDensity.current) { 40.dp.toPx() }
|
||||||
|
|
||||||
|
LaunchedEffect(newQueryExpanded) {
|
||||||
|
if (!newQueryExpanded && (newQueryNamespace != null || newQueryTag.isNotBlank())) {
|
||||||
|
rootQuery = if (rootQuerySnapshot == null) {
|
||||||
|
EditableSearchQueryState.Tag(newQueryNamespace, newQueryTag)
|
||||||
|
} else {
|
||||||
|
EditableSearchQueryState.And(listOf(
|
||||||
|
rootQuerySnapshot,
|
||||||
|
EditableSearchQueryState.Tag(newQueryNamespace, newQueryTag)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
newQueryNamespace = null
|
||||||
|
newQueryTag = ""
|
||||||
|
newQueryExpanded = true
|
||||||
|
requestScrollBy(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableTagChip(
|
||||||
|
newSearchQuery,
|
||||||
|
requestScrollTo = requestScrollTo
|
||||||
|
)
|
||||||
NewQueryChip(rootQuerySnapshot) { newState ->
|
NewQueryChip(rootQuerySnapshot) { newState ->
|
||||||
rootQuery = coalesceTags(rootQuerySnapshot, newState)
|
rootQuery = coalesceTags(rootQuerySnapshot, newState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
package xyz.quaver.pupil.ui.viewmodel
|
package xyz.quaver.pupil.ui.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.observeOn
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.pupil.networking.GalleryInfo
|
import xyz.quaver.pupil.networking.GalleryInfo
|
||||||
|
import xyz.quaver.pupil.networking.GallerySearchSource
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.composable.MainDestination
|
import xyz.quaver.pupil.ui.composable.MainDestination
|
||||||
import xyz.quaver.pupil.ui.composable.mainDestinations
|
import xyz.quaver.pupil.ui.composable.mainDestinations
|
||||||
@@ -11,6 +18,8 @@ import xyz.quaver.pupil.ui.composable.mainDestinations
|
|||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(MainUIState())
|
private val _uiState = MutableStateFlow(MainUIState())
|
||||||
val uiState: StateFlow<MainUIState> = _uiState
|
val uiState: StateFlow<MainUIState> = _uiState
|
||||||
|
private var searchSource: GallerySearchSource = GallerySearchSource(null)
|
||||||
|
private var job: Job? = null
|
||||||
|
|
||||||
fun navigateToDestination(destination: MainDestination) {
|
fun navigateToDestination(destination: MainDestination) {
|
||||||
_uiState.value = MainUIState(
|
_uiState.value = MainUIState(
|
||||||
@@ -24,6 +33,40 @@ class MainViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onQueryChange(query: SearchQuery?) {
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
query = query,
|
||||||
|
validRange = IntRange.EMPTY,
|
||||||
|
currentRange = IntRange.EMPTY
|
||||||
|
)
|
||||||
|
|
||||||
|
searchSource = GallerySearchSource(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSearchResult(range: IntRange) {
|
||||||
|
job?.cancel()
|
||||||
|
job = viewModelScope.launch {
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
loading = true,
|
||||||
|
currentRange = range
|
||||||
|
)
|
||||||
|
|
||||||
|
var error = false
|
||||||
|
val (galleries, galleryCount) = searchSource.load(range).getOrElse {
|
||||||
|
error = true
|
||||||
|
it.printStackTrace()
|
||||||
|
emptyList<GalleryInfo>() to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
galleries = galleries,
|
||||||
|
validRange = IntRange(1, galleryCount),
|
||||||
|
error = error,
|
||||||
|
loading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun navigateToDetail() {
|
fun navigateToDetail() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -32,7 +75,11 @@ class MainViewModel : ViewModel() {
|
|||||||
data class MainUIState(
|
data class MainUIState(
|
||||||
val currentDestination: MainDestination = mainDestinations.first(),
|
val currentDestination: MainDestination = mainDestinations.first(),
|
||||||
val query: SearchQuery? = null,
|
val query: SearchQuery? = null,
|
||||||
val loading: Boolean = true,
|
val galleries: List<GalleryInfo> = emptyList(),
|
||||||
|
val loading: Boolean = false,
|
||||||
|
val error: Boolean = false,
|
||||||
|
val validRange: IntRange = IntRange.EMPTY,
|
||||||
|
val currentRange: IntRange = IntRange.EMPTY,
|
||||||
val openedGallery: GalleryInfo? = null,
|
val openedGallery: GalleryInfo? = null,
|
||||||
val isDetailOnlyOpen: Boolean = false
|
val isDetailOnlyOpen: Boolean = false
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user