Skip to content

Commit

Permalink
feat(maps-list): add horizontally scrollable pager
Browse files Browse the repository at this point in the history
  • Loading branch information
aliernfrog authored Dec 20, 2024
1 parent e5b2e9c commit c9037d3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
Expand All @@ -19,6 +20,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Sort
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
Expand Down Expand Up @@ -94,7 +96,6 @@ fun MapsListScreen(
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val mapsToShow = mapsListViewModel.mapsToShow
val isMultiSelecting = mapsListViewModel.selectedMaps.isNotEmpty()
val listStyle = ListStyle.entries[mapsListViewModel.prefs.mapsListStyle.value]
val showMapThumbnails = mapsListViewModel.prefs.showMapThumbnailsInList.value
Expand Down Expand Up @@ -245,35 +246,43 @@ fun MapsListScreen(
}
}

AnimatedContent(targetState = listStyle) { style ->
when (style) {
ListStyle.LIST -> LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Header(mapsToShow)
}
HorizontalPager(
state = mapsListViewModel.pagerState,
beyondViewportPageCount = 1
) { page ->
val segment = mapsListViewModel.availableSegments[page]
val mapsToShow = mapsListViewModel.getFilteredMaps(segment)

items(mapsToShow) {
MapItem(it, isGrid = false)
}
AnimatedContent(targetState = listStyle) { style ->
when (style) {
ListStyle.LIST -> LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Header(segment, page, mapsToShow)
}

item {
Footer()
}
}
ListStyle.GRID -> LazyAdaptiveVerticalGrid(
modifier = Modifier.fillMaxSize()
) { maxLineSpan: Int ->
item(span = { GridItemSpan(maxLineSpan) }) {
Header(mapsToShow)
}
items(mapsToShow) {
MapItem(it, isGrid = false)
}

items(mapsToShow) {
MapItem(it, isGrid = true)
item {
Footer()
}
}
ListStyle.GRID -> LazyAdaptiveVerticalGrid(
modifier = Modifier.fillMaxSize()
) { maxLineSpan: Int ->
item(span = { GridItemSpan(maxLineSpan) }) {
Header(segment, page, mapsToShow)
}

items(mapsToShow) {
MapItem(it, isGrid = true)
}

item { Footer() }
item { Footer() }
}
}
}
}
Expand All @@ -282,22 +291,29 @@ fun MapsListScreen(

@Composable
private fun Header(
segment: MapsListSegment,
segmentIndex: Int,
mapsToShow: List<MapFile>,
mapsViewModel: MapsViewModel = koinViewModel(),
mapsListViewModel: MapsListViewModel = koinViewModel()
) {
val scope = rememberCoroutineScope()
Column {
Search(
searchQuery = mapsListViewModel.searchQuery,
onSearchQueryChange = { mapsListViewModel.searchQuery = it }
)
Filter(
segments = mapsListViewModel.availableSegments,
selectedSegment = mapsListViewModel.chosenSegment,
onSelectedSegmentChange = {
mapsListViewModel.chosenSegment = it
}
)
SegmentedButtons(
options = mapsListViewModel.availableSegments.map {
stringResource(it.labelId)
},
selectedIndex = segmentIndex,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) { scope.launch {
mapsListViewModel.pagerState.animateScrollToPage(it, animationSpec = tween(300))
} }
if (mapsToShow.isEmpty()) {
if (mapsViewModel.isLoadingMaps) Column(
modifier = Modifier.fillMaxWidth().padding(vertical = 18.dp),
Expand All @@ -308,7 +324,7 @@ private fun Header(
else ErrorWithIcon(
error = stringResource(
if (mapsListViewModel.searchQuery.isNotEmpty()) R.string.mapsList_searchNoMatches
else mapsListViewModel.chosenSegment.noMapsTextId
else segment.noMapsTextId
),
painter = rememberVectorPainter(Icons.Rounded.LocationOff),
modifier = Modifier.fillMaxWidth()
Expand Down Expand Up @@ -390,25 +406,6 @@ private fun Search(
)
}

@Composable
private fun Filter(
segments: List<MapsListSegment>,
selectedSegment: MapsListSegment,
onSelectedSegmentChange: (MapsListSegment) -> Unit
) {
SegmentedButtons(
options = segments.map {
stringResource(it.labelId)
},
selectedIndex = selectedSegment.ordinal,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
onSelectedSegmentChange(segments[it])
}
}

@Composable
private fun MultiSelectionDropdown(
expanded: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import androidx.compose.material3.SheetState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.unit.Density
import androidx.core.app.LocaleManagerCompat
import androidx.core.os.LocaleListCompat
Expand Down Expand Up @@ -222,8 +221,14 @@ class MainViewModel(
mapsViewModel.chooseMap(cached.first())
mapsViewModel.mapListShown = false
} else {
mapsViewModel.sharedMaps = cached.toMutableStateList()
mapsListViewModel.chosenSegment = MapsListSegment.SHARED
mapsViewModel.sharedMaps = cached
withContext(Dispatchers.Main) {
mapsListViewModel.availableSegments.indexOfFirst {
it == MapsListSegment.SHARED
}.let {
if (it > 0) mapsListViewModel.pagerState.scrollToPage(it)
}
}
}
progressState.currentProgress = null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
package com.aliernfrog.lactool.ui.viewmodel

import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aliernfrog.lactool.enum.MapAction
import com.aliernfrog.lactool.enum.MapsListSegment
import com.aliernfrog.lactool.enum.ListSorting
import com.aliernfrog.lactool.impl.MapFile
import com.aliernfrog.lactool.util.manager.PreferenceManager
import com.aliernfrog.toptoast.state.TopToastState
import kotlinx.coroutines.launch

class MapsListViewModel(
val topToastState: TopToastState,
val prefs: PreferenceManager,
private val mapsViewModel: MapsViewModel
) : ViewModel() {
var searchQuery by mutableStateOf("")
var chosenSegment by mutableStateOf(MapsListSegment.IMPORTED)
var selectedMaps = mutableStateListOf<MapFile>()

val availableSegments: List<MapsListSegment>
get() = MapsListSegment.entries.filter {
!(mapsViewModel.sharedMaps.isEmpty() && it == MapsListSegment.SHARED)
}
val selectedMaps = mutableStateListOf<MapFile>()
val availableSegments = mutableStateListOf<MapsListSegment>()
val pagerState = PagerState { availableSegments.size }

val selectedMapsActions: List<MapAction>
get() = MapAction.entries.filter { action ->
Expand All @@ -33,23 +33,28 @@ class MapsListViewModel(
}
}

/**
* Map list with filters and sorting options applied.
*/
val mapsToShow: List<MapFile>
get() {
val sorting = ListSorting.entries[prefs.mapsListSorting.value]
return chosenSegment.getMaps(mapsViewModel)
.filter {
it.name.contains(searchQuery, ignoreCase = true)
}
.sortedWith { m1, m2 ->
sorting.comparator.compare(m1.file, m2.file)
}
.let {
if (prefs.mapsListSortingReversed.value) it.reversed() else it
init {
viewModelScope.launch {
snapshotFlow { mapsViewModel.sharedMaps.isEmpty() }
.collect { sharedMapsIsEmpty ->
availableSegments.clear()
availableSegments.addAll(MapsListSegment.entries.filter {
!(sharedMapsIsEmpty && it == MapsListSegment.SHARED)
})
}
}
}

fun getFilteredMaps(segment: MapsListSegment) = segment.getMaps(mapsViewModel)
.filter {
it.name.contains(searchQuery, ignoreCase = true)
}
.sortedWith { m1, m2 ->
ListSorting.entries[prefs.mapsListSorting.value].comparator.compare(m1.file, m2.file)
}
.let {
if (prefs.mapsListSortingReversed.value) it.reversed() else it
}

fun isMapSelected(map: MapFile): Boolean {
return selectedMaps.any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBarState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -73,7 +72,7 @@ class MapsViewModel(
var isLoadingMaps by mutableStateOf(true)
var importedMaps by mutableStateOf(emptyList<MapFile>())
var exportedMaps by mutableStateOf(emptyList<MapFile>())
var sharedMaps = mutableStateListOf<MapFile>()
var sharedMaps by mutableStateOf(emptyList<MapFile>())
var mapsPendingDelete by mutableStateOf<List<MapFile>?>(null)
var mapNameEdit by mutableStateOf("")
var mapListShown by mutableStateOf(true)
Expand Down

0 comments on commit c9037d3

Please sign in to comment.