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:
OxygenCobalt 2021-08-22 17:59:07 -06:00
parent 6c5a68c929
commit 19e2fcbb90
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
26 changed files with 85 additions and 110 deletions

View file

@ -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()

View file

@ -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)
}
}
}

View file

@ -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
/**

View file

@ -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
/**

View file

@ -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
/**

View file

@ -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

View file

@ -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,

View file

@ -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() {

View file

@ -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()

View file

@ -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>()

View file

@ -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.

View file

@ -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

View file

@ -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)
}
}

View file

@ -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) {

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>