Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.lasthopesoftware.bluewater.client.browsing.files.details.FileDetailsV
import com.lasthopesoftware.bluewater.client.browsing.files.details.ListedFileDetailsViewModel
import com.lasthopesoftware.bluewater.client.browsing.files.list.FileListViewModel
import com.lasthopesoftware.bluewater.client.browsing.files.list.search.SearchFilesViewModel
import com.lasthopesoftware.bluewater.client.browsing.items.LoadItemData
import com.lasthopesoftware.bluewater.client.browsing.items.list.ItemListViewModel
import com.lasthopesoftware.bluewater.client.settings.LibrarySettingsViewModel
import com.lasthopesoftware.bluewater.client.stored.library.items.files.view.ActiveFileDownloadsViewModel
Expand All @@ -17,6 +18,7 @@ import com.lasthopesoftware.bluewater.shared.android.UndoStack
interface ScopedViewModelDependencies : ReusedViewModelDependencies {
val itemListViewModel: ItemListViewModel
val fileListViewModel: FileListViewModel
val itemDataLoader: LoadItemData
val activeFileDownloadsViewModel: ActiveFileDownloadsViewModel
val searchFilesViewModel: SearchFilesViewModel
val librarySettingsViewModel: LibrarySettingsViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.lasthopesoftware.bluewater.client.browsing.files.list.FileListViewMod
import com.lasthopesoftware.bluewater.client.browsing.files.list.search.SearchFilesViewModel
import com.lasthopesoftware.bluewater.client.browsing.files.properties.EditableFilePropertyDefinitionProvider
import com.lasthopesoftware.bluewater.client.browsing.files.properties.EditableLibraryFilePropertiesProvider
import com.lasthopesoftware.bluewater.client.browsing.items.AggregateItemViewModel
import com.lasthopesoftware.bluewater.client.browsing.items.list.ItemListViewModel
import com.lasthopesoftware.bluewater.client.settings.LibrarySettingsViewModel
import com.lasthopesoftware.bluewater.client.settings.PermissionsDependencies
Expand Down Expand Up @@ -40,6 +41,13 @@ class ScopedViewModelRegistry(
)
}

override val itemDataLoader by viewModelStoreOwner.buildViewModelLazily {
AggregateItemViewModel(
itemListViewModel,
fileListViewModel
)
}

override val activeFileDownloadsViewModel by viewModelStoreOwner.buildViewModelLazily {
ActiveFileDownloadsViewModel(
storedFileAccess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.lasthopesoftware.bluewater.client.browsing.files.access.ProvideLibrar
import com.lasthopesoftware.bluewater.client.browsing.items.IItem
import com.lasthopesoftware.bluewater.client.browsing.items.Item
import com.lasthopesoftware.bluewater.client.browsing.items.ItemId
import com.lasthopesoftware.bluewater.client.browsing.items.LoadItemData
import com.lasthopesoftware.bluewater.client.browsing.items.playlists.Playlist
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.stored.library.items.AccessStoredItems
Expand All @@ -19,7 +20,7 @@ import kotlinx.coroutines.flow.asStateFlow
class FileListViewModel(
private val itemFileProvider: ProvideLibraryFiles,
private val storedItemAccess: AccessStoredItems,
) : ViewModel(), TrackLoadedViewState, ServiceFilesListState {
) : ViewModel(), TrackLoadedViewState, ServiceFilesListState, LoadItemData {

private val mutableIsLoading = MutableInteractionState(true)
private val mutableFiles = MutableInteractionState(emptyList<ServiceFile>())
Expand All @@ -34,40 +35,45 @@ class FileListViewModel(
val itemValue = mutableItemValue.asStateFlow()
val isSynced = mutableIsSynced.asStateFlow()

fun loadItem(libraryId: LibraryId, item: IItem? = null): Promise<Unit> {
override fun loadItem(libraryId: LibraryId, item: IItem?): Promise<Unit> {
mutableIsLoading.value = libraryId != loadedLibraryId || item != loadedItem
mutableItemValue.value = item?.value ?: ""
mutableIsSynced.value = false
loadedLibraryId = libraryId

val promisedFiles = when (item) {
is Item -> itemFileProvider.promiseFiles(libraryId, ItemId(item.key))
is Playlist -> itemFileProvider.promiseFiles(libraryId, item.itemId)
else -> itemFileProvider.promiseFiles(libraryId)
}
return Promise.Proxy { cs ->
val promisedFiles = when (item) {
is Item -> itemFileProvider.promiseFiles(libraryId, ItemId(item.key))
is Playlist -> itemFileProvider.promiseFiles(libraryId, item.itemId)
else -> itemFileProvider.promiseFiles(libraryId)
}

val promisedFilesUpdate = promisedFiles.then { f -> mutableFiles.value = f }
cs.doCancel(promisedFiles)

val promisedSyncUpdate = item
?.let {
storedItemAccess
.isItemMarkedForSync(libraryId, it)
.then { isSynced ->
mutableIsSynced.value = isSynced
}
}
.keepPromise()
val promisedFilesUpdate = promisedFiles.then { f -> mutableFiles.value = f }

return Promise.whenAll(promisedFilesUpdate, promisedSyncUpdate)
.then { _ ->
loadedItem = item
}
.must { _ ->
mutableIsLoading.value = false
}
val promisedSyncUpdate = item
?.let {
storedItemAccess
.isItemMarkedForSync(libraryId, it)
.then { isSynced ->
mutableIsSynced.value = isSynced
}
}
.keepPromise()

Promise
.whenAll(promisedFilesUpdate, promisedSyncUpdate)
.then { _ ->
loadedItem = item
}
.must { _ ->
mutableIsLoading.value = false
}
}
}

fun promiseRefresh(): Promise<Unit> = loadedLibraryId
override fun promiseRefresh(): Promise<Unit> = loadedLibraryId
?.let { l ->
val item = loadedItem
loadedLibraryId = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.lasthopesoftware.bluewater.client.browsing.items

import androidx.lifecycle.ViewModel
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.shared.observables.InteractionState
import com.lasthopesoftware.bluewater.shared.observables.LiftedInteractionState
import com.lasthopesoftware.bluewater.shared.observables.mapNotNull
import com.lasthopesoftware.promises.extensions.UnitResponse
import com.lasthopesoftware.resources.closables.AutoCloseableManager
import com.namehillsoftware.handoff.promises.Promise
import io.reactivex.rxjava3.core.Observable

class AggregateItemViewModel(
vararg itemData: LoadItemData
) : ViewModel(), LoadItemData {
private val autoCloseableManager = AutoCloseableManager()
private val itemDataLoaders = arrayOf(*itemData)

override val isLoading: InteractionState<Boolean> by lazy {
autoCloseableManager.manage(
LiftedInteractionState(
Observable.combineLatest(itemDataLoaders.map { it.isLoading.mapNotNull() }) { sources ->
sources.any { it as Boolean }
},
false
)
)
}

override fun loadItem(libraryId: LibraryId, item: IItem?): Promise<Unit> {
val promisedLoads = itemDataLoaders.map { it.loadItem(libraryId, item) }

return Promise.whenAll(promisedLoads)
.then(
UnitResponse.respond(),
{
for (promisedLoad in promisedLoads)
promisedLoad.cancel()
throw it
}
)
}

override fun promiseRefresh(): Promise<Unit> {
val promisedRefreshes = itemDataLoaders.map { it.promiseRefresh() }
return Promise.whenAll(promisedRefreshes)
.then(
UnitResponse.respond(),
{
for (promisedRefresh in promisedRefreshes)
promisedRefresh.cancel()
throw it
}
)
}

override fun onCleared() {
autoCloseableManager.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lasthopesoftware.bluewater.client.browsing.items

import com.lasthopesoftware.bluewater.client.browsing.TrackLoadedViewState
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.namehillsoftware.handoff.promises.Promise

interface LoadItemData : TrackLoadedViewState {
fun loadItem(libraryId: LibraryId, item: IItem? = null): Promise<Unit>
fun promiseRefresh(): Promise<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.lasthopesoftware.bluewater.client.browsing.ScopedViewModelDependencie
import com.lasthopesoftware.bluewater.client.browsing.files.list.FileListViewModel
import com.lasthopesoftware.bluewater.client.browsing.files.list.ReusablePlaylistFileItemViewModelProvider
import com.lasthopesoftware.bluewater.client.browsing.items.IItem
import com.lasthopesoftware.bluewater.client.browsing.items.LoadItemData
import com.lasthopesoftware.bluewater.client.browsing.items.list.ConnectionLostView
import com.lasthopesoftware.bluewater.client.browsing.items.list.ItemListView
import com.lasthopesoftware.bluewater.client.browsing.items.list.ItemListViewModel
Expand All @@ -25,7 +26,6 @@ import com.lasthopesoftware.bluewater.shared.android.viewmodels.PooledCloseables
import com.lasthopesoftware.bluewater.shared.android.viewmodels.ViewModelInitAction
import com.lasthopesoftware.promises.extensions.suspend
import com.lasthopesoftware.resources.strings.GetStringResources
import com.namehillsoftware.handoff.promises.Promise
import java.io.IOException

@Composable
Expand All @@ -36,6 +36,7 @@ fun LoadedItemListView(viewModelDependencies: ScopedViewModelDependencies, libra
item,
itemListViewModel = itemListViewModel,
fileListViewModel = fileListViewModel,
itemDataLoader = itemDataLoader,
nowPlayingViewModel = nowPlayingFilePropertiesViewModel,
itemListMenuBackPressedHandler = itemListMenuBackPressedHandler,
reusablePlaylistFileItemViewModelProvider = reusablePlaylistFileItemViewModelProvider,
Expand All @@ -56,6 +57,7 @@ private fun LoadedItemListView(
item: IItem?,
itemListViewModel: ItemListViewModel,
fileListViewModel: FileListViewModel,
itemDataLoader: LoadItemData,
nowPlayingViewModel: NowPlayingFilePropertiesViewModel,
itemListMenuBackPressedHandler: ItemListMenuBackPressedHandler,
reusablePlaylistFileItemViewModelProvider: ReusablePlaylistFileItemViewModelProvider,
Expand All @@ -81,6 +83,7 @@ private fun LoadedItemListView(
ItemListView(
itemListViewModel,
fileListViewModel,
itemDataLoader,
nowPlayingViewModel,
itemListMenuBackPressedHandler,
reusablePlaylistFileItemViewModelProvider,
Expand All @@ -104,15 +107,13 @@ private fun LoadedItemListView(
if (!isConnectionLost) {
LaunchedEffect(item) {
try {
Promise.whenAll(
itemListViewModel.loadItem(libraryId, item),
fileListViewModel.loadItem(libraryId, item),
).suspend()
itemDataLoader.loadItem(libraryId, item).suspend()
} catch (e: IOException) {
if (ConnectionLostExceptionFilter.isConnectionLostException(e))
if (ConnectionLostExceptionFilter.isConnectionLostException(e)) {
isConnectionLost = true
else
} else {
applicationNavigation.backOut().suspend()
}
} catch (_: Exception) {
applicationNavigation.backOut().suspend()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import com.lasthopesoftware.bluewater.client.browsing.files.list.ViewPlaylistFil
import com.lasthopesoftware.bluewater.client.browsing.items.IItem
import com.lasthopesoftware.bluewater.client.browsing.items.Item
import com.lasthopesoftware.bluewater.client.browsing.items.ItemId
import com.lasthopesoftware.bluewater.client.browsing.items.LoadItemData
import com.lasthopesoftware.bluewater.client.browsing.items.list.menus.LabelledActiveDownloadsButton
import com.lasthopesoftware.bluewater.client.browsing.items.list.menus.LabelledSearchButton
import com.lasthopesoftware.bluewater.client.browsing.items.list.menus.LabelledSettingsButton
Expand Down Expand Up @@ -355,6 +356,7 @@ fun ChildItem(
fun ItemListView(
itemListViewModel: ItemListViewModel,
fileListViewModel: FileListViewModel,
itemDataLoader: LoadItemData,
nowPlayingViewModel: NowPlayingFilePropertiesViewModel,
itemListMenuBackPressedHandler: ItemListMenuBackPressedHandler,
trackHeadlineViewModelProvider: PooledCloseablesViewModel<ViewPlaylistFileItem>,
Expand Down Expand Up @@ -557,30 +559,28 @@ fun ItemListView(
)
}

val isFilesLoading by fileListViewModel.isLoading.subscribeAsState()
val isLoading by itemDataLoader.isLoading.subscribeAsState()

BoxWithConstraints(modifier = Modifier
.fillMaxSize()
.focusGroup()
) {
ControlSurface {
DetermineWindowControlColors()
val isItemsLoading by itemListViewModel.isLoading.subscribeAsState()

val collapsedHeight = appBarHeight
val expandedHeightPx = LocalDensity.current.remember { boxHeight.toPx() }
val collapsedHeightPx = LocalDensity.current.remember { collapsedHeight.toPx() }
val items by itemListViewModel.items.subscribeAsState()

var minVisibleItemsForScroll by remember { mutableIntStateOf(30) }
LaunchedEffect(lazyListState, itemListViewModel, fileListViewModel) {
LaunchedEffect(lazyListState, itemDataLoader) {
combine(
snapshotFlow { lazyListState.layoutInfo },
itemListViewModel.isLoading.mapNotNull().asFlow(),
fileListViewModel.isLoading.mapNotNull().asFlow(),
) { info, i, f -> Triple(info, i, f) }
.filterNot { (_, i, f) -> i || f }
.map { (info, _, _) -> info.visibleItemsInfo.size }
itemDataLoader.isLoading.mapNotNull().asFlow(),
) { info, i -> Pair(info, i) }
.filterNot { (_, i) -> i }
.map { (info, _) -> info.visibleItemsInfo.size }
.filter { it == 0 }
.distinctUntilChanged()
.take(1)
Expand Down Expand Up @@ -658,9 +658,7 @@ fun ItemListView(
) {
val actualExpandedHeight by remember { derivedStateOf { if (isHeaderTall) boxHeight else collapsedHeight } }
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
val isLoaded = !isItemsLoading && !isFilesLoading

if (isLoaded) LoadedItemListView(
if (!isLoading) LoadedItemListView(
anchoredScrollConnectionState,
{ _, p -> labeledAnchors.firstOrNull { (_, lp) -> p == lp }?.let { (s, _) -> Text(s) } },
anchoredScrollConnectionDispatcher::progressTo,
Expand All @@ -685,8 +683,7 @@ fun ItemListView(
)

UnlabelledRefreshButton(
itemListViewModel,
fileListViewModel,
itemDataLoader,
Modifier
.align(Alignment.TopEnd)
.padding(
Expand Down Expand Up @@ -772,10 +769,9 @@ fun ItemListView(
)
}

if (!isFilesLoading && !isItemsLoading) {
if (!isLoading) {
UnlabelledRefreshButton(
itemListViewModel,
fileListViewModel,
itemDataLoader,
Modifier.padding(horizontal = viewPaddingUnit)
)
}
Expand Down
Loading