-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
376 additions
and
22 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
anilist/src/commonMain/kotlin/dev/datlag/aniflow/anilist/ListRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package dev.datlag.aniflow.anilist | ||
|
||
import com.apollographql.apollo3.ApolloClient | ||
import com.apollographql.apollo3.api.Optional | ||
import dev.datlag.aniflow.anilist.model.Medium | ||
import dev.datlag.aniflow.anilist.model.User | ||
import dev.datlag.aniflow.anilist.type.MediaType | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.flow.* | ||
|
||
class ListRepository( | ||
private val client: ApolloClient, | ||
private val fallbackClient: ApolloClient, | ||
private val user: Flow<User?>, | ||
private val viewManga: Flow<Boolean> = flowOf(false), | ||
) { | ||
|
||
private val page = MutableStateFlow(0) | ||
private val _type = MutableStateFlow(MediaType.UNKNOWN__) | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
private 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( | ||
page, | ||
type, | ||
user.filterNotNull().distinctUntilChanged(), | ||
) { p, t, u -> | ||
Query( | ||
page = p, | ||
type = t, | ||
userId = u.id | ||
) | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
private val fallbackQuery = query.transformLatest { | ||
return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow()) | ||
}.mapNotNull { | ||
val data = it.data | ||
if (data == null) { | ||
if (it.hasErrors()) { | ||
State.fromGraphQL(data) | ||
} else { | ||
null | ||
} | ||
} else { | ||
State.fromGraphQL(data) | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalCoroutinesApi::class) | ||
val list = query.transformLatest { | ||
return@transformLatest emitAll(client.query(it.toGraphQL()).toFlow()) | ||
}.mapNotNull { | ||
val data = it.data | ||
if (data == null) { | ||
if (it.hasErrors()) { | ||
State.fromGraphQL(data) | ||
} else { | ||
null | ||
} | ||
} else { | ||
State.fromGraphQL(data) | ||
} | ||
}.transformLatest { | ||
return@transformLatest if (it is State.Error) { | ||
emitAll(fallbackQuery) | ||
} else { | ||
emit(it) | ||
} | ||
} | ||
|
||
private data class Query( | ||
val page: Int, | ||
val type: MediaType, | ||
val userId: Int | ||
) { | ||
fun toGraphQL() = ListQuery( | ||
page = Optional.present(page), | ||
type = if (type == MediaType.UNKNOWN__) { | ||
Optional.absent() | ||
} else { | ||
Optional.present(type) | ||
}, | ||
userId = Optional.present(userId) | ||
) | ||
} | ||
|
||
sealed interface State { | ||
data object None : State | ||
|
||
data class Success( | ||
val hasNextPage: Boolean, | ||
val medium: Collection<Medium> | ||
) : State | ||
|
||
data object Error : State | ||
|
||
companion object { | ||
fun fromGraphQL(query: ListQuery.Data?): State { | ||
val medium = query?.Page?.mediaListFilterNotNull()?.mapNotNull { | ||
Medium( | ||
media = it.media ?: return@mapNotNull null, | ||
list = it | ||
) | ||
} ?: return Error | ||
|
||
return Success( | ||
hasNextPage = query.Page.pageInfo?.hasNextPage ?: false, | ||
medium = medium.distinctBy { it.id } | ||
) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
...commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesComponent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 103 additions & 3 deletions
106
...rc/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/favorites/FavoritesScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,125 @@ | ||
package dev.datlag.aniflow.ui.navigation.screen.favorites | ||
|
||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.foundation.layout.* | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.foundation.lazy.items | ||
import androidx.compose.foundation.lazy.rememberLazyListState | ||
import androidx.compose.foundation.shape.CircleShape | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.rounded.FilterList | ||
import androidx.compose.material.icons.rounded.SwapVert | ||
import androidx.compose.material3.* | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.clip | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.unit.dp | ||
import dev.chrisbanes.haze.haze | ||
import dev.chrisbanes.haze.hazeChild | ||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi | ||
import dev.chrisbanes.haze.materials.HazeMaterials | ||
import dev.datlag.aniflow.LocalHaze | ||
import dev.datlag.aniflow.SharedRes | ||
import dev.datlag.aniflow.anilist.ListRepository | ||
import dev.datlag.aniflow.common.isScrollingUp | ||
import dev.datlag.aniflow.common.merge | ||
import dev.datlag.aniflow.common.plus | ||
import dev.datlag.aniflow.common.preferred | ||
import dev.datlag.aniflow.ui.navigation.screen.component.HidingNavigationBar | ||
import dev.datlag.aniflow.ui.navigation.screen.component.NavigationBarState | ||
import dev.datlag.aniflow.ui.navigation.screen.favorites.component.ListCard | ||
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle | ||
import dev.icerock.moko.resources.compose.stringResource | ||
import kotlinx.coroutines.flow.flowOf | ||
|
||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) | ||
@Composable | ||
fun FavoritesScreen(component: FavoritesComponent) { | ||
val listState = rememberLazyListState() | ||
|
||
Scaffold( | ||
topBar = { | ||
TopAppBar( | ||
title = { | ||
Text(text = stringResource(SharedRes.strings.app_name)) | ||
}, | ||
actions = { | ||
|
||
}, | ||
colors = TopAppBarDefaults.largeTopAppBarColors( | ||
containerColor = Color.Transparent, | ||
scrolledContainerColor = Color.Transparent, | ||
), | ||
modifier = Modifier.hazeChild( | ||
state = LocalHaze.current, | ||
style = HazeMaterials.thin( | ||
containerColor = MaterialTheme.colorScheme.surface, | ||
) | ||
).fillMaxWidth() | ||
) | ||
}, | ||
floatingActionButton = { | ||
ExtendedFloatingActionButton( | ||
onClick = {}, | ||
expanded = listState.isScrollingUp() && listState.canScrollForward, | ||
icon = { | ||
Icon( | ||
imageVector = Icons.Rounded.SwapVert, | ||
contentDescription = null | ||
) | ||
}, | ||
text = { | ||
Text(text = "Sort") | ||
} | ||
) | ||
}, | ||
bottomBar = { | ||
HidingNavigationBar( | ||
visible = true, | ||
visible = listState.isScrollingUp() && listState.canScrollForward, | ||
selected = NavigationBarState.Favorite, | ||
loggedIn = flowOf(true), | ||
onDiscover = component::viewDiscover, | ||
onHome = component::viewHome, | ||
onFavorites = { } | ||
) | ||
} | ||
) { | ||
) { padding -> | ||
val state by component.listState.collectAsStateWithLifecycle() | ||
|
||
when (val current = state) { | ||
is ListRepository.State.None -> { | ||
Box( | ||
modifier = Modifier.fillMaxSize().padding(padding), | ||
contentAlignment = Alignment.Center | ||
) { | ||
LinearProgressIndicator( | ||
modifier = Modifier.fillMaxWidth(0.2F).clip(CircleShape) | ||
) | ||
} | ||
} | ||
is ListRepository.State.Error -> { | ||
Text(text = "Error") | ||
} | ||
is ListRepository.State.Success -> { | ||
val titleLanguage by component.titleLanguage.collectAsStateWithLifecycle(null) | ||
|
||
LazyColumn( | ||
state = listState, | ||
modifier = Modifier.fillMaxSize().haze(state = LocalHaze.current), | ||
contentPadding = padding.plus(PaddingValues(8.dp)), | ||
verticalArrangement = Arrangement.spacedBy(8.dp), | ||
) { | ||
items(current.medium.toList()) { | ||
ListCard( | ||
medium = it, | ||
titleLanguage = titleLanguage, | ||
modifier = Modifier.fillParentMaxWidth().height(150.dp) | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.