search: improve keyboard management
Remove the janky requestFocus/clearFocus called on SearchFragment and replace them with InputMethodManager calls. This is generally more user friendly, especially when returning to search from navigation.
This commit is contained in:
parent
6c5a68c929
commit
19e2fcbb90
26 changed files with 85 additions and 110 deletions
|
@ -35,6 +35,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
|||
|
||||
/**
|
||||
* The single [AppCompatActivity] for Auxio.
|
||||
* TODO: Improve edge-to-edge everywhere
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val playbackModel: PlaybackViewModel by viewModels()
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
|
||||
package org.oxycblt.auxio.detail
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.canScroll
|
||||
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
|
||||
|
@ -34,7 +36,6 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.recycler.CenterSmoothScroller
|
||||
import org.oxycblt.auxio.showToast
|
||||
import org.oxycblt.auxio.ui.ActionMenu
|
||||
import org.oxycblt.auxio.ui.newMenu
|
||||
|
@ -136,7 +137,8 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,4 +189,27 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [LinearSmoothScroller] subclass that centers the item on the screen instead of
|
||||
* snapping to the top or bottom.
|
||||
*/
|
||||
private class CenterSmoothScroller(
|
||||
context: Context,
|
||||
target: Int
|
||||
) : LinearSmoothScroller(context) {
|
||||
init {
|
||||
targetPosition = target
|
||||
}
|
||||
|
||||
override fun calculateDtToFit(
|
||||
viewStart: Int,
|
||||
viewEnd: Int,
|
||||
boxStart: Int,
|
||||
boxEnd: Int,
|
||||
snapPreference: Int
|
||||
): Int {
|
||||
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,8 @@ import org.oxycblt.auxio.music.Album
|
|||
import org.oxycblt.auxio.music.BaseModel
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.recycler.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.DiffCallback
|
||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||
import org.oxycblt.auxio.setTextColorResource
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,9 +37,8 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
import org.oxycblt.auxio.music.Header
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.recycler.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.DiffCallback
|
||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||
import org.oxycblt.auxio.setTextColorResource
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,9 +33,8 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.recycler.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.DiffCallback
|
||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.Highlightable
|
||||
import org.oxycblt.auxio.setTextColorResource
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.recycler.viewholders
|
||||
package org.oxycblt.auxio.detail.adapters
|
||||
|
||||
/**
|
||||
* Interface that allows the highlighting of certain ViewHolders
|
|
@ -27,10 +27,10 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.BaseModel
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
|
||||
import org.oxycblt.auxio.recycler.AlbumViewHolder
|
||||
import org.oxycblt.auxio.recycler.ArtistViewHolder
|
||||
import org.oxycblt.auxio.recycler.GenreViewHolder
|
||||
import org.oxycblt.auxio.recycler.SongViewHolder
|
||||
|
||||
class HomeAdapter(
|
||||
private val doOnClick: (data: BaseModel) -> Unit,
|
||||
|
|
|
@ -47,8 +47,7 @@ import org.oxycblt.auxio.recycler.DisplayMode
|
|||
* views for each respective fragment.
|
||||
* TODO: Re-add sorting (but new and improved)
|
||||
* TODO: Add lift-on-scroll eventually [when I can file a bug report or hack it into working]
|
||||
* FIXME: Fix issue where for the toolbar will default to its collapsed state for basically no
|
||||
* reason
|
||||
* FIXME: Keep the collapsed state in the ViewModel so we can make sure it stays consistent
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
|
|
|
@ -39,6 +39,8 @@ import org.oxycblt.auxio.ui.newMenu
|
|||
|
||||
/*
|
||||
* Fragment that contains a list of items specified by a [DisplayMode].
|
||||
* TODO: Fix crash from not saving the display mode. This is getting really tiring.
|
||||
* Just keep the index for the tab we're working with and then just use that w/homeModel.
|
||||
*/
|
||||
class HomeListFragment : Fragment() {
|
||||
private val homeModel: HomeViewModel by viewModels()
|
||||
|
|
|
@ -33,12 +33,6 @@ import org.oxycblt.auxio.logD
|
|||
/**
|
||||
* Class that loads/constructs [Genre]s, [Artist]s, [Album]s, and [Song] objects from the filesystem
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
* FIXME: Here's a catalog of problems that I already know about with this abomination
|
||||
* - All loading is done at startup [Not efficent for large libraries, would require massive arch retooling to fix]
|
||||
* - Does not support the album artist tag [Nothing I can do that doesn't involve rolling my own loader]
|
||||
* - Genre system is a bottleneck [See Above]
|
||||
* Blame MediaStore, loading anything on this platform is a nightmare.
|
||||
*/
|
||||
class MusicLoader(private val context: Context) {
|
||||
var genres = mutableListOf<Genre>()
|
||||
|
|
|
@ -34,9 +34,9 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
import org.oxycblt.auxio.music.Header
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.recycler.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.DiffCallback
|
||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
||||
import org.oxycblt.auxio.recycler.HeaderViewHolder
|
||||
|
||||
/**
|
||||
* The single adapter for both the Next Queue and the User Queue.
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.recycler.viewholders
|
||||
package org.oxycblt.auxio.recycler
|
||||
|
||||
import android.view.View
|
||||
import androidx.databinding.ViewDataBinding
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* CenterSmoothScroller.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.recycler
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
|
||||
/**
|
||||
* [LinearSmoothScroller] subclass that centers the item on the screen instead of snapping to the
|
||||
* top or bottom.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class CenterSmoothScroller(context: Context, target: Int) : LinearSmoothScroller(context) {
|
||||
init {
|
||||
targetPosition = target
|
||||
}
|
||||
|
||||
override fun calculateDtToFit(
|
||||
viewStart: Int,
|
||||
viewEnd: Int,
|
||||
boxStart: Int,
|
||||
boxEnd: Int,
|
||||
snapPreference: Int
|
||||
): Int {
|
||||
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)
|
||||
}
|
||||
}
|
|
@ -18,25 +18,22 @@
|
|||
|
||||
package org.oxycblt.auxio.recycler
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.oxycblt.auxio.R
|
||||
|
||||
/**
|
||||
* An enum for determining what items to show in a given list.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
enum class DisplayMode(@DrawableRes val iconRes: Int) {
|
||||
SHOW_GENRES(R.drawable.ic_genre),
|
||||
SHOW_ARTISTS(R.drawable.ic_artist),
|
||||
SHOW_ALBUMS(R.drawable.ic_album),
|
||||
SHOW_SONGS(R.drawable.ic_song);
|
||||
enum class DisplayMode {
|
||||
SHOW_GENRES,
|
||||
SHOW_ARTISTS,
|
||||
SHOW_ALBUMS,
|
||||
SHOW_SONGS;
|
||||
|
||||
companion object {
|
||||
const val CONST_SHOW_ALL = 0xA107
|
||||
const val CONST_SHOW_GENRES = 0xA108
|
||||
const val CONST_SHOW_ARTISTS = 0xA109
|
||||
const val CONST_SHOW_ALBUMS = 0xA10A
|
||||
const val CONST_SHOW_SONGS = 0xA10B
|
||||
private const val CONST_SHOW_ALL = 0xA107
|
||||
private const val CONST_SHOW_GENRES = 0xA108
|
||||
private const val CONST_SHOW_ARTISTS = 0xA109
|
||||
private const val CONST_SHOW_ALBUMS = 0xA10A
|
||||
private const val CONST_SHOW_SONGS = 0xA10B
|
||||
|
||||
fun toSearchInt(value: DisplayMode?): Int {
|
||||
return when (value) {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.recycler.viewholders
|
||||
package org.oxycblt.auxio.recycler
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
|
@ -28,12 +28,12 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Header
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.recycler.AlbumViewHolder
|
||||
import org.oxycblt.auxio.recycler.ArtistViewHolder
|
||||
import org.oxycblt.auxio.recycler.DiffCallback
|
||||
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
||||
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
|
||||
import org.oxycblt.auxio.recycler.GenreViewHolder
|
||||
import org.oxycblt.auxio.recycler.HeaderViewHolder
|
||||
import org.oxycblt.auxio.recycler.SongViewHolder
|
||||
|
||||
/**
|
||||
* A Multi-ViewHolder adapter that displays the results of a search query.
|
||||
|
|
|
@ -52,7 +52,7 @@ import org.oxycblt.auxio.ui.newMenu
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class SearchFragment : Fragment() {
|
||||
// SearchViewModel only scoped to this Fragment
|
||||
// SearchViewModel is only scoped to this Fragment
|
||||
private val searchModel: SearchViewModel by viewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
@ -64,10 +64,15 @@ class SearchFragment : Fragment() {
|
|||
): View {
|
||||
val binding = FragmentSearchBinding.inflate(inflater)
|
||||
|
||||
val searchAdapter = SearchAdapter(::onItemSelection, ::newMenu)
|
||||
|
||||
val imm = requireContext().getSystemServiceSafe(InputMethodManager::class)
|
||||
|
||||
val searchAdapter = SearchAdapter(
|
||||
doOnClick = { item ->
|
||||
onItemSelection(item, imm)
|
||||
},
|
||||
::newMenu
|
||||
)
|
||||
|
||||
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
val defaultParams = toolbarParams.scrollFlags
|
||||
|
||||
|
@ -87,7 +92,7 @@ class SearchFragment : Fragment() {
|
|||
menu.findItem(itemId).isChecked = true
|
||||
|
||||
setNavigationOnClickListener {
|
||||
requireView().rootView.clearFocus()
|
||||
imm.hide()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
|
||||
|
@ -158,6 +163,8 @@ class SearchFragment : Fragment() {
|
|||
else -> return@observe
|
||||
}
|
||||
)
|
||||
|
||||
imm.hide()
|
||||
}
|
||||
|
||||
logD("Fragment created.")
|
||||
|
@ -171,19 +178,22 @@ class SearchFragment : Fragment() {
|
|||
searchModel.setNavigating(false)
|
||||
}
|
||||
|
||||
private fun InputMethodManager.hide() {
|
||||
hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_IMPLICIT_ONLY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles when an [item] is selected.
|
||||
* Handles all datatypes that are selectable.
|
||||
*/
|
||||
private fun onItemSelection(item: BaseModel) {
|
||||
private fun onItemSelection(item: BaseModel, imm: InputMethodManager) {
|
||||
if (item is Song) {
|
||||
playbackModel.playSong(item)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get rid of the keyboard if we are navigating
|
||||
requireView().rootView.clearFocus()
|
||||
imm.hide()
|
||||
|
||||
if (!searchModel.isNavigating) {
|
||||
searchModel.setNavigating(true)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.HeaderViewHolder">
|
||||
tools:context=".recycler.HeaderViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.AlbumViewHolder">
|
||||
tools:context=".recycler.AlbumViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<TextView
|
||||
android:id="@+id/song_track"
|
||||
android:layout_width="wrap_content"
|
||||
android:minWidth="@dimen/width_track_number"
|
||||
android:minWidth="@dimen/size_track_number"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@{@string/desc_track_number(song.track)}"
|
||||
android:gravity="center"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.ArtistViewHolder">
|
||||
tools:context=".recycler.ArtistViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.SongViewHolder">
|
||||
tools:context=".recycler.SongViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.GenreViewHolder">
|
||||
tools:context=".recycler.GenreViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.HeaderViewHolder">
|
||||
tools:context=".recycler.HeaderViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".recycler.viewholders.SongViewHolder">
|
||||
tools:context=".recycler.SongViewHolder">
|
||||
|
||||
<data>
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--
|
||||
TODO: Redo these dimens to line up with the 8dp grid. Tiny spacing can be used for
|
||||
internal elements, but micro spacing needs to be phased out.
|
||||
-->
|
||||
|
||||
<!-- Spacing Namespace | Dimens for padding/margin attributes -->
|
||||
<dimen name="spacing_small">8dp</dimen>
|
||||
<dimen name="spacing_medium">16dp</dimen>
|
||||
|
@ -13,10 +8,6 @@
|
|||
<dimen name="spacing_mid_huge">48dp</dimen>
|
||||
<dimen name="spacing_insane">128dp</dimen>
|
||||
|
||||
<!-- Width Namespace | Width for UI elements -->
|
||||
<dimen name="width_track_number">32dp</dimen>
|
||||
<dimen name="width_fast_scroll">20dp</dimen>
|
||||
|
||||
<!-- Size Namespace | Width & Heights for UI elements -->
|
||||
<dimen name="size_btn_small">48dp</dimen>
|
||||
<dimen name="size_btn_large">64dp</dimen>
|
||||
|
@ -32,6 +23,8 @@
|
|||
<dimen name="size_small_unb_ripple">20dp</dimen>
|
||||
<dimen name="size_unb_ripple">24dp</dimen>
|
||||
|
||||
<dimen name="size_track_number">32dp</dimen>
|
||||
|
||||
<!-- Text Size Namespace | Text Sizes -->
|
||||
<dimen name="text_size_small">16sp</dimen>
|
||||
<dimen name="text_size_medium">18sp</dimen>
|
||||
|
|
Loading…
Reference in a new issue