From e0d8be5150dff1febdacaf7b125cefa956a28d57 Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 20:33:17 -0400 Subject: [PATCH 1/8] add reorder button --- .../animite/profile/ui/MediaTrackingList.kt | 30 ++++++++++++------- profile/src/main/res/drawable/reorder.xml | 24 +++++++++++++++ profile/src/main/res/values/strings.xml | 1 + 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 profile/src/main/res/drawable/reorder.xml diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 81018d0e..99fdb639 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -76,7 +76,8 @@ fun MediaTrackingLists( item { ListOptions( expandAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = true } }, - collapseAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = false } } + collapseAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = false } }, + modifier = Modifier.fillMaxWidth() ) } namedLists.fastForEachIndexed { index, namedList -> @@ -149,22 +150,29 @@ private fun ListOptions( modifier: Modifier = Modifier ) { Row( - horizontalArrangement = Arrangement.spacedBy(LocalPaddings.current.small), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, modifier = modifier .padding(top = LocalPaddings.current.small) .padding(vertical = LocalPaddings.current.small) - .fillMaxWidth() ) { + Row(horizontalArrangement = Arrangement.spacedBy(LocalPaddings.current.small)) { + ListOption( + icon = ImageVector.vectorResource(R.drawable.expand_all), + text = stringResource(R.string.expand_all), + onClick = expandAll + ) + ListOption( + icon = ImageVector.vectorResource(R.drawable.collapse_all), + text = stringResource(R.string.collapse_all), + onClick = collapseAll + ) + } + ListOption( - icon = ImageVector.vectorResource(R.drawable.expand_all), - text = stringResource(R.string.expand_all), - onClick = expandAll - ) - ListOption( - icon = ImageVector.vectorResource(R.drawable.collapse_all), - text = stringResource(R.string.collapse_all), - onClick = collapseAll + icon = ImageVector.vectorResource(R.drawable.reorder), + text = stringResource(R.string.reorder), + onClick = {} ) } } diff --git a/profile/src/main/res/drawable/reorder.xml b/profile/src/main/res/drawable/reorder.xml new file mode 100644 index 00000000..e22c591f --- /dev/null +++ b/profile/src/main/res/drawable/reorder.xml @@ -0,0 +1,24 @@ + + + + diff --git a/profile/src/main/res/values/strings.xml b/profile/src/main/res/values/strings.xml index 34defc02..7717895c 100644 --- a/profile/src/main/res/values/strings.xml +++ b/profile/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ Expand All Collapse All + Reorder %1$d ep From 08e6587b6938ab99f395baf6c04d84c33757dafd Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 21:09:49 -0400 Subject: [PATCH 2/8] use reorderable --- profile/build.gradle.kts | 2 + .../animite/profile/ui/MediaTrackingList.kt | 105 +++++++++++++----- .../src/main/res/drawable/drag_indicator.xml | 24 ++++ profile/src/main/res/values/strings.xml | 1 + 4 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 profile/src/main/res/drawable/drag_indicator.xml diff --git a/profile/build.gradle.kts b/profile/build.gradle.kts index 7dbd99a0..ad1569d8 100644 --- a/profile/build.gradle.kts +++ b/profile/build.gradle.kts @@ -82,6 +82,8 @@ dependencies { implementation(libs.hilt.navigationCompose) ksp(libs.hilt.android.compiler) + implementation(libs.reorderable) + testImplementation(libs.test.junit) androidTestImplementation(libs.androidx.test.junit) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 99fdb639..44221cc1 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -1,5 +1,6 @@ package com.imashnake.animite.profile.ui +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -11,12 +12,15 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialShapes @@ -24,6 +28,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.toShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -54,9 +62,9 @@ import com.imashnake.animite.media.ext.res import com.imashnake.animite.profile.R import com.imashnake.animite.profile.dev.res import kotlinx.collections.immutable.ImmutableList -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set +import sh.calvin.reorderable.ReorderableCollectionItemScope +import sh.calvin.reorderable.ReorderableItem +import sh.calvin.reorderable.rememberReorderableLazyListState @Composable fun MediaTrackingLists( @@ -68,6 +76,11 @@ fun MediaTrackingLists( state: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(), ) { + var isReordering by remember { mutableStateOf(false) } + val reorderableLazyListState = rememberReorderableLazyListState(state) { from, to -> + // Update the list + } + LazyColumn( state = state, modifier = modifier, @@ -75,27 +88,37 @@ fun MediaTrackingLists( ) { item { ListOptions( + isReordering = isReordering, expandAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = true } }, collapseAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = false } }, + setIsReordering = { isReordering = it }, modifier = Modifier.fillMaxWidth() ) } namedLists.fastForEachIndexed { index, namedList -> stickyHeader(key = namedList.name) { namedList.name?.let { - Column(Modifier.animateItem()) { - Spacer( - modifier = Modifier - .fillMaxWidth() - .size(LocalPaddings.current.small) - .background(MaterialTheme.colorScheme.background) - ) - HeaderPill( - name = it, - size = namedList.list.size, - index = index, - listVisibility = listVisibility, - ) + ReorderableItem(reorderableLazyListState, key = it) { _ -> + Column(Modifier.animateItem()) { + Spacer( + modifier = Modifier + .fillMaxWidth() + .size(LocalPaddings.current.small) + .background( + if (isReordering) { + Color.Transparent + } else MaterialTheme.colorScheme.background + ) + ) + HeaderPill( + name = it, + size = namedList.list.size, + index = index, + listVisibility = listVisibility, + isReordering = isReordering, + reorderScope = this@ReorderableItem + ) + } } } } @@ -145,8 +168,10 @@ fun MediaTrackingLists( @Composable private fun ListOptions( + isReordering: Boolean, expandAll: () -> Unit, collapseAll: () -> Unit, + setIsReordering: (Boolean) -> Unit, modifier: Modifier = Modifier ) { Row( @@ -169,11 +194,24 @@ private fun ListOptions( ) } - ListOption( - icon = ImageVector.vectorResource(R.drawable.reorder), - text = stringResource(R.string.reorder), - onClick = {} - ) + AnimatedContent(isReordering) { + if (it) { + ListOption( + icon = Icons.Rounded.Check, + text = stringResource(R.string.done), + onClick = { setIsReordering(false) }, + background = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.padding(end = 10.dp) + ) + } else { + ListOption( + icon = ImageVector.vectorResource(R.drawable.reorder), + text = stringResource(R.string.reorder), + onClick = { collapseAll(); setIsReordering(true) } + ) + } + } } } @@ -182,7 +220,9 @@ private fun ListOption( icon: ImageVector, text: String, onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + background: Color = Color.Transparent, + contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant ) { val haptic = LocalHapticFeedback.current Row( @@ -190,6 +230,7 @@ private fun ListOption( horizontalArrangement = Arrangement.spacedBy(LocalPaddings.current.tiny), modifier = modifier .clip(CircleShape) + .background(background) .clickable { haptic.performHapticFeedback(HapticFeedbackType.ContextClick); onClick() } .padding(LocalPaddings.current.tiny) .padding(end = LocalPaddings.current.tiny) @@ -197,12 +238,12 @@ private fun ListOption( Icon( imageVector = icon, contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = contentColor, modifier = Modifier.size(dimensionResource(R.dimen.list_options_icon_size)) ) Text( text = text, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = contentColor, style = MaterialTheme.typography.labelSmall.copy(baselineShift = null), ) } @@ -214,6 +255,8 @@ private fun HeaderPill( size: Int, index: Int, listVisibility: SnapshotStateMap, + isReordering: Boolean, + reorderScope: ReorderableCollectionItemScope, modifier: Modifier = Modifier ) { val haptic = LocalHapticFeedback.current @@ -273,7 +316,19 @@ private fun HeaderPill( style = MaterialTheme.typography.bodyMedium.copy(baselineShift = null), ) - DropDownIcon(isDroppedDown = listVisibility[index] ?: true) + AnimatedContent(isReordering) { + if (!it) { + DropDownIcon(isDroppedDown = listVisibility[index] ?: true) + } else { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.drag_indicator), + contentDescription = null, + modifier = with(reorderScope) { + modifier.requiredSize(16.dp).draggableHandle() + } + ) + } + } } } } diff --git a/profile/src/main/res/drawable/drag_indicator.xml b/profile/src/main/res/drawable/drag_indicator.xml new file mode 100644 index 00000000..9826370e --- /dev/null +++ b/profile/src/main/res/drawable/drag_indicator.xml @@ -0,0 +1,24 @@ + + + + diff --git a/profile/src/main/res/values/strings.xml b/profile/src/main/res/values/strings.xml index 7717895c..18150abc 100644 --- a/profile/src/main/res/values/strings.xml +++ b/profile/src/main/res/values/strings.xml @@ -24,6 +24,7 @@ Expand All Collapse All Reorder + Done %1$d ep From ded78d39dde838e9db9a33cb8f3b19dbecb8544c Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 21:30:51 -0400 Subject: [PATCH 3/8] localize named list and reorder --- .../animite/profile/ui/MediaTrackingList.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 44221cc1..8ec8a73f 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -33,6 +33,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -76,9 +77,13 @@ fun MediaTrackingLists( state: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(), ) { + val namedLists = remember { namedLists.toMutableStateList() } + + val haptic = LocalHapticFeedback.current var isReordering by remember { mutableStateOf(false) } val reorderableLazyListState = rememberReorderableLazyListState(state) { from, to -> - // Update the list + namedLists.apply { add(to.index - 1, removeAt(from.index - 1)) } + haptic.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick) } LazyColumn( @@ -185,7 +190,7 @@ private fun ListOptions( ListOption( icon = ImageVector.vectorResource(R.drawable.expand_all), text = stringResource(R.string.expand_all), - onClick = expandAll + onClick = { if (!isReordering) expandAll() } ) ListOption( icon = ImageVector.vectorResource(R.drawable.collapse_all), @@ -271,8 +276,10 @@ private fun HeaderPill( } else { haptic.performHapticFeedback(ToggleOn) } - listVisibility[index]?.let { visibility -> - listVisibility[index] = !visibility + if (!isReordering) { + listVisibility[index]?.let { visibility -> + listVisibility[index] = !visibility + } } } .background( From 4f0db91b93d2fe639ce93a62376cffa0d0ae362a Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 21:32:01 -0400 Subject: [PATCH 4/8] add todo --- .../com/imashnake/animite/profile/ui/MediaTrackingList.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 8ec8a73f..740cdc71 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -204,7 +204,10 @@ private fun ListOptions( ListOption( icon = Icons.Rounded.Check, text = stringResource(R.string.done), - onClick = { setIsReordering(false) }, + onClick = { + // TODO: Call update user with list order. + setIsReordering(false) + }, background = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.padding(end = 10.dp) From 1ea9ad9abde54fe1659f84d980168e5326bc6420 Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 21:40:14 -0400 Subject: [PATCH 5/8] disable expand collapse if reordering --- .../animite/profile/ui/MediaTrackingList.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 740cdc71..838f12fe 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -1,6 +1,7 @@ package com.imashnake.animite.profile.ui import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -187,15 +188,18 @@ private fun ListOptions( .padding(vertical = LocalPaddings.current.small) ) { Row(horizontalArrangement = Arrangement.spacedBy(LocalPaddings.current.small)) { + val alpha by animateFloatAsState(if (isReordering) 0.3f else 1f) ListOption( icon = ImageVector.vectorResource(R.drawable.expand_all), text = stringResource(R.string.expand_all), - onClick = { if (!isReordering) expandAll() } + onClick = { if (!isReordering) expandAll() }, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = alpha), ) ListOption( - icon = ImageVector.vectorResource(R.drawable.collapse_all), text = stringResource(R.string.collapse_all), - onClick = collapseAll + onClick = { if (!isReordering) collapseAll() }, + icon = ImageVector.vectorResource(R.drawable.collapse_all), + contentColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = alpha) ) } @@ -274,11 +278,9 @@ private fun HeaderPill( .fillMaxWidth() .clip(CircleShape) .clickable { - if (listVisibility[index] == true) { - haptic.performHapticFeedback(ToggleOff) - } else { - haptic.performHapticFeedback(ToggleOn) - } + haptic.performHapticFeedback( + if (listVisibility[index] == true) ToggleOff else ToggleOn + ) if (!isReordering) { listVisibility[index]?.let { visibility -> listVisibility[index] = !visibility From 71a0e072c2c1238360acbc87f9aeac02b5c46e51 Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 22:04:51 -0400 Subject: [PATCH 6/8] add to mutation --- .../src/main/graphql/mutations/UpdateUser.graphql | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/anilist/src/main/graphql/mutations/UpdateUser.graphql b/api/anilist/src/main/graphql/mutations/UpdateUser.graphql index e3307eae..93a2710e 100644 --- a/api/anilist/src/main/graphql/mutations/UpdateUser.graphql +++ b/api/anilist/src/main/graphql/mutations/UpdateUser.graphql @@ -1,7 +1,18 @@ -mutation UpdateUser($profileColor: String) { - UpdateUser(profileColor: $profileColor) { +mutation UpdateUser( + $profileColor: String, + $animeListOptions: MediaListOptionsInput +) { + UpdateUser( + profileColor: $profileColor, + animeListOptions: $animeListOptions + ) { options { profileColor } + mediaListOptions { + animeList { + sectionOrder + } + } } } From c668a8e8d892ff24e57afbdcb9109e8090dc55da Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 22:19:32 -0400 Subject: [PATCH 7/8] add manga --- .../main/graphql/mutations/UpdateUser.graphql | 7 +++++- .../api/anilist/AnilistUserRepository.kt | 23 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/api/anilist/src/main/graphql/mutations/UpdateUser.graphql b/api/anilist/src/main/graphql/mutations/UpdateUser.graphql index 93a2710e..5aedbd48 100644 --- a/api/anilist/src/main/graphql/mutations/UpdateUser.graphql +++ b/api/anilist/src/main/graphql/mutations/UpdateUser.graphql @@ -1,10 +1,12 @@ mutation UpdateUser( $profileColor: String, - $animeListOptions: MediaListOptionsInput + $animeListOptions: MediaListOptionsInput, + $mangaListOptions: MediaListOptionsInput, ) { UpdateUser( profileColor: $profileColor, animeListOptions: $animeListOptions + mangaListOptions: $mangaListOptions ) { options { profileColor @@ -13,6 +15,9 @@ mutation UpdateUser( animeList { sectionOrder } + mangaList { + sectionOrder + } } } } diff --git a/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt b/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt index bc6eee42..48e752de 100644 --- a/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt +++ b/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt @@ -6,6 +6,7 @@ import com.apollographql.cache.normalized.FetchPolicy import com.apollographql.cache.normalized.fetchPolicy import com.imashnake.animite.api.anilist.sanitize.media.Media import com.imashnake.animite.api.anilist.sanitize.profile.User +import com.imashnake.animite.api.anilist.type.MediaListOptionsInput import com.imashnake.animite.api.anilist.type.MediaType import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter @@ -59,9 +60,27 @@ class AnilistUserRepository( .asResult { User.MediaCollection(it, type, language, mediaListOrder) } } - fun updateUser(profileColor: String?): Flow> { + fun updateUser( + profileColor: String? = null, + animeSectionOrder: List? = null, + mangaSectionOrder: List? = null, + ): Flow> { return apolloClient - .mutation(UpdateUserMutation(Optional.presentIfNotNull(profileColor))) + .mutation( + UpdateUserMutation( + profileColor = Optional.presentIfNotNull(profileColor), + animeListOptions = Optional.presentIfNotNull( + MediaListOptionsInput( + sectionOrder = Optional.presentIfNotNull(animeSectionOrder) + ) + ), + mangaListOptions = Optional.presentIfNotNull( + MediaListOptionsInput( + sectionOrder = Optional.presentIfNotNull(mangaSectionOrder) + ) + ) + ) + ) .fetchPolicy(FetchPolicy.NetworkOnly) .toFlow() .asResult { it.UpdateUser?.options?.profileColor } From 11bcfae4a34e682409b01228e506c997a9425ab1 Mon Sep 17 00:00:00 2001 From: imashnake0 Date: Mon, 27 Jul 2026 22:54:28 -0400 Subject: [PATCH 8/8] update user with media lists order --- .../api/anilist/AnilistUserRepository.kt | 4 +- .../animite/profile/ProfileScreen.kt | 10 ++-- .../animite/profile/ProfileViewModel.kt | 46 +++++++++++++++---- .../imashnake/animite/profile/tabs/Media.kt | 4 ++ .../animite/profile/ui/MediaTrackingList.kt | 6 ++- .../animite/settings/SettingsViewModel.kt | 6 +-- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt b/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt index 48e752de..26871280 100644 --- a/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt +++ b/api/anilist/src/main/kotlin/com/imashnake/animite/api/anilist/AnilistUserRepository.kt @@ -64,7 +64,7 @@ class AnilistUserRepository( profileColor: String? = null, animeSectionOrder: List? = null, mangaSectionOrder: List? = null, - ): Flow> { + ): Flow> { return apolloClient .mutation( UpdateUserMutation( @@ -83,6 +83,6 @@ class AnilistUserRepository( ) .fetchPolicy(FetchPolicy.NetworkOnly) .toFlow() - .asResult { it.UpdateUser?.options?.profileColor } + .asResult { it.UpdateUser } } } diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileScreen.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileScreen.kt index 0bc63ac2..1a6c462a 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileScreen.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileScreen.kt @@ -260,11 +260,11 @@ fun ProfileScreen( user = this@run, animeCollection = viewerAnimeLists.data, mangaCollection = viewerMangaLists.data, + updateAnimeListsOrder = viewModel::updateAnimeLists, + updateMangaListsOrder = viewModel::updateMangaLists, onNavigateToMediaItem = onNavigateToMediaItem, showUserDescription = showUserDescription, - onUserDescriptionClick = { - showUserDescriptionSheet = true - }, + onUserDescriptionClick = { showUserDescriptionSheet = true }, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, contentPadding = navigationComponentPaddingValues + insetPaddingValues, @@ -482,6 +482,8 @@ private fun UserTabs( user: User, animeCollection: User.MediaCollection?, mangaCollection: User.MediaCollection?, + updateAnimeListsOrder: (List) -> Unit, + updateMangaListsOrder: (List) -> Unit, onNavigateToMediaItem: (MediaPage) -> Unit, showUserDescription: Boolean, onUserDescriptionClick: () -> Unit, @@ -612,12 +614,14 @@ private fun UserTabs( ProfileTab.ANIME -> MediaTab( mediaCollection = animeCollection, listVisibility = animeListVisibility, + updateMediaListsOrder = updateAnimeListsOrder, onNavigateToMediaItem = onNavigateToMediaItem, contentPadding = mediaTabContentPadding, ) ProfileTab.MANGA -> MediaTab( mediaCollection = mangaCollection, listVisibility = mangaListVisibility, + updateMediaListsOrder = updateMangaListsOrder, onNavigateToMediaItem = onNavigateToMediaItem, contentPadding = mediaTabContentPadding, ) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileViewModel.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileViewModel.kt index ca8d3b14..40f88378 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileViewModel.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ProfileViewModel.kt @@ -38,6 +38,8 @@ class ProfileViewModel @Inject constructor( private val navArgs = savedStateHandle.toRoute() private val refreshTrigger = MutableSharedFlow() + private val animeListsRefreshTrigger = MutableSharedFlow() + private val mangaListsRefreshTrigger = MutableSharedFlow() var useNetwork = false @@ -71,7 +73,7 @@ class ProfileViewModel @Inject constructor( ) val viewerAnimeLists = combine( - flow = refreshTrigger.onStart { emit(Unit) }, + flow = animeListsRefreshTrigger.onStart { emit(Unit) }, flow2 = combine( preferencesRepository.viewerId.filterNotNull(), preferencesRepository.language.filterNotNull(), @@ -96,7 +98,7 @@ class ProfileViewModel @Inject constructor( ) val viewerMangaLists = combine( - flow = refreshTrigger.onStart { emit(Unit) }, + flow = mangaListsRefreshTrigger.onStart { emit(Unit) }, flow2 = combine( preferencesRepository.viewerId.filterNotNull(), preferencesRepository.language.filterNotNull(), @@ -120,23 +122,51 @@ class ProfileViewModel @Inject constructor( initialValue = Resource.loading(), ) - fun logOut() = viewModelScope.launch(Dispatchers.IO) { - with(preferencesRepository) { - setAccessToken(null) - setViewerId(null) - setViewerAvatar(null) + fun updateAnimeLists(sectionOrder: List) = viewModelScope.launch(Dispatchers.IO) { + userRepository.updateUser(animeSectionOrder = sectionOrder).collect { + preferencesRepository.setAnimeListOrder(it.getOrNull()?.mediaListOptions?.animeList?.sectionOrder?.filterNotNull()) } + refreshAnimeLists() + } + + fun updateMangaLists(sectionOrder: List) = viewModelScope.launch(Dispatchers.IO) { + userRepository.updateUser(mangaSectionOrder = sectionOrder).collect { + preferencesRepository.setMangaListOrder(it.getOrNull()?.mediaListOptions?.mangaList?.sectionOrder?.filterNotNull()) + } + refreshMangaLists() + } + + private fun refreshAnimeLists() = viewModelScope.launch(Dispatchers.IO) { + useNetwork = true + animeListsRefreshTrigger.emit(Unit) + useNetwork = false } - fun refresh(setIsRefreshing: (Boolean) -> Unit) = viewModelScope.launch { + private fun refreshMangaLists() = viewModelScope.launch(Dispatchers.IO) { + useNetwork = true + mangaListsRefreshTrigger.emit(Unit) + useNetwork = false + } + + fun refresh(setIsRefreshing: (Boolean) -> Unit) = viewModelScope.launch(Dispatchers.IO) { setIsRefreshing(true) useNetwork = true refreshTrigger.emit(Unit) + animeListsRefreshTrigger.emit(Unit) + mangaListsRefreshTrigger.emit(Unit) delay(1500L) useNetwork = false setIsRefreshing(false) } + fun logOut() = viewModelScope.launch(Dispatchers.IO) { + with(preferencesRepository) { + setAccessToken(null) + setViewerId(null) + setViewerAvatar(null) + } + } + val viewerAvatar = preferencesRepository.viewerAvatar fun saveViewerAvatar(avatarUrl: String?) = viewModelScope.launch(Dispatchers.IO) { preferencesRepository.setViewerAvatar(avatarUrl) diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/tabs/Media.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/tabs/Media.kt index 4ab4b9d2..cbb73196 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/tabs/Media.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/tabs/Media.kt @@ -25,6 +25,7 @@ import kotlinx.collections.immutable.ImmutableList fun MediaTab( mediaCollection: User.MediaCollection?, listVisibility: SnapshotStateMap, + updateMediaListsOrder: (List) -> Unit, onNavigateToMediaItem: (MediaPage) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(), @@ -48,6 +49,7 @@ fun MediaTab( type = mediaCollection.type, lists = mediaCollection.namedLists, listVisibility = listVisibility, + updateMediaListsOrder = updateMediaListsOrder, onNavigateToMediaItem = onNavigateToMediaItem, modifier = modifier, contentPadding = contentPadding, @@ -61,6 +63,7 @@ private fun UserMediaLists( type: Media.Small.Type, lists: ImmutableList, listVisibility: SnapshotStateMap, + updateMediaListsOrder: (List) -> Unit, onNavigateToMediaItem: (MediaPage) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(), @@ -69,6 +72,7 @@ private fun UserMediaLists( type = type, namedLists = lists, listVisibility = listVisibility, + updateMediaListsOrder = updateMediaListsOrder, onNavigateToMediaItem = onNavigateToMediaItem, contentPadding = contentPadding, modifier = modifier.fillMaxSize() diff --git a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt index 838f12fe..d33f5d78 100644 --- a/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt +++ b/profile/src/main/kotlin/com/imashnake/animite/profile/ui/MediaTrackingList.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed +import androidx.compose.ui.util.fastMapNotNull import com.imashnake.animite.api.anilist.sanitize.media.Media import com.imashnake.animite.api.anilist.sanitize.profile.User import com.imashnake.animite.api.anilist.sanitize.profile.User.ListNames.Companion.sanitize @@ -73,6 +74,7 @@ fun MediaTrackingLists( type: Media.Small.Type, namedLists: ImmutableList, listVisibility: SnapshotStateMap, + updateMediaListsOrder: (List) -> Unit, onNavigateToMediaItem: (MediaPage) -> Unit, modifier: Modifier = Modifier, state: LazyListState = rememberLazyListState(), @@ -98,6 +100,7 @@ fun MediaTrackingLists( expandAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = true } }, collapseAll = { listVisibility.forEach { (index, _) -> listVisibility[index] = false } }, setIsReordering = { isReordering = it }, + onDone = { updateMediaListsOrder(namedLists.fastMapNotNull { it.name }.toList()) }, modifier = Modifier.fillMaxWidth() ) } @@ -178,6 +181,7 @@ private fun ListOptions( expandAll: () -> Unit, collapseAll: () -> Unit, setIsReordering: (Boolean) -> Unit, + onDone: () -> Unit, modifier: Modifier = Modifier ) { Row( @@ -209,7 +213,7 @@ private fun ListOptions( icon = Icons.Rounded.Check, text = stringResource(R.string.done), onClick = { - // TODO: Call update user with list order. + onDone() setIsReordering(false) }, background = MaterialTheme.colorScheme.primaryContainer, diff --git a/settings/src/main/kotlin/com/imashnake/animite/settings/SettingsViewModel.kt b/settings/src/main/kotlin/com/imashnake/animite/settings/SettingsViewModel.kt index 48632e40..3abb98c8 100644 --- a/settings/src/main/kotlin/com/imashnake/animite/settings/SettingsViewModel.kt +++ b/settings/src/main/kotlin/com/imashnake/animite/settings/SettingsViewModel.kt @@ -69,9 +69,9 @@ class SettingsViewModel @Inject constructor( val profileColor = preferencesRepository.profileColor .filterNotNull() .flatMapLatest { - userRepository.updateUser(it).onEach { profileColor -> - preferencesRepository.setProfileColor(profileColor.getOrNull()) - } + userRepository.updateUser(it).onEach { result -> + preferencesRepository.setProfileColor(result.getOrNull()?.options?.profileColor) + }.map { result -> result.map { updateUser -> updateUser?.options?.profileColor } } } .asResource() .stateIn(