Skip to content

Commit

Permalink
prepare list view card
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed May 16, 2024
1 parent 2ea66d3 commit e160b5e
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 22 deletions.
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 }
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@ import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.aniflow.anilist.type.MediaListStatus
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*

class MediumRepository(
private val client: ApolloClient,
private val fallbackClient: ApolloClient,
private val isLoggedIn: Flow<Boolean>
private val fallbackClient: ApolloClient
) {

private val id = MutableStateFlow<Int?>(null)
private val query = combine(
id.filterNotNull(),
isLoggedIn.distinctUntilChanged()
) { t1, _ ->
Query(t1)
}
private val fallbackQuery = query.transform {
return@transform emitAll(fallbackClient.query(it.toGraphQL()).toFlow())

@OptIn(ExperimentalCoroutinesApi::class)
private val query = id.filterNotNull().mapLatest { Query(it) }

@OptIn(ExperimentalCoroutinesApi::class)
private val fallbackQuery = query.transformLatest {
return@transformLatest emitAll(fallbackClient.query(it.toGraphQL()).toFlow())
}.mapNotNull {
val data = it.data
if (data == null) {
Expand All @@ -35,8 +34,9 @@ class MediumRepository(
}
}

val medium = query.transform {
return@transform emitAll(client.query(it.toGraphQL()).toFlow())
@OptIn(ExperimentalCoroutinesApi::class)
val medium = query.transformLatest {
return@transformLatest emitAll(client.query(it.toGraphQL()).toFlow())
}.mapNotNull {
val data = it.data
if (data == null) {
Expand All @@ -48,8 +48,8 @@ class MediumRepository(
} else {
State.fromGraphQL(data)
}
}.transform {
return@transform if (it is State.Error) {
}.transformLatest {
return@transformLatest if (it is State.Error) {
emitAll(fallbackQuery)
} else {
emit(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ data object NetworkModule {
MediumRepository(
client = instance<ApolloClient>(Constants.AniList.APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(),
fallbackClient = instance<ApolloClient>(Constants.AniList.FALLBACK_APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(),
isLoggedIn = instance<UserHelper>().isLoggedIn
)
}
bindSingleton<TraceRepository> {
Expand All @@ -190,5 +189,15 @@ data object NetworkModule {
nsfw = appSettings.adultContent
)
}
bindSingleton<ListRepository> {
val appSettings = instance<Settings.PlatformAppSettings>()

ListRepository(
client = instance<ApolloClient>(Constants.AniList.APOLLO_CLIENT),
fallbackClient = instance<ApolloClient>(Constants.AniList.FALLBACK_APOLLO_CLIENT).newBuilder().fetchPolicy(FetchPolicy.NetworkFirst).build(),
user = instance<UserHelper>().user,
viewManga = appSettings.viewManga
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package dev.datlag.aniflow.ui.navigation.screen.favorites

import dev.datlag.aniflow.anilist.ListRepository
import dev.datlag.aniflow.settings.model.TitleLanguage
import dev.datlag.aniflow.ui.navigation.Component
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

interface FavoritesComponent : Component {
val listState: StateFlow<ListRepository.State>
val titleLanguage: Flow<TitleLanguage?>

fun viewDiscover()
fun viewHome()
Expand Down
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)
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import androidx.compose.runtime.remember
import com.arkivanov.decompose.ComponentContext
import dev.chrisbanes.haze.HazeState
import dev.datlag.aniflow.LocalHaze
import dev.datlag.aniflow.anilist.ListRepository
import dev.datlag.aniflow.common.onRender
import dev.datlag.aniflow.other.UserHelper
import dev.datlag.aniflow.settings.Settings
import dev.datlag.aniflow.settings.model.TitleLanguage
import dev.datlag.tooling.compose.ioDispatcher
import dev.datlag.tooling.compose.withMainContext
import kotlinx.coroutines.flow.collectLatest
import dev.datlag.tooling.decompose.ioScope
import kotlinx.coroutines.flow.*
import org.kodein.di.DI
import org.kodein.di.instance

Expand All @@ -20,8 +25,17 @@ class FavoritesScreenComponent(
private val onHome: () -> Unit,
) : FavoritesComponent, ComponentContext by componentContext {

private val userHelper by instance<UserHelper>()
val user = userHelper.user
private val appSettings by instance<Settings.PlatformAppSettings>()
override val titleLanguage: Flow<TitleLanguage?> = appSettings.titleLanguage

private val listRepository by instance<ListRepository>()
override val listState: StateFlow<ListRepository.State> = listRepository.list.flowOn(
context = ioDispatcher()
).stateIn(
scope = ioScope(),
started = SharingStarted.WhileSubscribed(),
initialValue = ListRepository.State.None
)

@Composable
override fun render() {
Expand Down
Loading

0 comments on commit e160b5e

Please sign in to comment.