Add landscape support to list fragments

Add a landscape mode for LibraryFragment & SongsFragment.
This commit is contained in:
OxygenCobalt 2020-12-14 12:01:24 -07:00
parent fca42f2cb0
commit 07e7e1ae5b
9 changed files with 38 additions and 23 deletions

View file

@ -12,6 +12,7 @@ import androidx.core.view.forEach
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.transition.Fade import androidx.transition.Fade
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -23,10 +24,12 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.utils.applyColor import org.oxycblt.auxio.utils.applyColor
import org.oxycblt.auxio.utils.isLandscape
import org.oxycblt.auxio.utils.resolveAttr import org.oxycblt.auxio.utils.resolveAttr
import org.oxycblt.auxio.utils.setupAlbumActions import org.oxycblt.auxio.utils.setupAlbumActions
import org.oxycblt.auxio.utils.setupArtistActions import org.oxycblt.auxio.utils.setupArtistActions
@ -92,7 +95,6 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
searchView.maxWidth = Int.MAX_VALUE searchView.maxWidth = Int.MAX_VALUE
searchView.setOnQueryTextListener(this@LibraryFragment) searchView.setOnQueryTextListener(this@LibraryFragment)
searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
libraryModel.updateSearchFocusStatus(hasFocus)
item.isVisible = !hasFocus item.isVisible = !hasFocus
} }
@ -100,6 +102,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
binding.libraryRecycler.adapter = searchAdapter binding.libraryRecycler.adapter = searchAdapter
setGroupVisible(R.id.group_sorting, false) setGroupVisible(R.id.group_sorting, false)
libraryModel.resetQuery() libraryModel.resetQuery()
return true return true
@ -108,6 +111,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
binding.libraryRecycler.adapter = libraryAdapter binding.libraryRecycler.adapter = libraryAdapter
setGroupVisible(R.id.group_sorting, true) setGroupVisible(R.id.group_sorting, true)
libraryModel.resetQuery() libraryModel.resetQuery()
return true return true
@ -119,6 +123,19 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
binding.libraryRecycler.apply { binding.libraryRecycler.apply {
adapter = libraryAdapter adapter = libraryAdapter
setHasFixedSize(true) setHasFixedSize(true)
if (isLandscape(resources)) {
layoutManager = GridLayoutManager(requireContext(), GridLayoutManager.VERTICAL).apply {
spanCount = 3
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (binding.libraryRecycler.adapter == searchAdapter) {
if (searchAdapter.currentList[position] is Header) 3 else 1
} else 1
}
}
}
}
} }
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
@ -128,7 +145,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
} }
libraryModel.searchResults.observe(viewLifecycleOwner) { libraryModel.searchResults.observe(viewLifecycleOwner) {
if (libraryModel.searchHasFocus) { if (binding.libraryRecycler.adapter == searchAdapter) {
searchAdapter.submitList(it) { searchAdapter.submitList(it) {
binding.libraryRecycler.scrollToPosition(0) binding.libraryRecycler.scrollToPosition(0)
} }

View file

@ -34,10 +34,6 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
private var mIsNavigating = false private var mIsNavigating = false
val isNavigating: Boolean get() = mIsNavigating val isNavigating: Boolean get() = mIsNavigating
private var mSearchHasFocus = false
val searchHasFocus: Boolean get() = mSearchHasFocus
private val settingsManager = SettingsManager.getInstance() private val settingsManager = SettingsManager.getInstance()
private val musicStore = MusicStore.getInstance() private val musicStore = MusicStore.getInstance()
@ -111,10 +107,6 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
} }
} }
fun updateSearchFocusStatus(value: Boolean) {
mSearchHasFocus = value
}
fun resetQuery() { fun resetQuery() {
mSearchResults.value = listOf() mSearchResults.value = listOf()
} }

View file

@ -45,7 +45,7 @@ class MusicStore private constructor() {
*/ */
suspend fun load(app: Application): MusicLoaderResponse { suspend fun load(app: Application): MusicLoaderResponse {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
logD("Starting initial music load...") this@MusicStore.logD("Starting initial music load...")
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
@ -83,7 +83,7 @@ class MusicStore private constructor() {
val elapsed = System.currentTimeMillis() - start val elapsed = System.currentTimeMillis() - start
logD("Music load completed successfully in ${elapsed}ms.") this@MusicStore.logD("Music load completed successfully in ${elapsed}ms.")
loaded = true loaded = true
} }

View file

@ -144,6 +144,7 @@ class MusicLoader(
}.toMutableList() }.toMutableList()
// Then try to associate any genres with their respective artists. // Then try to associate any genres with their respective artists.
// TODO: This is already querying all genre songs, just move it.
for (genre in genres) { for (genre in genres) {
val artistGenreCursor = resolver.query( val artistGenreCursor = resolver.query(
Genres.Members.getContentUri("external", genre.id), Genres.Members.getContentUri("external", genre.id),
@ -155,7 +156,6 @@ class MusicLoader(
artistGenreCursor?.let { cursor -> artistGenreCursor?.let { cursor ->
val idIndex = cursor.getColumnIndexOrThrow(Genres.Members.ARTIST_ID) val idIndex = cursor.getColumnIndexOrThrow(Genres.Members.ARTIST_ID)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex) val id = cursor.getLong(idIndex)

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.reddit.indicatorfastscroll.FastScrollerView import com.reddit.indicatorfastscroll.FastScrollerView
@ -17,6 +18,7 @@ import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.utils.isLandscape
import org.oxycblt.auxio.utils.setupSongActions import org.oxycblt.auxio.utils.setupSongActions
import kotlin.math.ceil import kotlin.math.ceil
@ -70,6 +72,12 @@ class SongsFragment : Fragment() {
adapter = songAdapter adapter = songAdapter
setHasFixedSize(true) setHasFixedSize(true)
if (isLandscape(resources)) {
layoutManager = GridLayoutManager(requireContext(), GridLayoutManager.VERTICAL).also {
it.spanCount = 3
}
}
post { post {
if (computeVerticalScrollRange() < height) { if (computeVerticalScrollRange() < height) {
binding.songFastScroll.visibility = View.GONE binding.songFastScroll.visibility = View.GONE

View file

@ -28,7 +28,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_weird" android:elevation="@dimen/elevation_high"
android:outlineProvider="bounds" android:outlineProvider="bounds"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -41,7 +41,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/background" android:background="@color/background"
app:elevation="@dimen/elevation_normal"
app:labelVisibilityMode="selected" app:labelVisibilityMode="selected"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -27,7 +27,6 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/playback_toolbar" android:id="@+id/playback_toolbar"
style="@style/Toolbar.Style.Icon" style="@style/Toolbar.Style.Icon"
android:elevation="0dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_playback" app:menu="@menu/menu_playback"
@ -38,13 +37,13 @@
android:id="@+id/playback_cover" android:id="@+id/playback_cover"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="@dimen/margin_mid_large" android:layout_margin="@dimen/margin_large"
android:contentDescription="@{@string/description_album_cover(song.name)}" android:contentDescription="@{@string/description_album_cover(song.name)}"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
android:outlineProvider="bounds" android:outlineProvider="bounds"
app:coverArt="@{song}" app:coverArt="@{song}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" app:layout_constraintTop_toBottomOf="@+id/playback_toolbar"
tools:src="@drawable/ic_song" /> tools:src="@drawable/ic_song" />

View file

@ -193,13 +193,13 @@
style="@style/Widget.AppCompat.Button.Borderless" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="@dimen/size_play_pause_compact" android:layout_width="@dimen/size_play_pause_compact"
android:layout_height="@dimen/size_play_pause_compact" android:layout_height="@dimen/size_play_pause_compact"
android:layout_marginEnd="@dimen/margin_mid_large" android:layout_marginStart="@dimen/margin_mid_large"
android:background="@drawable/ui_unbounded_ripple" android:background="@drawable/ui_unbounded_ripple"
android:contentDescription="@{playbackModel.isShuffling() ? @string/description_shuffle_off : @string/description_shuffle_on" android:contentDescription="@{playbackModel.isShuffling() ? @string/description_shuffle_off : @string/description_shuffle_on"
android:onClick="@{() -> playbackModel.invertShuffleStatus()}" android:onClick="@{() -> playbackModel.invertShuffleStatus()}"
android:src="@drawable/ic_shuffle_large" android:src="@drawable/ic_shuffle_large"
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause" app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/playback_skip_next"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" /> app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
<ImageButton <ImageButton
@ -207,13 +207,13 @@
style="@style/Widget.AppCompat.Button.Borderless" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="@dimen/size_play_pause_compact" android:layout_width="@dimen/size_play_pause_compact"
android:layout_height="@dimen/size_play_pause_compact" android:layout_height="@dimen/size_play_pause_compact"
android:layout_marginStart="@dimen/margin_mid_large" android:layout_marginEnd="@dimen/margin_mid_large"
android:background="@drawable/ui_unbounded_ripple" android:background="@drawable/ui_unbounded_ripple"
android:contentDescription="@string/description_change_loop" android:contentDescription="@string/description_change_loop"
android:onClick="@{() -> playbackModel.incrementLoopStatus()}" android:onClick="@{() -> playbackModel.incrementLoopStatus()}"
android:src="@drawable/ic_loop_large" android:src="@drawable/ic_loop_large"
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause" app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@+id/playback_skip_prev"
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" /> app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -10,6 +10,7 @@
<dimen name="margin_mid_small">10dp</dimen> <dimen name="margin_mid_small">10dp</dimen>
<dimen name="margin_medium">16dp</dimen> <dimen name="margin_medium">16dp</dimen>
<dimen name="margin_mid_large">24dp</dimen> <dimen name="margin_mid_large">24dp</dimen>
<dimen name="margin_large">32dp</dimen>
<!-- Height Namespace | Height for UI elements --> <!-- Height Namespace | Height for UI elements -->
<dimen name="height_compact_progress">2dp</dimen> <dimen name="height_compact_progress">2dp</dimen>
@ -39,7 +40,6 @@
<!-- Misc --> <!-- Misc -->
<dimen name="elevation_normal">4dp</dimen> <dimen name="elevation_normal">4dp</dimen>
<dimen name="elevation_weird">4.2dp</dimen>
<dimen name="elevation_high">8dp</dimen> <dimen name="elevation_high">8dp</dimen>
<dimen name="offset_thumb">4dp</dimen> <dimen name="offset_thumb">4dp</dimen>
</resources> </resources>