Add landscape support to list fragments
Add a landscape mode for LibraryFragment & SongsFragment.
This commit is contained in:
parent
fca42f2cb0
commit
07e7e1ae5b
9 changed files with 38 additions and 23 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
Loading…
Reference in a new issue